Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
add prices for ByteDance Video API nodes
  • Loading branch information
bigcat88 committed Sep 4, 2025
commit 779eb0e33e8a3834198835700a935b471d03546a
78 changes: 77 additions & 1 deletion src/composables/node/useNodePricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,66 @@ const pixversePricingCalculator = (node: LGraphNode): string => {
return '$0.9/Run'
}

const byteDanceVideoPricingCalculator = (node: LGraphNode): string => {
const modelWidget = node.widgets?.find(
(w) => w.name === 'model'
) as IComboWidget
const durationWidget = node.widgets?.find(
(w) => w.name === 'duration'
) as IComboWidget
const resolutionWidget = node.widgets?.find(
(w) => w.name === 'resolution'
) as IComboWidget

if (!modelWidget || !durationWidget || !resolutionWidget) return 'Token-based'

const model = String(modelWidget.value).toLowerCase()
const resolution = String(resolutionWidget.value).toLowerCase()
const seconds = parseFloat(String(durationWidget.value))
const priceByModel: Record<string, Record<string, [number, number]>> = {
'seedance-1-0-pro': {
'480p': [0.23, 0.24],
'720p': [0.51, 0.56],
'1080p': [1.18, 1.22]
},
'seedance-1-0-lite': {
'480p': [0.17, 0.18],
'720p': [0.37, 0.41],
'1080p': [0.85, 0.88]
}
}

const modelKey = model.includes('seedance-1-0-pro')
? 'seedance-1-0-pro'
: model.includes('seedance-1-0-lite')
? 'seedance-1-0-lite'
: ''

const resKey = resolution.includes('1080')
? '1080p'
: resolution.includes('720')
? '720p'
: resolution.includes('480')
? '480p'
: ''

const baseRange =
modelKey && resKey ? priceByModel[modelKey]?.[resKey] : undefined
if (!baseRange) return 'Token-based'

const [min10s, max10s] = baseRange
const scale = seconds / 10
const minCost = min10s * scale
const maxCost = max10s * scale

const minStr = `$${minCost.toFixed(2)}/Run`
const maxStr = `$${maxCost.toFixed(2)}/Run`

return minStr === maxStr
? minStr
: `$${minCost.toFixed(2)}-$${maxCost.toFixed(2)}/Run`
}

/**
* Static pricing data for API nodes, now supporting both strings and functions
*/
Expand Down Expand Up @@ -1441,6 +1501,18 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
}
return 'Token-based'
}
},
ByteDanceTextToVideoNode: {
displayPrice: byteDanceVideoPricingCalculator
},
ByteDanceImageToVideoNode: {
displayPrice: byteDanceVideoPricingCalculator
},
ByteDanceFirstLastFrameNode: {
displayPrice: byteDanceVideoPricingCalculator
},
ByteDanceImageReferenceNode: {
displayPrice: byteDanceVideoPricingCalculator
}
}

Expand Down Expand Up @@ -1531,7 +1603,11 @@ export const useNodePricing = () => {
OpenAIChatNode: ['model'],
// ByteDance
ByteDanceImageNode: ['model'],
ByteDanceImageEditNode: ['model']
ByteDanceImageEditNode: ['model'],
ByteDanceTextToVideoNode: ['model', 'duration', 'resolution'],
ByteDanceImageToVideoNode: ['model', 'duration', 'resolution'],
ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'],
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution']
}
return widgetMap[nodeType] || []
}
Expand Down
82 changes: 82 additions & 0 deletions tests-ui/tests/composables/node/useNodePricing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1780,4 +1780,86 @@ describe('useNodePricing', () => {
})
})
})

describe('dynamic pricing - ByteDance Seedance video nodes', () => {
it('should return base 10s range for PRO 1080p on ByteDanceTextToVideoNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('ByteDanceTextToVideoNode', [
{ name: 'model', value: 'seedance-1-0-pro' },
{ name: 'duration', value: '10' },
{ name: 'resolution', value: '1080p' }
])

const price = getNodeDisplayPrice(node)
expect(price).toBe('$1.18-$1.22/Run')
})

it('should scale to half for 5s PRO 1080p on ByteDanceTextToVideoNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('ByteDanceTextToVideoNode', [
{ name: 'model', value: 'seedance-1-0-pro' },
{ name: 'duration', value: '5' },
{ name: 'resolution', value: '1080p' }
])

const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.59-$0.61/Run')
})

it('should scale for 8s PRO 480p on ByteDanceImageToVideoNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('ByteDanceImageToVideoNode', [
{ name: 'model', value: 'seedance-1-0-pro' },
{ name: 'duration', value: '8' },
{ name: 'resolution', value: '480p' }
])

const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.18-$0.19/Run')
})

it('should scale correctly for 12s PRO 720p on ByteDanceFirstLastFrameNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('ByteDanceFirstLastFrameNode', [
{ name: 'model', value: 'seedance-1-0-pro' },
{ name: 'duration', value: '12' },
{ name: 'resolution', value: '720p' }
])

const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.61-$0.67/Run')
})

it('should collapse to a single value when min and max round equal for LITE 480p 3s on ByteDanceImageReferenceNode', () => {
const { getNodeDisplayPrice } = useNodePricing()
const node = createMockNode('ByteDanceImageReferenceNode', [
{ name: 'model', value: 'seedance-1-0-lite' },
{ name: 'duration', value: '3' },
{ name: 'resolution', value: '480p' }
])

const price = getNodeDisplayPrice(node)
expect(price).toBe('$0.05/Run') // 0.17..0.18 scaled by 0.3 both round to 0.05
})

it('should return Token-based when required widgets are missing', () => {
const { getNodeDisplayPrice } = useNodePricing()
const missingModel = createMockNode('ByteDanceFirstLastFrameNode', [
{ name: 'duration', value: '10' },
{ name: 'resolution', value: '1080p' }
])
const missingResolution = createMockNode('ByteDanceImageToVideoNode', [
{ name: 'model', value: 'seedance-1-0-pro' },
{ name: 'duration', value: '10' }
])
const missingDuration = createMockNode('ByteDanceTextToVideoNode', [
{ name: 'model', value: 'seedance-1-0-lite' },
{ name: 'resolution', value: '720p' }
])

expect(getNodeDisplayPrice(missingModel)).toBe('Token-based')
expect(getNodeDisplayPrice(missingResolution)).toBe('Token-based')
expect(getNodeDisplayPrice(missingDuration)).toBe('Token-based')
})
})
})
Loading