Skip to content

Commit fca0ea7

Browse files
authored
feat(api-nodes): add pricing for new LTXV-2 models (#6307)
## Summary For the upcoming LTXV API nodes. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6307-feat-api-nodes-add-pricing-for-new-LTXV-2-models-2986d73d365081db9994deffc219c6c4) by [Unito](https://www.unito.io)
1 parent 9f36158 commit fca0ea7

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

src/composables/node/useNodePricing.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,46 @@ const byteDanceVideoPricingCalculator = (node: LGraphNode): string => {
169169
: `$${minCost.toFixed(2)}-$${maxCost.toFixed(2)}/Run`
170170
}
171171

172+
const ltxvPricingCalculator = (node: LGraphNode): string => {
173+
const modelWidget = node.widgets?.find(
174+
(w) => w.name === 'model'
175+
) as IComboWidget
176+
const durationWidget = node.widgets?.find(
177+
(w) => w.name === 'duration'
178+
) as IComboWidget
179+
const resolutionWidget = node.widgets?.find(
180+
(w) => w.name === 'resolution'
181+
) as IComboWidget
182+
183+
const fallback = '$0.04-0.24/second'
184+
if (!modelWidget || !durationWidget || !resolutionWidget) return fallback
185+
186+
const model = String(modelWidget.value).toLowerCase()
187+
const resolution = String(resolutionWidget.value).toLowerCase()
188+
const seconds = parseFloat(String(durationWidget.value))
189+
const priceByModel: Record<string, Record<string, number>> = {
190+
'ltx-2 (pro)': {
191+
'1920x1080': 0.06,
192+
'2560x1440': 0.12,
193+
'3840x2160': 0.24
194+
},
195+
'ltx-2 (fast)': {
196+
'1920x1080': 0.04,
197+
'2560x1440': 0.08,
198+
'3840x2160': 0.16
199+
}
200+
}
201+
202+
const modelTable = priceByModel[model]
203+
if (!modelTable) return fallback
204+
205+
const pps = modelTable[resolution]
206+
if (!pps) return fallback
207+
208+
const cost = (pps * seconds).toFixed(2)
209+
return `$${cost}/Run`
210+
}
211+
172212
// ---- constants ----
173213
const SORA_SIZES = {
174214
BASIC: new Set(['720x1280', '1280x720']),
@@ -1694,6 +1734,12 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
16941734
},
16951735
WanImageToImageApi: {
16961736
displayPrice: '$0.03/Run'
1737+
},
1738+
LtxvApiTextToVideo: {
1739+
displayPrice: ltxvPricingCalculator
1740+
},
1741+
LtxvApiImageToVideo: {
1742+
displayPrice: ltxvPricingCalculator
16971743
}
16981744
}
16991745

@@ -1796,7 +1842,9 @@ export const useNodePricing = () => {
17961842
ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'],
17971843
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution'],
17981844
WanTextToVideoApi: ['duration', 'size'],
1799-
WanImageToVideoApi: ['duration', 'resolution']
1845+
WanImageToVideoApi: ['duration', 'resolution'],
1846+
LtxvApiTextToVideo: ['model', 'duration', 'resolution'],
1847+
LtxvApiImageToVideo: ['model', 'duration', 'resolution']
18001848
}
18011849
return widgetMap[nodeType] || []
18021850
}

tests-ui/tests/composables/node/useNodePricing.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,4 +2189,54 @@ describe('useNodePricing', () => {
21892189
expect(price).toBe('$0.05-0.15/second')
21902190
})
21912191
})
2192+
2193+
describe('dynamic pricing - LtxvApiTextToVideo', () => {
2194+
it('should return $0.30 for Pro 1080p 5s', () => {
2195+
const { getNodeDisplayPrice } = useNodePricing()
2196+
const node = createMockNode('LtxvApiTextToVideo', [
2197+
{ name: 'model', value: 'LTX-2 (Pro)' },
2198+
{ name: 'duration', value: '5' },
2199+
{ name: 'resolution', value: '1920x1080' }
2200+
])
2201+
2202+
const price = getNodeDisplayPrice(node)
2203+
expect(price).toBe('$0.30/Run') // 0.06 * 5
2204+
})
2205+
2206+
it('should parse "10s" duration strings', () => {
2207+
const { getNodeDisplayPrice } = useNodePricing()
2208+
const node = createMockNode('LtxvApiTextToVideo', [
2209+
{ name: 'model', value: 'LTX-2 (Fast)' },
2210+
{ name: 'duration', value: '10' },
2211+
{ name: 'resolution', value: '3840x2160' }
2212+
])
2213+
2214+
const price = getNodeDisplayPrice(node)
2215+
expect(price).toBe('$1.60/Run') // 0.16 * 10
2216+
})
2217+
2218+
it('should fall back when a required widget is missing (no resolution)', () => {
2219+
const { getNodeDisplayPrice } = useNodePricing()
2220+
const node = createMockNode('LtxvApiTextToVideo', [
2221+
{ name: 'model', value: 'LTX-2 (Pro)' },
2222+
{ name: 'duration', value: '5' }
2223+
// missing resolution
2224+
])
2225+
2226+
const price = getNodeDisplayPrice(node)
2227+
expect(price).toBe('$0.04-0.24/second')
2228+
})
2229+
2230+
it('should fall back for unknown model', () => {
2231+
const { getNodeDisplayPrice } = useNodePricing()
2232+
const node = createMockNode('LtxvApiTextToVideo', [
2233+
{ name: 'model', value: 'LTX-3 (Pro)' },
2234+
{ name: 'duration', value: 5 },
2235+
{ name: 'resolution', value: '1920x1080' }
2236+
])
2237+
2238+
const price = getNodeDisplayPrice(node)
2239+
expect(price).toBe('$0.04-0.24/second')
2240+
})
2241+
})
21922242
})

0 commit comments

Comments
 (0)