Feat: Add Badges for Vue Nodes (#6243)

## Summary

Adds the badges to the header for Vue nodes.


## Review Focus

Design, mostly. Any structures here I'm not handling but should be?

## Screenshots
<img width="1514" height="642" alt="image"
src="https://github.com/user-attachments/assets/387fd2f6-bb4b-4fee-b273-6166a52a3552"
/>

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6243-Feat-Add-Badges-for-Vue-Nodes-2956d73d36508184a250d67b127ed4b1)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Alexander Brown
2025-10-24 11:24:27 -07:00
committed by GitHub
parent 7663517f54
commit 233004c837
5 changed files with 41 additions and 6 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -20,6 +20,7 @@ import type { WidgetValue } from '@/types/simplifiedWidget'
import type {
LGraph,
LGraphBadge,
LGraphNode,
LGraphTriggerAction,
LGraphTriggerEvent,
@@ -51,6 +52,8 @@ export interface VueNodeData {
mode: number
selected: boolean
executing: boolean
apiNode?: boolean
badges?: (LGraphBadge | (() => LGraphBadge))[]
subgraphId?: string | null
widgets?: SafeWidgetData[]
inputs?: INodeInputSlot[]
@@ -117,7 +120,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
}
// Extract safe data from LiteGraph node for Vue consumption
const extractVueNodeData = (node: LGraphNode): VueNodeData => {
function extractVueNodeData(node: LGraphNode): VueNodeData {
// Determine subgraph ID - null for root graph, string for subgraphs
const subgraphId =
node.graph && 'id' in node.graph && node.graph !== node.graph.rootGraph
@@ -192,6 +195,9 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
node.constructor?.name ||
'Unknown'
const apiNode = node.constructor?.nodeData?.api_node ?? false
const badges = node.badges
return {
id: String(node.id),
title: typeof node.title === 'string' ? node.title : '',
@@ -200,6 +206,8 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
selected: node.selected || false,
executing: false, // Will be updated separately based on execution state
subgraphId,
apiNode,
badges,
hasErrors: !!node.has_errors,
widgets: safeWidgets,
inputs: node.inputs ? [...node.inputs] : undefined,

View File

@@ -34,8 +34,8 @@
{
transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`,
zIndex: zIndex,
backgroundColor: nodeBodyBackgroundColor,
opacity: nodeOpacity
opacity: nodeOpacity,
'--node-component-surface': nodeBodyBackgroundColor
},
dragStyle
]"

View File

@@ -0,0 +1,19 @@
<template>
<div
v-if="badge.icon || badge.text"
class="flex flex-auto items-center rounded-sm bg-node-component-surface px-1 py-0.5"
:style="{
color: badge.fgColor,
backgroundColor: badge.bgColor
}"
>
{{ badge.text }}
</div>
</template>
<script setup lang="ts">
import type { LGraphBadge } from '@/lib/litegraph/src/LGraphBadge'
const { badge } = defineProps<{
badge: LGraphBadge
}>()
</script>

View File

@@ -38,6 +38,8 @@
</div>
<div v-if="isSubgraphNode" class="icon-[comfy--workflow] size-4" />
<div v-if="isApiNode" class="icon-[lucide--dollar-sign] size-4" />
<!-- Node Title -->
<div
v-tooltip.top="tooltipConfig"
@@ -60,7 +62,8 @@
<LODFallback />
</div>
<div class="lod-toggle flex shrink-0 items-center">
<div class="lod-toggle flex shrink-0 items-center justify-between gap-2">
<NodeBadge v-for="badge of nodeBadges" :key="badge.text" :badge />
<IconButton
v-if="isSubgraphNode"
v-tooltip.top="enterSubgraphTooltipConfig"
@@ -80,14 +83,16 @@
</template>
<script setup lang="ts">
import { computed, onErrorCaptured, ref, watch } from 'vue'
import { computed, onErrorCaptured, ref, toValue, watch } from 'vue'
import IconButton from '@/components/button/IconButton.vue'
import EditableText from '@/components/common/EditableText.vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { st } from '@/i18n'
import type { LGraphBadge } from '@/lib/litegraph/src/LGraphBadge'
import { useSettingStore } from '@/platform/settings/settingStore'
import NodeBadge from '@/renderer/extensions/vueNodes/components/NodeBadge.vue'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils'
import { app } from '@/scripts/app'
@@ -183,8 +188,11 @@ watch(
}
)
const nodeBadges = computed<LGraphBadge[]>(() =>
(nodeData?.badges ?? []).map(toValue)
)
const isPinned = computed(() => Boolean(nodeData?.flags?.pinned))
const isApiNode = computed(() => Boolean(nodeData?.apiNode))
// Subgraph detection
const isSubgraphNode = computed(() => {
if (!nodeData?.id) return false