More UI improvements
This commit is contained in:
19710
public/workflows/defaultWorkflow.json
Normal file
19710
public/workflows/defaultWorkflow.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,11 @@
|
|||||||
import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode } from "@litegraph-ts/core";
|
import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode, type ContextMenuItem, type IContextMenuItem } from "@litegraph-ts/core";
|
||||||
import type ComfyApp from "./components/ComfyApp";
|
import type ComfyApp from "./components/ComfyApp";
|
||||||
import queueState from "./stores/queueState";
|
import queueState from "./stores/queueState";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import uiState from "./stores/uiState";
|
import uiState from "./stores/uiState";
|
||||||
import layoutState from "./stores/layoutState";
|
import layoutState from "./stores/layoutState";
|
||||||
|
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||||
|
import { ComfyReroute } from "./nodes";
|
||||||
|
|
||||||
export type SerializedGraphCanvasState = {
|
export type SerializedGraphCanvasState = {
|
||||||
offset: Vector2,
|
offset: Vector2,
|
||||||
@@ -250,4 +252,88 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private reinstantiate(_value: IContextMenuItem, _options, mouseEvent, prevMenu, node?: LGraphNode) {
|
||||||
|
if ((node as any).isBackendNode)
|
||||||
|
return
|
||||||
|
|
||||||
|
const newNode = LiteGraph.createNode(node.type);
|
||||||
|
|
||||||
|
for (let index = 0; index < newNode.inputs.length; index++) {
|
||||||
|
const newInput = newNode.inputs[index];
|
||||||
|
const oldInput = node.inputs[index]
|
||||||
|
|
||||||
|
if (oldInput && newInput.type === oldInput.type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let link: LLink | null = null;
|
||||||
|
|
||||||
|
if (oldInput) {
|
||||||
|
link = node.getInputLink(index);
|
||||||
|
node.disconnectInput(index)
|
||||||
|
oldInput.type = newInput.type
|
||||||
|
oldInput.name = newInput.name
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node.addInput(newInput.name, newInput.type, newInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
const reroute = LiteGraph.createNode(ComfyReroute);
|
||||||
|
reroute.properties.ignoreTypes = true;
|
||||||
|
node.graph.add(reroute)
|
||||||
|
const inputPos = node.getConnectionPos(true, index);
|
||||||
|
reroute.pos = [inputPos[0] - 140, inputPos[1] + LiteGraph.NODE_SLOT_HEIGHT / 2];
|
||||||
|
reroute.connect(0, node, index);
|
||||||
|
if (link != null)
|
||||||
|
node.graph.getNodeById(link.target_id).connect(link.target_slot, reroute, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < newNode.outputs.length; index++) {
|
||||||
|
const newOutput = newNode.outputs[index];
|
||||||
|
const oldOutput = node.outputs[index]
|
||||||
|
|
||||||
|
if (oldOutput && newOutput.type === oldOutput.type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let links = []
|
||||||
|
|
||||||
|
if (oldOutput) {
|
||||||
|
links = node.getOutputLinks(index)
|
||||||
|
node.disconnectOutput(index)
|
||||||
|
oldOutput.type = newOutput.type
|
||||||
|
oldOutput.name = newOutput.name
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node.addOutput(newOutput.name, newOutput.type, newOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
const reroute = LiteGraph.createNode(ComfyReroute);
|
||||||
|
reroute.properties.ignoreTypes = true;
|
||||||
|
node.graph.add(reroute)
|
||||||
|
const rerouteSize = reroute.computeSize();
|
||||||
|
const outputPos = node.getConnectionPos(false, index);
|
||||||
|
reroute.pos = [outputPos[0] + rerouteSize[0] + 20, outputPos[1] + LiteGraph.NODE_SLOT_HEIGHT / 2];
|
||||||
|
node.connect(index, reroute, 0);
|
||||||
|
for (const link of links) {
|
||||||
|
reroute.connect(0, link.target_id, link.target_slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override getNodeMenuOptions(node: LGraphNode): ContextMenuItem[] {
|
||||||
|
const options = super.getNodeMenuOptions(node);
|
||||||
|
|
||||||
|
options.push(
|
||||||
|
{
|
||||||
|
content: "Reinstantiate",
|
||||||
|
has_submenu: false,
|
||||||
|
disabled: false,
|
||||||
|
callback: this.reinstantiate.bind(this)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,10 +95,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if isHidden(container) && edit}
|
{#if isHidden(container) && edit}
|
||||||
<div class="handle handle-hidden" class:hidden={!edit} />
|
<div class="handle handle-hidden" style="z-index: {zIndex+100}" class:hidden={!edit} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if showHandles}
|
{#if showHandles}
|
||||||
<div class="handle handle-container" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</Block>
|
</Block>
|
||||||
|
|||||||
@@ -91,10 +91,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if isHidden(container) && edit}
|
{#if isHidden(container) && edit}
|
||||||
<div class="handle handle-hidden" class:hidden={!edit} />
|
<div class="handle handle-hidden" style="z-index: {zIndex+100}" class:hidden={!edit} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if showHandles}
|
{#if showHandles}
|
||||||
<div class="handle handle-container" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
</Block>
|
</Block>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
import { ImageViewer } from "$lib/ImageViewer";
|
import { ImageViewer } from "$lib/ImageViewer";
|
||||||
import type { ComfyAPIStatus } from "$lib/api";
|
import type { ComfyAPIStatus } from "$lib/api";
|
||||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
||||||
import defaultGraph from "$lib/defaultGraph"
|
|
||||||
|
|
||||||
import { LGraph } from "@litegraph-ts/core";
|
import { LGraph } from "@litegraph-ts/core";
|
||||||
import LightboxModal from "./LightboxModal.svelte";
|
import LightboxModal from "./LightboxModal.svelte";
|
||||||
@@ -132,7 +131,7 @@
|
|||||||
async function doLoadDefault() {
|
async function doLoadDefault() {
|
||||||
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
await app.deserialize(defaultGraph)
|
await app.initDefaultGraph();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType } from "@litegraph-ts/core";
|
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType } from "@litegraph-ts/core";
|
||||||
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
||||||
import ComfyAPI, { type ComfyAPIQueueStatus } from "$lib/api"
|
import ComfyAPI, { type ComfyAPIQueueStatus } from "$lib/api"
|
||||||
import defaultGraph from "$lib/defaultGraph"
|
|
||||||
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type TypedEmitter from "typed-emitter";
|
import type TypedEmitter from "typed-emitter";
|
||||||
@@ -32,6 +31,7 @@ import uiState from "$lib/stores/uiState";
|
|||||||
import { download, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
import { download, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
||||||
import notify from "$lib/notify";
|
import notify from "$lib/notify";
|
||||||
import configState from "$lib/stores/configState";
|
import configState from "$lib/stores/configState";
|
||||||
|
import { blankGraph } from "$lib/defaultGraph";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
|
|
||||||
@@ -436,7 +436,16 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initDefaultGraph() {
|
async initDefaultGraph() {
|
||||||
const state = structuredClone(defaultGraph)
|
let state = null;
|
||||||
|
try {
|
||||||
|
const graphResponse = await fetch("/workflows/defaultWorkflow.json");
|
||||||
|
state = await graphResponse.json() as SerializedAppState;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error("Failed to load default graph", error)
|
||||||
|
notify(`Failed to load default graph: ${error}`, { type: "error" })
|
||||||
|
state = structuredClone(blankGraph)
|
||||||
|
}
|
||||||
await this.deserialize(state)
|
await this.deserialize(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,7 +849,7 @@ export default class ComfyApp {
|
|||||||
const def = defs[backendNode.type];
|
const def = defs[backendNode.type];
|
||||||
const rawValues = def["input"]["required"][inputSlot.name][0];
|
const rawValues = def["input"]["required"][inputSlot.name][0];
|
||||||
|
|
||||||
console.warn("[ComfyApp] Reconfiguring combo widget", backendNode.type, "=>", comboNode.type, inputSlot.config.values.length)
|
console.warn("[ComfyApp] Reconfiguring combo widget", backendNode.type, "=>", comboNode.type, rawValues.length)
|
||||||
comboNode.doAutoConfig(inputSlot, { includeProperties: new Set(["values"]), setWidgetTitle: false })
|
comboNode.doAutoConfig(inputSlot, { includeProperties: new Set(["values"]), setWidgetTitle: false })
|
||||||
|
|
||||||
comboNode.formatValues(rawValues)
|
comboNode.formatValues(rawValues)
|
||||||
|
|||||||
@@ -110,10 +110,10 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if isHidden(container) && edit}
|
{#if isHidden(container) && edit}
|
||||||
<div class="handle handle-hidden" class:hidden={!edit} />
|
<div class="handle handle-hidden" style="z-index: {zIndex+100}" class:hidden={!edit} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if showHandles}
|
{#if showHandles}
|
||||||
<div class="handle handle-container" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
</Block>
|
</Block>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
18610
src/lib/defaultGraph.ts
18610
src/lib/defaultGraph.ts
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,9 @@ import queueState from "$lib/stores/queueState";
|
|||||||
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout, type PropertyLayout } from "@litegraph-ts/core";
|
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout, type PropertyLayout } from "@litegraph-ts/core";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||||
import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
|
import type { ComfyWidgetNode, GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes";
|
||||||
import type { NotifyOptions } from "$lib/notify";
|
import type { NotifyOptions } from "$lib/notify";
|
||||||
import { convertComfyOutputToGradio } from "$lib/utils";
|
import { convertComfyOutputToGradio, uploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils";
|
||||||
|
|
||||||
export class ComfyQueueEvents extends ComfyGraphNode {
|
export class ComfyQueueEvents extends ComfyGraphNode {
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -579,3 +579,83 @@ LiteGraph.registerNodeType({
|
|||||||
desc: "Wraps an event's parameter such that passing it into a ComfyWidgetNode's 'store' action will not trigger its 'changed' event",
|
desc: "Wraps an event's parameter such that passing it into a ComfyWidgetNode's 'store' action will not trigger its 'changed' event",
|
||||||
type: "events/no_change"
|
type: "events/no_change"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export interface ComfyUploadImageActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
folderType: "output" | "temp"
|
||||||
|
lastUploadedImageFile: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ComfyUploadImageAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyUploadImageActionProperties = {
|
||||||
|
tags: [],
|
||||||
|
folderType: "output",
|
||||||
|
lastUploadedImageFile: null
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "filename", type: "string" },
|
||||||
|
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "input_filename", type: "string" },
|
||||||
|
{ name: "uploaded", type: BuiltInSlotType.EVENT }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
private _promise = null;
|
||||||
|
|
||||||
|
displayWidget: ITextWidget;
|
||||||
|
|
||||||
|
constructor(title?: string) {
|
||||||
|
super(title);
|
||||||
|
this.displayWidget = this.addWidget<ITextWidget>(
|
||||||
|
"text",
|
||||||
|
"File",
|
||||||
|
this.properties.lastUploadedImageFile,
|
||||||
|
"lastUploadedImageFile"
|
||||||
|
);
|
||||||
|
this.displayWidget.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
this.setOutputData(0, this.properties.lastUploadedImageFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
if (action !== "trigger" || this._promise != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const filename = this.getInputData(0)
|
||||||
|
if (typeof filename !== "string" || !filename) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: GalleryOutputEntry = {
|
||||||
|
filename,
|
||||||
|
subfolder: "",
|
||||||
|
type: this.properties.folderType || "output"
|
||||||
|
}
|
||||||
|
|
||||||
|
this._promise = uploadImageToComfyUI(data)
|
||||||
|
.then((json: ComfyUploadImageAPIResponse) => {
|
||||||
|
console.debug("[UploadImageAction] Succeeded", json)
|
||||||
|
this.properties.lastUploadedImageFile = json.name;
|
||||||
|
this.triggerSlot(1, this.properties.lastUploadedImageFile);
|
||||||
|
this._promise = null;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error("Error uploading:", e)
|
||||||
|
notify(`Error uploading image to ComfyUi: ${e}`, { type: "error", timeout: 10000 })
|
||||||
|
this.properties.lastUploadedImageFile = null;
|
||||||
|
this._promise = null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyUploadImageAction,
|
||||||
|
title: "Comfy.UploadImageAction",
|
||||||
|
desc: "Uploads an image from the specified ComfyUI folder into its input folder",
|
||||||
|
type: "actions/store_images"
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp, type PropertyLayout, type IComboWidget, type SerializedLGraphNode } from "@litegraph-ts/core";
|
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp, type PropertyLayout, type IComboWidget, type SerializedLGraphNode } from "@litegraph-ts/core";
|
||||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||||
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||||
|
import { uploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils";
|
||||||
|
|
||||||
export interface ComfyImageCacheNodeProperties extends ComfyGraphNodeProperties {
|
export interface ComfyImageCacheNodeProperties extends ComfyGraphNodeProperties {
|
||||||
images: GalleryOutput | null,
|
images: GalleryOutput | null,
|
||||||
@@ -12,10 +13,6 @@ export interface ComfyImageCacheNodeProperties extends ComfyGraphNodeProperties
|
|||||||
|
|
||||||
type ImageCacheState = "none" | "uploading" | "failed" | "cached"
|
type ImageCacheState = "none" | "uploading" | "failed" | "cached"
|
||||||
|
|
||||||
interface ComfyUploadImageAPIResponse {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A node that can act as both an input and output image node by uploading
|
* A node that can act as both an input and output image node by uploading
|
||||||
* the output file into ComfyUI's input folder.
|
* the output file into ComfyUI's input folder.
|
||||||
@@ -174,23 +171,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode {
|
|||||||
this.properties.filenames[newIndex] = { filename: null, status: "uploading" }
|
this.properties.filenames[newIndex] = { filename: null, status: "uploading" }
|
||||||
this.onPropertyChanged("filenames", this.properties.filenames)
|
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||||
|
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
const promise = uploadImageToComfyUI(data)
|
||||||
const params = new URLSearchParams(data)
|
|
||||||
|
|
||||||
const promise = fetch(url + "/view?" + params)
|
|
||||||
.then((r) => r.blob())
|
|
||||||
.then((blob) => {
|
|
||||||
console.debug("Fetchin", url, params)
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("image", blob, data.filename);
|
|
||||||
return fetch(
|
|
||||||
new Request(url + "/upload/image", {
|
|
||||||
body: formData,
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.then((r) => r.json())
|
|
||||||
.then((json: ComfyUploadImageAPIResponse) => {
|
.then((json: ComfyUploadImageAPIResponse) => {
|
||||||
console.debug("Gottem", json)
|
console.debug("Gottem", json)
|
||||||
if (lastGenNumber === this.properties.genNumber) {
|
if (lastGenNumber === this.properties.genNumber) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"
|
|||||||
export interface ComfyRerouteProperties extends ComfyGraphNodeProperties {
|
export interface ComfyRerouteProperties extends ComfyGraphNodeProperties {
|
||||||
showOutputText: boolean;
|
showOutputText: boolean;
|
||||||
horizontal: boolean;
|
horizontal: boolean;
|
||||||
|
ignoreTypes: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ComfyReroute extends ComfyGraphNode {
|
export default class ComfyReroute extends ComfyGraphNode {
|
||||||
@@ -24,7 +25,8 @@ export default class ComfyReroute extends ComfyGraphNode {
|
|||||||
override properties: ComfyRerouteProperties = {
|
override properties: ComfyRerouteProperties = {
|
||||||
tags: [],
|
tags: [],
|
||||||
showOutputText: ComfyReroute.defaultVisibility,
|
showOutputText: ComfyReroute.defaultVisibility,
|
||||||
horizontal: false
|
horizontal: false,
|
||||||
|
ignoreTypes: false
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -61,6 +63,7 @@ export default class ComfyReroute extends ComfyGraphNode {
|
|||||||
override onConnectionsChange(type: LConnectionKind, slotIndex: number, isConnected: boolean, link: LLink, ioSlot: (INodeInputSlot | INodeOutputSlot)) {
|
override onConnectionsChange(type: LConnectionKind, slotIndex: number, isConnected: boolean, link: LLink, ioSlot: (INodeInputSlot | INodeOutputSlot)) {
|
||||||
this.applyOrientation();
|
this.applyOrientation();
|
||||||
|
|
||||||
|
this.canInheritSlotTypes = !this.properties.ignoreTypes;
|
||||||
super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot);
|
super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ export type GalleryOutputEntry = {
|
|||||||
|
|
||||||
export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
||||||
index: number,
|
index: number,
|
||||||
updateMode: "replace" | "append"
|
updateMode: "replace" | "append",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||||
@@ -616,7 +616,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
tags: [],
|
tags: [],
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
index: 0,
|
index: 0,
|
||||||
updateMode: "replace"
|
updateMode: "replace",
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -629,7 +629,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
{ name: "selected_index", type: "number" },
|
{ name: "selected_index", type: "number" },
|
||||||
{ name: "width", type: "number" },
|
{ name: "width", type: "number" },
|
||||||
{ name: "height", type: "number" },
|
{ name: "height", type: "number" },
|
||||||
{ name: "any_selected", type: "boolean" },
|
{ name: "filename", type: "string" },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,12 +644,15 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
override outputIndex = null;
|
override outputIndex = null;
|
||||||
override changedIndex = null;
|
override changedIndex = null;
|
||||||
|
|
||||||
anyImageSelected: boolean = false;
|
selectedFilename: string | null = null;
|
||||||
|
|
||||||
|
selectedIndexWidget: ITextWidget;
|
||||||
modeWidget: IComboWidget;
|
modeWidget: IComboWidget;
|
||||||
|
|
||||||
constructor(name?: string) {
|
constructor(name?: string) {
|
||||||
super(name, [])
|
super(name, [])
|
||||||
|
this.selectedIndexWidget = this.addWidget("text", "Selected", String(this.properties.index), "index")
|
||||||
|
this.selectedIndexWidget.disabled = true;
|
||||||
this.modeWidget = this.addWidget("combo", "Mode", this.properties.updateMode, null, { property: "updateMode", values: ["replace", "append"] })
|
this.modeWidget = this.addWidget("combo", "Mode", this.properties.updateMode, null, { property: "updateMode", values: ["replace", "append"] })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,10 +665,20 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
imageSize: Vector2 = [1, 1]
|
imageSize: Vector2 = [1, 1]
|
||||||
|
|
||||||
override onExecute() {
|
override onExecute() {
|
||||||
this.setOutputData(0, this.properties.index)
|
const index = this.properties.index;
|
||||||
|
|
||||||
|
this.setOutputData(0, index)
|
||||||
this.setOutputData(1, this.imageSize[0])
|
this.setOutputData(1, this.imageSize[0])
|
||||||
this.setOutputData(2, this.imageSize[1])
|
this.setOutputData(2, this.imageSize[1])
|
||||||
this.setOutputData(3, this.anyImageSelected)
|
|
||||||
|
let filename: string | null = null;
|
||||||
|
if (index != null) {
|
||||||
|
const entry = get(this.value)[index];
|
||||||
|
if (entry)
|
||||||
|
filename = entry.name
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setOutputData(3, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
override onAction(action: any, param: any, options: { action_call?: string }) {
|
override onAction(action: any, param: any, options: { action_call?: string }) {
|
||||||
@@ -700,11 +713,8 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override setValue(value: any, noChangedEvent: boolean = false) {
|
override setValue(value: any, noChangedEvent: boolean = false) {
|
||||||
console.warn("SETVALUE", value)
|
|
||||||
super.setValue(value, noChangedEvent)
|
super.setValue(value, noChangedEvent)
|
||||||
|
this.setProperty("index", null)
|
||||||
this.setProperty("index", 0)
|
|
||||||
this.anyImageSelected = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -887,6 +897,9 @@ export class ComfyImageUploadNode extends ComfyWidgetNode<Array<GradioFileData>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "store", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{ name: "filename", type: "string" }, // TODO support batches
|
{ name: "filename", type: "string" }, // TODO support batches
|
||||||
{ name: "width", type: "number" },
|
{ name: "width", type: "number" },
|
||||||
@@ -900,6 +913,7 @@ export class ComfyImageUploadNode extends ComfyWidgetNode<Array<GradioFileData>>
|
|||||||
override defaultValue = [];
|
override defaultValue = [];
|
||||||
override outputIndex = null;
|
override outputIndex = null;
|
||||||
override changedIndex = 3;
|
override changedIndex = 3;
|
||||||
|
override storeActionName = "store";
|
||||||
override saveUserState = false;
|
override saveUserState = false;
|
||||||
|
|
||||||
imageSize: Vector2 = [1, 1];
|
imageSize: Vector2 = [1, 1];
|
||||||
@@ -908,6 +922,18 @@ export class ComfyImageUploadNode extends ComfyWidgetNode<Array<GradioFileData>>
|
|||||||
super(name, [])
|
super(name, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override parseValue(value: any): GradioFileData[] {
|
||||||
|
if (value == null)
|
||||||
|
return []
|
||||||
|
|
||||||
|
if (typeof value === "string" && value !== "") { // Single filename
|
||||||
|
return [{ name: value, data: value, orig_name: value, is_file: true }]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override onExecute(param: any, options: object) {
|
override onExecute(param: any, options: object) {
|
||||||
super.onExecute(param, options);
|
super.onExecute(param, options);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { get } from "svelte/store"
|
|||||||
import layoutState from "$lib/stores/layoutState"
|
import layoutState from "$lib/stores/layoutState"
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
import type { SerializedLGraph } from "@litegraph-ts/core";
|
import type { SerializedLGraph } from "@litegraph-ts/core";
|
||||||
import type { GalleryOutput } from "./nodes/ComfyWidgetNodes";
|
import type { GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes";
|
||||||
|
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||||
|
|
||||||
export function clamp(n: number, min: number, max: number): number {
|
export function clamp(n: number, min: number, max: number): number {
|
||||||
return Math.min(Math.max(n, min), max)
|
return Math.min(Math.max(n, min), max)
|
||||||
@@ -125,10 +126,13 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat
|
|||||||
// TODO configure backend URL
|
// TODO configure backend URL
|
||||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
const url = `http://${location.hostname}:8188` // TODO make configurable
|
||||||
const params = new URLSearchParams(r)
|
const params = new URLSearchParams(r)
|
||||||
return {
|
const fileData: GradioFileData = {
|
||||||
name: null,
|
name: r.filename,
|
||||||
|
orig_name: r.filename,
|
||||||
|
is_file: false,
|
||||||
data: url + "/view?" + params
|
data: url + "/view?" + params
|
||||||
}
|
}
|
||||||
|
return fileData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,3 +149,27 @@ export function jsonToJsObject(json: string): string {
|
|||||||
.replace(regex, "$1:");
|
.replace(regex, "$1:");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ComfyUploadImageAPIResponse {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uploadImageToComfyUI(data: GalleryOutputEntry): Promise<ComfyUploadImageAPIResponse> {
|
||||||
|
const url = `http://${location.hostname}:8188` // TODO make configurable
|
||||||
|
const params = new URLSearchParams(data)
|
||||||
|
|
||||||
|
return fetch(url + "/view?" + params)
|
||||||
|
.then((r) => r.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
console.debug("Fetchin", url, params)
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("image", blob, data.filename);
|
||||||
|
return fetch(
|
||||||
|
new Request(url + "/upload/image", {
|
||||||
|
body: formData,
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper comfy-combo" class:updated={lightUp}>
|
<div class="wrapper comfy-combo" class:mobile={isMobile} class:updated={lightUp}>
|
||||||
{#key $valuesForCombo}
|
{#key $valuesForCombo}
|
||||||
{#if node !== null && nodeValue !== null}
|
{#if node !== null && nodeValue !== null}
|
||||||
{#if $valuesForCombo == null}
|
{#if $valuesForCombo == null}
|
||||||
@@ -173,12 +173,14 @@
|
|||||||
<VirtualList
|
<VirtualList
|
||||||
items={filteredItems}
|
items={filteredItems}
|
||||||
width="100%"
|
width="100%"
|
||||||
height={300}
|
height={isMobile ? 300 : 600}
|
||||||
itemCount={filteredItems?.length}
|
itemCount={filteredItems?.length}
|
||||||
itemSize={50}
|
itemSize={isMobile ? 50 : 24}
|
||||||
|
overscanCount={5}
|
||||||
scrollToIndex={hoverItemIndex}>
|
scrollToIndex={hoverItemIndex}>
|
||||||
<div slot="item"
|
<div slot="item"
|
||||||
class="comfy-select-item"
|
class="comfy-select-item"
|
||||||
|
class:mobile={isMobile}
|
||||||
let:index={i}
|
let:index={i}
|
||||||
let:style
|
let:style
|
||||||
{style}
|
{style}
|
||||||
@@ -260,7 +262,11 @@
|
|||||||
.comfy-select-list {
|
.comfy-select-list {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
width: 30rem;
|
width: 30rem;
|
||||||
|
|
||||||
|
> :global(.virtual-list-wrapper) {
|
||||||
|
box-shadow: var(--block-shadow);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.comfy-empty-list {
|
.comfy-empty-list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -268,12 +274,10 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
color: var(--neutral-700)
|
color: var(--neutral-400)
|
||||||
}
|
}
|
||||||
|
|
||||||
.comfy-select-item {
|
.comfy-select-item {
|
||||||
font-size: 16px;
|
|
||||||
padding: 1.2rem;
|
|
||||||
border: 1px solid var(--neutral-300);
|
border: 1px solid var(--neutral-300);
|
||||||
border-top: none;
|
border-top: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -281,6 +285,15 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0.2rem;
|
||||||
|
|
||||||
|
&.mobile {
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
&.hover {
|
&.hover {
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
node = widget.node as ComfyGalleryNode
|
node = widget.node as ComfyGalleryNode
|
||||||
nodeValue = node.value;
|
nodeValue = node.value;
|
||||||
propsChanged = node.propsChanged;
|
propsChanged = node.propsChanged;
|
||||||
node.anyImageSelected = false;
|
|
||||||
|
|
||||||
if ($nodeValue != null) {
|
if ($nodeValue != null) {
|
||||||
if (node.properties.index < 0 || node.properties.index >= $nodeValue.length) {
|
if (node.properties.index < 0 || node.properties.index >= $nodeValue.length) {
|
||||||
@@ -135,22 +134,19 @@
|
|||||||
|
|
||||||
// Update index
|
// Update index
|
||||||
node.setProperty("index", e.detail.index as number)
|
node.setProperty("index", e.detail.index as number)
|
||||||
node.anyImageSelected = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if ($propsChanged > -1 && widget && $nodeValue) {
|
$: if ($propsChanged > -1 && widget && $nodeValue) {
|
||||||
if (widget.attrs.variant === "image") {
|
if (widget.attrs.variant === "image") {
|
||||||
selected_image = $nodeValue.length - 1
|
selected_image = $nodeValue.length - 1
|
||||||
node.setProperty("index", selected_image)
|
node.setProperty("index", selected_image)
|
||||||
node.anyImageSelected = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.setProperty("index", null)
|
node.setProperty("index", null)
|
||||||
node.anyImageSelected = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: node.anyImageSelected = selected_image != null;
|
$: node.setProperty("index", selected_image)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if widget && node && nodeValue && $nodeValue}
|
{#if widget && node && nodeValue && $nodeValue}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||||
|
|
||||||
import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||||
import defaultGraph from "$lib/defaultGraph";
|
|
||||||
|
|
||||||
export let app: ComfyApp | null = null;
|
export let app: ComfyApp | null = null;
|
||||||
|
|
||||||
async function doLoadDefault() {
|
async function doLoadDefault() {
|
||||||
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
await app.deserialize(defaultGraph)
|
await app.initDefaultGraph();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user