From 9d0fee44e3ecacf193b2a3d428034ddf0d18d1a0 Mon Sep 17 00:00:00 2001 From: bymyself Date: Sat, 17 May 2025 23:01:57 -0700 Subject: [PATCH] feat: add API node pricing display in node badges - Adds pricing data for API nodes from various providers - Displays pricing in the credits badge next to API nodes - Implements pricing display in gold badge color to distinguish costs --- src/composables/node/apiNodeCosts.ts | 396 +++++++++++++++++++++++++ src/composables/node/useNodeBadge.ts | 13 +- src/composables/node/useNodePricing.ts | 103 +++++++ src/constants/apiNodeNames.ts | 63 ++++ src/types/apiNodeTypes.ts | 11 + 5 files changed, 582 insertions(+), 4 deletions(-) create mode 100644 src/composables/node/apiNodeCosts.ts create mode 100644 src/composables/node/useNodePricing.ts create mode 100644 src/constants/apiNodeNames.ts diff --git a/src/composables/node/apiNodeCosts.ts b/src/composables/node/apiNodeCosts.ts new file mode 100644 index 000000000..a8b605bc2 --- /dev/null +++ b/src/composables/node/apiNodeCosts.ts @@ -0,0 +1,396 @@ +import { ApiNodeCostRecord } from '@/types/apiNodeTypes' + +/** + * API Node cost data sourced from pricing database + * + * GENERATED FILE - DO NOT EDIT DIRECTLY + * Generated from Price Run Range for each API Node CSV + */ + +export const apiNodeCosts: ApiNodeCostRecord = { + FluxProCannyNode: { + vendor: 'Unknown', + nodeName: 'Flux 1: Canny Control Image', + pricingParams: '-', + pricePerRunRange: '$0.05', + displayPrice: '$0.05/Run' + }, + FluxProDepthNode: { + vendor: 'Unknown', + nodeName: 'Flux 1: Depth Control Image', + pricingParams: '-', + pricePerRunRange: '$0.05', + displayPrice: '$0.05/Run' + }, + FluxProExpandNode: { + vendor: 'Unknown', + nodeName: 'Flux 1: Expand Image', + pricingParams: '-', + pricePerRunRange: '$0.05', + rateDocumentation: 'https://docs.bfl.ml/pricing/', + displayPrice: '$0.05/Run' + }, + FluxProFillNode: { + vendor: 'Unknown', + nodeName: 'Flux 1: Fill Image', + pricingParams: '-', + pricePerRunRange: '$0.05', + displayPrice: '$0.05/Run' + }, + FluxProUltraImageNode: { + vendor: 'Unknown', + nodeName: 'Flux 1.1: [pro] Ultra Image', + pricingParams: '-', + pricePerRunRange: '$0.06', + displayPrice: '$0.06/Run' + }, + IdeogramV1: { + vendor: 'Unknown', + nodeName: 'Ideogram V1', + pricingParams: '-', + pricePerRunRange: '$0.06', + rateDocumentation: 'https://about.ideogram.ai/api-pricing', + displayPrice: '$0.06/Run' + }, + IdeogramV2: { + vendor: 'Unknown', + nodeName: 'Ideogram V2', + pricingParams: '-', + pricePerRunRange: '$0.08', + displayPrice: '$0.08/Run' + }, + IdeogramV3: { + vendor: 'Unknown', + nodeName: 'Ideogram V3', + pricingParams: 'rendering_speed', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + KlingCameraControlI2VNode: { + vendor: 'Unknown', + nodeName: 'Kling Image to Video (Camera Control)', + pricingParams: '-', + pricePerRunRange: '$0.49', + displayPrice: '$0.49/Run' + }, + KlingCameraControlT2VNode: { + vendor: 'Unknown', + nodeName: 'Kling Text to Video (Camera Control)', + pricingParams: '-', + pricePerRunRange: '$0.14', + displayPrice: '$0.14/Run' + }, + KlingDualCharacterVideoEffectNode: { + vendor: 'Unknown', + nodeName: 'Kling Dual Character Video Effects', + pricingParams: 'Priced the same as t2v based on mode, model, and duration.', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + KlingImage2VideoNode: { + vendor: 'Unknown', + nodeName: 'Kling Image to Video', + pricingParams: 'Same as Text to Video', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + KlingImageGenerationNode: { + vendor: 'Unknown', + nodeName: 'Kling Image Generation', + pricingParams: 'modality | model', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + KlingLipSyncAudioToVideoNode: { + vendor: 'Unknown', + nodeName: 'Kling Lip Sync Video with Audio', + pricingParams: 'duration of input video', + pricePerRunRange: '$0.07', + displayPrice: '$0.07/Run' + }, + KlingLipSyncTextToVideoNode: { + vendor: 'Unknown', + nodeName: 'Kling Lip Sync Video with Text', + pricingParams: 'duration of input video', + pricePerRunRange: '$0.07', + displayPrice: '$0.07/Run' + }, + KlingSingleImageVideoEffectNode: { + vendor: 'Unknown', + nodeName: 'Kling Video Effects', + pricingParams: 'effect_scene', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + KlingStartEndFrameNode: { + vendor: 'Unknown', + nodeName: 'Kling Start-End Frame to Video', + pricingParams: 'Same as text to video', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + KlingTextToVideoNode: { + vendor: 'Unknown', + nodeName: 'Kling Text to Video', + pricingParams: 'model | duration | mode', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + KlingVideoExtendNode: { + vendor: 'Unknown', + nodeName: 'Kling Video Extend', + pricingParams: '-', + pricePerRunRange: '$0.28', + displayPrice: '$0.28/Run' + }, + KlingVirtualTryOnNode: { + vendor: 'Unknown', + nodeName: 'Kling Virtual Try On', + pricingParams: '-', + pricePerRunRange: '$0.07', + displayPrice: '$0.07/Run' + }, + LumaImageToVideoNode: { + vendor: 'Unknown', + nodeName: 'Luma Image to Video', + pricingParams: 'Same as Text to Video', + pricePerRunRange: 'dynamic', + rateDocumentation: 'https://lumalabs.ai/api/pricing', + displayPrice: 'Variable pricing' + }, + LumaVideoNode: { + vendor: 'Unknown', + nodeName: 'Luma Text to Video', + pricingParams: 'model | resolution | duration', + pricePerRunRange: 'dynamic', + rateDocumentation: 'https://lumalabs.ai/api/pricing', + displayPrice: 'Variable pricing' + }, + MinimaxImageToVideoNode: { + vendor: 'Unknown', + nodeName: 'MiniMax Image to Video', + pricingParams: '-', + pricePerRunRange: '$0.43', + rateDocumentation: 'https://www.minimax.io/price', + displayPrice: '$0.43/Run' + }, + MinimaxTextToVideoNode: { + vendor: 'Unknown', + nodeName: 'MiniMax Text to Video', + pricingParams: '-', + pricePerRunRange: '$0.43', + rateDocumentation: 'https://www.minimax.io/price', + displayPrice: '$0.43/Run' + }, + OpenAIDalle2: { + vendor: 'Unknown', + nodeName: 'dall-e-2', + pricingParams: 'size', + pricePerRunRange: 'dynamic', + rateDocumentation: 'https://platform.openai.com/docs/pricing', + displayPrice: 'Variable pricing' + }, + OpenAIDalle3: { + vendor: 'Unknown', + nodeName: 'dall-e-3', + pricingParams: '1024×1024 | hd', + pricePerRunRange: '$0.08', + displayPrice: '$0.08/Run' + }, + OpenAIGPTImage1: { + vendor: 'Unknown', + nodeName: 'gpt-image-1', + pricingParams: 'medium', + pricePerRunRange: '$[0.046 - 0.07]', + displayPrice: '$0.07/Run' + }, + PikaImageToVideoNode2_2: { + vendor: 'Unknown', + nodeName: 'Pika Image to Video', + pricingParams: 'duration | resolution', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + PikaScenesV2_2: { + vendor: 'Unknown', + nodeName: 'Pika Scenes (Video Image Composition)', + pricingParams: 'duration | resolution', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + PikaStartEndFrameNode2_2: { + vendor: 'Unknown', + nodeName: 'Pika Start and End Frame to Video', + pricingParams: 'duration | resolution', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + PikaTextToVideoNode2_2: { + vendor: 'Unknown', + nodeName: 'Pika Text to Video', + pricingParams: 'duration | resolution', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + Pikadditions: { + vendor: 'Unknown', + nodeName: 'Pikadditions (Video Object Insertion)', + pricingParams: '-', + pricePerRunRange: '$0.3', + displayPrice: '$0.3/Run' + }, + Pikaffects: { + vendor: 'Unknown', + nodeName: 'Pikaffects (Video Effects)', + pricingParams: '-', + pricePerRunRange: '0.45', + displayPrice: '$0.45/Run' + }, + Pikaswaps: { + vendor: 'Unknown', + nodeName: 'Pika Swaps (Video Object Replacement)', + pricingParams: '-', + pricePerRunRange: '$0.3', + displayPrice: '$0.3/Run' + }, + PixverseImageToVideoNode: { + vendor: 'Unknown', + nodeName: 'PixVerse Image to Video', + pricingParams: 'same as text to video', + pricePerRunRange: '$0.9', + displayPrice: '$0.9/Run' + }, + PixverseTextToVideoNode: { + vendor: 'Unknown', + nodeName: 'PixVerse Text to Video', + pricingParams: 'duration | quality | motion_mode', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + PixverseTransitionVideoNode: { + vendor: 'Unknown', + nodeName: 'PixVerse Transition Video', + pricingParams: 'same as text to video', + pricePerRunRange: '$0.9', + displayPrice: '$0.9/Run' + }, + RecraftCrispUpscaleNode: { + vendor: 'Unknown', + nodeName: 'Recraft Crisp Upscale Image', + pricingParams: '-', + pricePerRunRange: '$0.004', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$0.004/Run' + }, + RecraftImageInpaintingNode: { + vendor: 'Unknown', + nodeName: 'Recraft Image Inpainting', + pricingParams: 'n', + pricePerRunRange: '$$0.04 x n', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$$0.04 x n/Run' + }, + RecraftImageToImageNode: { + vendor: 'Unknown', + nodeName: 'Recraft Image to Image', + pricingParams: 'n', + pricePerRunRange: '$0.04 x n', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$0.04 x n/Run' + }, + RecraftRemoveBackgroundNode: { + vendor: 'Unknown', + nodeName: 'Recraft Remove Background', + pricingParams: '-', + pricePerRunRange: '$0.01', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$0.01/Run' + }, + RecraftReplaceBackgroundNode: { + vendor: 'Unknown', + nodeName: 'Recraft Replace Background', + pricingParams: 'n', + pricePerRunRange: '$0.04', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$0.04/Run' + }, + RecraftTextToImageNode: { + vendor: 'Unknown', + nodeName: 'Recraft Text to Image', + pricingParams: 'model | n', + pricePerRunRange: '$0.04 x n', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$0.04 x n/Run' + }, + RecraftTextToVectorNode: { + vendor: 'Unknown', + nodeName: 'Recraft Text to Vector', + pricingParams: 'model | n', + pricePerRunRange: '$0.08 x n', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$0.08 x n/Run' + }, + RecraftVectorizeImageNode: { + vendor: 'Unknown', + nodeName: 'Recraft Vectorize Image', + pricingParams: '-', + pricePerRunRange: '$0.01', + rateDocumentation: 'https://www.recraft.ai/docs#pricing', + displayPrice: '$0.01/Run' + }, + StabilityStableImageSD_3_5Node: { + vendor: 'Unknown', + nodeName: 'Stability AI Stable Diffusion 3.5 Image', + pricingParams: 'model', + pricePerRunRange: 'dynamic', + displayPrice: 'Variable pricing' + }, + StabilityStableImageUltraNode: { + vendor: 'Unknown', + nodeName: 'Stability AI Stable Image Ultra', + pricingParams: '-', + pricePerRunRange: '$0.08', + displayPrice: '$0.08/Run' + }, + StabilityUpscaleConservativeNode: { + vendor: 'Unknown', + nodeName: 'Stability AI Upscale Conservative', + pricingParams: '-', + pricePerRunRange: '$0.25', + displayPrice: '$0.25/Run' + }, + StabilityUpscaleCreativeNode: { + vendor: 'Unknown', + nodeName: 'Stability AI Upscale Creative', + pricingParams: '-', + pricePerRunRange: '$0.25', + displayPrice: '$0.25/Run' + }, + StabilityUpscaleFastNode: { + vendor: 'Unknown', + nodeName: 'Stability AI Upscale Fast', + pricingParams: '-', + pricePerRunRange: '$0.01', + displayPrice: '$0.01/Run' + }, + VeoVideoGenerationNode: { + vendor: 'Unknown', + nodeName: 'Google Veo2 Video Generation', + pricingParams: '10s', + pricePerRunRange: '$5.0', + rateDocumentation: + 'https://cloud.google.com/vertex-ai/generative-ai/pricing', + displayPrice: '$5.0/Run' + } +} + +/** + * Get the display price for a node + * Returns a default value if the node isn't found + */ +export function getNodeDisplayPrice( + nodeName: string, + defaultPrice = '0.02/Run (approx)' +): string { + return apiNodeCosts[nodeName]?.displayPrice || defaultPrice +} diff --git a/src/composables/node/useNodeBadge.ts b/src/composables/node/useNodeBadge.ts index be629bd03..88d75104a 100644 --- a/src/composables/node/useNodeBadge.ts +++ b/src/composables/node/useNodeBadge.ts @@ -13,6 +13,8 @@ import { useSettingStore } from '@/stores/settingStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { NodeBadgeMode } from '@/types/nodeSource' +import { useNodePricing } from './useNodePricing' + /** * Add LGraphBadge to LGraphNode based on settings. * @@ -57,6 +59,8 @@ export const useNodeBadge = () => { } onMounted(() => { + const nodePricing = useNodePricing() + extensionStore.registerExtension({ name: 'Comfy.NodeBadge', nodeCreated(node: LGraphNode) { @@ -95,9 +99,12 @@ export const useNodeBadge = () => { node.badges.push(() => badge.value) if (node.constructor.nodeData?.api_node) { + // Get price from our mapping service + const price = nodePricing.getNodePriceDisplay(node) + const creditsBadge = computed(() => { return new LGraphBadge({ - text: '', + text: price ?? '', iconOptions: { unicode: '\ue96b', fontFamily: 'PrimeIcons', @@ -108,9 +115,7 @@ export const useNodeBadge = () => { fgColor: colorPaletteStore.completedActivePalette.colors.litegraph_base .BADGE_FG_COLOR, - bgColor: - colorPaletteStore.completedActivePalette.colors.litegraph_base - .BADGE_BG_COLOR + bgColor: '#8D6932' }) }) diff --git a/src/composables/node/useNodePricing.ts b/src/composables/node/useNodePricing.ts new file mode 100644 index 000000000..b56004cb7 --- /dev/null +++ b/src/composables/node/useNodePricing.ts @@ -0,0 +1,103 @@ +import type { LGraphNode } from '@comfyorg/litegraph' + +// Direct mapping of node names to prices +export const NODE_PRICES: Record = { + // OpenAI models + OpenAIDalle2: '$0.02/Run', + OpenAIDalle3: '$0.08/Run', + OpenAIGPTImage1: '$0.07/Run', + // Ideogram models + IdeogramV1: '$0.06/Run', + IdeogramV2: '$0.08/Run', + IdeogramV3: 'Variable pricing', + // Minimax models + MinimaxTextToVideoNode: '$0.43/Run', + MinimaxImageToVideoNode: '$0.43/Run', + // Google Veo + VeoVideoGenerationNode: '$5.0/Run', + // Kling models + KlingTextToVideoNode: 'Variable pricing', + KlingImage2VideoNode: 'Variable pricing', + KlingCameraControlI2VNode: '$0.49/Run', + KlingCameraControlT2VNode: '$0.14/Run', + KlingStartEndFrameNode: 'Variable pricing', + KlingVideoExtendNode: '$0.28/Run', + KlingLipSyncAudioToVideoNode: '$0.07/Run', + KlingLipSyncTextToVideoNode: '$0.07/Run', + KlingVirtualTryOnNode: '$0.07/Run', + KlingImageGenerationNode: 'Variable pricing', + KlingSingleImageVideoEffectNode: 'Variable pricing', + KlingDualCharacterVideoEffectNode: 'Variable pricing', + // Flux Pro models + FluxProUltraImageNode: '$0.06/Run', + FluxProExpandNode: '$0.05/Run', + FluxProFillNode: '$0.05/Run', + FluxProCannyNode: '$0.05/Run', + FluxProDepthNode: '$0.05/Run', + // Luma models + LumaVideoNode: 'Variable pricing', + LumaImageToVideoNode: 'Variable pricing', + LumaImageNode: 'Variable pricing', + LumaImageModifyNode: 'Variable pricing', + // Recraft models + RecraftTextToImageNode: '$0.04/Run', + RecraftImageToImageNode: '$0.04/Run', + RecraftImageInpaintingNode: '$0.04/Run', + RecraftTextToVectorNode: '$0.08/Run', + RecraftVectorizeImageNode: '$0.01/Run', + RecraftRemoveBackgroundNode: '$0.01/Run', + RecraftReplaceBackgroundNode: '$0.04/Run', + RecraftCrispUpscaleNode: '$0.004/Run', + RecraftCreativeUpscaleNode: '$0.004/Run', + // Pixverse models + PixverseTextToVideoNode: '$0.9/Run', + PixverseImageToVideoNode: '$0.9/Run', + PixverseTransitionVideoNode: '$0.9/Run', + // Stability models + StabilityStableImageUltraNode: '$0.08/Run', + StabilityStableImageSD_3_5Node: 'Variable pricing', + StabilityUpscaleConservativeNode: '$0.25/Run', + StabilityUpscaleCreativeNode: '$0.25/Run', + StabilityUpscaleFastNode: '$0.01/Run', + // Pika models + PikaImageToVideoNode2_2: 'Variable pricing', + PikaTextToVideoNode2_2: 'Variable pricing', + PikaScenesV2_2: 'Variable pricing', + PikaStartEndFrameNode2_2: 'Variable pricing', + Pikadditions: '$0.3/Run', + Pikaswaps: '$0.3/Run', + Pikaffects: '$0.45/Run' +} + +/** + * Simple utility function to get the price for a node + * Returns a formatted price string or default value if the node isn't found + */ +export function getNodePrice( + node: LGraphNode, + defaultPrice = '0.02/Run (approx)' +): string { + if (!node.constructor.nodeData?.api_node) { + return '' + } + return NODE_PRICES[node.constructor.name] || defaultPrice +} + +/** + * Composable to get node pricing information for API nodes + */ +export const useNodePricing = () => { + /** + * Get the price display for a node + */ + const getNodePriceDisplay = (node: LGraphNode): string => { + if (!node.constructor.nodeData?.api_node) { + return '' + } + return NODE_PRICES[node.constructor.name] || '0.02/Run (approx)' + } + + return { + getNodePriceDisplay + } +} diff --git a/src/constants/apiNodeNames.ts b/src/constants/apiNodeNames.ts new file mode 100644 index 000000000..c18d62785 --- /dev/null +++ b/src/constants/apiNodeNames.ts @@ -0,0 +1,63 @@ +const apiNodeNames = [ + 'IdeogramV1', + 'IdeogramV2', + 'IdeogramV3', + 'MinimaxTextToVideoNode', + 'MinimaxImageToVideoNode', + 'VeoVideoGenerationNode', + 'KlingCameraControls', + 'KlingTextToVideoNode', + 'KlingImage2VideoNode', + 'KlingCameraControlI2VNode', + 'KlingCameraControlT2VNode', + 'KlingStartEndFrameNode', + 'KlingVideoExtendNode', + 'KlingLipSyncAudioToVideoNode', + 'KlingLipSyncTextToVideoNode', + 'KlingVirtualTryOnNode', + 'KlingImageGenerationNode', + 'KlingSingleImageVideoEffectNode', + 'KlingDualCharacterVideoEffectNode', + 'FluxProUltraImageNode', + 'FluxProExpandNode', + 'FluxProFillNode', + 'FluxProCannyNode', + 'FluxProDepthNode', + 'LumaImageNode', + 'LumaImageModifyNode', + 'LumaVideoNode', + 'LumaImageToVideoNode', + 'LumaReferenceNode', + 'LumaConceptsNode', + 'RecraftTextToImageNode', + 'RecraftImageToImageNode', + 'RecraftImageInpaintingNode', + 'RecraftTextToVectorNode', + 'RecraftVectorizeImageNode', + 'RecraftRemoveBackgroundNode', + 'RecraftReplaceBackgroundNode', + 'RecraftCrispUpscaleNode', + 'RecraftCreativeUpscaleNode', + 'RecraftStyleV3RealisticImage', + 'RecraftStyleV3DigitalIllustration', + 'RecraftStyleV3LogoRaster', + 'RecraftStyleV3InfiniteStyleLibrary', + 'RecraftColorRGB', + 'RecraftControls', + 'PixverseTextToVideoNode', + 'PixverseImageToVideoNode', + 'PixverseTransitionVideoNode', + 'PixverseTemplateNode', + 'StabilityStableImageUltraNode', + 'StabilityStableImageSD_3_5Node', + 'StabilityUpscaleConservativeNode', + 'StabilityUpscaleCreativeNode', + 'StabilityUpscaleFastNode', + 'PikaImageToVideoNode2_2', + 'PikaTextToVideoNode2_2', + 'PikaScenesV2_2', + 'Pikadditions', + 'Pikaswaps', + 'Pikaffects', + 'PikaStartEndFrameNode2_2' +] diff --git a/src/types/apiNodeTypes.ts b/src/types/apiNodeTypes.ts index 44386ac79..53af52d9d 100644 --- a/src/types/apiNodeTypes.ts +++ b/src/types/apiNodeTypes.ts @@ -2,3 +2,14 @@ export interface ApiNodeCost { name: string cost: number } + +export interface ApiNodeCostData { + vendor: string + nodeName: string + pricingParams: string + pricePerRunRange: string + displayPrice: string + rateDocumentation?: string +} + +export type ApiNodeCostRecord = Record