Skip to content

Commit 4748378

Browse files
authored
add prices for ByteDance Video API nodes (#5336)
1 parent ad64dbb commit 4748378

File tree

2 files changed

+159
-1
lines changed

2 files changed

+159
-1
lines changed

src/composables/node/useNodePricing.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,66 @@ const pixversePricingCalculator = (node: LGraphNode): string => {
109109
return '$0.9/Run'
110110
}
111111

112+
const byteDanceVideoPricingCalculator = (node: LGraphNode): string => {
113+
const modelWidget = node.widgets?.find(
114+
(w) => w.name === 'model'
115+
) as IComboWidget
116+
const durationWidget = node.widgets?.find(
117+
(w) => w.name === 'duration'
118+
) as IComboWidget
119+
const resolutionWidget = node.widgets?.find(
120+
(w) => w.name === 'resolution'
121+
) as IComboWidget
122+
123+
if (!modelWidget || !durationWidget || !resolutionWidget) return 'Token-based'
124+
125+
const model = String(modelWidget.value).toLowerCase()
126+
const resolution = String(resolutionWidget.value).toLowerCase()
127+
const seconds = parseFloat(String(durationWidget.value))
128+
const priceByModel: Record<string, Record<string, [number, number]>> = {
129+
'seedance-1-0-pro': {
130+
'480p': [0.23, 0.24],
131+
'720p': [0.51, 0.56],
132+
'1080p': [1.18, 1.22]
133+
},
134+
'seedance-1-0-lite': {
135+
'480p': [0.17, 0.18],
136+
'720p': [0.37, 0.41],
137+
'1080p': [0.85, 0.88]
138+
}
139+
}
140+
141+
const modelKey = model.includes('seedance-1-0-pro')
142+
? 'seedance-1-0-pro'
143+
: model.includes('seedance-1-0-lite')
144+
? 'seedance-1-0-lite'
145+
: ''
146+
147+
const resKey = resolution.includes('1080')
148+
? '1080p'
149+
: resolution.includes('720')
150+
? '720p'
151+
: resolution.includes('480')
152+
? '480p'
153+
: ''
154+
155+
const baseRange =
156+
modelKey && resKey ? priceByModel[modelKey]?.[resKey] : undefined
157+
if (!baseRange) return 'Token-based'
158+
159+
const [min10s, max10s] = baseRange
160+
const scale = seconds / 10
161+
const minCost = min10s * scale
162+
const maxCost = max10s * scale
163+
164+
const minStr = `$${minCost.toFixed(2)}/Run`
165+
const maxStr = `$${maxCost.toFixed(2)}/Run`
166+
167+
return minStr === maxStr
168+
? minStr
169+
: `$${minCost.toFixed(2)}-$${maxCost.toFixed(2)}/Run`
170+
}
171+
112172
/**
113173
* Static pricing data for API nodes, now supporting both strings and functions
114174
*/
@@ -1441,6 +1501,18 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
14411501
}
14421502
return 'Token-based'
14431503
}
1504+
},
1505+
ByteDanceTextToVideoNode: {
1506+
displayPrice: byteDanceVideoPricingCalculator
1507+
},
1508+
ByteDanceImageToVideoNode: {
1509+
displayPrice: byteDanceVideoPricingCalculator
1510+
},
1511+
ByteDanceFirstLastFrameNode: {
1512+
displayPrice: byteDanceVideoPricingCalculator
1513+
},
1514+
ByteDanceImageReferenceNode: {
1515+
displayPrice: byteDanceVideoPricingCalculator
14441516
}
14451517
}
14461518

@@ -1531,7 +1603,11 @@ export const useNodePricing = () => {
15311603
OpenAIChatNode: ['model'],
15321604
// ByteDance
15331605
ByteDanceImageNode: ['model'],
1534-
ByteDanceImageEditNode: ['model']
1606+
ByteDanceImageEditNode: ['model'],
1607+
ByteDanceTextToVideoNode: ['model', 'duration', 'resolution'],
1608+
ByteDanceImageToVideoNode: ['model', 'duration', 'resolution'],
1609+
ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'],
1610+
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution']
15351611
}
15361612
return widgetMap[nodeType] || []
15371613
}

tests-ui/tests/composables/node/useNodePricing.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,4 +1780,86 @@ describe('useNodePricing', () => {
17801780
})
17811781
})
17821782
})
1783+
1784+
describe('dynamic pricing - ByteDance Seedance video nodes', () => {
1785+
it('should return base 10s range for PRO 1080p on ByteDanceTextToVideoNode', () => {
1786+
const { getNodeDisplayPrice } = useNodePricing()
1787+
const node = createMockNode('ByteDanceTextToVideoNode', [
1788+
{ name: 'model', value: 'seedance-1-0-pro' },
1789+
{ name: 'duration', value: '10' },
1790+
{ name: 'resolution', value: '1080p' }
1791+
])
1792+
1793+
const price = getNodeDisplayPrice(node)
1794+
expect(price).toBe('$1.18-$1.22/Run')
1795+
})
1796+
1797+
it('should scale to half for 5s PRO 1080p on ByteDanceTextToVideoNode', () => {
1798+
const { getNodeDisplayPrice } = useNodePricing()
1799+
const node = createMockNode('ByteDanceTextToVideoNode', [
1800+
{ name: 'model', value: 'seedance-1-0-pro' },
1801+
{ name: 'duration', value: '5' },
1802+
{ name: 'resolution', value: '1080p' }
1803+
])
1804+
1805+
const price = getNodeDisplayPrice(node)
1806+
expect(price).toBe('$0.59-$0.61/Run')
1807+
})
1808+
1809+
it('should scale for 8s PRO 480p on ByteDanceImageToVideoNode', () => {
1810+
const { getNodeDisplayPrice } = useNodePricing()
1811+
const node = createMockNode('ByteDanceImageToVideoNode', [
1812+
{ name: 'model', value: 'seedance-1-0-pro' },
1813+
{ name: 'duration', value: '8' },
1814+
{ name: 'resolution', value: '480p' }
1815+
])
1816+
1817+
const price = getNodeDisplayPrice(node)
1818+
expect(price).toBe('$0.18-$0.19/Run')
1819+
})
1820+
1821+
it('should scale correctly for 12s PRO 720p on ByteDanceFirstLastFrameNode', () => {
1822+
const { getNodeDisplayPrice } = useNodePricing()
1823+
const node = createMockNode('ByteDanceFirstLastFrameNode', [
1824+
{ name: 'model', value: 'seedance-1-0-pro' },
1825+
{ name: 'duration', value: '12' },
1826+
{ name: 'resolution', value: '720p' }
1827+
])
1828+
1829+
const price = getNodeDisplayPrice(node)
1830+
expect(price).toBe('$0.61-$0.67/Run')
1831+
})
1832+
1833+
it('should collapse to a single value when min and max round equal for LITE 480p 3s on ByteDanceImageReferenceNode', () => {
1834+
const { getNodeDisplayPrice } = useNodePricing()
1835+
const node = createMockNode('ByteDanceImageReferenceNode', [
1836+
{ name: 'model', value: 'seedance-1-0-lite' },
1837+
{ name: 'duration', value: '3' },
1838+
{ name: 'resolution', value: '480p' }
1839+
])
1840+
1841+
const price = getNodeDisplayPrice(node)
1842+
expect(price).toBe('$0.05/Run') // 0.17..0.18 scaled by 0.3 both round to 0.05
1843+
})
1844+
1845+
it('should return Token-based when required widgets are missing', () => {
1846+
const { getNodeDisplayPrice } = useNodePricing()
1847+
const missingModel = createMockNode('ByteDanceFirstLastFrameNode', [
1848+
{ name: 'duration', value: '10' },
1849+
{ name: 'resolution', value: '1080p' }
1850+
])
1851+
const missingResolution = createMockNode('ByteDanceImageToVideoNode', [
1852+
{ name: 'model', value: 'seedance-1-0-pro' },
1853+
{ name: 'duration', value: '10' }
1854+
])
1855+
const missingDuration = createMockNode('ByteDanceTextToVideoNode', [
1856+
{ name: 'model', value: 'seedance-1-0-lite' },
1857+
{ name: 'resolution', value: '720p' }
1858+
])
1859+
1860+
expect(getNodeDisplayPrice(missingModel)).toBe('Token-based')
1861+
expect(getNodeDisplayPrice(missingResolution)).toBe('Token-based')
1862+
expect(getNodeDisplayPrice(missingDuration)).toBe('Token-based')
1863+
})
1864+
})
17831865
})

0 commit comments

Comments
 (0)