@@ -30,6 +30,11 @@ import { workflowClient } from "@/api/client";
3030import { useModelsStore } from "@/stores/modelsStore" ;
3131import { getFormFieldsFromModel } from "@/lib/schemaToForm" ;
3232import { formFieldsToModelParamSchema } from "../../../lib/model-converter" ;
33+ import {
34+ formatWorkflowCost ,
35+ getWorkflowNodeCostPreview ,
36+ hasWorkflowCostDiscount ,
37+ } from "../../../lib/cost-preview" ;
3338import type { NodeStatus } from "@/workflow/types/execution" ;
3439import type { WaveSpeedModel } from "@/workflow/types/node-defs" ;
3540import type { FormFieldConfig } from "@/lib/schemaToForm" ;
@@ -242,6 +247,15 @@ function CustomNodeComponent({
242247 const isAITask = data . nodeType === "ai-task/run" ;
243248 const currentModelId = String ( data . params ?. modelId ?? "" ) . trim ( ) ;
244249 const currentModel = useModelsStore ( ( s ) => s . getModelById ( currentModelId ) ) ;
250+ const costPreview = useMemo (
251+ ( ) =>
252+ getWorkflowNodeCostPreview ( {
253+ nodeType : data . nodeType ,
254+ params : data . params ,
255+ model : currentModel ,
256+ } ) ,
257+ [ data . nodeType , data . params , currentModel ] ,
258+ ) ;
245259
246260 const schema = useMemo ( ( ) => {
247261 if ( isAITask && currentModel ) {
@@ -850,15 +864,16 @@ function CustomNodeComponent({
850864 ref = { nodeRef }
851865 className = { `
852866 relative rounded-xl
853- bg-[hsl(var(--card))] text-[hsl(var(--card-foreground))]
867+ bg-white text-[hsl(var(--card-foreground))]
868+ dark:bg-slate-800
854869 border-2
855870 ${ resizing ? "" : "transition-all duration-300" }
856871 ${ running ? ( isInsideIterator ? "border-blue-500 animate-pulse-subtle" : "border-blue-500 animate-pulse-subtle" ) : "" }
857872 ${ ! running && selected ? ( isInsideIterator ? "border-blue-500 shadow-[0_0_20px_rgba(96,165,250,.25)] ring-1 ring-blue-500/30" : "border-blue-500 shadow-[0_0_20px_rgba(96,165,250,.25)] ring-1 ring-blue-500/30" ) : "" }
858873 ${ ! running && ! selected && status === "confirmed" ? "border-green-500/70" : "" }
859874 ${ ! running && ! selected && status === "unconfirmed" ? "border-orange-500/70" : "" }
860875 ${ ! running && ! selected && status === "error" ? "border-red-500/70" : "" }
861- ${ ! running && ! selected && status === "idle" ? ( hovered ? "border-[hsl(var(-- border))] shadow-lg " : "border-[hsl(var(-- border))] shadow-md " ) : "" }
876+ ${ ! running && ! selected && status === "idle" ? ( hovered ? "border-slate-300 shadow-lg dark: border-slate-500 dark: shadow-[0_0_0_1px_rgba(148,163,184,.16),0_16px_36px_rgba(0,0,0,.45)] " : "border-slate-200 shadow-md dark: border-slate-600/80 dark: shadow-[0_0_0_1px_rgba(148,163,184,.10),0_12px_28px_rgba(0,0,0,.38)] " ) : "" }
862877 ${ isInsideIterator && ! running && ! selected && status === "idle" ? "ring-1 ring-blue-500/20" : "" }
863878 ` }
864879 style = { { width : savedWidth , minHeight : savedHeight , fontSize : 13 } }
@@ -905,12 +920,42 @@ function CustomNodeComponent({
905920 < NodeIcon className = "w-3.5 h-3.5 text-primary" />
906921 </ div >
907922 ) }
908- < span className = "font-semibold text-[13px] truncate" >
923+ < span className = "font-semibold text-[13px] truncate min-w-0 flex-1 " >
909924 { nodeLabel }
910925 </ span >
911926 < span className = "text-[10px] text-[hsl(var(--muted-foreground))] opacity-50 font-mono flex-shrink-0" >
912927 { shortId }
913928 </ span >
929+ { costPreview && (
930+ < Tooltip delayDuration = { 0 } >
931+ < TooltipTrigger asChild >
932+ < span className = "nodrag nopan flex-shrink-0 inline-flex items-center gap-1 rounded-full border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-semibold text-emerald-700 dark:text-emerald-300" >
933+ < span > { t ( "workflow.estimated" , "Est." ) } </ span >
934+ { hasWorkflowCostDiscount ( costPreview ) ? (
935+ < span className = "inline-flex items-baseline gap-1" >
936+ < span className = "line-through opacity-60" >
937+ ${ formatWorkflowCost ( costPreview . price ) }
938+ </ span >
939+ < span >
940+ ${ formatWorkflowCost ( costPreview . discountedPrice ) }
941+ </ span >
942+ </ span >
943+ ) : (
944+ < span > ${ formatWorkflowCost ( costPreview . price ) } </ span >
945+ ) }
946+ </ span >
947+ </ TooltipTrigger >
948+ < TooltipContent side = "top" className = "max-w-[240px]" >
949+ { t (
950+ "workflow.costEstimateHint" ,
951+ "Estimated base price before running. Actual API cost may vary with inputs." ,
952+ ) }
953+ { costPreview . runCount > 1
954+ ? ` ${ t ( "workflow.runCount" , "Run Count" ) } : ${ costPreview . runCount } `
955+ : "" }
956+ </ TooltipContent >
957+ </ Tooltip >
958+ ) }
914959 </ div >
915960 { /* ── Running status bar ── */ }
916961 { running && (
0 commit comments