@@ -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