diff --git a/src/composables/node/useNodePricing.ts b/src/composables/node/useNodePricing.ts index 3e8891fd82..19bd55f349 100644 --- a/src/composables/node/useNodePricing.ts +++ b/src/composables/node/useNodePricing.ts @@ -111,30 +111,55 @@ const apiNodeCosts: Record = displayPrice: '$0.06/Run' }, IdeogramV1: { - displayPrice: '$0.06/Run' + displayPrice: (node: LGraphNode): string => { + const numImagesWidget = node.widgets?.find( + (w) => w.name === 'num_images' + ) as IComboWidget + if (!numImagesWidget) return '$0.06 x num_images/Run' + + const numImages = Number(numImagesWidget.value) || 1 + const cost = (0.06 * numImages).toFixed(2) + return `$${cost}/Run` + } }, IdeogramV2: { - displayPrice: '$0.08/Run' + displayPrice: (node: LGraphNode): string => { + const numImagesWidget = node.widgets?.find( + (w) => w.name === 'num_images' + ) as IComboWidget + if (!numImagesWidget) return '$0.08 x num_images/Run' + + const numImages = Number(numImagesWidget.value) || 1 + const cost = (0.08 * numImages).toFixed(2) + return `$${cost}/Run` + } }, IdeogramV3: { displayPrice: (node: LGraphNode): string => { const renderingSpeedWidget = node.widgets?.find( (w) => w.name === 'rendering_speed' ) as IComboWidget + const numImagesWidget = node.widgets?.find( + (w) => w.name === 'num_images' + ) as IComboWidget if (!renderingSpeedWidget) - return '$0.03-0.08/Run (varies with rendering speed)' + return '$0.03-0.08 x num_images/Run (varies with rendering speed & num_images)' + + const numImages = Number(numImagesWidget?.value) || 1 + let basePrice = 0.06 // default balanced price const renderingSpeed = String(renderingSpeedWidget.value) if (renderingSpeed.toLowerCase().includes('quality')) { - return '$0.08/Run' + basePrice = 0.08 } else if (renderingSpeed.toLowerCase().includes('balanced')) { - return '$0.06/Run' + basePrice = 0.06 } else if (renderingSpeed.toLowerCase().includes('turbo')) { - return '$0.03/Run' + basePrice = 0.03 } - return '$0.06/Run' + const totalCost = (basePrice * numImages).toFixed(2) + return `$${totalCost}/Run` } }, KlingCameraControlI2VNode: { @@ -897,7 +922,9 @@ export const useNodePricing = () => { OpenAIDalle3: ['size', 'quality'], OpenAIDalle2: ['size'], OpenAIGPTImage1: ['quality'], - IdeogramV3: ['rendering_speed'], + IdeogramV1: ['num_images'], + IdeogramV2: ['num_images'], + IdeogramV3: ['rendering_speed', 'num_images'], VeoVideoGenerationNode: ['duration_seconds'], LumaVideoNode: ['model', 'resolution', 'duration'], LumaImageToVideoNode: ['model', 'resolution', 'duration'], diff --git a/tests-ui/tests/composables/node/useNodePricing.test.ts b/tests-ui/tests/composables/node/useNodePricing.test.ts index 70ae5c6215..92b9ac9ee1 100644 --- a/tests-ui/tests/composables/node/useNodePricing.test.ts +++ b/tests-ui/tests/composables/node/useNodePricing.test.ts @@ -335,7 +335,31 @@ describe('useNodePricing', () => { const node = createMockNode('IdeogramV3', []) const price = getNodeDisplayPrice(node) - expect(price).toBe('$0.03-0.08/Run (varies with rendering speed)') + expect(price).toBe( + '$0.03-0.08 x num_images/Run (varies with rendering speed & num_images)' + ) + }) + + it('should multiply price by num_images for Quality rendering speed', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('IdeogramV3', [ + { name: 'rendering_speed', value: 'Quality' }, + { name: 'num_images', value: 3 } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.24/Run') // 0.08 * 3 + }) + + it('should multiply price by num_images for Turbo rendering speed', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('IdeogramV3', [ + { name: 'rendering_speed', value: 'Turbo' }, + { name: 'num_images', value: 5 } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.15/Run') // 0.03 * 5 }) }) @@ -742,6 +766,29 @@ describe('useNodePricing', () => { expect(widgetNames).toEqual([]) }) + describe('Ideogram nodes with num_images parameter', () => { + it('should return correct widget names for IdeogramV1', () => { + const { getRelevantWidgetNames } = useNodePricing() + + const widgetNames = getRelevantWidgetNames('IdeogramV1') + expect(widgetNames).toEqual(['num_images']) + }) + + it('should return correct widget names for IdeogramV2', () => { + const { getRelevantWidgetNames } = useNodePricing() + + const widgetNames = getRelevantWidgetNames('IdeogramV2') + expect(widgetNames).toEqual(['num_images']) + }) + + it('should return correct widget names for IdeogramV3', () => { + const { getRelevantWidgetNames } = useNodePricing() + + const widgetNames = getRelevantWidgetNames('IdeogramV3') + expect(widgetNames).toEqual(['rendering_speed', 'num_images']) + }) + }) + describe('Recraft nodes with n parameter', () => { it('should return correct widget names for RecraftTextToImageNode', () => { const { getRelevantWidgetNames } = useNodePricing() @@ -759,6 +806,54 @@ describe('useNodePricing', () => { }) }) + describe('Ideogram nodes dynamic pricing', () => { + it('should calculate dynamic pricing for IdeogramV1 based on num_images value', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('IdeogramV1', [ + { name: 'num_images', value: 3 } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.18/Run') // 0.06 * 3 + }) + + it('should calculate dynamic pricing for IdeogramV2 based on num_images value', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('IdeogramV2', [ + { name: 'num_images', value: 4 } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.32/Run') // 0.08 * 4 + }) + + it('should fall back to static display when num_images widget is missing for IdeogramV1', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('IdeogramV1', []) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.06 x num_images/Run') + }) + + it('should fall back to static display when num_images widget is missing for IdeogramV2', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('IdeogramV2', []) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.08 x num_images/Run') + }) + + it('should handle edge case when num_images value is 1 for IdeogramV1', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('IdeogramV1', [ + { name: 'num_images', value: 1 } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.06/Run') // 0.06 * 1 + }) + }) + describe('Recraft nodes dynamic pricing', () => { it('should calculate dynamic pricing for RecraftTextToImageNode based on n value', () => { const { getNodeDisplayPrice } = useNodePricing()