[API Node] Add cost indicators on API nodes (#3924)

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Chenlei Hu <huchenlei@proton.me>
This commit is contained in:
Christian Byrne
2025-05-20 18:59:11 -07:00
committed by GitHub
parent 356886dc29
commit f08ec0a981
19 changed files with 506 additions and 6 deletions

View File

@@ -6,6 +6,7 @@ import {
import _ from 'lodash'
import { computed, onMounted, watch } from 'vue'
import { useNodePricing } from '@/composables/node/useNodePricing'
import { app } from '@/scripts/app'
import { useExtensionStore } from '@/stores/extensionStore'
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
@@ -42,9 +43,21 @@ export const useNodeBadge = () => {
) as NodeBadgeMode
)
watch([nodeSourceBadgeMode, nodeIdBadgeMode, nodeLifeCycleBadgeMode], () => {
app.graph?.setDirtyCanvas(true, true)
})
const showApiPricingBadge = computed(() =>
settingStore.get('Comfy.NodeBadge.ShowApiPricing')
)
watch(
[
nodeSourceBadgeMode,
nodeIdBadgeMode,
nodeLifeCycleBadgeMode,
showApiPricingBadge
],
() => {
app.graph?.setDirtyCanvas(true, true)
}
)
const nodeDefStore = useNodeDefStore()
function badgeTextVisible(
@@ -58,6 +71,8 @@ export const useNodeBadge = () => {
}
onMounted(() => {
const nodePricing = useNodePricing()
extensionStore.registerExtension({
name: 'Comfy.NodeBadge',
nodeCreated(node: LGraphNode) {
@@ -95,13 +110,15 @@ export const useNodeBadge = () => {
node.badges.push(() => badge.value)
if (node.constructor.nodeData?.api_node) {
if (node.constructor.nodeData?.api_node && showApiPricingBadge.value) {
const price = nodePricing.getNodeDisplayPrice(node)
// Always add the badge for API nodes, with or without price text
const creditsBadge = computed(() => {
// Use dynamic background color based on the theme
const isLightTheme =
colorPaletteStore.completedActivePalette.light_theme
return new LGraphBadge({
text: '',
text: price,
iconOptions: {
unicode: '\ue96b',
fontFamily: 'PrimeIcons',

View File

@@ -0,0 +1,426 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { ApiNodeCostRecord } from '@/types/apiNodeTypes'
const apiNodeCosts: ApiNodeCostRecord = {
FluxProCannyNode: {
vendor: 'BFL',
nodeName: 'Flux 1: Canny Control Image',
pricingParams: '-',
pricePerRunRange: '$0.05',
displayPrice: '$0.05/Run'
},
FluxProDepthNode: {
vendor: 'BFL',
nodeName: 'Flux 1: Depth Control Image',
pricingParams: '-',
pricePerRunRange: '$0.05',
displayPrice: '$0.05/Run'
},
FluxProExpandNode: {
vendor: 'BFL',
nodeName: 'Flux 1: Expand Image',
pricingParams: '-',
pricePerRunRange: '$0.05',
rateDocumentationUrl: 'https://docs.bfl.ml/pricing/',
displayPrice: '$0.05/Run'
},
FluxProFillNode: {
vendor: 'BFL',
nodeName: 'Flux 1: Fill Image',
pricingParams: '-',
pricePerRunRange: '$0.05',
displayPrice: '$0.05/Run'
},
FluxProUltraImageNode: {
vendor: 'BFL',
nodeName: 'Flux 1.1: [pro] Ultra Image',
pricingParams: '-',
pricePerRunRange: '$0.06',
displayPrice: '$0.06/Run'
},
IdeogramV1: {
vendor: 'Ideogram',
nodeName: 'Ideogram V1',
pricingParams: '-',
pricePerRunRange: '$0.06',
rateDocumentationUrl: 'https://about.ideogram.ai/api-pricing',
displayPrice: '$0.06/Run'
},
IdeogramV2: {
vendor: 'Ideogram',
nodeName: 'Ideogram V2',
pricingParams: '-',
pricePerRunRange: '$0.08',
displayPrice: '$0.08/Run'
},
IdeogramV3: {
vendor: 'Ideogram',
nodeName: 'Ideogram V3',
pricingParams: 'rendering_speed',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (low to medium)'
},
KlingCameraControlI2VNode: {
vendor: 'Kling',
nodeName: 'Kling Image to Video (Camera Control)',
pricingParams: '-',
pricePerRunRange: '$0.49',
displayPrice: '$0.49/Run'
},
KlingCameraControlT2VNode: {
vendor: 'Kling',
nodeName: 'Kling Text to Video (Camera Control)',
pricingParams: '-',
pricePerRunRange: '$0.14',
displayPrice: '$0.14/Run'
},
KlingDualCharacterVideoEffectNode: {
vendor: 'Kling',
nodeName: 'Kling Dual Character Video Effects',
pricingParams: 'Priced the same as t2v based on mode, model, and duration.',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
KlingImage2VideoNode: {
vendor: 'Kling',
nodeName: 'Kling Image to Video',
pricingParams: 'Same as Text to Video',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
KlingImageGenerationNode: {
vendor: 'Kling',
nodeName: 'Kling Image Generation',
pricingParams: 'modality | model',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (low)'
},
KlingLipSyncAudioToVideoNode: {
vendor: 'Kling',
nodeName: 'Kling Lip Sync Video with Audio',
pricingParams: 'duration of input video',
pricePerRunRange: '$0.07',
displayPrice: '$0.07/Run'
},
KlingLipSyncTextToVideoNode: {
vendor: 'Kling',
nodeName: 'Kling Lip Sync Video with Text',
pricingParams: 'duration of input video',
pricePerRunRange: '$0.07',
displayPrice: '$0.07/Run'
},
KlingSingleImageVideoEffectNode: {
vendor: 'Kling',
nodeName: 'Kling Video Effects',
pricingParams: 'effect_scene',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
KlingStartEndFrameNode: {
vendor: 'Kling',
nodeName: 'Kling Start-End Frame to Video',
pricingParams: 'Same as text to video',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
KlingTextToVideoNode: {
vendor: 'Kling',
nodeName: 'Kling Text to Video',
pricingParams: 'model | duration | mode',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium to high)'
},
KlingVideoExtendNode: {
vendor: 'Kling',
nodeName: 'Kling Video Extend',
pricingParams: '-',
pricePerRunRange: '$0.28',
displayPrice: '$0.28/Run'
},
KlingVirtualTryOnNode: {
vendor: 'Kling',
nodeName: 'Kling Virtual Try On',
pricingParams: '-',
pricePerRunRange: '$0.07',
displayPrice: '$0.07/Run'
},
LumaImageToVideoNode: {
vendor: 'Luma',
nodeName: 'Luma Image to Video',
pricingParams: 'Same as Text to Video',
pricePerRunRange: 'dynamic',
rateDocumentationUrl: 'https://lumalabs.ai/api/pricing',
displayPrice: 'Variable pricing (medium to high)'
},
LumaVideoNode: {
vendor: 'Luma',
nodeName: 'Luma Text to Video',
pricingParams: 'model | resolution | duration',
pricePerRunRange: 'dynamic',
rateDocumentationUrl: 'https://lumalabs.ai/api/pricing',
displayPrice: 'Variable pricing (medium to high)'
},
MinimaxImageToVideoNode: {
vendor: 'Minimax',
nodeName: 'MiniMax Image to Video',
pricingParams: '-',
pricePerRunRange: '$0.43',
rateDocumentationUrl: 'https://www.minimax.io/price',
displayPrice: '$0.43/Run'
},
MinimaxTextToVideoNode: {
vendor: 'Minimax',
nodeName: 'MiniMax Text to Video',
pricingParams: '-',
pricePerRunRange: '$0.43',
rateDocumentationUrl: 'https://www.minimax.io/price',
displayPrice: '$0.43/Run'
},
OpenAIDalle2: {
vendor: 'OpenAI',
nodeName: 'dall-e-2',
pricingParams: 'size',
pricePerRunRange: 'dynamic',
rateDocumentationUrl: 'https://platform.openai.com/docs/pricing',
displayPrice: 'Variable pricing (low)'
},
OpenAIDalle3: {
vendor: 'OpenAI',
nodeName: 'dall-e-3',
pricingParams: 'size | quality',
pricePerRunRange: 'dynamic',
rateDocumentationUrl: 'https://platform.openai.com/docs/pricing',
displayPrice: 'Variable pricing (medium)'
},
OpenAIGPTImage1: {
vendor: 'OpenAI',
nodeName: 'gpt-image-1',
pricingParams: 'quality',
pricePerRunRange: 'dynamic',
rateDocumentationUrl: 'https://platform.openai.com/docs/pricing',
displayPrice: 'Variable pricing (low to high)'
},
PikaImageToVideoNode2_2: {
vendor: 'Pika',
nodeName: 'Pika Image to Video',
pricingParams: 'duration | resolution',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
PikaScenesV2_2: {
vendor: 'Pika',
nodeName: 'Pika Scenes (Video Image Composition)',
pricingParams: 'duration | resolution',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
PikaStartEndFrameNode2_2: {
vendor: 'Pika',
nodeName: 'Pika Start and End Frame to Video',
pricingParams: 'duration | resolution',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
PikaTextToVideoNode2_2: {
vendor: 'Pika',
nodeName: 'Pika Text to Video',
pricingParams: 'duration | resolution',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium)'
},
Pikadditions: {
vendor: 'Pika',
nodeName: 'Pikadditions (Video Object Insertion)',
pricingParams: '-',
pricePerRunRange: '$0.3',
displayPrice: '$0.3/Run'
},
Pikaffects: {
vendor: 'Pika',
nodeName: 'Pikaffects (Video Effects)',
pricingParams: '-',
pricePerRunRange: '$0.45',
displayPrice: '$0.45/Run'
},
Pikaswaps: {
vendor: 'Pika',
nodeName: 'Pika Swaps (Video Object Replacement)',
pricingParams: '-',
pricePerRunRange: '$0.3',
displayPrice: '$0.3/Run'
},
PixverseImageToVideoNode: {
vendor: 'Pixverse',
nodeName: 'PixVerse Image to Video',
pricingParams: 'same as text to video',
pricePerRunRange: '$0.9',
displayPrice: '$0.9/Run'
},
PixverseTextToVideoNode: {
vendor: 'Pixverse',
nodeName: 'PixVerse Text to Video',
pricingParams: 'duration | quality | motion_mode',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (medium to high)'
},
PixverseTransitionVideoNode: {
vendor: 'Pixverse',
nodeName: 'PixVerse Transition Video',
pricingParams: 'same as text to video',
pricePerRunRange: '$0.9',
displayPrice: '$0.9/Run'
},
RecraftCreativeUpscaleNode: {
vendor: 'Recraft',
nodeName: 'Recraft Creative Upscale Image',
pricingParams: '-',
pricePerRunRange: '$0.25',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.25/Run'
},
RecraftCrispUpscaleNode: {
vendor: 'Recraft',
nodeName: 'Recraft Crisp Upscale Image',
pricingParams: '-',
pricePerRunRange: '$0.004',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.004/Run'
},
RecraftImageInpaintingNode: {
vendor: 'Recraft',
nodeName: 'Recraft Image Inpainting',
pricingParams: 'n',
pricePerRunRange: '$$0.04 x n',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.04 x n/Run'
},
RecraftImageToImageNode: {
vendor: 'Recraft',
nodeName: 'Recraft Image to Image',
pricingParams: 'n',
pricePerRunRange: '$0.04 x n',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.04 x n/Run'
},
RecraftRemoveBackgroundNode: {
vendor: 'Recraft',
nodeName: 'Recraft Remove Background',
pricingParams: '-',
pricePerRunRange: '$0.01',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.01/Run'
},
RecraftReplaceBackgroundNode: {
vendor: 'Recraft',
nodeName: 'Recraft Replace Background',
pricingParams: 'n',
pricePerRunRange: '$0.04',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.04/Run'
},
RecraftTextToImageNode: {
vendor: 'Recraft',
nodeName: 'Recraft Text to Image',
pricingParams: 'model | n',
pricePerRunRange: '$0.04 x n',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.04 x n/Run'
},
RecraftTextToVectorNode: {
vendor: 'Recraft',
nodeName: 'Recraft Text to Vector',
pricingParams: 'model | n',
pricePerRunRange: '$0.08 x n',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.08 x n/Run'
},
RecraftVectorizeImageNode: {
vendor: 'Recraft',
nodeName: 'Recraft Vectorize Image',
pricingParams: '-',
pricePerRunRange: '$0.01',
rateDocumentationUrl: 'https://www.recraft.ai/docs#pricing',
displayPrice: '$0.01/Run'
},
StabilityStableImageSD_3_5Node: {
vendor: 'Stability',
nodeName: 'Stability AI Stable Diffusion 3.5 Image',
pricingParams: 'model',
pricePerRunRange: 'dynamic',
displayPrice: 'Variable pricing (low)'
},
StabilityStableImageUltraNode: {
vendor: 'Stability',
nodeName: 'Stability AI Stable Image Ultra',
pricingParams: '-',
pricePerRunRange: '$0.08',
displayPrice: '$0.08/Run'
},
StabilityUpscaleConservativeNode: {
vendor: 'Stability',
nodeName: 'Stability AI Upscale Conservative',
pricingParams: '-',
pricePerRunRange: '$0.25',
displayPrice: '$0.25/Run'
},
StabilityUpscaleCreativeNode: {
vendor: 'Stability',
nodeName: 'Stability AI Upscale Creative',
pricingParams: '-',
pricePerRunRange: '$0.25',
displayPrice: '$0.25/Run'
},
StabilityUpscaleFastNode: {
vendor: 'Stability',
nodeName: 'Stability AI Upscale Fast',
pricingParams: '-',
pricePerRunRange: '$0.01',
displayPrice: '$0.01/Run'
},
VeoVideoGenerationNode: {
vendor: 'Veo',
nodeName: 'Google Veo2 Video Generation',
pricingParams: 'duration_seconds',
pricePerRunRange: 'dynamic',
rateDocumentationUrl:
'https://cloud.google.com/vertex-ai/generative-ai/pricing',
displayPrice: 'Variable pricing (high)'
},
LumaTextToImageNode: {
vendor: 'Luma',
nodeName: 'Luma Text to Image',
pricingParams: 'model | aspect_ratio',
pricePerRunRange: 'dynamic',
rateDocumentationUrl: 'https://lumalabs.ai/api/pricing',
displayPrice: 'Variable pricing (low to medium)'
},
LumaImageToImageNode: {
vendor: 'Luma',
nodeName: 'Luma Image to Image',
pricingParams: 'Same as Text to Image',
pricePerRunRange: 'dynamic',
rateDocumentationUrl: 'https://lumalabs.ai/api/pricing',
displayPrice: 'Variable pricing (low to medium)'
}
}
/**
* Composable to get node pricing information for API nodes
*/
export const useNodePricing = () => {
const getNodePrice = (nodeName: string): string =>
apiNodeCosts[nodeName]?.displayPrice || ''
/**
* Get the price display for a node
*/
const getNodeDisplayPrice = (node: LGraphNode): string => {
if (!node.constructor.nodeData?.api_node) return ''
return getNodePrice(node.constructor.nodeData.name)
}
return {
getNodeDisplayPrice
}
}

View File

@@ -322,6 +322,14 @@ export const CORE_SETTINGS: SettingParams[] = [
options: [NodeBadgeMode.None, NodeBadgeMode.ShowAll],
defaultValue: NodeBadgeMode.ShowAll
},
{
id: 'Comfy.NodeBadge.ShowApiPricing',
category: ['Comfy', 'API Nodes'],
name: 'Show API node pricing badge',
type: 'boolean',
defaultValue: true,
versionAdded: '1.20.3'
},
{
id: 'Comfy.ConfirmClear',
category: ['Comfy', 'Workflow', 'ConfirmClear'],

View File

@@ -886,7 +886,8 @@
"3D": "3D",
"Light": "Light",
"User": "User",
"Credits": "Credits"
"Credits": "Credits",
"API Nodes": "API Nodes"
},
"serverConfigItems": {
"listen": {

View File

@@ -225,6 +225,9 @@
"Hide built-in": "Hide built-in"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "Show API node pricing badge"
},
"Comfy_NodeSearchBoxImpl": {
"name": "Node search box implementation",
"options": {

View File

@@ -1001,6 +1001,7 @@
},
"settingsCategories": {
"3D": "3D",
"API Nodes": "Nodos API",
"About": "Acerca de",
"Appearance": "Apariencia",
"BrushAdjustment": "Ajuste de Pincel",

View File

@@ -193,6 +193,9 @@
"Show all": "Mostrar todo"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "Mostrar insignia de precios de nodo API"
},
"Comfy_NodeSearchBoxImpl": {
"name": "Implementación de la caja de búsqueda de nodos",
"options": {

View File

@@ -1001,6 +1001,7 @@
},
"settingsCategories": {
"3D": "3D",
"API Nodes": "Nœuds API",
"About": "À Propos",
"Appearance": "Apparence",
"BrushAdjustment": "Ajustement de Brosse",

View File

@@ -193,6 +193,9 @@
"Show all": "Afficher tout"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "Afficher linsigne de tarification API du nœud"
},
"Comfy_NodeSearchBoxImpl": {
"name": "Implémentation de la boîte de recherche de nœud",
"options": {

View File

@@ -1001,6 +1001,7 @@
},
"settingsCategories": {
"3D": "3D",
"API Nodes": "APIード",
"About": "情報",
"Appearance": "外観",
"BrushAdjustment": "ブラシ調整",

View File

@@ -193,6 +193,9 @@
"Show all": "すべて表示"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "APIードの料金バッジを表示"
},
"Comfy_NodeSearchBoxImpl": {
"name": "ノード検索ボックスの実装",
"options": {

View File

@@ -1001,6 +1001,7 @@
},
"settingsCategories": {
"3D": "3D",
"API Nodes": "API 노드",
"About": "정보",
"Appearance": "모양",
"BrushAdjustment": "브러시 조정",

View File

@@ -193,6 +193,9 @@
"Show all": "모두 표시"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "API 노드 가격 배지 표시"
},
"Comfy_NodeSearchBoxImpl": {
"name": "노드 검색 상자 구현",
"options": {

View File

@@ -1001,6 +1001,7 @@
},
"settingsCategories": {
"3D": "3D",
"API Nodes": "API-узлы",
"About": "О программе",
"Appearance": "Внешний вид",
"BrushAdjustment": "Настройка кисти",

View File

@@ -193,6 +193,9 @@
"Show all": "Показать все"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "Показать значок стоимости узла API"
},
"Comfy_NodeSearchBoxImpl": {
"name": "Реализация поискового поля нод",
"options": {

View File

@@ -1001,6 +1001,7 @@
},
"settingsCategories": {
"3D": "3D",
"API Nodes": "API 节点",
"About": "关于",
"Appearance": "外观",
"BrushAdjustment": "画笔调整",

View File

@@ -193,6 +193,9 @@
"Show all": "显示全部"
}
},
"Comfy_NodeBadge_ShowApiPricing": {
"name": "显示 API 节点定价徽章"
},
"Comfy_NodeSearchBoxImpl": {
"name": "节点搜索框",
"options": {

View File

@@ -421,6 +421,7 @@ const zSettings = z.object({
'Comfy.NodeBadge.NodeSourceBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.NodeIdBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.NodeLifeCycleBadgeMode': zNodeBadgeMode,
'Comfy.NodeBadge.ShowApiPricing': z.boolean(),
'Comfy.QueueButton.BatchCountLimit': z.number(),
'Comfy.Queue.MaxHistoryItems': z.number(),
'Comfy.Keybinding.UnsetBindings': z.array(zKeybinding),

View File

@@ -2,3 +2,23 @@ export interface ApiNodeCost {
name: string
cost: number
}
/**
* Information about an API node's cost and pricing details
*/
export interface ApiNodeCostData {
/** The vendor/company providing the API service (e.g., 'OpenAI', 'Stability') */
vendor: string
/** The human-readable name of the node as displayed in the UI */
nodeName: string
/** Parameters that affect pricing (e.g., 'size | quality', 'duration', '-' if none) */
pricingParams: string
/** The price range per run (e.g., '$0.05', '$0.04 x n', 'dynamic') */
pricePerRunRange: string
/** Formatted price string for display in the UI */
displayPrice: string
/** URL to the vendor's pricing documentation page */
rateDocumentationUrl?: string
}
export type ApiNodeCostRecord = Record<string, ApiNodeCostData>