mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
feat(api-nodes-pricing): add prices for Flux2ProImageNode (#6921)
## Summary Price badges for https://github.com/comfyanonymous/ComfyUI/pull/10880 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6921-feat-api-nodes-pricing-add-prices-for-Flux2ProImageNode-2b66d73d365081f2b269c77df7ef93d6) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -303,6 +303,46 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
FluxProKontextMaxNode: {
|
||||
displayPrice: '$0.08/Run'
|
||||
},
|
||||
Flux2ProImageNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const widthW = node.widgets?.find(
|
||||
(w) => w.name === 'width'
|
||||
) as IComboWidget
|
||||
const heightW = node.widgets?.find(
|
||||
(w) => w.name === 'height'
|
||||
) as IComboWidget
|
||||
|
||||
const w = Number(widthW?.value)
|
||||
const h = Number(heightW?.value)
|
||||
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
|
||||
// global min/max for this node given schema bounds (1MP..4MP output)
|
||||
return '$0.03–$0.15/Run'
|
||||
}
|
||||
|
||||
// Is the 'images' input connected?
|
||||
const imagesInput = node.inputs?.find(
|
||||
(i) => i.name === 'images'
|
||||
) as INodeInputSlot
|
||||
const hasRefs =
|
||||
typeof imagesInput?.link !== 'undefined' && imagesInput.link != null
|
||||
|
||||
// Output cost: ceil((w*h)/MP); first MP $0.03, each additional $0.015
|
||||
const MP = 1024 * 1024
|
||||
const outMP = Math.max(1, Math.floor((w * h + MP - 1) / MP))
|
||||
const outputCost = 0.03 + 0.015 * Math.max(outMP - 1, 0)
|
||||
|
||||
if (hasRefs) {
|
||||
// Unknown ref count/size on the frontend:
|
||||
// min extra is $0.015, max extra is $0.120 (8 MP cap / 8 refs)
|
||||
const minTotal = outputCost + 0.015
|
||||
const maxTotal = outputCost + 0.12
|
||||
return `~$${parseFloat(minTotal.toFixed(3))}–$${parseFloat(maxTotal.toFixed(3))}/Run`
|
||||
}
|
||||
|
||||
// Precise text-to-image price
|
||||
return `$${parseFloat(outputCost.toFixed(3))}/Run`
|
||||
}
|
||||
},
|
||||
OpenAIVideoSora2: {
|
||||
displayPrice: sora2PricingCalculator
|
||||
},
|
||||
@@ -1809,6 +1849,7 @@ export const useNodePricing = () => {
|
||||
IdeogramV3: ['rendering_speed', 'num_images', 'character_image'],
|
||||
FluxProKontextProNode: [],
|
||||
FluxProKontextMaxNode: [],
|
||||
Flux2ProImageNode: ['width', 'height', 'images'],
|
||||
VeoVideoGenerationNode: ['duration_seconds'],
|
||||
Veo3VideoGenerationNode: ['model', 'generate_audio'],
|
||||
LumaVideoNode: ['model', 'resolution', 'duration'],
|
||||
|
||||
@@ -94,6 +94,42 @@ describe('useNodePricing', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - Flux2ProImageNode', () => {
|
||||
it('should return precise price for text-to-image 1024x1024 (no refs)', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Flux2ProImageNode', [
|
||||
{ name: 'width', value: 1024 },
|
||||
{ name: 'height', value: 1024 }
|
||||
])
|
||||
|
||||
// 1024x1024 => 1 MP => $0.03
|
||||
expect(getNodeDisplayPrice(node)).toBe('$0.03/Run')
|
||||
})
|
||||
|
||||
it('should return minimum estimate when refs are connected (1024x1024)', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode(
|
||||
'Flux2ProImageNode',
|
||||
[
|
||||
{ name: 'width', value: 1024 },
|
||||
{ name: 'height', value: 1024 }
|
||||
],
|
||||
true,
|
||||
// connect the 'images' input
|
||||
[{ name: 'images', connected: true }]
|
||||
)
|
||||
|
||||
// 1024x1024 => 1 MP output = $0.03, min input add = $0.015 => ~$0.045 min
|
||||
expect(getNodeDisplayPrice(node)).toBe('~$0.045–$0.15/Run')
|
||||
})
|
||||
|
||||
it('should show fallback when width/height are missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Flux2ProImageNode', [])
|
||||
expect(getNodeDisplayPrice(node)).toBe('$0.03–$0.15/Run')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - KlingTextToVideoNode', () => {
|
||||
it('should return high price for kling-v2-1-master model', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
|
||||
Reference in New Issue
Block a user