mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-08 00:50:05 +00:00
Merge sidebar-documentation onto main
The sidebar-documentation branch has diverged enough from main to merging non-trivial Remove deprecated type usage. Move localization to language file.
This commit is contained in:
130
browser_tests/documentationSidebar.spec.ts
Normal file
130
browser_tests/documentationSidebar.spec.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from './ComfyPage'
|
||||
const nodeDef = {
|
||||
title: 'TestNodeAdvancedDoc'
|
||||
}
|
||||
|
||||
test.describe('Documentation Sidebar', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Floating')
|
||||
await comfyPage.loadWorkflow('default')
|
||||
})
|
||||
|
||||
test.afterEach(async ({ comfyPage }) => {
|
||||
const currentThemeId = await comfyPage.menu.getThemeId()
|
||||
if (currentThemeId !== 'dark') {
|
||||
await comfyPage.menu.toggleTheme()
|
||||
}
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test('Sidebar registered', async ({ comfyPage }) => {
|
||||
await expect(
|
||||
comfyPage.page.locator('.documentation-tab-button')
|
||||
).toBeVisible()
|
||||
})
|
||||
test('Parses help for basic node', async ({ comfyPage }) => {
|
||||
await comfyPage.page.locator('.documentation-tab-button').click()
|
||||
const docPane = comfyPage.page.locator('.sidebar-content-container')
|
||||
//Check that each independently parsed element exists
|
||||
await expect(docPane).toContainText('Load Checkpoint')
|
||||
await expect(docPane).toContainText('Loads a diffusion model')
|
||||
await expect(docPane).toContainText('The name of the checkpoint')
|
||||
await expect(docPane).toContainText('The VAE model used')
|
||||
})
|
||||
test('Responds to hovering over node', async ({ comfyPage }) => {
|
||||
await comfyPage.page.locator('.documentation-tab-button').click()
|
||||
const docPane = comfyPage.page.locator('.sidebar-content-container')
|
||||
await comfyPage.page.mouse.move(321, 593)
|
||||
const tooltipTimeout = 500
|
||||
await comfyPage.page.waitForTimeout(tooltipTimeout + 16)
|
||||
await expect(comfyPage.page.locator('.node-tooltip')).not.toBeVisible()
|
||||
await expect(
|
||||
comfyPage.page.locator('.sidebar-content-container>div>div:nth-child(4)')
|
||||
).toBeFocused()
|
||||
})
|
||||
test('Updates when a new node is selected', async ({ comfyPage }) => {
|
||||
await comfyPage.page.locator('.documentation-tab-button').click()
|
||||
const docPane = comfyPage.page.locator('.sidebar-content-container')
|
||||
await comfyPage.page.mouse.click(557, 440)
|
||||
await expect(docPane).not.toContainText('Load Checkpoint')
|
||||
await expect(docPane).toContainText('CLIP Text Encode (Prompt)')
|
||||
await expect(docPane).toContainText('The text to be encoded')
|
||||
await expect(docPane).toContainText(
|
||||
'A conditioning containing the embedded text'
|
||||
)
|
||||
})
|
||||
test('Responds to a change in theme', async ({ comfyPage }) => {
|
||||
await comfyPage.page.locator('.documentation-tab-button').click()
|
||||
const docPane = comfyPage.page.locator('.sidebar-content-container')
|
||||
await comfyPage.menu.toggleTheme()
|
||||
await expect(docPane).toHaveScreenshot(
|
||||
'documentation-sidebar-light-theme.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
test.describe('Advanced Description tests', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
//register test node and add to graph
|
||||
await comfyPage.page.evaluate(async (node) => {
|
||||
const app = window['app']
|
||||
await app.registerNodeDef(node.name, node)
|
||||
app.addNodeOnGraph(node)
|
||||
}, advDocNode)
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Floating')
|
||||
})
|
||||
test.afterEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
test('Description displays as raw html', async ({ comfyPage }) => {
|
||||
await comfyPage.page.locator('.documentation-tab-button').click()
|
||||
const docPane = comfyPage.page.locator('.sidebar-content-container>div')
|
||||
await expect(docPane).toHaveJSProperty(
|
||||
'innerHTML',
|
||||
advDocNode.description[1]
|
||||
)
|
||||
})
|
||||
test('selected function', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const app = window['app']
|
||||
const desc =
|
||||
LiteGraph.registered_node_types['Test_AdvancedDescription'].nodeData
|
||||
.description
|
||||
desc[2].select = (element, name, value) => {
|
||||
element.children[0].innerText = name + ' ' + value
|
||||
}
|
||||
})
|
||||
await comfyPage.page.locator('.documentation-tab-button').click()
|
||||
const docPane = comfyPage.page.locator('.sidebar-content-container')
|
||||
await comfyPage.page.mouse.move(307, 80)
|
||||
const tooltipTimeout = 500
|
||||
await comfyPage.page.waitForTimeout(tooltipTimeout + 16)
|
||||
await expect(docPane).toContainText('int_input 0')
|
||||
})
|
||||
})
|
||||
|
||||
const advDocNode = {
|
||||
display_name: 'Node With Advanced Description',
|
||||
name: 'Test_AdvancedDescription',
|
||||
input: {
|
||||
required: {
|
||||
int_input: [
|
||||
'INT',
|
||||
{ tooltip: "an input tooltip that won't be displayed in sidebar" }
|
||||
]
|
||||
}
|
||||
},
|
||||
output: ['INT'],
|
||||
output_name: ['int_output'],
|
||||
output_tooltips: ["An output tooltip that won't be displayed in the sidebar"],
|
||||
output_is_list: false,
|
||||
description: [
|
||||
'A node with description in the advanced format',
|
||||
`
|
||||
A long form description that will be displayed in the sidebar.
|
||||
<div doc_title="INT">Can include arbitrary html</div>
|
||||
<div doc_title="int_input">or out of order widgets</div>
|
||||
`,
|
||||
{}
|
||||
]
|
||||
}
|
||||
@@ -10,20 +10,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { LiteGraph } from '@comfyorg/litegraph'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useHoveredItemStore } from '@/stores/graphStore'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
let idleTimeout: number
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const hoveredItemStore = useHoveredItemStore()
|
||||
const tooltipRef = ref<HTMLDivElement>()
|
||||
const tooltipText = ref('')
|
||||
const left = ref<string>()
|
||||
const top = ref<string>()
|
||||
|
||||
const hideTooltip = () => (tooltipText.value = null)
|
||||
const clearHovered = () => (hoveredItemStore.value = null)
|
||||
|
||||
const showTooltip = async (tooltip: string | null | undefined) => {
|
||||
if (!tooltip) return
|
||||
@@ -43,6 +46,36 @@ const showTooltip = async (tooltip: string | null | undefined) => {
|
||||
top.value = comfyApp.canvas.mouse[1] + rect.height + 'px'
|
||||
}
|
||||
}
|
||||
watch(hoveredItemStore, (hoveredItem) => {
|
||||
if (!hoveredItem.value) {
|
||||
return hideTooltip()
|
||||
}
|
||||
const item = hoveredItem.value
|
||||
const nodeDef =
|
||||
nodeDefStore.nodeDefsByName[item.node.type] ??
|
||||
LiteGraph.registered_node_types[item.node.type]?.nodeData
|
||||
if (item.type == 'Title') {
|
||||
let description = nodeDef.description
|
||||
if (Array.isArray(description)) {
|
||||
description = description[0]
|
||||
}
|
||||
return showTooltip(description)
|
||||
} else if (item.type == 'Input') {
|
||||
showTooltip(nodeDef.input.getInput(item.inputName)?.tooltip)
|
||||
} else if (item.type == 'Output') {
|
||||
showTooltip(nodeDef?.output?.all?.[item.outputSlot]?.tooltip)
|
||||
} else if (item.type == 'Widget') {
|
||||
showTooltip(
|
||||
item.widget.tooltip ??
|
||||
(
|
||||
nodeDef.input.optional?.[item.widget.name] ??
|
||||
nodeDef.input.required?.[item.widget.name]
|
||||
)?.tooltip
|
||||
)
|
||||
} else {
|
||||
hideTooltip()
|
||||
}
|
||||
})
|
||||
|
||||
const onIdle = () => {
|
||||
const { canvas } = comfyApp
|
||||
@@ -50,13 +83,15 @@ const onIdle = () => {
|
||||
if (!node) return
|
||||
|
||||
const ctor = node.constructor as { title_mode?: 0 | 1 | 2 | 3 }
|
||||
const nodeDef = nodeDefStore.nodeDefsByName[node.type]
|
||||
const nodeDef =
|
||||
nodeDefStore.nodeDefsByName[node.type] ??
|
||||
LiteGraph.registered_node_types[node.type]?.nodeData
|
||||
|
||||
if (
|
||||
ctor.title_mode !== LiteGraph.NO_TITLE &&
|
||||
canvas.graph_mouse[1] < node.pos[1] // If we are over a node, but not within the node then we are on its title
|
||||
) {
|
||||
return showTooltip(nodeDef.description)
|
||||
hoveredItemStore.value = { node, type: 'Title' }
|
||||
}
|
||||
|
||||
if (node.flags?.collapsed) return
|
||||
@@ -69,7 +104,7 @@ const onIdle = () => {
|
||||
)
|
||||
if (inputSlot !== -1) {
|
||||
const inputName = node.inputs[inputSlot].name
|
||||
return showTooltip(nodeDef.input.getInput(inputName)?.tooltip)
|
||||
hoveredItemStore.value = { node, type: 'Input', inputName }
|
||||
}
|
||||
|
||||
const outputSlot = canvas.isOverNodeOutput(
|
||||
@@ -79,20 +114,18 @@ const onIdle = () => {
|
||||
[0, 0]
|
||||
)
|
||||
if (outputSlot !== -1) {
|
||||
return showTooltip(nodeDef.output.all?.[outputSlot]?.tooltip)
|
||||
hoveredItemStore.value = { node, type: 'Output', outputSlot }
|
||||
}
|
||||
|
||||
const widget = comfyApp.canvas.getWidgetAtCursor()
|
||||
// Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these
|
||||
if (widget && !widget.element) {
|
||||
return showTooltip(
|
||||
widget.tooltip ?? nodeDef.input.getInput(widget.name)?.tooltip
|
||||
)
|
||||
hoveredItemStore.value = { node, type: 'Widget', widget }
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
hideTooltip()
|
||||
clearHovered()
|
||||
clearTimeout(idleTimeout)
|
||||
|
||||
if ((e.target as Node).nodeName !== 'CANVAS') return
|
||||
@@ -100,7 +133,7 @@ const onMouseMove = (e: MouseEvent) => {
|
||||
}
|
||||
|
||||
useEventListener(window, 'mousemove', onMouseMove)
|
||||
useEventListener(window, 'click', hideTooltip)
|
||||
useEventListener(window, 'click', clearHovered)
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -74,7 +74,11 @@ https://github.com/Nuked88/ComfyUI-N-Sidebar/blob/7ae7da4a9761009fb6629bc04c6830
|
||||
backgroundColor: litegraphColors.WIDGET_BGCOLOR
|
||||
}"
|
||||
>
|
||||
{{ nodeDef.description }}
|
||||
{{
|
||||
Array.isArray(nodeDef.description)
|
||||
? nodeDef.description[0]
|
||||
: nodeDef.description
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
223
src/components/sidebar/tabs/DocumentationSidebarTab.vue
Normal file
223
src/components/sidebar/tabs/DocumentationSidebarTab.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<div v-if="!hasAnyDoc()">No documentation available</div>
|
||||
<div v-else-if="rawDoc" ref="docElement" v-html="rawDoc"></div>
|
||||
<div v-else ref="docElement">
|
||||
<div class="doc-node">{{ title }}</div>
|
||||
<div>{{ description }}</div>
|
||||
<div v-if="inputs.length" class="doc-section">Inputs</div>
|
||||
<div
|
||||
v-if="inputs.length"
|
||||
v-for="input in inputs"
|
||||
tabindex="-1"
|
||||
class="doc-item"
|
||||
>
|
||||
{{ input[0] }}
|
||||
<div>{{ input[1] }}</div>
|
||||
</div>
|
||||
<div v-if="outputs.length" class="doc-section">Outputs</div>
|
||||
<div
|
||||
v-if="outputs.length"
|
||||
v-for="output in outputs"
|
||||
tabindex="-1"
|
||||
class="doc-item"
|
||||
>
|
||||
{{ output[0] }}
|
||||
<div>{{ output[1] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { useHoveredItemStore } from '@/stores/graphStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const hoveredItemStore = useHoveredItemStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const docElement = ref(null)
|
||||
|
||||
let def
|
||||
const rawDoc = ref(null)
|
||||
const description = ref(null)
|
||||
const title = ref(null)
|
||||
const inputs = ref([])
|
||||
const outputs = ref([])
|
||||
|
||||
function setCollapse(el, doCollapse) {
|
||||
if (doCollapse) {
|
||||
el.children[0].children[0].innerHTML = '+'
|
||||
Object.assign(el.children[1].style, {
|
||||
color: '#CCC',
|
||||
overflowX: 'hidden',
|
||||
width: '0px',
|
||||
minWidth: 'calc(100% - 20px)',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
})
|
||||
for (let child of el.children[1].children) {
|
||||
if (child.style.display != 'none') {
|
||||
child.origDisplay = child.style.display
|
||||
}
|
||||
child.style.display = 'none'
|
||||
}
|
||||
} else {
|
||||
el.children[0].children[0].innerHTML = '-'
|
||||
Object.assign(el.children[1].style, {
|
||||
color: '',
|
||||
overflowX: '',
|
||||
width: '100%',
|
||||
minWidth: '',
|
||||
textOverflow: '',
|
||||
whiteSpace: ''
|
||||
})
|
||||
for (let child of el.children[1].children) {
|
||||
child.style.display = child.origDisplay
|
||||
}
|
||||
}
|
||||
}
|
||||
function collapseOnClick() {
|
||||
let doCollapse = this.children[0].innerHTML == '-'
|
||||
setCollapse(this.parentElement, doCollapse)
|
||||
}
|
||||
function selectHelp(name: string, value?: string) {
|
||||
if (!docElement.value || !name) {
|
||||
return null
|
||||
}
|
||||
if (def.description[2]?.select) {
|
||||
return def.description[2].select(docElement.value, name, value)
|
||||
}
|
||||
//attempt to navigate to name in help
|
||||
function collapseUnlessMatch(items, t) {
|
||||
var match = items.querySelector('[doc_title="' + t + '"]')
|
||||
if (!match) {
|
||||
for (let i of items.children) {
|
||||
if (i.innerHTML.slice(0, t.length + 5).includes(t)) {
|
||||
match = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
return null
|
||||
}
|
||||
//For longer documentation items with fewer collapsable elements,
|
||||
//scroll to make sure the entirety of the selected item is visible
|
||||
match.scrollIntoView({ block: 'nearest' })
|
||||
//The previous floating help implementation would try to scroll the window
|
||||
//itself if the display was partiall offscreen. As the sidebar documentation
|
||||
//does not pan with the canvas, this should no longer be needed
|
||||
//window.scrollTo(0, 0)
|
||||
for (let i of items.querySelectorAll('.doc_collapse')) {
|
||||
if (i.contains(match)) {
|
||||
setCollapse(i, false)
|
||||
} else {
|
||||
setCollapse(i, true)
|
||||
}
|
||||
}
|
||||
return match
|
||||
}
|
||||
let target = collapseUnlessMatch(docElement.value, name)
|
||||
if (target) {
|
||||
target.focus()
|
||||
if (value) {
|
||||
collapseUnlessMatch(target, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateNode(node?) {
|
||||
node ||= app?.canvas?.current_node
|
||||
if (!node) {
|
||||
// Graph has no nodes
|
||||
return
|
||||
}
|
||||
def = LiteGraph.getNodeType(node.type).nodeData
|
||||
title.value = def.display_name
|
||||
if (Array.isArray(def.description)) {
|
||||
rawDoc.value = def.description[1]
|
||||
outputs.value = []
|
||||
inputs.value = []
|
||||
return
|
||||
} else {
|
||||
rawDoc.value = null
|
||||
}
|
||||
description.value = def.description
|
||||
let input_temp = []
|
||||
for (let k in def?.input?.required) {
|
||||
if (def.input.required[k][1]?.tooltip) {
|
||||
input_temp.push([k, def.input.required[k][1].tooltip])
|
||||
}
|
||||
}
|
||||
for (let k in def?.optional?.required) {
|
||||
if (def.input.optional[k][1]?.tooltip) {
|
||||
input_temp.push([k, def.input.optional[k][1].tooltip])
|
||||
}
|
||||
}
|
||||
inputs.value = input_temp
|
||||
if (def.output_tooltips) {
|
||||
const outputs_temp = []
|
||||
const output_name = def.output_name || def.output
|
||||
for (let i = 0; i < def.output_tooltips.length; i++) {
|
||||
outputs_temp[i] = [output_name[i], def.output_tooltips[i]]
|
||||
}
|
||||
outputs.value = outputs_temp
|
||||
} else {
|
||||
outputs.value = []
|
||||
}
|
||||
}
|
||||
function hasAnyDoc() {
|
||||
return def?.description || inputs.value.length || outputs.value.length
|
||||
}
|
||||
watch(hoveredItemStore, (hoveredItem) => {
|
||||
if (!hoveredItem.value) {
|
||||
return
|
||||
}
|
||||
const item = hoveredItem.value
|
||||
if (item.node.id != app?.canvas?.current_node.id) {
|
||||
return
|
||||
}
|
||||
const nodeDef = nodeDefStore.nodeDefsByName[item.node.type]
|
||||
if (item.type == 'DESCRIPTION') {
|
||||
return
|
||||
} else if (item.type == 'Input') {
|
||||
selectHelp(item.inputName)
|
||||
hoveredItem.value = null
|
||||
} else if (item.type == 'Output') {
|
||||
selectHelp(nodeDef?.output?.all?.[item.outputSlot]?.name)
|
||||
hoveredItem.value = null
|
||||
} else if (item.type == 'Widget') {
|
||||
selectHelp(item.widget.name, item.widget.value)
|
||||
hoveredItem.value = null
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => canvasStore?.canvas?.current_node, updateNode)
|
||||
updateNode()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.doc-node {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.doc-section {
|
||||
background-color: var(--comfy-menu-bg);
|
||||
}
|
||||
.doc-item div {
|
||||
margin-inline-start: 1vw;
|
||||
}
|
||||
@keyframes selectAnimation {
|
||||
0% {
|
||||
background-color: #5555;
|
||||
}
|
||||
80% {
|
||||
background-color: #5555;
|
||||
}
|
||||
100% {
|
||||
background-color: #0000;
|
||||
}
|
||||
}
|
||||
.doc-item:focus {
|
||||
animation: selectAnimation 2s;
|
||||
}
|
||||
</style>
|
||||
18
src/hooks/sidebarTabs/documentationSidebarTab.ts
Normal file
18
src/hooks/sidebarTabs/documentationSidebarTab.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useQueuePendingTaskCountStore } from '@/stores/queueStore'
|
||||
import { markRaw } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import DocumentationSidebarTab from '@/components/sidebar/tabs/DocumentationSidebarTab.vue'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
export const useDocumentationSidebarTab = (): SidebarTabExtension => {
|
||||
const { t } = useI18n()
|
||||
const queuePendingTaskCountStore = useQueuePendingTaskCountStore()
|
||||
return {
|
||||
id: 'documentation',
|
||||
icon: 'mdi mdi-help',
|
||||
title: t('sideToolbar.documentation'),
|
||||
tooltip: t('sideToolbar.documentation'),
|
||||
component: markRaw(DocumentationSidebarTab),
|
||||
type: 'vue'
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,7 @@ export default {
|
||||
},
|
||||
modelLibrary: 'Model Library',
|
||||
downloads: 'Downloads',
|
||||
documentation: 'Display documentation for nodes',
|
||||
queueTab: {
|
||||
showFlatList: 'Show Flat List',
|
||||
backToAllTasks: 'Back to All Tasks',
|
||||
|
||||
@@ -137,6 +137,7 @@ export class ComfyApp {
|
||||
canvas: LGraphCanvas
|
||||
dragOverNode: LGraphNode | null
|
||||
canvasEl: HTMLCanvasElement
|
||||
tooltipCallback?: (node: LGraphNode, name: string, value?: string) => boolean
|
||||
// x, y, scale
|
||||
zoom_drag_start: [number, number, number] | null
|
||||
lastNodeErrors: any[] | null
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { LGraphNode, LGraphGroup, LGraphCanvas } from '@comfyorg/litegraph'
|
||||
import type { ComfyNodeItem } from '@/types/comfy'
|
||||
import { defineStore } from 'pinia'
|
||||
import { shallowRef } from 'vue'
|
||||
|
||||
@@ -10,6 +11,14 @@ export const useTitleEditorStore = defineStore('titleEditor', () => {
|
||||
}
|
||||
})
|
||||
|
||||
export const useHoveredItemStore = defineStore('hoveredItem', () => {
|
||||
const hoveredItem = shallowRef<ComfyNodeItem | null>(null)
|
||||
|
||||
return {
|
||||
hoveredItem
|
||||
}
|
||||
})
|
||||
|
||||
export const useCanvasStore = defineStore('canvas', () => {
|
||||
/**
|
||||
* The LGraphCanvas instance.
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
import {
|
||||
type ComfyNodeDef,
|
||||
type ComfyInputsSpec as ComfyInputsSpecSchema,
|
||||
type InputSpec
|
||||
type InputSpec,
|
||||
type DescriptionSpec
|
||||
} from '@/types/apiTypes'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ComfyWidgetConstructor } from '@/scripts/widgets'
|
||||
@@ -158,7 +159,7 @@ export class ComfyNodeDefImpl {
|
||||
display_name: string
|
||||
category: string
|
||||
python_module: string
|
||||
description: string
|
||||
description: DescriptionSpec
|
||||
deprecated: boolean
|
||||
experimental: boolean
|
||||
input: ComfyInputsSpec
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useModelLibrarySidebarTab } from '@/hooks/sidebarTabs/modelLibrarySideb
|
||||
import { useNodeLibrarySidebarTab } from '@/hooks/sidebarTabs/nodeLibrarySidebarTab'
|
||||
import { useQueueSidebarTab } from '@/hooks/sidebarTabs/queueSidebarTab'
|
||||
import { useWorkflowsSidebarTab } from '@/hooks/sidebarTabs/workflowsSidebarTab'
|
||||
import { useDocumentationSidebarTab } from '@/hooks/sidebarTabs/documentationSidebarTab'
|
||||
import { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
@@ -57,6 +58,7 @@ export const useSidebarTabStore = defineStore('sidebarTab', () => {
|
||||
registerSidebarTab(useNodeLibrarySidebarTab())
|
||||
registerSidebarTab(useModelLibrarySidebarTab())
|
||||
registerSidebarTab(useWorkflowsSidebarTab())
|
||||
registerSidebarTab(useDocumentationSidebarTab())
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -356,6 +356,11 @@ const zComfyComboOutput = z.array(z.any())
|
||||
const zComfyOutputTypesSpec = z.array(
|
||||
z.union([zComfyNodeDataType, zComfyComboOutput])
|
||||
)
|
||||
const zDescriptionSpec = z.union([
|
||||
z.string(),
|
||||
z.tuple([z.string(), z.string()]),
|
||||
z.tuple([z.string(), z.string(), z.record(z.string(), z.any())])
|
||||
])
|
||||
|
||||
const zComfyNodeDef = z.object({
|
||||
input: zComfyInputsSpec.optional(),
|
||||
@@ -365,7 +370,7 @@ const zComfyNodeDef = z.object({
|
||||
output_tooltips: z.array(z.string()).optional(),
|
||||
name: z.string(),
|
||||
display_name: z.string(),
|
||||
description: z.string(),
|
||||
description: zDescriptionSpec,
|
||||
category: z.string(),
|
||||
output_node: z.boolean(),
|
||||
python_module: z.string(),
|
||||
@@ -378,6 +383,7 @@ export type InputSpec = z.infer<typeof zInputSpec>
|
||||
export type ComfyInputsSpec = z.infer<typeof zComfyInputsSpec>
|
||||
export type ComfyOutputTypesSpec = z.infer<typeof zComfyOutputTypesSpec>
|
||||
export type ComfyNodeDef = z.infer<typeof zComfyNodeDef>
|
||||
export type DescriptionSpec = z.infer<typeof zDescriptionSpec>
|
||||
|
||||
export function validateComfyNodeDef(
|
||||
data: any,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import type { LGraphNode, IWidget } from '@comfyorg/litegraph'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { ComfyNodeDef } from '@/types/apiTypes'
|
||||
import type { ComfyNodeDef, DescriptionSpec } from '@/types/apiTypes'
|
||||
import type { Keybinding } from '@/types/keyBindingTypes'
|
||||
import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import type { SettingParams } from '@/types/settingTypes'
|
||||
@@ -159,3 +159,9 @@ export interface ComfyExtension {
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type ComfyNodeItem =
|
||||
| { node: LGraphNode; type: 'Title' }
|
||||
| { node: LGraphNode; type: 'Output'; outputSlot: number }
|
||||
| { node: LGraphNode; type: 'Input'; inputName: string }
|
||||
| { node: LGraphNode; type: 'Widget'; widget: IWidget }
|
||||
|
||||
Reference in New Issue
Block a user