V2 Node Search (+ hidden Node Library changes) (#8987)

## Summary

Redesigned node search with categories

## Changes

- **What**: Adds a v2 search component, leaving the existing
implementation untouched
- It also brings onboard the incomplete node library & preview changes,
disabled and behind a hidden setting
- **Breaking**: Changes the 'default' value of the node search setting
to v2, adding v1 (legacy) as an option

## Screenshots (if applicable)




https://github.com/user-attachments/assets/2ab797df-58f0-48e8-8b20-2a1809e3735f

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8987-V2-Node-Search-hidden-Node-Library-changes-30c6d73d36508160902bcb92553f147c)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Yourz <crazilou@vip.qq.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
pythongosssss
2026-02-20 09:10:03 +00:00
committed by GitHub
parent 8f5cdead73
commit 6902e38e6a
183 changed files with 7972 additions and 127 deletions

View File

@@ -0,0 +1,143 @@
<template>
<div
class="flex w-50 flex-col overflow-hidden rounded-2xl bg-(--base-background) border border-(--border-default)"
>
<div ref="previewContainerRef" class="overflow-hidden p-3">
<div ref="previewWrapperRef" class="origin-top-left scale-50">
<LGraphNodePreview :node-def="nodeDef" position="relative" />
</div>
</div>
<!-- Content Section -->
<div class="flex flex-col gap-2 p-3 pt-1">
<!-- Title -->
<h3 class="text-xs font-semibold text-foreground m-0">
{{ nodeDef.display_name }}
</h3>
<!-- Category Path -->
<p
v-if="showCategoryPath && nodeDef.category"
class="text-xs text-muted-foreground -mt-1"
>
{{ nodeDef.category.replaceAll('/', ' > ') }}
</p>
<!-- Badges -->
<div class="flex flex-wrap gap-2 empty:hidden">
<NodePricingBadge :node-def="nodeDef" />
<NodeProviderBadge :node-def="nodeDef" />
</div>
<!-- Description -->
<p
v-if="nodeDef.description"
class="text-[11px] font-normal leading-normal text-muted-foreground m-0"
>
{{ nodeDef.description }}
</p>
<!-- Divider -->
<div
v-if="(inputs.length > 0 || outputs.length > 0) && showInputsAndOutputs"
class="border-t border-border-default"
/>
<!-- Inputs Section -->
<div
v-if="inputs.length > 0 && showInputsAndOutputs"
class="flex flex-col gap-1"
>
<h4
class="text-xxs font-semibold uppercase tracking-wide text-muted-foreground m-0"
>
{{ $t('nodeHelpPage.inputs') }}
</h4>
<div
v-for="input in inputs"
:key="input.name"
class="flex items-center justify-between gap-2 text-xxs"
>
<span class="shrink-0 text-foreground">{{ input.name }}</span>
<span class="min-w-0 truncate text-muted-foreground">{{
input.type
}}</span>
</div>
</div>
<!-- Outputs Section -->
<div
v-if="outputs.length > 0 && showInputsAndOutputs"
class="flex flex-col gap-1"
>
<h4
class="text-xxs font-semibold uppercase tracking-wide text-muted-foreground m-0"
>
{{ $t('nodeHelpPage.outputs') }}
</h4>
<div
v-for="output in outputs"
:key="output.name"
class="flex items-center justify-between gap-2 text-xxs"
>
<span class="shrink-0 text-foreground">{{ output.name }}</span>
<span class="min-w-0 truncate text-muted-foreground">{{
output.type
}}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useResizeObserver } from '@vueuse/core'
import { computed, ref } from 'vue'
import NodePricingBadge from '@/components/node/NodePricingBadge.vue'
import NodeProviderBadge from '@/components/node/NodeProviderBadge.vue'
import LGraphNodePreview from '@/renderer/extensions/vueNodes/components/LGraphNodePreview.vue'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
const SCALE_FACTOR = 0.5
const PREVIEW_CONTAINER_PADDING_PX = 24 // p-3 top + bottom (12px × 2)
const {
nodeDef,
showInputsAndOutputs = true,
showCategoryPath = false
} = defineProps<{
nodeDef: ComfyNodeDefImpl
showInputsAndOutputs?: boolean
showCategoryPath?: boolean
}>()
const previewContainerRef = ref<HTMLElement>()
const previewWrapperRef = ref<HTMLElement>()
useResizeObserver(previewWrapperRef, (entries) => {
const entry = entries[0]
if (entry && previewContainerRef.value) {
const scaledHeight = entry.contentRect.height * SCALE_FACTOR
previewContainerRef.value.style.height = `${scaledHeight + PREVIEW_CONTAINER_PADDING_PX}px`
}
})
const inputs = computed(() => {
if (!nodeDef.inputs) return []
return Object.entries(nodeDef.inputs)
.filter(([_, input]) => !input.hidden)
.map(([name, input]) => ({
name,
type: input.type
}))
})
const outputs = computed(() => {
if (!nodeDef.outputs) return []
return nodeDef.outputs.map((output) => ({
name: output.name,
type: output.type
}))
})
</script>

View File

@@ -0,0 +1,43 @@
<template>
<BadgePill
v-if="nodeDef.api_node"
v-show="priceLabel"
:text="priceLabel"
icon="icon-[comfy--credits]"
border-style="#f59e0b"
filled
/>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import BadgePill from '@/components/common/BadgePill.vue'
import { evaluateNodeDefPricing } from '@/composables/node/useNodePricing'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
const { nodeDef } = defineProps<{
nodeDef: ComfyNodeDefImpl
}>()
const priceLabel = ref('')
watch(
() => nodeDef.name,
(name) => {
if (!nodeDef.api_node) {
priceLabel.value = ''
return
}
const capturedName = name
evaluateNodeDefPricing(nodeDef)
.then((label) => {
if (nodeDef.name === capturedName) priceLabel.value = label
})
.catch((e) => {
console.error('[NodePricingBadge] pricing evaluation failed:', e)
})
},
{ immediate: true }
)
</script>

View File

@@ -0,0 +1,26 @@
<template>
<BadgePill
v-if="nodeDef.api_node && providerName"
:text="providerName"
:icon="getProviderIcon(providerName)"
:border-style="getProviderBorderStyle(providerName)"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import BadgePill from '@/components/common/BadgePill.vue'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import {
getProviderBorderStyle,
getProviderIcon,
getProviderName
} from '@/utils/categoryUtil'
const { nodeDef } = defineProps<{
nodeDef: ComfyNodeDefImpl
}>()
const providerName = computed(() => getProviderName(nodeDef.category))
</script>