Skip to content

Commit 31ce2dd

Browse files
committed
fix(api-nodes-pricing): adjust prices for Tripo3D
1 parent 4adcf09 commit 31ce2dd

File tree

2 files changed

+203
-183
lines changed

2 files changed

+203
-183
lines changed

src/composables/node/useNodePricing.ts

Lines changed: 159 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,123 @@ const sora2PricingCalculator: PricingFunction = (node: LGraphNode): string => {
277277
return formatRunPrice(perSec, duration)
278278
}
279279

280+
/**
281+
* Pricing for Tripo 3D generation nodes (Text / Image / Multiview)
282+
* based on Tripo credits:
283+
*
284+
* Turbo / V3 / V2.5 / V2.0:
285+
* Text -> 10 (no texture) / 20 (standard texture)
286+
* Image -> 20 (no texture) / 30 (standard texture)
287+
* Multiview -> 20 (no texture) / 30 (standard texture)
288+
*
289+
* V1.4:
290+
* Text -> 20
291+
* Image -> 30
292+
* (Multiview treated same as Image if used)
293+
*
294+
* Advanced extras (added on top of generation credits):
295+
* quad -> +5 credits
296+
* style -> +5 credits (if style != "None")
297+
* HD texture -> +10 credits (texture_quality = "detailed")
298+
* detailed geometry -> +20 credits (geometry_quality = "detailed")
299+
*
300+
* 1 credit = $0.01
301+
*/
302+
const calculateTripo3DGenerationPrice = (
303+
node: LGraphNode,
304+
task: 'text' | 'image' | 'multiview'
305+
): string => {
306+
const getWidget = (name: string): IComboWidget | undefined =>
307+
node.widgets?.find((w) => w.name === name) as IComboWidget | undefined
308+
309+
const getString = (name: string, defaultValue: string): string => {
310+
const widget = getWidget(name)
311+
if (!widget || widget.value === undefined || widget.value === null) {
312+
return defaultValue
313+
}
314+
return String(widget.value)
315+
}
316+
317+
const getBool = (name: string, defaultValue: boolean): boolean => {
318+
const widget = getWidget(name)
319+
if (!widget || widget.value === undefined || widget.value === null) {
320+
return defaultValue
321+
}
322+
323+
const v = widget.value
324+
if (typeof v === 'number') return v !== 0
325+
const lower = String(v).toLowerCase()
326+
if (lower === 'true') return true
327+
if (lower === 'false') return false
328+
329+
return defaultValue
330+
}
331+
332+
// ---- read widget values with sensible defaults (mirroring backend) ----
333+
const modelVersionRaw = getString('model_version', '').toLowerCase()
334+
if (modelVersionRaw === '')
335+
return '$0.1-0.65/Run (varies with quad, style, texture & quality)'
336+
const styleRaw = getString('style', 'None')
337+
const hasStyle = styleRaw.toLowerCase() !== 'none'
338+
339+
// Backend defaults: texture=true, pbr=true, quad=false, qualities="standard"
340+
const hasTexture = getBool('texture', false)
341+
const hasPbr = getBool('pbr', false)
342+
const quad = getBool('quad', false)
343+
344+
const textureQualityRaw = getString(
345+
'texture_quality',
346+
'standard'
347+
).toLowerCase()
348+
const geometryQualityRaw = getString(
349+
'geometry_quality',
350+
'standard'
351+
).toLowerCase()
352+
353+
const isHdTexture = textureQualityRaw === 'detailed'
354+
const isDetailedGeometry = geometryQualityRaw === 'detailed'
355+
356+
const withTexture = hasTexture || hasPbr
357+
358+
let baseCredits: number
359+
360+
if (modelVersionRaw.includes('v1.4')) {
361+
// V1.4 model: Text=20, Image=30, Refine=30
362+
if (task === 'text') {
363+
baseCredits = 20
364+
} else {
365+
// treat Multiview same as Image if V1.4 is ever used there
366+
baseCredits = 30
367+
}
368+
} else {
369+
// V3.0, V2.5, V2.0 models
370+
if (!withTexture) {
371+
if (task === 'text') {
372+
baseCredits = 10 // Text to 3D without texture
373+
} else {
374+
baseCredits = 20 // Image/Multiview to 3D without texture
375+
}
376+
} else {
377+
if (task === 'text') {
378+
baseCredits = 20 // Text to 3D with standard texture
379+
} else {
380+
baseCredits = 30 // Image/Multiview to 3D with standard texture
381+
}
382+
}
383+
}
384+
385+
// ---- advanced extras on top of base generation ----
386+
let credits = baseCredits
387+
388+
if (hasStyle) credits += 5 // Style
389+
if (quad) credits += 5 // Quad Topology
390+
if (isHdTexture) credits += 10 // HD Texture
391+
if (isDetailedGeometry) credits += 20 // Detailed Geometry Quality
392+
393+
const dollars = credits * 0.01
394+
return `$${dollars.toFixed(2)}/Run`
395+
}
396+
280397
/**
281398
* Static pricing data for API nodes, now supporting both strings and functions
282399
*/
@@ -1327,119 +1444,16 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
13271444
},
13281445
// Tripo nodes - using actual node names from ComfyUI
13291446
TripoTextToModelNode: {
1330-
displayPrice: (node: LGraphNode): string => {
1331-
const quadWidget = node.widgets?.find(
1332-
(w) => w.name === 'quad'
1333-
) as IComboWidget
1334-
const styleWidget = node.widgets?.find(
1335-
(w) => w.name === 'style'
1336-
) as IComboWidget
1337-
const textureWidget = node.widgets?.find(
1338-
(w) => w.name === 'texture'
1339-
) as IComboWidget
1340-
const textureQualityWidget = node.widgets?.find(
1341-
(w) => w.name === 'texture_quality'
1342-
) as IComboWidget
1343-
1344-
if (!quadWidget || !styleWidget || !textureWidget)
1345-
return '$0.1-0.4/Run (varies with quad, style, texture & quality)'
1346-
1347-
const quad = String(quadWidget.value).toLowerCase() === 'true'
1348-
const style = String(styleWidget.value).toLowerCase()
1349-
const texture = String(textureWidget.value).toLowerCase() === 'true'
1350-
const textureQuality = String(
1351-
textureQualityWidget?.value || 'standard'
1352-
).toLowerCase()
1353-
1354-
// Pricing logic based on CSV data
1355-
if (style.includes('none')) {
1356-
if (!quad) {
1357-
if (!texture) return '$0.10/Run'
1358-
else return '$0.15/Run'
1359-
} else {
1360-
if (textureQuality.includes('detailed')) {
1361-
if (!texture) return '$0.30/Run'
1362-
else return '$0.35/Run'
1363-
} else {
1364-
if (!texture) return '$0.20/Run'
1365-
else return '$0.25/Run'
1366-
}
1367-
}
1368-
} else {
1369-
// any style
1370-
if (!quad) {
1371-
if (!texture) return '$0.15/Run'
1372-
else return '$0.20/Run'
1373-
} else {
1374-
if (textureQuality.includes('detailed')) {
1375-
if (!texture) return '$0.35/Run'
1376-
else return '$0.40/Run'
1377-
} else {
1378-
if (!texture) return '$0.25/Run'
1379-
else return '$0.30/Run'
1380-
}
1381-
}
1382-
}
1383-
}
1447+
displayPrice: (node: LGraphNode): string =>
1448+
calculateTripo3DGenerationPrice(node, 'text')
13841449
},
13851450
TripoImageToModelNode: {
1386-
displayPrice: (node: LGraphNode): string => {
1387-
const quadWidget = node.widgets?.find(
1388-
(w) => w.name === 'quad'
1389-
) as IComboWidget
1390-
const styleWidget = node.widgets?.find(
1391-
(w) => w.name === 'style'
1392-
) as IComboWidget
1393-
const textureWidget = node.widgets?.find(
1394-
(w) => w.name === 'texture'
1395-
) as IComboWidget
1396-
const textureQualityWidget = node.widgets?.find(
1397-
(w) => w.name === 'texture_quality'
1398-
) as IComboWidget
1399-
1400-
if (!quadWidget || !styleWidget || !textureWidget)
1401-
return '$0.2-0.5/Run (varies with quad, style, texture & quality)'
1402-
1403-
const quad = String(quadWidget.value).toLowerCase() === 'true'
1404-
const style = String(styleWidget.value).toLowerCase()
1405-
const texture = String(textureWidget.value).toLowerCase() === 'true'
1406-
const textureQuality = String(
1407-
textureQualityWidget?.value || 'standard'
1408-
).toLowerCase()
1409-
1410-
// Pricing logic based on CSV data for Image to Model
1411-
if (style.includes('none')) {
1412-
if (!quad) {
1413-
if (!texture) return '$0.20/Run'
1414-
else return '$0.25/Run'
1415-
} else {
1416-
if (textureQuality.includes('detailed')) {
1417-
if (!texture) return '$0.40/Run'
1418-
else return '$0.45/Run'
1419-
} else {
1420-
if (!texture) return '$0.30/Run'
1421-
else return '$0.35/Run'
1422-
}
1423-
}
1424-
} else {
1425-
// any style
1426-
if (!quad) {
1427-
if (!texture) return '$0.25/Run'
1428-
else return '$0.30/Run'
1429-
} else {
1430-
if (textureQuality.includes('detailed')) {
1431-
if (!texture) return '$0.45/Run'
1432-
else return '$0.50/Run'
1433-
} else {
1434-
if (!texture) return '$0.35/Run'
1435-
else return '$0.40/Run'
1436-
}
1437-
}
1438-
}
1439-
}
1451+
displayPrice: (node: LGraphNode): string =>
1452+
calculateTripo3DGenerationPrice(node, 'image')
14401453
},
1441-
TripoRefineNode: {
1442-
displayPrice: '$0.3/Run'
1454+
TripoMultiviewToModelNode: {
1455+
displayPrice: (node: LGraphNode): string =>
1456+
calculateTripo3DGenerationPrice(node, 'multiview')
14431457
},
14441458
TripoTextureNode: {
14451459
displayPrice: (node: LGraphNode): string => {
@@ -1453,67 +1467,20 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
14531467
return textureQuality.includes('detailed') ? '$0.2/Run' : '$0.1/Run'
14541468
}
14551469
},
1470+
TripoRigNode: {
1471+
displayPrice: '$0.25/Run'
1472+
},
1473+
TripoConversionNode: {
1474+
displayPrice: '$0.05-0.10/Run'
1475+
},
14561476
TripoConvertModelNode: {
14571477
displayPrice: '$0.10/Run'
14581478
},
14591479
TripoRetargetRiggedModelNode: {
14601480
displayPrice: '$0.10/Run'
14611481
},
1462-
TripoMultiviewToModelNode: {
1463-
displayPrice: (node: LGraphNode): string => {
1464-
const quadWidget = node.widgets?.find(
1465-
(w) => w.name === 'quad'
1466-
) as IComboWidget
1467-
const styleWidget = node.widgets?.find(
1468-
(w) => w.name === 'style'
1469-
) as IComboWidget
1470-
const textureWidget = node.widgets?.find(
1471-
(w) => w.name === 'texture'
1472-
) as IComboWidget
1473-
const textureQualityWidget = node.widgets?.find(
1474-
(w) => w.name === 'texture_quality'
1475-
) as IComboWidget
1476-
1477-
if (!quadWidget || !styleWidget || !textureWidget)
1478-
return '$0.2-0.5/Run (varies with quad, style, texture & quality)'
1479-
1480-
const quad = String(quadWidget.value).toLowerCase() === 'true'
1481-
const style = String(styleWidget.value).toLowerCase()
1482-
const texture = String(textureWidget.value).toLowerCase() === 'true'
1483-
const textureQuality = String(
1484-
textureQualityWidget?.value || 'standard'
1485-
).toLowerCase()
1486-
1487-
// Pricing logic based on CSV data for Multiview to Model (same as Image to Model)
1488-
if (style.includes('none')) {
1489-
if (!quad) {
1490-
if (!texture) return '$0.20/Run'
1491-
else return '$0.25/Run'
1492-
} else {
1493-
if (textureQuality.includes('detailed')) {
1494-
if (!texture) return '$0.40/Run'
1495-
else return '$0.45/Run'
1496-
} else {
1497-
if (!texture) return '$0.30/Run'
1498-
else return '$0.35/Run'
1499-
}
1500-
}
1501-
} else {
1502-
// any style
1503-
if (!quad) {
1504-
if (!texture) return '$0.25/Run'
1505-
else return '$0.30/Run'
1506-
} else {
1507-
if (textureQuality.includes('detailed')) {
1508-
if (!texture) return '$0.45/Run'
1509-
else return '$0.50/Run'
1510-
} else {
1511-
if (!texture) return '$0.35/Run'
1512-
else return '$0.40/Run'
1513-
}
1514-
}
1515-
}
1516-
}
1482+
TripoRefineNode: {
1483+
displayPrice: '$0.30/Run'
15171484
},
15181485
// Google/Gemini nodes
15191486
GeminiNode: {
@@ -1809,6 +1776,7 @@ export const useNodePricing = () => {
18091776
IdeogramV3: ['rendering_speed', 'num_images', 'character_image'],
18101777
FluxProKontextProNode: [],
18111778
FluxProKontextMaxNode: [],
1779+
Flux2ProImageNode: ['width', 'height', 'images'],
18121780
VeoVideoGenerationNode: ['duration_seconds'],
18131781
Veo3VideoGenerationNode: ['model', 'generate_audio'],
18141782
LumaVideoNode: ['model', 'resolution', 'duration'],
@@ -1843,8 +1811,32 @@ export const useNodePricing = () => {
18431811
RunwayImageToVideoNodeGen4: ['duration'],
18441812
RunwayFirstLastFrameNode: ['duration'],
18451813
// Tripo nodes
1846-
TripoTextToModelNode: ['quad', 'style', 'texture', 'texture_quality'],
1847-
TripoImageToModelNode: ['quad', 'style', 'texture', 'texture_quality'],
1814+
TripoTextToModelNode: [
1815+
'model_version',
1816+
'quad',
1817+
'style',
1818+
'texture',
1819+
'pbr',
1820+
'texture_quality',
1821+
'geometry_quality'
1822+
],
1823+
TripoImageToModelNode: [
1824+
'model_version',
1825+
'quad',
1826+
'style',
1827+
'texture',
1828+
'pbr',
1829+
'texture_quality',
1830+
'geometry_quality'
1831+
],
1832+
TripoMultiviewToModelNode: [
1833+
'model_version',
1834+
'quad',
1835+
'texture',
1836+
'pbr',
1837+
'texture_quality',
1838+
'geometry_quality'
1839+
],
18481840
TripoTextureNode: ['texture_quality'],
18491841
// Google/Gemini nodes
18501842
GeminiNode: ['model'],

0 commit comments

Comments
 (0)