Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 237 additions & 1 deletion src/composables/node/useNodePricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2161,6 +2161,238 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =

return formatCreditsRangeLabel(minTotal, maxTotal)
}
},
Vidu2TextToVideoNode: {
displayPrice: (node: LGraphNode): string => {
const durationWidget = node.widgets?.find(
(w) => w.name === 'duration'
) as IComboWidget
const resolutionWidget = node.widgets?.find(
(w) => w.name === 'resolution'
) as IComboWidget

if (!durationWidget || !resolutionWidget) {
return formatCreditsRangeLabel(0.075, 0.6, {
note: '(varies with duration & resolution)'
})
}

const duration = parseFloat(String(durationWidget.value))
const resolution = String(resolutionWidget.value).toLowerCase()

// Text-to-Video uses Q2 model only
// 720P: Starts at $0.075, +$0.025/sec
// 1080P: Starts at $0.10, +$0.05/sec
let basePrice: number
let pricePerSecond: number

if (resolution.includes('1080')) {
basePrice = 0.1
pricePerSecond = 0.05
} else {
// 720P default
basePrice = 0.075
pricePerSecond = 0.025
}

if (!Number.isFinite(duration) || duration <= 0) {
return formatCreditsRangeLabel(0.075, 0.6, {
note: '(varies with duration & resolution)'
})
}

const cost = basePrice + pricePerSecond * (duration - 1)
return formatCreditsLabel(cost)
}
},
Vidu2ImageToVideoNode: {
displayPrice: (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

if (!modelWidget || !durationWidget || !resolutionWidget) {
return formatCreditsRangeLabel(0.04, 1.0, {
note: '(varies with model, duration & resolution)'
})
}

const model = String(modelWidget.value).toLowerCase()
const duration = parseFloat(String(durationWidget.value))
const resolution = String(resolutionWidget.value).toLowerCase()
const is1080p = resolution.includes('1080')

let basePrice: number
let pricePerSecond: number

if (model.includes('q2-pro-fast')) {
// Q2-pro-fast: 720P $0.04+$0.01/sec, 1080P $0.08+$0.02/sec
basePrice = is1080p ? 0.08 : 0.04
pricePerSecond = is1080p ? 0.02 : 0.01
} else if (model.includes('q2-pro')) {
// Q2-pro: 720P $0.075+$0.05/sec, 1080P $0.275+$0.075/sec
basePrice = is1080p ? 0.275 : 0.075
pricePerSecond = is1080p ? 0.075 : 0.05
} else if (model.includes('q2-turbo')) {
// Q2-turbo: 720P special pricing, 1080P $0.175+$0.05/sec
if (is1080p) {
basePrice = 0.175
pricePerSecond = 0.05
} else {
// 720P: $0.04 at 1s, $0.05 at 2s, +$0.05/sec beyond 2s
if (duration <= 1) {
return formatCreditsLabel(0.04)
}
if (duration <= 2) {
return formatCreditsLabel(0.05)
}
const cost = 0.05 + 0.05 * (duration - 2)
return formatCreditsLabel(cost)
}
} else {
return formatCreditsRangeLabel(0.04, 1.0, {
note: '(varies with model, duration & resolution)'
})
}

if (!Number.isFinite(duration) || duration <= 0) {
return formatCreditsRangeLabel(0.04, 1.0, {
note: '(varies with model, duration & resolution)'
})
}

const cost = basePrice + pricePerSecond * (duration - 1)
return formatCreditsLabel(cost)
}
},
Vidu2ReferenceVideoNode: {
displayPrice: (node: LGraphNode): string => {
const durationWidget = node.widgets?.find(
(w) => w.name === 'duration'
) as IComboWidget
const resolutionWidget = node.widgets?.find(
(w) => w.name === 'resolution'
) as IComboWidget
const audioWidget = node.widgets?.find(
(w) => w.name === 'audio'
) as IComboWidget

if (!durationWidget) {
return formatCreditsRangeLabel(0.125, 1.5, {
note: '(varies with duration, resolution & audio)'
})
}

const duration = parseFloat(String(durationWidget.value))
const resolution = String(resolutionWidget?.value ?? '').toLowerCase()
const is1080p = resolution.includes('1080')

// Check if audio is enabled (adds $0.75)
const audioValue = audioWidget?.value
const hasAudio =
audioValue !== undefined &&
audioValue !== null &&
String(audioValue).toLowerCase() !== 'false' &&
String(audioValue).toLowerCase() !== 'none' &&
audioValue !== ''

// Reference-to-Video uses Q2 model
// 720P: Starts at $0.125, +$0.025/sec
// 1080P: Starts at $0.375, +$0.05/sec
let basePrice: number
let pricePerSecond: number

if (is1080p) {
basePrice = 0.375
pricePerSecond = 0.05
} else {
// 720P default
basePrice = 0.125
pricePerSecond = 0.025
}

let cost = basePrice
if (Number.isFinite(duration) && duration > 0) {
cost = basePrice + pricePerSecond * (duration - 1)
}

// Audio adds $0.75 on top
if (hasAudio) {
cost += 0.075
}

return formatCreditsLabel(cost)
}
},
Vidu2StartEndToVideoNode: {
displayPrice: (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

if (!modelWidget || !durationWidget || !resolutionWidget) {
return formatCreditsRangeLabel(0.04, 1.0, {
note: '(varies with model, duration & resolution)'
})
}

const model = String(modelWidget.value).toLowerCase()
const duration = parseFloat(String(durationWidget.value))
const resolution = String(resolutionWidget.value).toLowerCase()
const is1080p = resolution.includes('1080')

let basePrice: number
let pricePerSecond: number

if (model.includes('q2-pro-fast')) {
// Q2-pro-fast: 720P $0.04+$0.01/sec, 1080P $0.08+$0.02/sec
basePrice = is1080p ? 0.08 : 0.04
pricePerSecond = is1080p ? 0.02 : 0.01
} else if (model.includes('q2-pro')) {
// Q2-pro: 720P $0.075+$0.05/sec, 1080P $0.275+$0.075/sec
basePrice = is1080p ? 0.275 : 0.075
pricePerSecond = is1080p ? 0.075 : 0.05
} else if (model.includes('q2-turbo')) {
// Q2-turbo: 720P special pricing, 1080P $0.175+$0.05/sec
if (is1080p) {
basePrice = 0.175
pricePerSecond = 0.05
} else {
// 720P: $0.04 at 1s, $0.05 at 2s, +$0.05/sec beyond 2s
if (!Number.isFinite(duration) || duration <= 1) {
return formatCreditsLabel(0.04)
}
if (duration <= 2) {
return formatCreditsLabel(0.05)
}
const cost = 0.05 + 0.05 * (duration - 2)
return formatCreditsLabel(cost)
}
} else {
return formatCreditsRangeLabel(0.04, 1.0, {
note: '(varies with model, duration & resolution)'
})
}

if (!Number.isFinite(duration) || duration <= 0) {
return formatCreditsLabel(basePrice)
}

const cost = basePrice + pricePerSecond * (duration - 1)
return formatCreditsLabel(cost)
}
}
}

Expand Down Expand Up @@ -2316,7 +2548,11 @@ export const useNodePricing = () => {
WanImageToVideoApi: ['duration', 'resolution'],
WanReferenceVideoApi: ['duration', 'size'],
LtxvApiTextToVideo: ['model', 'duration', 'resolution'],
LtxvApiImageToVideo: ['model', 'duration', 'resolution']
LtxvApiImageToVideo: ['model', 'duration', 'resolution'],
Vidu2TextToVideoNode: ['model', 'duration', 'resolution'],
Vidu2ImageToVideoNode: ['model', 'duration', 'resolution'],
Vidu2ReferenceVideoNode: ['audio', 'duration', 'resolution'],
Vidu2StartEndToVideoNode: ['model', 'duration', 'resolution']
}
return widgetMap[nodeType] || []
}
Expand Down
Loading