From 584119433d2dacb9b6d1fe60bc3f4c68437d50a8 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Mon, 8 May 2023 01:25:10 -0500 Subject: [PATCH] Image and radio widgets --- src/lib/ComfyGraph.ts | 13 +- src/lib/components/AccordionContainer.svelte | 3 +- src/lib/components/ComfyApp.ts | 6 +- src/lib/nodes/ComfyActionNodes.ts | 39 +++--- src/lib/nodes/ComfyGraphNode.ts | 14 +++ src/lib/nodes/ComfyImageCacheNode.ts | 5 +- src/lib/nodes/ComfyPickFirstNode.ts | 13 +- src/lib/nodes/ComfyReroute.ts | 5 +- src/lib/nodes/ComfySelector.ts | 8 +- src/lib/nodes/ComfyValueControl.ts | 5 +- src/lib/nodes/ComfyWidgetNodes.ts | 124 ++++++++++++++----- src/lib/stores/layoutState.ts | 26 +++- src/lib/utils.ts | 15 ++- src/lib/widgets/CheckboxWidget.svelte | 7 +- src/lib/widgets/ComboWidget.svelte | 4 +- src/lib/widgets/GalleryWidget.svelte | 75 ++++++++--- src/lib/widgets/RadioWidget.svelte | 64 ++++++++++ 17 files changed, 332 insertions(+), 94 deletions(-) create mode 100644 src/lib/widgets/RadioWidget.svelte diff --git a/src/lib/ComfyGraph.ts b/src/lib/ComfyGraph.ts index 28778be..0fb058b 100644 --- a/src/lib/ComfyGraph.ts +++ b/src/lib/ComfyGraph.ts @@ -1,4 +1,4 @@ -import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions } from "@litegraph-ts/core"; +import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions, LGraphCanvas } from "@litegraph-ts/core"; import GraphSync from "./GraphSync"; import EventEmitter from "events"; import type TypedEmitter from "typed-emitter"; @@ -53,6 +53,17 @@ export default class ComfyGraph extends LGraph { layoutState.nodeAdded(node) this.graphSync.onNodeAdded(node); + // All nodes whether they come from base litegraph or ComfyBox should + // have tags added to them. Can't override serialization for existing + // node types to add `tags` as anew field so putting it in properties is better. + if (node.properties.tags == null) + node.properties.tags = [] + + if ((node as any).canInheritSlotTypes && node.inputs.length > 1) { + node.color ||= LGraphCanvas.node_colors["green"].color; + node.bgColor ||= LGraphCanvas.node_colors["green"].bgColor; + } + if ("outputProperties" in node) { const widgetNode = node as ComfyWidgetNode; for (const propName of widgetNode.outputProperties) { diff --git a/src/lib/components/AccordionContainer.svelte b/src/lib/components/AccordionContainer.svelte index aa16886..64ff363 100644 --- a/src/lib/components/AccordionContainer.svelte +++ b/src/lib/components/AccordionContainer.svelte @@ -166,7 +166,8 @@ } :global(.label-wrap > span:not(.icon)) { - color: var(--block-title-text-color); + /* color: var(--block-title-text-color); */ + font-size: 16px; } .handle { diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index c35e692..0ea031c 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -548,8 +548,6 @@ export default class ComfyApp { break; } - console.debug("[graphToPrompt] consider link", JSON.stringify(link), parent.id) - if (nextLink && !seen[nextLink.id]) { seen[nextLink.id] = true const inputNode = parent.graph.getNodeById(nextLink.origin_id) as ComfyGraphNode; @@ -558,7 +556,7 @@ export default class ComfyApp { parent = null; } else { - console.debug("[graphToPrompt] Traverse upstream link", JSON.stringify(link), parent.id, inputNode?.id, inputNode?.isBackendNode) + console.debug("[graphToPrompt] Traverse upstream link", parent.id, inputNode?.id, inputNode?.isBackendNode) link = nextLink; parent = inputNode; } @@ -571,7 +569,7 @@ export default class ComfyApp { if (tag && !hasTag(parent, tag)) continue; - console.debug("[graphToPrompt] final link", JSON.stringify(link), parent.id, node.id) + console.debug("[graphToPrompt] final link", parent.id, node.id) const input = node.inputs[i] // TODO can null be a legitimate value in some cases? // Nodes like CLIPLoader will never have a value in the frontend, hence "null". diff --git a/src/lib/nodes/ComfyActionNodes.ts b/src/lib/nodes/ComfyActionNodes.ts index 7ac6332..55e1e7b 100644 --- a/src/lib/nodes/ComfyActionNodes.ts +++ b/src/lib/nodes/ComfyActionNodes.ts @@ -1,21 +1,13 @@ -import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, BuiltInSlotType, type ITextWidget, type SerializedLGraphNode, NodeMode, type IToggleWidget } from "@litegraph-ts/core"; -import ComfyGraphNode from "./ComfyGraphNode"; -import { Watch } from "@litegraph-ts/nodes-basic"; import type { SerializedPrompt } from "$lib/components/ComfyApp"; -import { toast } from '@zerodevx/svelte-toast' -import type { GalleryOutput } from "./ComfyWidgetNodes"; -import { get } from "svelte/store"; -import queueState from "$lib/stores/queueState"; import notify from "$lib/notify"; import layoutState from "$lib/stores/layoutState"; - -export interface ComfyQueueEventsProperties extends Record { -} +import queueState from "$lib/stores/queueState"; +import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout } from "@litegraph-ts/core"; +import { get } from "svelte/store"; +import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; +import type { GalleryOutput } from "./ComfyWidgetNodes"; export class ComfyQueueEvents extends ComfyGraphNode { - override properties: ComfyQueueEventsProperties = { - } - static slotLayout: SlotLayout = { outputs: [ { name: "beforeQueued", type: BuiltInSlotType.EVENT }, @@ -57,7 +49,7 @@ LiteGraph.registerNodeType({ type: "actions/queue_events" }) -export interface ComfyStoreImagesActionProperties extends Record { +export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties { images: GalleryOutput | null } @@ -96,13 +88,14 @@ LiteGraph.registerNodeType({ type: "actions/store_images" }) -export interface ComfyCopyActionProperties extends Record { +export interface ComfyCopyActionProperties extends ComfyGraphNodeProperties { value: any } export class ComfyCopyAction extends ComfyGraphNode { override properties: ComfyCopyActionProperties = { - value: null + value: null, + tags: [] } static slotLayout: SlotLayout = { @@ -148,7 +141,7 @@ LiteGraph.registerNodeType({ type: "actions/copy" }) -export interface ComfySwapActionProperties extends Record { +export interface ComfySwapActionProperties extends ComfyGraphNodeProperties { } export class ComfySwapAction extends ComfyGraphNode { @@ -182,13 +175,14 @@ LiteGraph.registerNodeType({ type: "actions/swap" }) -export interface ComfyNotifyActionProperties extends Record { +export interface ComfyNotifyActionProperties extends ComfyGraphNodeProperties { message: string } export class ComfyNotifyAction extends ComfyGraphNode { override properties: ComfyNotifyActionProperties = { - message: "Nya." + message: "Nya.", + tags: [] } static slotLayout: SlotLayout = { @@ -213,7 +207,7 @@ LiteGraph.registerNodeType({ type: "actions/notify" }) -export interface ComfyExecuteSubgraphActionProperties extends Record { +export interface ComfyExecuteSubgraphActionProperties extends ComfyGraphNodeProperties { tag: string | null, } @@ -253,7 +247,7 @@ LiteGraph.registerNodeType({ type: "actions/execute_subgraph" }) -export interface ComfySetNodeModeActionProperties extends Record { +export interface ComfySetNodeModeActionProperties extends ComfyGraphNodeProperties { targetTags: string, enable: boolean, } @@ -261,7 +255,8 @@ export interface ComfySetNodeModeActionProperties extends Record { export class ComfySetNodeModeAction extends ComfyGraphNode { override properties: ComfySetNodeModeActionProperties = { targetTags: "", - enable: false + enable: false, + tags: [] } static slotLayout: SlotLayout = { diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index 31916e9..5ece8af 100644 --- a/src/lib/nodes/ComfyGraphNode.ts +++ b/src/lib/nodes/ComfyGraphNode.ts @@ -17,7 +17,15 @@ export type DefaultWidgetLayout = { inputs?: Record, } +export interface ComfyGraphNodeProperties extends Record { + tags: string[] +} + export default class ComfyGraphNode extends LGraphNode { + override properties: ComfyGraphNodeProperties = { + tags: [] + } + isBackendNode?: boolean; beforeQueued?(subgraph: string | null): void; @@ -58,6 +66,11 @@ export default class ComfyGraphNode extends LGraphNode { return null; } + constructor(title?: string) { + super(title) + this.addProperty("tags", [], "array") + } + private inheritSlotTypes(type: LConnectionKind, isConnected: boolean) { // Prevent multiple connections to different types when we have no input if (isConnected && type === LConnectionKind.OUTPUT) { @@ -261,6 +274,7 @@ export default class ComfyGraphNode extends LGraphNode { comfyInput.defaultWidgetNode = widgetNode.class as any } } + this.saveUserState = (o as any).saveUserState; if (this.saveUserState == null) this.saveUserState = true diff --git a/src/lib/nodes/ComfyImageCacheNode.ts b/src/lib/nodes/ComfyImageCacheNode.ts index 21a6016..99f4ad1 100644 --- a/src/lib/nodes/ComfyImageCacheNode.ts +++ b/src/lib/nodes/ComfyImageCacheNode.ts @@ -1,8 +1,8 @@ import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp, type PropertyLayout, type IComboWidget } from "@litegraph-ts/core"; -import ComfyGraphNode from "./ComfyGraphNode"; +import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; import type { GalleryOutput } from "./ComfyWidgetNodes"; -export interface ComfyImageCacheNodeProperties extends Record { +export interface ComfyImageCacheNodeProperties extends ComfyGraphNodeProperties { images: GalleryOutput | null, index: number, filenames: Record, @@ -18,6 +18,7 @@ type ImageCacheState = "none" | "uploading" | "failed" | "cached" */ export default class ComfyImageCacheNode extends ComfyGraphNode { override properties: ComfyImageCacheNodeProperties = { + tags: [], images: null, index: 0, filenames: {}, diff --git a/src/lib/nodes/ComfyPickFirstNode.ts b/src/lib/nodes/ComfyPickFirstNode.ts index daeace6..481d1fd 100644 --- a/src/lib/nodes/ComfyPickFirstNode.ts +++ b/src/lib/nodes/ComfyPickFirstNode.ts @@ -1,8 +1,8 @@ import { BuiltInSlotType, LiteGraph, NodeMode, type INodeInputSlot, type SlotLayout, type INodeOutputSlot, LLink, LConnectionKind, type ITextWidget } from "@litegraph-ts/core"; -import ComfyGraphNode from "./ComfyGraphNode"; +import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; import { Watch } from "@litegraph-ts/nodes-basic"; -export interface ComfyPickFirstNodeProperties extends Record { +export interface ComfyPickFirstNodeProperties extends ComfyGraphNodeProperties { acceptNullLinkData: boolean } @@ -19,6 +19,7 @@ function nextLetter(s: string): string { export default class ComfyPickFirstNode extends ComfyGraphNode { override properties: ComfyPickFirstNodeProperties = { + tags: [], acceptNullLinkData: false } @@ -136,10 +137,12 @@ export default class ComfyPickFirstNode extends ComfyGraphNode { if (this.isValidLink(link)) { // Copy frontend-only inputs. const node = this.getInputNode(index); - if (node != null && !(node as any).isBackendNode) { + if (node != null) { this.selected = index; - this.displayWidget.value = Watch.toString(link.data) - this.setOutputData(0, link.data) + if (!(node as any).isBackendNode) { + this.displayWidget.value = Watch.toString(link.data) + this.setOutputData(0, link.data) + } return } } diff --git a/src/lib/nodes/ComfyReroute.ts b/src/lib/nodes/ComfyReroute.ts index 0b990c8..8dedc05 100644 --- a/src/lib/nodes/ComfyReroute.ts +++ b/src/lib/nodes/ComfyReroute.ts @@ -1,7 +1,7 @@ import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, NodeMode } from "@litegraph-ts/core"; -import ComfyGraphNode from "./ComfyGraphNode"; +import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; -export interface ComfyRerouteProperties extends Record { +export interface ComfyRerouteProperties extends ComfyGraphNodeProperties { showOutputText: boolean; horizontal: boolean; } @@ -22,6 +22,7 @@ export default class ComfyReroute extends ComfyGraphNode { override collapsable: boolean = false; override properties: ComfyRerouteProperties = { + tags: [], showOutputText: ComfyReroute.defaultVisibility, horizontal: false } diff --git a/src/lib/nodes/ComfySelector.ts b/src/lib/nodes/ComfySelector.ts index 9c65079..fabd039 100644 --- a/src/lib/nodes/ComfySelector.ts +++ b/src/lib/nodes/ComfySelector.ts @@ -1,12 +1,13 @@ import { BuiltInSlotType, LConnectionKind, LLink, LiteGraph, NodeMode, type INodeInputSlot, type SlotLayout, type INodeOutputSlot } from "@litegraph-ts/core"; -import ComfyGraphNode from "./ComfyGraphNode"; +import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; -export interface ComfySelectorProperties extends Record { +export interface ComfySelectorProperties extends ComfyGraphNodeProperties { value: any } export default class ComfySelector extends ComfyGraphNode { override properties: ComfySelectorProperties = { + tags: [], value: null } @@ -78,12 +79,13 @@ LiteGraph.registerNodeType({ type: "utils/selector" }) -export interface ComfySelectorTwoProperties extends Record { +export interface ComfySelectorTwoProperties extends ComfyGraphNodeProperties { value: any } export class ComfySelectorTwo extends ComfyGraphNode { override properties: ComfySelectorTwoProperties = { + tags: [], value: null } diff --git a/src/lib/nodes/ComfyValueControl.ts b/src/lib/nodes/ComfyValueControl.ts index efda565..570faae 100644 --- a/src/lib/nodes/ComfyValueControl.ts +++ b/src/lib/nodes/ComfyValueControl.ts @@ -1,10 +1,10 @@ import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core"; -import ComfyGraphNode, { type DefaultWidgetLayout } from "./ComfyGraphNode"; +import ComfyGraphNode, { type ComfyGraphNodeProperties, type DefaultWidgetLayout } from "./ComfyGraphNode"; import { clamp } from "$lib/utils"; import ComboWidget from "$lib/widgets/ComboWidget.svelte"; import { ComfyComboNode } from "./ComfyWidgetNodes"; -export interface ComfyValueControlProperties extends Record { +export interface ComfyValueControlProperties extends ComfyGraphNodeProperties { value: any, action: "fixed" | "increment" | "decrement" | "randomize", min: number, @@ -16,6 +16,7 @@ const INT_MAX = 1125899906842624; export default class ComfyValueControl extends ComfyGraphNode { override properties: ComfyValueControlProperties = { + tags: [], value: null, action: "fixed", min: -INT_MAX, diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index 5b14601..1dd90e2 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -1,21 +1,34 @@ -import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout, type IComboWidget, NodeMode } from "@litegraph-ts/core"; -import ComfyGraphNode from "./ComfyGraphNode"; +import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout, type IComboWidget, NodeMode, type INumberWidget } from "@litegraph-ts/core"; +import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; +import type { SvelteComponentDev } from "svelte/internal"; +import { Watch } from "@litegraph-ts/nodes-basic"; +import type IComfyInputSlot from "$lib/IComfyInputSlot"; +import { writable, type Unsubscriber, type Writable, get } from "svelte/store"; +import { clamp, convertComfyOutputToGradio, range } from "$lib/utils" +import layoutState from "$lib/stores/layoutState"; +import type { FileData as GradioFileData } from "@gradio/upload"; +import queueState from "$lib/stores/queueState"; + import ComboWidget from "$lib/widgets/ComboWidget.svelte"; import RangeWidget from "$lib/widgets/RangeWidget.svelte"; import TextWidget from "$lib/widgets/TextWidget.svelte"; import GalleryWidget from "$lib/widgets/GalleryWidget.svelte"; import ButtonWidget from "$lib/widgets/ButtonWidget.svelte"; import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte"; -import type { SvelteComponentDev } from "svelte/internal"; -import { Watch } from "@litegraph-ts/nodes-basic"; -import type IComfyInputSlot from "$lib/IComfyInputSlot"; -import { writable, type Unsubscriber, type Writable, get } from "svelte/store"; -import { clamp, range } from "$lib/utils" -import layoutState from "$lib/stores/layoutState"; -import type { FileData as GradioFileData } from "@gradio/upload"; -import queueState from "$lib/stores/queueState"; +import RadioWidget from "$lib/widgets/RadioWidget.svelte"; -export interface ComfyWidgetProperties extends Record { +/* + * NOTE: If you want to add a new widget but it has the same input/output type + * as another one of the existing widgets, best to create a new "variant" of + * that widget instead. + * + * - Go to layoutState, look for `ALL_ATTRIBUTES,` insert or find a "variant" + * attribute and set `validNodeTypes` to the type of the litegraph node + * - Add a new entry in the `values` array, like "knob" or "dial" for ComfySliderWidget + * - Add an {#if widget.attrs.variant === <...>} statement in the corresponding Svelte component + */ + +export interface ComfyWidgetProperties extends ComfyGraphNodeProperties { defaultValue: any } @@ -98,9 +111,9 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { this.notifyPropsChanged(); // Also need to notify the parent container since it's what controls the // hidden state of the widget - const layoutEntry = layoutState.findLayoutEntryForNode(this.id) - if (layoutEntry && layoutEntry.parent) - layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1) + // const layoutEntry = layoutState.findLayoutEntryForNode(this.id) + // if (layoutEntry && layoutEntry.parent) + // layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1) return result; } @@ -131,7 +144,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { /* * Logic to run if this widget can be treated as output (slider, combo, text) */ - override onExecute() { + override onExecute(param: any, options: object) { if (this.copyFromInputLink) { if (this.inputs.length >= this.inputIndex) { const data = this.getInputData(this.inputIndex) @@ -186,6 +199,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { } notifyPropsChanged() { + console.debug("propsChanged", this) this.propsChanged.set(get(this.propsChanged) + 1) } @@ -248,6 +262,7 @@ export interface ComfySliderProperties extends ComfyWidgetProperties { export class ComfySliderNode extends ComfyWidgetNode { override properties: ComfySliderProperties = { + tags: [], defaultValue: 0, min: 0, max: 10, @@ -312,6 +327,7 @@ export interface ComfyComboProperties extends ComfyWidgetProperties { export class ComfyComboNode extends ComfyWidgetNode { override properties: ComfyComboProperties = { + tags: [], defaultValue: "A", values: ["A", "B", "C", "D"] } @@ -404,6 +420,7 @@ export interface ComfyTextProperties extends ComfyWidgetProperties { export class ComfyTextNode extends ComfyWidgetNode { override properties: ComfyTextProperties = { + tags: [], defaultValue: "", multiline: false } @@ -462,6 +479,7 @@ export interface ComfyGalleryProperties extends ComfyWidgetProperties { export class ComfyGalleryNode extends ComfyWidgetNode { override properties: ComfyGalleryProperties = { + tags: [], defaultValue: [], index: 0, updateMode: "replace" @@ -514,7 +532,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode { const data = param as GalleryOutput console.debug("[ComfyGalleryNode] Received output!", data) - const galleryItems: GradioFileData[] = this.convertItems(data) + const galleryItems: GradioFileData[] = convertComfyOutputToGradio(data) if (this.properties.updateMode === "append") { const currentValue = get(this.value) @@ -532,18 +550,6 @@ export class ComfyGalleryNode extends ComfyWidgetNode { return `Images: ${value?.length || 0}` } - private convertItems(output: GalleryOutput): GradioFileData[] { - return output.images.map(r => { - // TODO configure backend URL - const url = `http://${location.hostname}:8188` // TODO make configurable - const params = new URLSearchParams(r) - return { - name: null, - data: url + "/view?" + params - } - }); - } - override setValue(value: any) { if (Array.isArray(value)) { super.setValue(value) @@ -572,6 +578,7 @@ export interface ComfyButtonProperties extends ComfyWidgetProperties { export class ComfyButtonNode extends ComfyWidgetNode { override properties: ComfyButtonProperties = { + tags: [], defaultValue: false, param: "bang" } @@ -614,6 +621,7 @@ export interface ComfyCheckboxProperties extends ComfyWidgetProperties { export class ComfyCheckboxNode extends ComfyWidgetNode { override properties: ComfyCheckboxProperties = { + tags: [], defaultValue: false, } @@ -646,3 +654,63 @@ LiteGraph.registerNodeType({ desc: "Checkbox that stores a boolean value", type: "ui/checkbox" }) + +export interface ComfyRadioProperties extends ComfyWidgetProperties { + choices: string[] +} + +export class ComfyRadioNode extends ComfyWidgetNode { + override properties: ComfyRadioProperties = { + tags: [], + choices: ["Choice A", "Choice B", "Choice C"], + defaultValue: "Choice A", + } + + static slotLayout: SlotLayout = { + outputs: [ + { name: "value", type: "string" }, + { name: "index", type: "number" }, + { name: "changed", type: BuiltInSlotType.EVENT }, + ] + } + + override svelteComponentType = RadioWidget; + override defaultValue = ""; + override changedIndex = 2; + + indexWidget: INumberWidget; + + index = 0; + + constructor(name?: string) { + super(name, "Choice A") + this.indexWidget = this.addWidget("number", "Index", this.index) + this.indexWidget.disabled = true; + } + + override onExecute(param: any, options: object) { + super.onExecute(param, options); + this.setOutputData(1, this.index) + } + + override setValue(value: string) { + const index = this.properties.choices.indexOf(value) + if (index == -1) + return; + + this.index = index; + this.indexWidget.value = index; + + const changed = value != get(this.value); + super.setValue(value) + if (changed) + this.triggerSlot(2, { value: value, index: index }) + } +} + +LiteGraph.registerNodeType({ + class: ComfyRadioNode, + title: "UI.Radio", + desc: "Radio that outputs a string and index", + type: "ui/radio" +}) diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index 99649b8..b6bb944 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -394,6 +394,18 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ values: ["large", "small"], defaultValue: "large" }, + + // Gallery + { + name: "variant", + type: "enum", + location: "widget", + editable: true, + validNodeTypes: ["ui/gallery"], + values: ["gallery", "image"], + defaultValue: "gallery", + refreshPanelOnChange: true + }, ] }, { @@ -483,7 +495,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ defaultValue: "bang" }, - // gallery + // Gallery { name: "updateMode", type: "enum", @@ -494,6 +506,18 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ defaultValue: "replace" }, + // Radio + { + name: "choices", + type: "string", + location: "nodeProps", + editable: true, + validNodeTypes: ["ui/radio"], + defaultValue: ["Choice A", "Choice B", "Choice C"], + serialize: serializeStringArray, + deserialize: deserializeStringArray, + }, + // Workflow { name: "defaultSubgraph", diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 96701cc..e148067 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -6,6 +6,7 @@ import { get } from "svelte/store" import layoutState from "$lib/stores/layoutState" import type { SvelteComponentDev } from "svelte/internal"; import type { SerializedLGraph } from "@litegraph-ts/core"; +import type { GalleryOutput } from "./nodes/ComfyWidgetNodes"; export function clamp(n: number, min: number, max: number): number { return Math.min(Math.max(n, min), max) @@ -91,7 +92,7 @@ export function promptToGraphVis(prompt: SerializedPrompt): string { } else { // Value - out += `"${id}-${inpName}-${typeof i}" -> "${outNode.title}"\n` + out += `"${id}-${inpName}-${i}" -> "${outNode.title}"\n` } } } @@ -118,3 +119,15 @@ export const debounce = (callback: Function, wait = 250) => { timeout = setTimeout(next, wait); }; }; + +export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileData[] { + return output.images.map(r => { + // TODO configure backend URL + const url = `http://${location.hostname}:8188` // TODO make configurable + const params = new URLSearchParams(r) + return { + name: null, + data: url + "/view?" + params + } + }); +} diff --git a/src/lib/widgets/CheckboxWidget.svelte b/src/lib/widgets/CheckboxWidget.svelte index a48d557..503ffae 100644 --- a/src/lib/widgets/CheckboxWidget.svelte +++ b/src/lib/widgets/CheckboxWidget.svelte @@ -32,7 +32,12 @@ {#key $attrsChanged} {#if node !== null} - + {/if} {/key} diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index ebac48b..f9cb37f 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -136,7 +136,9 @@ :global(.svelte-select) { width: auto; - max-width: 30rem; + max-width: 16rem; + --font-size: 13px; + --height: 32px; } :global(.svelte-select-list) { diff --git a/src/lib/widgets/GalleryWidget.svelte b/src/lib/widgets/GalleryWidget.svelte index 837e08a..e65f2f9 100644 --- a/src/lib/widgets/GalleryWidget.svelte +++ b/src/lib/widgets/GalleryWidget.svelte @@ -1,7 +1,8 @@ - +{/if} diff --git a/src/lib/widgets/RadioWidget.svelte b/src/lib/widgets/RadioWidget.svelte new file mode 100644 index 0000000..78788bb --- /dev/null +++ b/src/lib/widgets/RadioWidget.svelte @@ -0,0 +1,64 @@ + + +
+
+ {#key $propsChanged} + {#key $attrsChanged} + {#if node !== null && node.properties.choices} + + + + {/if} + {/key} + {/key} +
+
+ +