mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 08:00:05 +00:00
add prices for ByteDance Video API nodes (#5336)
This commit is contained in:
committed by
snomiao
parent
dfc101bc8f
commit
181742b6aa
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] || []
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user