mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 15:40:10 +00:00
[Hotfix] Cherry-pick pricing fixes to core/1.23 (#4383)
This commit is contained in:
@@ -111,30 +111,55 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
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: {
|
||||
@@ -250,30 +275,33 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w) => w.name === 'model_name'
|
||||
) as IComboWidget
|
||||
const nWidget = node.widgets?.find(
|
||||
(w) => w.name === 'n'
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget)
|
||||
return '$0.0035-0.028/Run (varies with modality & model)'
|
||||
return '$0.0035-0.028 x n/Run (varies with modality & model)'
|
||||
|
||||
const model = String(modelWidget.value)
|
||||
const n = Number(nWidget?.value) || 1
|
||||
let basePrice = 0.014 // default
|
||||
|
||||
if (modality.includes('text to image')) {
|
||||
if (model.includes('kling-v1')) {
|
||||
return '$0.0035/Run'
|
||||
} else if (
|
||||
model.includes('kling-v1-5') ||
|
||||
model.includes('kling-v2')
|
||||
) {
|
||||
return '$0.014/Run'
|
||||
if (model.includes('kling-v1-5') || model.includes('kling-v2')) {
|
||||
basePrice = 0.014
|
||||
} else if (model.includes('kling-v1')) {
|
||||
basePrice = 0.0035
|
||||
}
|
||||
} else if (modality.includes('image to image')) {
|
||||
if (model.includes('kling-v1')) {
|
||||
return '$0.0035/Run'
|
||||
} else if (model.includes('kling-v1-5')) {
|
||||
return '$0.028/Run'
|
||||
if (model.includes('kling-v1-5')) {
|
||||
basePrice = 0.028
|
||||
} else if (model.includes('kling-v1')) {
|
||||
basePrice = 0.0035
|
||||
}
|
||||
}
|
||||
|
||||
return '$0.014/Run'
|
||||
const totalCost = (basePrice * n).toFixed(4)
|
||||
return `$${totalCost}/Run`
|
||||
}
|
||||
},
|
||||
KlingLipSyncAudioToVideoNode: {
|
||||
@@ -498,19 +526,26 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
const sizeWidget = node.widgets?.find(
|
||||
(w) => w.name === 'size'
|
||||
) as IComboWidget
|
||||
const nWidget = node.widgets?.find(
|
||||
(w) => w.name === 'n'
|
||||
) as IComboWidget
|
||||
|
||||
if (!sizeWidget) return '$0.016-0.02/Run (varies with size)'
|
||||
if (!sizeWidget) return '$0.016-0.02 x n/Run (varies with size & n)'
|
||||
|
||||
const size = String(sizeWidget.value)
|
||||
const n = Number(nWidget?.value) || 1
|
||||
let basePrice = 0.02 // default
|
||||
|
||||
if (size.includes('1024x1024')) {
|
||||
return '$0.02/Run'
|
||||
basePrice = 0.02
|
||||
} else if (size.includes('512x512')) {
|
||||
return '$0.018/Run'
|
||||
basePrice = 0.018
|
||||
} else if (size.includes('256x256')) {
|
||||
return '$0.016/Run'
|
||||
basePrice = 0.016
|
||||
}
|
||||
|
||||
return '$0.02/Run'
|
||||
const totalCost = (basePrice * n).toFixed(3)
|
||||
return `$${totalCost}/Run`
|
||||
}
|
||||
},
|
||||
OpenAIDalle3: {
|
||||
@@ -545,19 +580,30 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
const qualityWidget = node.widgets?.find(
|
||||
(w) => w.name === 'quality'
|
||||
) as IComboWidget
|
||||
const nWidget = node.widgets?.find(
|
||||
(w) => w.name === 'n'
|
||||
) as IComboWidget
|
||||
|
||||
if (!qualityWidget) return '$0.011-0.30/Run (varies with quality)'
|
||||
if (!qualityWidget)
|
||||
return '$0.011-0.30 x n/Run (varies with quality & n)'
|
||||
|
||||
const quality = String(qualityWidget.value)
|
||||
const n = Number(nWidget?.value) || 1
|
||||
let basePriceRange = '$0.046-0.07' // default medium
|
||||
|
||||
if (quality.includes('high')) {
|
||||
return '$0.167-0.30/Run'
|
||||
basePriceRange = '$0.167-0.30'
|
||||
} else if (quality.includes('medium')) {
|
||||
return '$0.046-0.07/Run'
|
||||
basePriceRange = '$0.046-0.07'
|
||||
} else if (quality.includes('low')) {
|
||||
return '$0.011-0.02/Run'
|
||||
basePriceRange = '$0.011-0.02'
|
||||
}
|
||||
|
||||
return '$0.046-0.07/Run'
|
||||
if (n === 1) {
|
||||
return `${basePriceRange}/Run`
|
||||
} else {
|
||||
return `${basePriceRange} x ${n}/Run`
|
||||
}
|
||||
}
|
||||
},
|
||||
PikaImageToVideoNode2_2: {
|
||||
@@ -692,6 +738,42 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
RecraftCrispUpscaleNode: {
|
||||
displayPrice: '$0.004/Run'
|
||||
},
|
||||
RecraftGenerateColorFromImageNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const nWidget = node.widgets?.find(
|
||||
(w) => w.name === 'n'
|
||||
) as IComboWidget
|
||||
if (!nWidget) return '$0.04 x n/Run'
|
||||
|
||||
const n = Number(nWidget.value) || 1
|
||||
const cost = (0.04 * n).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
RecraftGenerateImageNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const nWidget = node.widgets?.find(
|
||||
(w) => w.name === 'n'
|
||||
) as IComboWidget
|
||||
if (!nWidget) return '$0.04 x n/Run'
|
||||
|
||||
const n = Number(nWidget.value) || 1
|
||||
const cost = (0.04 * n).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
RecraftGenerateVectorImageNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const nWidget = node.widgets?.find(
|
||||
(w) => w.name === 'n'
|
||||
) as IComboWidget
|
||||
if (!nWidget) return '$0.08 x n/Run'
|
||||
|
||||
const n = Number(nWidget.value) || 1
|
||||
const cost = (0.08 * n).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
RecraftImageInpaintingNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const nWidget = node.widgets?.find(
|
||||
@@ -747,7 +829,16 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
}
|
||||
},
|
||||
RecraftVectorizeImageNode: {
|
||||
displayPrice: '$0.01/Run'
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const nWidget = node.widgets?.find(
|
||||
(w) => w.name === 'n'
|
||||
) as IComboWidget
|
||||
if (!nWidget) return '$0.01 x n/Run'
|
||||
|
||||
const n = Number(nWidget.value) || 1
|
||||
const cost = (0.01 * n).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
StabilityStableImageSD_3_5Node: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
@@ -856,6 +947,63 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
|
||||
return '$0.0172/Run'
|
||||
}
|
||||
},
|
||||
MoonvalleyTxt2VideoNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const lengthWidget = node.widgets?.find(
|
||||
(w) => w.name === 'length'
|
||||
) as IComboWidget
|
||||
|
||||
// If no length widget exists, default to 5s pricing
|
||||
if (!lengthWidget) return '$1.50/Run'
|
||||
|
||||
const length = String(lengthWidget.value)
|
||||
if (length === '5s') {
|
||||
return '$1.50/Run'
|
||||
} else if (length === '10s') {
|
||||
return '$3.00/Run'
|
||||
}
|
||||
|
||||
return '$1.50/Run'
|
||||
}
|
||||
},
|
||||
MoonvalleyImg2VideoNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const lengthWidget = node.widgets?.find(
|
||||
(w) => w.name === 'length'
|
||||
) as IComboWidget
|
||||
|
||||
// If no length widget exists, default to 5s pricing
|
||||
if (!lengthWidget) return '$1.50/Run'
|
||||
|
||||
const length = String(lengthWidget.value)
|
||||
if (length === '5s') {
|
||||
return '$1.50/Run'
|
||||
} else if (length === '10s') {
|
||||
return '$3.00/Run'
|
||||
}
|
||||
|
||||
return '$1.50/Run'
|
||||
}
|
||||
},
|
||||
MoonvalleyVideo2VideoNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const lengthWidget = node.widgets?.find(
|
||||
(w) => w.name === 'length'
|
||||
) as IComboWidget
|
||||
|
||||
// If no length widget exists, default to 5s pricing
|
||||
if (!lengthWidget) return '$2.25/Run'
|
||||
|
||||
const length = String(lengthWidget.value)
|
||||
if (length === '5s') {
|
||||
return '$2.25/Run'
|
||||
} else if (length === '10s') {
|
||||
return '$4.00/Run'
|
||||
}
|
||||
|
||||
return '$2.25/Run'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,14 +1038,16 @@ export const useNodePricing = () => {
|
||||
const widgetMap: Record<string, string[]> = {
|
||||
KlingTextToVideoNode: ['mode', 'model_name', 'duration'],
|
||||
KlingImage2VideoNode: ['mode', 'model_name', 'duration'],
|
||||
KlingImageGenerationNode: ['modality', 'model_name'],
|
||||
KlingImageGenerationNode: ['modality', 'model_name', 'n'],
|
||||
KlingDualCharacterVideoEffectNode: ['mode', 'model_name', 'duration'],
|
||||
KlingSingleImageVideoEffectNode: ['effect_scene'],
|
||||
KlingStartEndFrameNode: ['mode', 'model_name', 'duration'],
|
||||
OpenAIDalle3: ['size', 'quality'],
|
||||
OpenAIDalle2: ['size'],
|
||||
OpenAIGPTImage1: ['quality'],
|
||||
IdeogramV3: ['rendering_speed'],
|
||||
OpenAIDalle2: ['size', 'n'],
|
||||
OpenAIGPTImage1: ['quality', 'n'],
|
||||
IdeogramV1: ['num_images'],
|
||||
IdeogramV2: ['num_images'],
|
||||
IdeogramV3: ['rendering_speed', 'num_images'],
|
||||
VeoVideoGenerationNode: ['duration_seconds'],
|
||||
LumaVideoNode: ['model', 'resolution', 'duration'],
|
||||
LumaImageToVideoNode: ['model', 'resolution', 'duration'],
|
||||
@@ -918,7 +1068,14 @@ export const useNodePricing = () => {
|
||||
RecraftTextToImageNode: ['n'],
|
||||
RecraftImageToImageNode: ['n'],
|
||||
RecraftImageInpaintingNode: ['n'],
|
||||
RecraftTextToVectorNode: ['n']
|
||||
RecraftTextToVectorNode: ['n'],
|
||||
RecraftVectorizeImageNode: ['n'],
|
||||
RecraftGenerateColorFromImageNode: ['n'],
|
||||
RecraftGenerateImageNode: ['n'],
|
||||
RecraftGenerateVectorImageNode: ['n'],
|
||||
MoonvalleyTxt2VideoNode: ['length'],
|
||||
MoonvalleyImg2VideoNode: ['length'],
|
||||
MoonvalleyVideo2VideoNode: ['length']
|
||||
}
|
||||
return widgetMap[nodeType] || []
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.02/Run')
|
||||
expect(price).toBe('$0.020/Run')
|
||||
})
|
||||
|
||||
it('should return $0.018 for 512x512 size', () => {
|
||||
@@ -255,7 +255,7 @@ describe('useNodePricing', () => {
|
||||
const node = createMockNode('OpenAIDalle2', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.016-0.02/Run (varies with size)')
|
||||
expect(price).toBe('$0.016-0.02 x n/Run (varies with size & n)')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -295,7 +295,7 @@ describe('useNodePricing', () => {
|
||||
const node = createMockNode('OpenAIGPTImage1', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.011-0.30/Run (varies with quality)')
|
||||
expect(price).toBe('$0.011-0.30 x n/Run (varies with quality & n)')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
@@ -799,4 +894,133 @@ describe('useNodePricing', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('OpenAI nodes dynamic pricing with n parameter', () => {
|
||||
it('should calculate dynamic pricing for OpenAIDalle2 based on size and n', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('OpenAIDalle2', [
|
||||
{ name: 'size', value: '1024x1024' },
|
||||
{ name: 'n', value: 3 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.060/Run') // 0.02 * 3
|
||||
})
|
||||
|
||||
it('should calculate dynamic pricing for OpenAIGPTImage1 based on quality and n', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('OpenAIGPTImage1', [
|
||||
{ name: 'quality', value: 'low' },
|
||||
{ name: 'n', value: 2 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.011-0.02 x 2/Run')
|
||||
})
|
||||
|
||||
it('should fall back to static display when n widget is missing for OpenAIDalle2', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('OpenAIDalle2', [
|
||||
{ name: 'size', value: '512x512' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.018/Run') // n defaults to 1
|
||||
})
|
||||
})
|
||||
|
||||
describe('KlingImageGenerationNode dynamic pricing with n parameter', () => {
|
||||
it('should calculate dynamic pricing for text-to-image with kling-v1', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('KlingImageGenerationNode', [
|
||||
{ name: 'model_name', value: 'kling-v1' },
|
||||
{ name: 'n', value: 4 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.0140/Run') // 0.0035 * 4
|
||||
})
|
||||
|
||||
it('should calculate dynamic pricing for text-to-image with kling-v1-5', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
// Mock node without image input (text-to-image mode)
|
||||
const node = createMockNode('KlingImageGenerationNode', [
|
||||
{ name: 'model_name', value: 'kling-v1-5' },
|
||||
{ name: 'n', value: 2 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.0280/Run') // For kling-v1-5 text-to-image: 0.014 * 2
|
||||
})
|
||||
|
||||
it('should fall back to static display when model widget is missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('KlingImageGenerationNode', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.0035-0.028 x n/Run (varies with modality & model)')
|
||||
})
|
||||
})
|
||||
|
||||
describe('New Recraft nodes dynamic pricing', () => {
|
||||
it('should calculate dynamic pricing for RecraftGenerateImageNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('RecraftGenerateImageNode', [
|
||||
{ name: 'n', value: 3 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.12/Run') // 0.04 * 3
|
||||
})
|
||||
|
||||
it('should calculate dynamic pricing for RecraftVectorizeImageNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('RecraftVectorizeImageNode', [
|
||||
{ name: 'n', value: 5 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05/Run') // 0.01 * 5
|
||||
})
|
||||
|
||||
it('should calculate dynamic pricing for RecraftGenerateVectorImageNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('RecraftGenerateVectorImageNode', [
|
||||
{ name: 'n', value: 2 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.16/Run') // 0.08 * 2
|
||||
})
|
||||
})
|
||||
|
||||
describe('Widget names for reactive updates', () => {
|
||||
it('should include n parameter for OpenAI nodes', () => {
|
||||
const { getRelevantWidgetNames } = useNodePricing()
|
||||
|
||||
expect(getRelevantWidgetNames('OpenAIDalle2')).toEqual(['size', 'n'])
|
||||
expect(getRelevantWidgetNames('OpenAIGPTImage1')).toEqual([
|
||||
'quality',
|
||||
'n'
|
||||
])
|
||||
})
|
||||
|
||||
it('should include n parameter for Kling and new Recraft nodes', () => {
|
||||
const { getRelevantWidgetNames } = useNodePricing()
|
||||
|
||||
expect(getRelevantWidgetNames('KlingImageGenerationNode')).toEqual([
|
||||
'modality',
|
||||
'model_name',
|
||||
'n'
|
||||
])
|
||||
expect(getRelevantWidgetNames('RecraftVectorizeImageNode')).toEqual(['n'])
|
||||
expect(getRelevantWidgetNames('RecraftGenerateImageNode')).toEqual(['n'])
|
||||
expect(getRelevantWidgetNames('RecraftGenerateVectorImageNode')).toEqual([
|
||||
'n'
|
||||
])
|
||||
expect(
|
||||
getRelevantWidgetNames('RecraftGenerateColorFromImageNode')
|
||||
).toEqual(['n'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user