mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
feat(price-badges): add price badges for Vidu2 nodes (#7927)
## Summary Price badges for the new nodes. ## Screenshots (if applicable) <img width="1427" height="1084" alt="Screenshot From 2026-01-09 13-29-59" src="https://github.com/user-attachments/assets/b805d138-fa72-4987-8aa0-ff9ee10ac9a7" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7927-feat-price-badges-add-price-badges-for-Vidu2-nodes-2e36d73d365081a0b6f4de768dce11e4) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] || []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user