mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 10:59:53 +00:00
add pricing for new api nodes (#5724)
## Summary Added prices for the new upcoming API nodes. Backport required.
This commit is contained in:
@@ -1548,6 +1548,71 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
},
|
||||
ByteDanceImageReferenceNode: {
|
||||
displayPrice: byteDanceVideoPricingCalculator
|
||||
},
|
||||
WanTextToVideoApi: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const durationWidget = node.widgets?.find(
|
||||
(w) => w.name === 'duration'
|
||||
) as IComboWidget
|
||||
const resolutionWidget = node.widgets?.find(
|
||||
(w) => w.name === 'size'
|
||||
) as IComboWidget
|
||||
|
||||
if (!durationWidget || !resolutionWidget) return '$0.05-0.15/second'
|
||||
|
||||
const seconds = parseFloat(String(durationWidget.value))
|
||||
const resolutionStr = String(resolutionWidget.value).toLowerCase()
|
||||
|
||||
const resKey = resolutionStr.includes('1080')
|
||||
? '1080p'
|
||||
: resolutionStr.includes('720')
|
||||
? '720p'
|
||||
: resolutionStr.includes('480')
|
||||
? '480p'
|
||||
: resolutionStr.match(/^\s*(\d{3,4}p)/)?.[1] ?? ''
|
||||
|
||||
const pricePerSecond: Record<string, number> = {
|
||||
'480p': 0.05,
|
||||
'720p': 0.1,
|
||||
'1080p': 0.15
|
||||
}
|
||||
|
||||
const pps = pricePerSecond[resKey]
|
||||
if (isNaN(seconds) || !pps) return '$0.05-0.15/second'
|
||||
|
||||
const cost = (pps * seconds).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
WanImageToVideoApi: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const durationWidget = node.widgets?.find(
|
||||
(w) => w.name === 'duration'
|
||||
) as IComboWidget
|
||||
const resolutionWidget = node.widgets?.find(
|
||||
(w) => w.name === 'resolution'
|
||||
) as IComboWidget
|
||||
|
||||
if (!durationWidget || !resolutionWidget) return '$0.05-0.15/second'
|
||||
|
||||
const seconds = parseFloat(String(durationWidget.value))
|
||||
const resolution = String(resolutionWidget.value).trim().toLowerCase()
|
||||
|
||||
const pricePerSecond: Record<string, number> = {
|
||||
'480p': 0.05,
|
||||
'720p': 0.1,
|
||||
'1080p': 0.15
|
||||
}
|
||||
|
||||
const pps = pricePerSecond[resolution]
|
||||
if (isNaN(seconds) || !pps) return '$0.05-0.15/second'
|
||||
|
||||
const cost = (pps * seconds).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
WanTextToImageApi: {
|
||||
displayPrice: '$0.03/Run'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1647,7 +1712,9 @@ export const useNodePricing = () => {
|
||||
ByteDanceTextToVideoNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceImageToVideoNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution']
|
||||
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution'],
|
||||
WanTextToVideoApi: ['duration', 'size'],
|
||||
WanImageToVideoApi: ['duration', 'resolution']
|
||||
}
|
||||
return widgetMap[nodeType] || []
|
||||
}
|
||||
|
||||
@@ -1894,4 +1894,159 @@ describe('useNodePricing', () => {
|
||||
expect(getNodeDisplayPrice(missingDuration)).toBe('Token-based')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - WanTextToVideoApi', () => {
|
||||
it('should return $1.50 for 10s at 1080p', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanTextToVideoApi', [
|
||||
{ name: 'duration', value: '10' },
|
||||
{ name: 'size', value: '1080p: 4:3 (1632x1248)' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$1.50/Run') // 0.15 * 10
|
||||
})
|
||||
|
||||
it('should return $0.50 for 5s at 720p', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanTextToVideoApi', [
|
||||
{ name: 'duration', value: 5 },
|
||||
{ name: 'size', value: '720p: 16:9 (1280x720)' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.50/Run') // 0.10 * 5
|
||||
})
|
||||
|
||||
it('should return $0.15 for 3s at 480p', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanTextToVideoApi', [
|
||||
{ name: 'duration', value: '3' },
|
||||
{ name: 'size', value: '480p: 1:1 (624x624)' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.15/Run') // 0.05 * 3
|
||||
})
|
||||
|
||||
it('should fall back when widgets are missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const missingBoth = createMockNode('WanTextToVideoApi', [])
|
||||
const missingSize = createMockNode('WanTextToVideoApi', [
|
||||
{ name: 'duration', value: '5' }
|
||||
])
|
||||
const missingDuration = createMockNode('WanTextToVideoApi', [
|
||||
{ name: 'size', value: '1080p' }
|
||||
])
|
||||
|
||||
expect(getNodeDisplayPrice(missingBoth)).toBe('$0.05-0.15/second')
|
||||
expect(getNodeDisplayPrice(missingSize)).toBe('$0.05-0.15/second')
|
||||
expect(getNodeDisplayPrice(missingDuration)).toBe('$0.05-0.15/second')
|
||||
})
|
||||
|
||||
it('should fall back on invalid duration', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanTextToVideoApi', [
|
||||
{ name: 'duration', value: 'invalid' },
|
||||
{ name: 'size', value: '1080p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05-0.15/second')
|
||||
})
|
||||
|
||||
it('should fall back on unknown resolution', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanTextToVideoApi', [
|
||||
{ name: 'duration', value: '10' },
|
||||
{ name: 'size', value: '2K' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05-0.15/second')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - WanImageToVideoApi', () => {
|
||||
it('should return $0.80 for 8s at 720p', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'duration', value: 8 },
|
||||
{ name: 'resolution', value: '720p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.80/Run') // 0.10 * 8
|
||||
})
|
||||
|
||||
it('should return $0.60 for 12s at 480P', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'duration', value: '12' },
|
||||
{ name: 'resolution', value: '480P' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.60/Run') // 0.05 * 12
|
||||
})
|
||||
|
||||
it('should return $1.50 for 10s at 1080p', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'duration', value: '10' },
|
||||
{ name: 'resolution', value: '1080p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$1.50/Run') // 0.15 * 10
|
||||
})
|
||||
|
||||
it('should handle "5s" string duration at 1080P', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'duration', value: '5s' },
|
||||
{ name: 'resolution', value: '1080P' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.75/Run') // 0.15 * 5
|
||||
})
|
||||
|
||||
it('should fall back when widgets are missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const missingBoth = createMockNode('WanImageToVideoApi', [])
|
||||
const missingRes = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'duration', value: '5' }
|
||||
])
|
||||
const missingDuration = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'resolution', value: '1080p' }
|
||||
])
|
||||
|
||||
expect(getNodeDisplayPrice(missingBoth)).toBe('$0.05-0.15/second')
|
||||
expect(getNodeDisplayPrice(missingRes)).toBe('$0.05-0.15/second')
|
||||
expect(getNodeDisplayPrice(missingDuration)).toBe('$0.05-0.15/second')
|
||||
})
|
||||
|
||||
it('should fall back on invalid duration', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'duration', value: 'invalid' },
|
||||
{ name: 'resolution', value: '720p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05-0.15/second')
|
||||
})
|
||||
|
||||
it('should fall back on unknown resolution', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('WanImageToVideoApi', [
|
||||
{ name: 'duration', value: '10' },
|
||||
{ name: 'resolution', value: 'weird-res' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05-0.15/second')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user