Image and radio widgets
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -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<any, any> {
|
||||
}
|
||||
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<any, any> {
|
||||
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
|
||||
images: GalleryOutput | null
|
||||
}
|
||||
|
||||
@@ -96,13 +88,14 @@ LiteGraph.registerNodeType({
|
||||
type: "actions/store_images"
|
||||
})
|
||||
|
||||
export interface ComfyCopyActionProperties extends Record<any, any> {
|
||||
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<any, any> {
|
||||
export interface ComfySwapActionProperties extends ComfyGraphNodeProperties {
|
||||
}
|
||||
|
||||
export class ComfySwapAction extends ComfyGraphNode {
|
||||
@@ -182,13 +175,14 @@ LiteGraph.registerNodeType({
|
||||
type: "actions/swap"
|
||||
})
|
||||
|
||||
export interface ComfyNotifyActionProperties extends Record<any, any> {
|
||||
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<any, any> {
|
||||
export interface ComfyExecuteSubgraphActionProperties extends ComfyGraphNodeProperties {
|
||||
tag: string | null,
|
||||
}
|
||||
|
||||
@@ -253,7 +247,7 @@ LiteGraph.registerNodeType({
|
||||
type: "actions/execute_subgraph"
|
||||
})
|
||||
|
||||
export interface ComfySetNodeModeActionProperties extends Record<any, any> {
|
||||
export interface ComfySetNodeModeActionProperties extends ComfyGraphNodeProperties {
|
||||
targetTags: string,
|
||||
enable: boolean,
|
||||
}
|
||||
@@ -261,7 +255,8 @@ export interface ComfySetNodeModeActionProperties extends Record<any, any> {
|
||||
export class ComfySetNodeModeAction extends ComfyGraphNode {
|
||||
override properties: ComfySetNodeModeActionProperties = {
|
||||
targetTags: "",
|
||||
enable: false
|
||||
enable: false,
|
||||
tags: []
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
|
||||
@@ -17,7 +17,15 @@ export type DefaultWidgetLayout = {
|
||||
inputs?: Record<number, DefaultWidgetSpec>,
|
||||
}
|
||||
|
||||
export interface ComfyGraphNodeProperties extends Record<string, any> {
|
||||
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
|
||||
|
||||
@@ -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<any, any> {
|
||||
export interface ComfyImageCacheNodeProperties extends ComfyGraphNodeProperties {
|
||||
images: GalleryOutput | null,
|
||||
index: number,
|
||||
filenames: Record<number, { filename: string | null, status: ImageCacheState }>,
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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<any, any> {
|
||||
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;
|
||||
if (!(node as any).isBackendNode) {
|
||||
this.displayWidget.value = Watch.toString(link.data)
|
||||
this.setOutputData(0, link.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<any, any> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<any, any> {
|
||||
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<any, any> {
|
||||
export interface ComfySelectorTwoProperties extends ComfyGraphNodeProperties {
|
||||
value: any
|
||||
}
|
||||
|
||||
export class ComfySelectorTwo extends ComfyGraphNode {
|
||||
override properties: ComfySelectorTwoProperties = {
|
||||
tags: [],
|
||||
value: null
|
||||
}
|
||||
|
||||
|
||||
@@ -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<any, any> {
|
||||
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,
|
||||
|
||||
@@ -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<string, any> {
|
||||
/*
|
||||
* 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<T = any> 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<T = any> 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<T = any> 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<number> {
|
||||
override properties: ComfySliderProperties = {
|
||||
tags: [],
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
max: 10,
|
||||
@@ -312,6 +327,7 @@ export interface ComfyComboProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
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<string> {
|
||||
override properties: ComfyTextProperties = {
|
||||
tags: [],
|
||||
defaultValue: "",
|
||||
multiline: false
|
||||
}
|
||||
@@ -462,6 +479,7 @@ export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
override properties: ComfyGalleryProperties = {
|
||||
tags: [],
|
||||
defaultValue: [],
|
||||
index: 0,
|
||||
updateMode: "replace"
|
||||
@@ -514,7 +532,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
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<GradioFileData[]> {
|
||||
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<boolean> {
|
||||
override properties: ComfyButtonProperties = {
|
||||
tags: [],
|
||||
defaultValue: false,
|
||||
param: "bang"
|
||||
}
|
||||
@@ -614,6 +621,7 @@ export interface ComfyCheckboxProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyCheckboxNode extends ComfyWidgetNode<boolean> {
|
||||
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<string> {
|
||||
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"
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,7 +32,12 @@
|
||||
{#key $attrsChanged}
|
||||
{#if node !== null}
|
||||
<Block>
|
||||
<Checkbox disabled={isDisabled(widget)} label={widget.attrs.title} bind:value={$nodeValue} on:select={onSelect} />
|
||||
<Checkbox
|
||||
disabled={isDisabled(widget)}
|
||||
label={widget.attrs.title}
|
||||
bind:value={$nodeValue}
|
||||
on:select={onSelect}
|
||||
/>
|
||||
</Block>
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
@@ -136,7 +136,9 @@
|
||||
|
||||
:global(.svelte-select) {
|
||||
width: auto;
|
||||
max-width: 30rem;
|
||||
max-width: 16rem;
|
||||
--font-size: 13px;
|
||||
--height: 32px;
|
||||
}
|
||||
|
||||
:global(.svelte-select-list) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
import { Gallery } from "@gradio/gallery";
|
||||
import { Image } from "@gradio/icons";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
import type { WidgetLayout } from "$lib/stores/layoutState";
|
||||
import type { Writable } from "svelte/store";
|
||||
@@ -123,8 +124,26 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper comfy-gallery-widget gradio-gallery" bind:this={element}>
|
||||
{#if widget && node && nodeValue && $nodeValue != null}
|
||||
{#if widget && node && nodeValue && $nodeValue != null}
|
||||
{#if widget.attrs.variant === "image"}
|
||||
<div class="wrapper comfy-image-widget" bind:this={element}>
|
||||
<Block variant="solid" padding={false}>
|
||||
{#if widget.attrs.title}
|
||||
<BlockLabel
|
||||
show_label={true}
|
||||
Icon={Image}
|
||||
label={widget.attrs.title || "Image"}
|
||||
/>
|
||||
{/if}
|
||||
{#if $nodeValue.length > 0}
|
||||
<img src={$nodeValue[$nodeValue.length-1].data}/>
|
||||
{:else}
|
||||
<Empty size="large" unpadded_box={true}><Image /></Empty>
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="wrapper comfy-gallery-widget gradio-gallery" bind:this={element}>
|
||||
<Block variant="solid" padding={false}>
|
||||
<div class="padding">
|
||||
<Gallery
|
||||
@@ -138,8 +157,9 @@
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.wrapper {
|
||||
@@ -148,13 +168,28 @@
|
||||
:global(> .block) {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
|
||||
:global(button.thumbnail-lg) {
|
||||
width: var(--size-32);
|
||||
}
|
||||
|
||||
&.comfy-image-widget {
|
||||
aspect-ratio: 1/1;
|
||||
|
||||
:global(> .block) {
|
||||
height: 100%;
|
||||
|
||||
:global(img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.padding {
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
.wrapper :global(button.thumbnail-lg) {
|
||||
width: var(--size-32);
|
||||
}
|
||||
</style>
|
||||
|
||||
64
src/lib/widgets/RadioWidget.svelte
Normal file
64
src/lib/widgets/RadioWidget.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import type { ComfyRadioNode } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Radio } from "@gradio/form";
|
||||
import { get, type Writable, writable } from "svelte/store";
|
||||
import { isDisabled } from "./utils"
|
||||
import type { SelectData } from "@gradio/utils";
|
||||
import { clamp } from "$lib/utils";
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyRadioNode | null = null;
|
||||
let nodeValue: Writable<string> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
let attrsChanged: Writable<number> | null = null;
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
function setNodeValue(widget: WidgetLayout) {
|
||||
if (widget) {
|
||||
node = widget.node as ComfyRadioNode
|
||||
nodeValue = node.value;
|
||||
attrsChanged = widget.attrsChanged;
|
||||
}
|
||||
};
|
||||
|
||||
$: node && $propsChanged && clampIndex();
|
||||
|
||||
function clampIndex() {
|
||||
node.index = clamp(node.index, 0, node.properties.choices?.length || 0)
|
||||
}
|
||||
|
||||
function onSelect(e: CustomEvent<SelectData>) {
|
||||
$nodeValue = e.detail.value
|
||||
node.setValue($nodeValue)
|
||||
node.index = e.detail.index as number
|
||||
navigator.vibrate(20)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper gradio-checkbox">
|
||||
<div class="inner">
|
||||
{#key $propsChanged}
|
||||
{#key $attrsChanged}
|
||||
{#if node !== null && node.properties.choices}
|
||||
<Block>
|
||||
<Radio
|
||||
elem_id="radio"
|
||||
choices={node.properties.choices}
|
||||
disabled={isDisabled(widget)}
|
||||
label={widget.attrs.title}
|
||||
value={$nodeValue}
|
||||
on:select={onSelect}
|
||||
/>
|
||||
</Block>
|
||||
{/if}
|
||||
{/key}
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
Reference in New Issue
Block a user