mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
increase some API nodes pricing (#7156)
## Summary Changes for Runway, Luma and Ideogram nodes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7156-increase-some-API-nodes-pricing-2bf6d73d3650818d96c7ce53b3d77ef1) by [Unito](https://www.unito.io) --------- Co-authored-by: Luke Mino-Altherr <luke@comfy.org> Co-authored-by: Hunter <huntcsg@users.noreply.github.com>
This commit is contained in:
@@ -40,12 +40,12 @@ const calculateRunwayDurationPrice = (node: LGraphNode): string => {
|
||||
(w) => w.name === 'duration'
|
||||
) as IComboWidget
|
||||
|
||||
if (!durationWidget) return '$0.05/second'
|
||||
if (!durationWidget) return '$0.0715/second'
|
||||
|
||||
const duration = Number(durationWidget.value)
|
||||
// If duration is 0 or NaN, don't fall back to 5 seconds - just use 0
|
||||
const validDuration = isNaN(duration) ? 5 : duration
|
||||
const cost = (0.05 * validDuration).toFixed(2)
|
||||
const cost = (0.0715 * validDuration).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
|
||||
@@ -377,11 +377,11 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
(w) => w.name === 'turbo'
|
||||
) as IComboWidget
|
||||
|
||||
if (!numImagesWidget) return '$0.02-0.06 x num_images/Run'
|
||||
if (!numImagesWidget) return '$0.03-0.09 x num_images/Run'
|
||||
|
||||
const numImages = Number(numImagesWidget.value) || 1
|
||||
const turbo = String(turboWidget?.value).toLowerCase() === 'true'
|
||||
const basePrice = turbo ? 0.02 : 0.06
|
||||
const basePrice = turbo ? 0.0286 : 0.0858
|
||||
const cost = (basePrice * numImages).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
@@ -395,11 +395,11 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
(w) => w.name === 'turbo'
|
||||
) as IComboWidget
|
||||
|
||||
if (!numImagesWidget) return '$0.05-0.08 x num_images/Run'
|
||||
if (!numImagesWidget) return '$0.07-0.11 x num_images/Run'
|
||||
|
||||
const numImages = Number(numImagesWidget.value) || 1
|
||||
const turbo = String(turboWidget?.value).toLowerCase() === 'true'
|
||||
const basePrice = turbo ? 0.05 : 0.08
|
||||
const basePrice = turbo ? 0.0715 : 0.1144
|
||||
const cost = (basePrice * numImages).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
@@ -420,29 +420,29 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
characterInput.link != null
|
||||
|
||||
if (!renderingSpeedWidget)
|
||||
return '$0.03-0.08 x num_images/Run (varies with rendering speed & num_images)'
|
||||
return '$0.04-0.11 x num_images/Run (varies with rendering speed & num_images)'
|
||||
|
||||
const numImages = Number(numImagesWidget?.value) || 1
|
||||
let basePrice = 0.06 // default balanced price
|
||||
let basePrice = 0.0858 // default balanced price
|
||||
|
||||
const renderingSpeed = String(renderingSpeedWidget.value)
|
||||
if (renderingSpeed.toLowerCase().includes('quality')) {
|
||||
if (hasCharacter) {
|
||||
basePrice = 0.2
|
||||
basePrice = 0.286
|
||||
} else {
|
||||
basePrice = 0.09
|
||||
basePrice = 0.1287
|
||||
}
|
||||
} else if (renderingSpeed.toLowerCase().includes('default')) {
|
||||
if (hasCharacter) {
|
||||
basePrice = 0.15
|
||||
basePrice = 0.2145
|
||||
} else {
|
||||
basePrice = 0.06
|
||||
basePrice = 0.0858
|
||||
}
|
||||
} else if (renderingSpeed.toLowerCase().includes('turbo')) {
|
||||
if (hasCharacter) {
|
||||
basePrice = 0.1
|
||||
basePrice = 0.143
|
||||
} else {
|
||||
basePrice = 0.03
|
||||
basePrice = 0.0429
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,7 +758,7 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget || !resolutionWidget || !durationWidget) {
|
||||
return '$0.14-11.47/Run (varies with model, resolution & duration)'
|
||||
return '$0.20-16.40/Run (varies with model, resolution & duration)'
|
||||
}
|
||||
|
||||
const model = String(modelWidget.value)
|
||||
@@ -767,33 +767,33 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
|
||||
if (model.includes('ray-flash-2')) {
|
||||
if (duration.includes('5s')) {
|
||||
if (resolution.includes('4k')) return '$2.19/Run'
|
||||
if (resolution.includes('1080p')) return '$0.55/Run'
|
||||
if (resolution.includes('720p')) return '$0.24/Run'
|
||||
if (resolution.includes('540p')) return '$0.14/Run'
|
||||
if (resolution.includes('4k')) return '$3.13/Run'
|
||||
if (resolution.includes('1080p')) return '$0.79/Run'
|
||||
if (resolution.includes('720p')) return '$0.34/Run'
|
||||
if (resolution.includes('540p')) return '$0.20/Run'
|
||||
} else if (duration.includes('9s')) {
|
||||
if (resolution.includes('4k')) return '$3.95/Run'
|
||||
if (resolution.includes('1080p')) return '$0.99/Run'
|
||||
if (resolution.includes('720p')) return '$0.43/Run'
|
||||
if (resolution.includes('540p')) return '$0.252/Run'
|
||||
if (resolution.includes('4k')) return '$5.65/Run'
|
||||
if (resolution.includes('1080p')) return '$1.42/Run'
|
||||
if (resolution.includes('720p')) return '$0.61/Run'
|
||||
if (resolution.includes('540p')) return '$0.36/Run'
|
||||
}
|
||||
} else if (model.includes('ray-2')) {
|
||||
if (duration.includes('5s')) {
|
||||
if (resolution.includes('4k')) return '$6.37/Run'
|
||||
if (resolution.includes('1080p')) return '$1.59/Run'
|
||||
if (resolution.includes('720p')) return '$0.71/Run'
|
||||
if (resolution.includes('540p')) return '$0.40/Run'
|
||||
if (resolution.includes('4k')) return '$9.11/Run'
|
||||
if (resolution.includes('1080p')) return '$2.27/Run'
|
||||
if (resolution.includes('720p')) return '$1.02/Run'
|
||||
if (resolution.includes('540p')) return '$0.57/Run'
|
||||
} else if (duration.includes('9s')) {
|
||||
if (resolution.includes('4k')) return '$11.47/Run'
|
||||
if (resolution.includes('1080p')) return '$2.87/Run'
|
||||
if (resolution.includes('720p')) return '$1.28/Run'
|
||||
if (resolution.includes('540p')) return '$0.72/Run'
|
||||
if (resolution.includes('4k')) return '$16.40/Run'
|
||||
if (resolution.includes('1080p')) return '$4.10/Run'
|
||||
if (resolution.includes('720p')) return '$1.83/Run'
|
||||
if (resolution.includes('540p')) return '$1.03/Run'
|
||||
}
|
||||
} else if (model.includes('ray-1.6')) {
|
||||
return '$0.35/Run'
|
||||
} else if (model.includes('ray-1-6')) {
|
||||
return '$0.50/Run'
|
||||
}
|
||||
|
||||
return '$0.55/Run'
|
||||
return '$0.79/Run'
|
||||
}
|
||||
},
|
||||
LumaVideoNode: {
|
||||
@@ -809,7 +809,7 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget || !resolutionWidget || !durationWidget) {
|
||||
return '$0.14-11.47/Run (varies with model, resolution & duration)'
|
||||
return '$0.20-16.40/Run (varies with model, resolution & duration)'
|
||||
}
|
||||
|
||||
const model = String(modelWidget.value)
|
||||
@@ -818,33 +818,33 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
|
||||
if (model.includes('ray-flash-2')) {
|
||||
if (duration.includes('5s')) {
|
||||
if (resolution.includes('4k')) return '$2.19/Run'
|
||||
if (resolution.includes('1080p')) return '$0.55/Run'
|
||||
if (resolution.includes('720p')) return '$0.24/Run'
|
||||
if (resolution.includes('540p')) return '$0.14/Run'
|
||||
if (resolution.includes('4k')) return '$3.13/Run'
|
||||
if (resolution.includes('1080p')) return '$0.79/Run'
|
||||
if (resolution.includes('720p')) return '$0.34/Run'
|
||||
if (resolution.includes('540p')) return '$0.20/Run'
|
||||
} else if (duration.includes('9s')) {
|
||||
if (resolution.includes('4k')) return '$3.95/Run'
|
||||
if (resolution.includes('1080p')) return '$0.99/Run'
|
||||
if (resolution.includes('720p')) return '$0.43/Run'
|
||||
if (resolution.includes('540p')) return '$0.252/Run'
|
||||
if (resolution.includes('4k')) return '$5.65/Run'
|
||||
if (resolution.includes('1080p')) return '$1.42/Run'
|
||||
if (resolution.includes('720p')) return '$0.61/Run'
|
||||
if (resolution.includes('540p')) return '$0.36/Run'
|
||||
}
|
||||
} else if (model.includes('ray-2')) {
|
||||
if (duration.includes('5s')) {
|
||||
if (resolution.includes('4k')) return '$6.37/Run'
|
||||
if (resolution.includes('1080p')) return '$1.59/Run'
|
||||
if (resolution.includes('720p')) return '$0.71/Run'
|
||||
if (resolution.includes('540p')) return '$0.40/Run'
|
||||
if (resolution.includes('4k')) return '$9.11/Run'
|
||||
if (resolution.includes('1080p')) return '$2.27/Run'
|
||||
if (resolution.includes('720p')) return '$1.02/Run'
|
||||
if (resolution.includes('540p')) return '$0.57/Run'
|
||||
} else if (duration.includes('9s')) {
|
||||
if (resolution.includes('4k')) return '$11.47/Run'
|
||||
if (resolution.includes('1080p')) return '$2.87/Run'
|
||||
if (resolution.includes('720p')) return '$1.28/Run'
|
||||
if (resolution.includes('540p')) return '$0.72/Run'
|
||||
if (resolution.includes('4k')) return '$16.40/Run'
|
||||
if (resolution.includes('1080p')) return '$4.10/Run'
|
||||
if (resolution.includes('720p')) return '$1.83/Run'
|
||||
if (resolution.includes('540p')) return '$1.03/Run'
|
||||
}
|
||||
} else if (model.includes('ray-1-6')) {
|
||||
return '$0.35/Run'
|
||||
return '$0.50/Run'
|
||||
}
|
||||
|
||||
return '$0.55/Run'
|
||||
return '$0.79/Run'
|
||||
}
|
||||
},
|
||||
MinimaxImageToVideoNode: {
|
||||
@@ -1326,18 +1326,18 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget || !aspectRatioWidget) {
|
||||
return '$0.0045-0.0182/Run (varies with model & aspect ratio)'
|
||||
return '$0.0064-0.026/Run (varies with model & aspect ratio)'
|
||||
}
|
||||
|
||||
const model = String(modelWidget.value)
|
||||
|
||||
if (model.includes('photon-flash-1')) {
|
||||
return '$0.0019/Run'
|
||||
return '$0.0027/Run'
|
||||
} else if (model.includes('photon-1')) {
|
||||
return '$0.0073/Run'
|
||||
return '$0.0104/Run'
|
||||
}
|
||||
|
||||
return '$0.0172/Run'
|
||||
return '$0.0246/Run'
|
||||
}
|
||||
},
|
||||
LumaImageModifyNode: {
|
||||
@@ -1347,18 +1347,18 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget) {
|
||||
return '$0.0019-0.0073/Run (varies with model)'
|
||||
return '$0.0027-0.0104/Run (varies with model)'
|
||||
}
|
||||
|
||||
const model = String(modelWidget.value)
|
||||
|
||||
if (model.includes('photon-flash-1')) {
|
||||
return '$0.0019/Run'
|
||||
return '$0.0027/Run'
|
||||
} else if (model.includes('photon-1')) {
|
||||
return '$0.0073/Run'
|
||||
return '$0.0104/Run'
|
||||
}
|
||||
|
||||
return '$0.0172/Run'
|
||||
return '$0.0246/Run'
|
||||
}
|
||||
},
|
||||
MoonvalleyTxt2VideoNode: {
|
||||
@@ -1420,7 +1420,7 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
},
|
||||
// Runway nodes - using actual node names from ComfyUI
|
||||
RunwayTextToImageNode: {
|
||||
displayPrice: '$0.08/Run'
|
||||
displayPrice: '$0.11/Run'
|
||||
},
|
||||
RunwayImageToVideoNodeGen3a: {
|
||||
displayPrice: calculateRunwayDurationPrice
|
||||
|
||||
@@ -577,32 +577,32 @@ describe('useNodePricing', () => {
|
||||
{
|
||||
rendering_speed: 'Quality',
|
||||
character_image: false,
|
||||
expected: '$0.09/Run'
|
||||
expected: '$0.13/Run'
|
||||
},
|
||||
{
|
||||
rendering_speed: 'Quality',
|
||||
character_image: true,
|
||||
expected: '$0.20/Run'
|
||||
expected: '$0.29/Run'
|
||||
},
|
||||
{
|
||||
rendering_speed: 'Default',
|
||||
character_image: false,
|
||||
expected: '$0.06/Run'
|
||||
expected: '$0.09/Run'
|
||||
},
|
||||
{
|
||||
rendering_speed: 'Default',
|
||||
character_image: true,
|
||||
expected: '$0.15/Run'
|
||||
expected: '$0.21/Run'
|
||||
},
|
||||
{
|
||||
rendering_speed: 'Turbo',
|
||||
character_image: false,
|
||||
expected: '$0.03/Run'
|
||||
expected: '$0.04/Run'
|
||||
},
|
||||
{
|
||||
rendering_speed: 'Turbo',
|
||||
character_image: true,
|
||||
expected: '$0.10/Run'
|
||||
expected: '$0.14/Run'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -623,7 +623,7 @@ describe('useNodePricing', () => {
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$0.03-0.08 x num_images/Run (varies with rendering speed & num_images)'
|
||||
'$0.04-0.11 x num_images/Run (varies with rendering speed & num_images)'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -635,7 +635,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.27/Run') // 0.09 * 3
|
||||
expect(price).toBe('$0.39/Run') // 0.09 * 3 * 1.43
|
||||
})
|
||||
|
||||
it('should multiply price by num_images for Turbo rendering speed', () => {
|
||||
@@ -646,7 +646,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.15/Run') // 0.03 * 5
|
||||
expect(price).toBe('$0.21/Run') // 0.03 * 5 * 1.43
|
||||
})
|
||||
})
|
||||
|
||||
@@ -770,7 +770,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$2.19/Run')
|
||||
expect(price).toBe('$3.13/Run')
|
||||
})
|
||||
|
||||
it('should return $6.37 for ray-2 4K 5s', () => {
|
||||
@@ -782,7 +782,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$6.37/Run')
|
||||
expect(price).toBe('$9.11/Run')
|
||||
})
|
||||
|
||||
it('should return $0.35 for ray-1-6 model', () => {
|
||||
@@ -794,7 +794,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.35/Run')
|
||||
expect(price).toBe('$0.50/Run')
|
||||
})
|
||||
|
||||
it('should return range when widgets are missing', () => {
|
||||
@@ -803,7 +803,7 @@ describe('useNodePricing', () => {
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$0.14-11.47/Run (varies with model, resolution & duration)'
|
||||
'$0.20-16.40/Run (varies with model, resolution & duration)'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1192,7 +1192,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.18/Run') // 0.06 * 3
|
||||
expect(price).toBe('$0.26/Run') // 0.06 * 3 * 1.43
|
||||
})
|
||||
|
||||
it('should calculate dynamic pricing for IdeogramV2 based on num_images value', () => {
|
||||
@@ -1202,7 +1202,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.32/Run') // 0.08 * 4
|
||||
expect(price).toBe('$0.46/Run') // 0.08 * 4 * 1.43
|
||||
})
|
||||
|
||||
it('should fall back to static display when num_images widget is missing for IdeogramV1', () => {
|
||||
@@ -1210,7 +1210,7 @@ describe('useNodePricing', () => {
|
||||
const node = createMockNode('IdeogramV1', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.02-0.06 x num_images/Run')
|
||||
expect(price).toBe('$0.03-0.09 x num_images/Run')
|
||||
})
|
||||
|
||||
it('should fall back to static display when num_images widget is missing for IdeogramV2', () => {
|
||||
@@ -1218,7 +1218,7 @@ describe('useNodePricing', () => {
|
||||
const node = createMockNode('IdeogramV2', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05-0.08 x num_images/Run')
|
||||
expect(price).toBe('$0.07-0.11 x num_images/Run')
|
||||
})
|
||||
|
||||
it('should handle edge case when num_images value is 1 for IdeogramV1', () => {
|
||||
@@ -1228,7 +1228,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.06/Run') // 0.06 * 1 (turbo=false by default)
|
||||
expect(price).toBe('$0.09/Run') // 0.06 * 1 * 1.43 (turbo=false by default)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1435,7 +1435,7 @@ describe('useNodePricing', () => {
|
||||
const node = createMockNode('RunwayTextToImageNode')
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.08/Run')
|
||||
expect(price).toBe('$0.11/Run')
|
||||
})
|
||||
|
||||
it('should calculate dynamic pricing for RunwayImageToVideoNodeGen3a', () => {
|
||||
@@ -1445,7 +1445,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.50/Run') // 0.05 * 10
|
||||
expect(price).toBe('$0.71/Run') // 0.05 * 10 * 1.43
|
||||
})
|
||||
|
||||
it('should return fallback for RunwayImageToVideoNodeGen3a without duration', () => {
|
||||
@@ -1453,7 +1453,7 @@ describe('useNodePricing', () => {
|
||||
const node = createMockNode('RunwayImageToVideoNodeGen3a', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05/second')
|
||||
expect(price).toBe('$0.0715/second')
|
||||
})
|
||||
|
||||
it('should handle zero duration for RunwayImageToVideoNodeGen3a', () => {
|
||||
@@ -1473,7 +1473,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.25/Run') // Falls back to 5 seconds: 0.05 * 5
|
||||
expect(price).toBe('$0.36/Run') // Falls back to 5 seconds: 0.05 * 5 * 1.43
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1810,8 +1810,8 @@ describe('useNodePricing', () => {
|
||||
// Test edge cases
|
||||
const testCases = [
|
||||
{ duration: 0, expected: '$0.00/Run' }, // Now correctly handles 0 duration
|
||||
{ duration: 1, expected: '$0.05/Run' },
|
||||
{ duration: 30, expected: '$1.50/Run' }
|
||||
{ duration: 1, expected: '$0.07/Run' },
|
||||
{ duration: 30, expected: '$2.15/Run' }
|
||||
]
|
||||
|
||||
testCases.forEach(({ duration, expected }) => {
|
||||
@@ -1828,7 +1828,7 @@ describe('useNodePricing', () => {
|
||||
{ name: 'duration', value: 'invalid-string' }
|
||||
])
|
||||
// When Number('invalid-string') returns NaN, it falls back to 5 seconds
|
||||
expect(getNodeDisplayPrice(node)).toBe('$0.25/Run')
|
||||
expect(getNodeDisplayPrice(node)).toBe('$0.36/Run')
|
||||
})
|
||||
|
||||
it('should handle missing duration widget gracefully', () => {
|
||||
@@ -1841,7 +1841,7 @@ describe('useNodePricing', () => {
|
||||
|
||||
nodes.forEach((nodeType) => {
|
||||
const node = createMockNode(nodeType, [])
|
||||
expect(getNodeDisplayPrice(node)).toBe('$0.05/second')
|
||||
expect(getNodeDisplayPrice(node)).toBe('$0.0715/second')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user