diff --git a/src/composables/node/useNodePricing.ts b/src/composables/node/useNodePricing.ts index 26f9541453..d1ae221695 100644 --- a/src/composables/node/useNodePricing.ts +++ b/src/composables/node/useNodePricing.ts @@ -169,6 +169,46 @@ const byteDanceVideoPricingCalculator = (node: LGraphNode): string => { : `$${minCost.toFixed(2)}-$${maxCost.toFixed(2)}/Run` } +const ltxvPricingCalculator = (node: LGraphNode): string => { + const modelWidget = node.widgets?.find( + (w) => w.name === 'model' + ) as IComboWidget + const durationWidget = node.widgets?.find( + (w) => w.name === 'duration' + ) as IComboWidget + const resolutionWidget = node.widgets?.find( + (w) => w.name === 'resolution' + ) as IComboWidget + + const fallback = '$0.04-0.24/second' + if (!modelWidget || !durationWidget || !resolutionWidget) return fallback + + const model = String(modelWidget.value).toLowerCase() + const resolution = String(resolutionWidget.value).toLowerCase() + const seconds = parseFloat(String(durationWidget.value)) + const priceByModel: Record> = { + 'ltx-2 (pro)': { + '1920x1080': 0.06, + '2560x1440': 0.12, + '3840x2160': 0.24 + }, + 'ltx-2 (fast)': { + '1920x1080': 0.04, + '2560x1440': 0.08, + '3840x2160': 0.16 + } + } + + const modelTable = priceByModel[model] + if (!modelTable) return fallback + + const pps = modelTable[resolution] + if (!pps) return fallback + + const cost = (pps * seconds).toFixed(2) + return `$${cost}/Run` +} + // ---- constants ---- const SORA_SIZES = { BASIC: new Set(['720x1280', '1280x720']), @@ -1694,6 +1734,12 @@ const apiNodeCosts: Record = }, WanImageToImageApi: { displayPrice: '$0.03/Run' + }, + LtxvApiTextToVideo: { + displayPrice: ltxvPricingCalculator + }, + LtxvApiImageToVideo: { + displayPrice: ltxvPricingCalculator } } @@ -1796,7 +1842,9 @@ export const useNodePricing = () => { ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'], ByteDanceImageReferenceNode: ['model', 'duration', 'resolution'], WanTextToVideoApi: ['duration', 'size'], - WanImageToVideoApi: ['duration', 'resolution'] + WanImageToVideoApi: ['duration', 'resolution'], + LtxvApiTextToVideo: ['model', 'duration', 'resolution'], + LtxvApiImageToVideo: ['model', 'duration', 'resolution'] } return widgetMap[nodeType] || [] } diff --git a/tests-ui/tests/composables/node/useNodePricing.test.ts b/tests-ui/tests/composables/node/useNodePricing.test.ts index 36697cbc8b..34ff90f834 100644 --- a/tests-ui/tests/composables/node/useNodePricing.test.ts +++ b/tests-ui/tests/composables/node/useNodePricing.test.ts @@ -2189,4 +2189,54 @@ describe('useNodePricing', () => { expect(price).toBe('$0.05-0.15/second') }) }) + + describe('dynamic pricing - LtxvApiTextToVideo', () => { + it('should return $0.30 for Pro 1080p 5s', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('LtxvApiTextToVideo', [ + { name: 'model', value: 'LTX-2 (Pro)' }, + { name: 'duration', value: '5' }, + { name: 'resolution', value: '1920x1080' } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.30/Run') // 0.06 * 5 + }) + + it('should parse "10s" duration strings', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('LtxvApiTextToVideo', [ + { name: 'model', value: 'LTX-2 (Fast)' }, + { name: 'duration', value: '10' }, + { name: 'resolution', value: '3840x2160' } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$1.60/Run') // 0.16 * 10 + }) + + it('should fall back when a required widget is missing (no resolution)', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('LtxvApiTextToVideo', [ + { name: 'model', value: 'LTX-2 (Pro)' }, + { name: 'duration', value: '5' } + // missing resolution + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.04-0.24/second') + }) + + it('should fall back for unknown model', () => { + const { getNodeDisplayPrice } = useNodePricing() + const node = createMockNode('LtxvApiTextToVideo', [ + { name: 'model', value: 'LTX-3 (Pro)' }, + { name: 'duration', value: 5 }, + { name: 'resolution', value: '1920x1080' } + ]) + + const price = getNodeDisplayPrice(node) + expect(price).toBe('$0.04-0.24/second') + }) + }) })