A whole lotta things

This commit is contained in:
space-nuko
2023-04-05 01:02:47 -05:00
parent 0f703c2123
commit 32f1900568
13 changed files with 1291 additions and 356 deletions

View File

@@ -1,11 +1,14 @@
<script lang="ts">
import { onMount } from "svelte";
import { Pane, Splitpanes } from 'svelte-splitpanes';
import { Tabs } from '@svelteuidev/core';
import { Backpack, Gear } from 'radix-icons-svelte';
import ComfyUIPane from "./ComfyUIPane.svelte";
import ComfyApp from "./ComfyApp";
import { LGraphNode } from "litegraph.js";
let app: ComfyApp = undefined;
let uiPane: ComfyUIPane = undefined;
function refreshView(event) {
app.resizeCanvas();
@@ -13,6 +16,23 @@
onMount(async () => {
app = new ComfyApp();
app.eventBus.on("nodeAdded", (node: LGraphNode) => {
uiPane.addNodeUI(node);
});
app.eventBus.on("nodeRemoved", (node: LGraphNode) => {
uiPane.removeNodeUI(node);
});
app.eventBus.on("configured", (graph: LGraph) => {
uiPane.configureFinished(graph);
});
app.eventBus.on("cleared", () => {
uiPane.clear();
});
await app.setup();
refreshView();
@@ -20,25 +40,26 @@
})
</script>
<Tabs>
<Tabs.Tab label="Workspace" icon={Backpack}>
<div id="container">
<Splitpanes on:resize={refreshView}>
<Pane class="sidebar" size={20} minSize={20}>I have a min width of 20%</Pane>
<Pane>
<Splitpanes on:resize={refreshView} horizontal="{true}">
<Pane minSize={15}>I have a min height of 15%</Pane>
<Pane minSize={10}>
<canvas id="graph-canvas" />
</Pane>
</Splitpanes>
<div id="dropzone" class="dropzone"></div>
<div id="container">
<Splitpanes theme="comfy" on:resize={refreshView}>
<Pane size={20} minSize={10}>
<div>
Sidebar
</div>
</Pane>
<Pane>
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
<Pane minSize={15}>
<ComfyUIPane bind:this={uiPane} {app} />
</Pane>
<Pane minSize={10}>
<canvas id="graph-canvas" />
</Pane>
</Splitpanes>
</div>
</Tabs.Tab>
<Tabs.Tab label="Settings" icon={Gear}>
</Tabs.Tab>
</Tabs>
</Pane>
</Splitpanes>
</div>
<style>
#container {
@@ -47,24 +68,6 @@
width: 100%;
}
.panel {
overflow: auto;
position: relative;
}
.resizer[data-direction='horizontal'] {
background-color: #cbd5e0;
cursor: ew-resize;
height: 100%;
width: 2px;
}
.resizer[data-direction='vertical'] {
background-color: #cbd5e0;
cursor: ns-resize;
height: 2px;
width: 100%;
}
#comfy-content {
grid-area: content;
height: 100vh;
@@ -81,6 +84,19 @@
height: 100%;
}
.dropzone {
box-sizing: border-box;
display: none;
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 99999;
background: #60a7dc80;
border: 4px dashed #60a7dc;
}
:global(html, body) {
width: 100%;
height: 100%;
@@ -88,6 +104,20 @@
font-family: Arial;
}
:global(.splitpanes.comfy.splitpanes--horizontal>.splitpanes__splitter) {
min-height: 20px;
cursor: row-resize;
}
:global(.splitpanes.comfy.splitpanes--vertical>.splitpanes__splitter) {
min-width: 20px;
cursor: col-resize;
}
:global(.splitpanes.comfy) {
max-height: calc(100vh - 60px);
}
:global(.splitpanes__pane) {
box-shadow: 0 0 3px rgba(0, 0, 0, .2) inset;
justify-content: center;

View File

@@ -1,19 +1,31 @@
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode } from "litegraph.js";
import type { LGraphNodeBase } from "litegraph.js";
import type { LGraphNodeBase, LConnectionKind, INodeSlot } from "litegraph.js";
import ComfyAPI from "$lib/api"
import { ComfyWidgets } from "$lib/widgets"
import defaultGraph from "$lib/defaultGraph"
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
import EventEmitter from "events";
import type TypedEmitter from "typed-emitter";
type QueueItem = { num: number, batchCount: number }
type ComfyAppEvents = {
configured: (graph: LGraph) => void
nodeAdded: (node: LGraphNode) => void
nodeRemoved: (node: LGraphNode) => void
nodeConnectionChanged: (kind: LConnectionKind, node: LGraphNode, slot: INodeSlot, targetNode: LGraphNode, targetSlot: INodeSlot) => void
cleared: () => void
}
export default class ComfyApp {
api: ComfyAPI;
canvasEl: HTMLCanvasElement | null = null;
canvasCtx: CanvasRenderingContext2D | null = null;
lGraph: LGraph | null = null;
lCanvas: LGraphCanvas | null = null;
dropZone: HTMLElement | null = null;
nodeOutputs: Record<string, any> = {};
eventBus: TypedEmitter<ComfyAppEvents> = new EventEmitter() as TypedEmitter<ComfyAppEvents>;
private queueItems: QueueItem[] = [];
private processingQueue: boolean = false;
@@ -31,6 +43,8 @@ export default class ComfyApp {
this.lCanvas = new LGraphCanvas(this.canvasEl, this.lGraph);
this.canvasCtx = this.canvasEl.getContext("2d");
this.addGraphLifecycleHooks();
LiteGraph.release_link_on_empty_shows_menu = true;
LiteGraph.alt_drag_do_clone_nodes = true;
@@ -63,7 +77,7 @@ export default class ComfyApp {
// this.#addDrawNodeHandler();
// this.#addDrawGroupsHandler();
// this.#addApiUpdateHandlers();
// this.#addDropHandler();
this.addDropHandler();
// this.#addPasteHandler();
// this.#addKeyboardHandler();
@@ -101,6 +115,52 @@ export default class ComfyApp {
}
private graphOnConfigure() {
console.log("Configured");
this.eventBus.emit("configured", this.lGraph);
}
private graphOnBeforeChange(graph: LGraph, info: any) {
console.log("BeforeChange", info);
this.eventBus.emit("beforeChange", graph, info);
}
private graphOnAfterChange(graph: LGraph, info: any) {
console.log("AfterChange", info);
this.eventBus.emit("afterChange", graph, info);
}
private graphOnNodeAdded(node: LGraphNode) {
console.log("Added", node);
this.eventBus.emit("nodeAdded", node);
}
private graphOnNodeRemoved(node: LGraphNode) {
console.log("Removed", node);
this.eventBus.emit("nodeRemoved", node);
}
private graphOnNodeConnectionChange(kind: LConnectionKind, node: LGraphNode, slot: INodeSlot, targetNode: LGraphNode, targetSlot: INodeSlot) {
console.log("ConnectionChange", node);
this.eventBus.emit("nodeConnectionChanged", kind, node, slot, targetNode, targetSlot);
}
private canvasOnClear() {
console.log("CanvasClear");
this.eventBus.emit("cleared");
}
private addGraphLifecycleHooks() {
this.lGraph.onConfigure = this.graphOnConfigure.bind(this);
this.lGraph.onBeforeChange = this.graphOnBeforeChange.bind(this);
this.lGraph.onAfterChange = this.graphOnAfterChange.bind(this);
this.lGraph.onNodeAdded = this.graphOnNodeAdded.bind(this);
this.lGraph.onNodeRemoved = this.graphOnNodeRemoved.bind(this);
this.lGraph.onNodeConnectionChange = this.graphOnNodeConnectionChange.bind(this);
this.lCanvas.onClear = this.canvasOnClear.bind(this);
}
private async registerNodes() {
const app = this;
@@ -183,235 +243,277 @@ export default class ComfyApp {
// await this.#invokeExtensionsAsync("registerCustomNodes");
}
/**
* Populates the graph with the specified workflow data
* @param {*} graphData A serialized graph object
*/
loadGraphData(graphData: any = null) {
this.clean();
private showDropZone() {
this.dropZone.style.display = "block";
}
if (!graphData) {
graphData = structuredClone(defaultGraph);
}
private hideDropZone() {
this.dropZone.style.display = "none";
}
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
for (let n of graphData.nodes) {
if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader";
}
private allowDrag(event: DragEvent) {
if (event.dataTransfer.items?.length > 0) {
event.dataTransfer.dropEffect = 'copy';
this.showDropZone();
event.preventDefault();
}
}
this.lGraph.configure(graphData);
private async handleDrop(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
this.hideDropZone();
for (const node of this.lGraph._nodes) {
const size = node.computeSize();
size[0] = Math.max(node.size[0], size[0]);
size[1] = Math.max(node.size[1], size[1]);
node.size = size;
if (event.dataTransfer.files.length > 0) {
await this.handleFile(event.dataTransfer.files[0]);
}
}
if (node.widgets) {
// If you break something in the backend and want to patch workflows in the frontend
// This is the place to do this
for (let widget of node.widgets) {
if (node.type == "KSampler" || node.type == "KSamplerAdvanced") {
if (widget.name == "sampler_name") {
if (widget.value.constructor === String && widget.value.startsWith("sample_")) {
widget.value = widget.value.slice(7);
}
}
}
}
}
private addDropHandler() {
this.dropZone = document.getElementById("dropzone");
// this.#invokeExtensions("loadedGraphNode", node);
}
}
window.addEventListener('dragenter', this.allowDrag.bind(this));
this.dropZone.addEventListener('dragover', this.allowDrag.bind(this));
this.dropZone.addEventListener('dragleave', this.hideDropZone.bind(this));
this.dropZone.addEventListener('drop', this.handleDrop.bind(this));
}
/**
* Converts the current graph workflow for sending to the API
* @returns The workflow and node links
*/
async graphToPrompt() {
const workflow = this.lGraph.serialize();
const output = {};
// Process nodes in order of execution
for (const node of this.lGraph.computeExecutionOrder(false, null)) {
const n = workflow.nodes.find((n) => n.id === node.id);
/**
* Populates the graph with the specified workflow data
* @param {*} graphData A serialized graph object
*/
loadGraphData(graphData: any = null) {
this.clean();
if (node.isVirtualNode) {
// Don't serialize frontend only nodes but let them make changes
if (node.applyToGraph) {
node.applyToGraph(workflow);
}
continue;
}
if (!graphData) {
graphData = structuredClone(defaultGraph);
}
if (node.mode === 2) {
// Don't serialize muted nodes
continue;
}
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
for (let n of graphData.nodes) {
if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader";
}
const inputs = {};
const widgets = node.widgets;
this.lGraph.configure(graphData);
// Store all widget values
if (widgets) {
for (const i in widgets) {
const widget = widgets[i];
if (!widget.options || widget.options.serialize !== false) {
inputs[widget.name] = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
}
}
}
for (const node of this.lGraph._nodes) {
const size = node.computeSize();
size[0] = Math.max(node.size[0], size[0]);
size[1] = Math.max(node.size[1], size[1]);
node.size = size;
// Store all node links
for (let i in node.inputs) {
let parent = node.getInputNode(i);
if (parent) {
let link = node.getInputLink(i);
while (parent && parent.isVirtualNode) {
link = parent.getInputLink(link.origin_slot);
if (link) {
parent = parent.getInputNode(link.origin_slot);
} else {
parent = null;
}
}
if (node.widgets) {
// If you break something in the backend and want to patch workflows in the frontend
// This is the place to do this
for (let widget of node.widgets) {
if (node.type == "KSampler" || node.type == "KSamplerAdvanced") {
if (widget.name == "sampler_name") {
if (widget.value.constructor === String && widget.value.startsWith("sample_")) {
widget.value = widget.value.slice(7);
}
}
}
}
}
if (link) {
inputs[node.inputs[i].name] = [String(link.origin_id), parseInt(link.origin_slot)];
}
}
}
// this.#invokeExtensions("loadedGraphNode", node);
}
}
output[String(node.id)] = {
inputs,
class_type: node.comfyClass,
};
}
/**
* Converts the current graph workflow for sending to the API
* @returns The workflow and node links
*/
async graphToPrompt(frontendState: Record<number, any[]> = {}) {
const workflow = this.lGraph.serialize();
// Remove inputs connected to removed nodes
const output = {};
// Process nodes in order of execution
for (const node of this.lGraph.computeExecutionOrder(false, null)) {
const fromFrontend = frontendState[node.id];
if (fromFrontend) {
console.log("Set values!", node, fromFrontend)
node.widgets_values = fromFrontend;
}
for (const o in output) {
for (const i in output[o].inputs) {
if (Array.isArray(output[o].inputs[i])
&& output[o].inputs[i].length === 2
&& !output[output[o].inputs[i][0]]) {
delete output[o].inputs[i];
}
}
}
const n = workflow.nodes.find((n) => n.id === node.id);
return { workflow, output };
}
if (node.isVirtualNode) {
// Don't serialize frontend only nodes but let them make changes
if (node.applyToGraph) {
node.applyToGraph(workflow);
}
continue;
}
async queuePrompt(num: number, batchCount: number = 1) {
this.queueItems.push({ num, batchCount });
if (node.mode === 2) {
// Don't serialize muted nodes
continue;
}
// Only have one action process the items so each one gets a unique seed correctly
if (this.processingQueue) {
return;
}
const inputs = {};
const widgets = node.widgets;
this.processingQueue = true;
try {
while (this.queueItems.length) {
({ num, batchCount } = this.queueItems.pop());
// Store all widget values
if (widgets) {
for (const i in widgets) {
const widget = widgets[i];
if (!widget.options || widget.options.serialize !== false) {
inputs[widget.name] = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
}
}
}
// Store all node links
for (let i in node.inputs) {
let parent = node.getInputNode(i);
if (parent) {
let link = node.getInputLink(i);
while (parent && parent.isVirtualNode) {
link = parent.getInputLink(link.origin_slot);
if (link) {
parent = parent.getInputNode(link.origin_slot);
} else {
parent = null;
}
}
if (link) {
inputs[node.inputs[i].name] = [String(link.origin_id), parseInt(link.origin_slot)];
}
}
}
output[String(node.id)] = {
inputs,
class_type: node.comfyClass,
};
}
// Remove inputs connected to removed nodes
for (const o in output) {
for (const i in output[o].inputs) {
if (Array.isArray(output[o].inputs[i])
&& output[o].inputs[i].length === 2
&& !output[output[o].inputs[i][0]]) {
delete output[o].inputs[i];
}
}
}
return { workflow, output };
}
async queuePrompt(num: number, batchCount: number = 1, frontendState: Record<number, any[]> = {}) {
this.queueItems.push({ num, batchCount });
// Only have one action process the items so each one gets a unique seed correctly
if (this.processingQueue) {
return;
}
this.processingQueue = true;
try {
while (this.queueItems.length) {
({ num, batchCount } = this.queueItems.pop());
console.log(`Queue get! ${num} ${batchCount}`);
for (let i = 0; i < batchCount; i++) {
const p = await this.graphToPrompt();
for (let i = 0; i < batchCount; i++) {
const p = await this.graphToPrompt(frontendState);
try {
await this.api.queuePrompt(num, p);
} catch (error) {
// this.ui.dialog.show(error.response || error.toString());
try {
await this.api.queuePrompt(num, p);
} catch (error) {
// this.ui.dialog.show(error.response || error.toString());
console.error(error.response || error.toString())
break;
}
break;
}
for (const n of p.workflow.nodes) {
const node = this.lGraph.getNodeById(n.id);
if (node.widgets) {
for (const widget of node.widgets) {
// Allow widgets to run callbacks after a prompt has been queued
// e.g. random seed after every gen
// if (widget.afterQueued) {
// widget.afterQueued();
// }
}
}
}
for (const n of p.workflow.nodes) {
const node = this.lGraph.getNodeById(n.id);
if (node.widgets) {
for (const widget of node.widgets) {
// Allow widgets to run callbacks after a prompt has been queued
// e.g. random seed after every gen
// if (widget.afterQueued) {
// widget.afterQueued();
// }
}
}
}
this.lCanvas.draw(true, true);
// await this.ui.queue.update();
}
}
} finally {
this.lCanvas.draw(true, true);
// await this.ui.queue.update();
}
}
} finally {
console.log("Queue finished!");
this.processingQueue = false;
}
}
this.processingQueue = false;
}
}
/**
* Loads workflow data from the specified file
*/
async handleFile(file: File) {
if (file.type === "image/png") {
const pngInfo = await getPngMetadata(file);
if (pngInfo) {
if (pngInfo.workflow) {
this.loadGraphData(JSON.parse(pngInfo.workflow));
} else if (pngInfo.parameters) {
importA1111(this.lGraph, pngInfo.parameters);
}
}
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
const reader = new FileReader();
reader.onload = () => {
this.loadGraphData(JSON.parse(reader.result));
};
reader.readAsText(file);
}
}
/**
* Loads workflow data from the specified file
*/
async handleFile(file: File) {
if (file.type === "image/png") {
const pngInfo = await getPngMetadata(file);
if (pngInfo) {
if (pngInfo.workflow) {
this.loadGraphData(JSON.parse(pngInfo.workflow));
} else if (pngInfo.parameters) {
importA1111(this.lGraph, pngInfo.parameters);
}
}
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
const reader = new FileReader();
reader.onload = () => {
this.loadGraphData(JSON.parse(reader.result as string));
};
reader.readAsText(file);
}
}
// registerExtension(extension) {
// if (!extension.name) {
// throw new Error("Extensions must have a 'name' property.");
// }
// if (this.extensions.find((ext) => ext.name === extension.name)) {
// throw new Error(`Extension named '${extension.name}' already registered.`);
// }
// this.extensions.push(extension);
// }
// registerExtension(extension) {
// if (!extension.name) {
// throw new Error("Extensions must have a 'name' property.");
// }
// if (this.extensions.find((ext) => ext.name === extension.name)) {
// throw new Error(`Extension named '${extension.name}' already registered.`);
// }
// this.extensions.push(extension);
// }
/**
* Refresh combo list on whole nodes
*/
async refreshComboInNodes() {
const defs = await this.api.getNodeDefs();
/**
* Refresh combo list on whole nodes
*/
async refreshComboInNodes() {
const defs = await this.api.getNodeDefs();
for(let nodeNum in this.lGraph._nodes) {
const node = this.lGraph._nodes[nodeNum];
for(let nodeNum in this.lGraph._nodes) {
const node = this.lGraph._nodes[nodeNum];
const def = defs[node.type];
const def = defs[node.type];
for(const widgetNum in node.widgets) {
const widget = node.widgets[widgetNum]
for(const widgetNum in node.widgets) {
const widget = node.widgets[widgetNum]
if(widget.type == "combo" && def["input"]["required"][widget.name] !== undefined) {
widget.options.values = def["input"]["required"][widget.name][0];
if(widget.type == "combo" && def["input"]["required"][widget.name] !== undefined) {
widget.options.values = def["input"]["required"][widget.name][0];
if(!widget.options.values.includes(widget.value)) {
widget.value = widget.options.values[0];
}
}
}
}
}
if(!widget.options.values.includes(widget.value)) {
widget.value = widget.options.values[0];
}
}
}
}
}
/**
* Clean current state
*/
clean() {
this.nodeOutputs = {};
}
/**
* Clean current state
*/
clean() {
this.nodeOutputs = {};
}
}

View File

@@ -0,0 +1,165 @@
<script lang="ts">
import { Button } from "@gradio/button";
import { Block, BlockTitle } from "@gradio/atoms";
import { Dropdown, Range, TextBox } from "@gradio/form";
import { LGraphNode, LGraph } from "litegraph.js";
import type { IWidget } from "litegraph.js";
import ComfyApp from "./ComfyApp";
export let app: ComfyApp;
export function clear() {
nodes = {};
items = {};
state = {};
}
export function addNodeUI(node: LGraphNode) {
if (node.widgets) {
for (const [i, widget] of node.widgets.entries()) {
nodes[node.id] = node;
node.onPropertyChanged = (k, v) => {
console.log("PROPCHANGE", k, v)
};
if (!items[node.id]) {
items[node.id] = []
}
items[node.id].push({ node, widget })
if (!state[node.id]) {
state[node.id] = []
}
state[node.id].push(widget.value);
}
}
nodes = nodes;
items = items;
state = state;
}
export function removeNodeUI(node: LGraphNode) {
delete nodes[node.id]
delete state[node.id]
delete items[node.id]
nodes = nodes;
items = items;
state = state;
}
export function configureFinished(graph: LGraph) {
for (const node of graph.computeExecutionOrder(false, null)) {
if (node.widgets_values) {
for (const [j, value] of node.widgets_values.entries()) {
state[node.id][j] = value;
}
}
}
nodes = nodes;
state = state;
}
function getState() {
}
function queuePrompt() {
console.log("Queuing!", state);
app.queuePrompt(0, 1, state);
}
let nodes: Record<number, LGraphNode> = {};
let items: Record<number, { node: LGraphNode, widget: IWidget }[]> = {};
let state: Record<number, any[]> = {};
</script>
<div id="comfy-ui-panes">
<div class="v-pane">
{#each Object.keys(items) as id}
{@const node = nodes[id]}
<Block>
<label for={id}>
<BlockTitle>{node.title}</BlockTitle>
</label>
{#each items[id] as item, i}
{#if item.widget.type == "combo"}
<div class="wrapper">
<Dropdown
bind:value={state[id][i]}
choices={item.widget.options.values}
multiselect={false}
max_choices={1},
label={item.widget.name}
show_label={true}
disabled={item.widget.options.values.length === 0}
on:change
on:select
on:blur
/>
</div>
{:else if item.widget.type == "number"}
<div class="wrapper">
<Range
bind:value={state[id][i]}
minimum={item.widget.options.min}
maximum={item.widget.options.max}
step={item.widget.options.step}
label={item.widget.name}
show_label={true}
on:change
on:release
/>
</div>
{:else if item.widget.type == "text"}
<div class="wrapper">
<TextBox
bind:value={state[id][i]}
label={item.widget.name}
lines={item.widget.options.multiline ? 5 : 1}
show_label={true}
on:change
on:submit
on:blur
on:select
/>
</div>
{/if}
{/each}
</Block>
{/each}
</div>
<div class="v-pane">
</div>
<div class="v-pane">
<div class="wrapper">
<Button variant="primary" on:click={queuePrompt}>
Run
</Button>
</div>
</div>
</div>
<style>
#comfy-ui-panes {
width: 100%;
height: 100%;
}
.v-pane {
border: 1px solid grey;
float: left;
height: 100%;
overflow: auto;
position: relative;
width: 33%;
}
.wrapper {
padding: 10px;
width: 100%;
}
</style>

View File

@@ -1,7 +1,7 @@
import type { IWidget, LGraphNode } from "litegraph.js";
import type ComfyApp from "$lib/components/ComfyApp";
interface WidgetData {
export interface WidgetData {
widget: IWidget,
minWidth?: number,
minHeight?: number
@@ -55,7 +55,7 @@ const STRING: WidgetFactory = (node: LGraphNode, inputName: string, inputData: a
// if (multiline) {
// return addMultilineWidget(node, inputName, { defaultVal, ...inputData[1] }, app);
// } else {
return { widget: node.addWidget("text", inputName, defaultVal, () => {}, {}) };
return { widget: node.addWidget("text", inputName, defaultVal, () => {}, { multiline }) };
// }
};

View File

@@ -1,8 +1,374 @@
<script lang="ts">
import { SvelteUIProvider } from '@svelteuidev/core';
import ComfyApp from "$lib/components/ComfyApp.svelte"
import "litegraph.js/css/litegraph.css";
</script>
<SvelteUIProvider themeObserver="light">
<ComfyApp/>
</SvelteUIProvider>
<ComfyApp/>
<style>
:root {
--primary-50: #fff7ed;
--primary-100: #ffedd5;
--primary-200: #fed7aa;
--primary-300: #fdba74;
--primary-400: #fb923c;
--primary-500: #f97316;
--primary-600: #ea580c;
--primary-700: #c2410c;
--primary-800: #9a3412;
--primary-900: #7c2d12;
--primary-950: #6c2e12;
--secondary-50: #eff6ff;
--secondary-100: #dbeafe;
--secondary-200: #bfdbfe;
--secondary-300: #93c5fd;
--secondary-400: #60a5fa;
--secondary-500: #3b82f6;
--secondary-600: #2563eb;
--secondary-700: #1d4ed8;
--secondary-800: #1e40af;
--secondary-900: #1e3a8a;
--secondary-950: #1d3660;
--neutral-50: #f9fafb;
--neutral-100: #f3f4f6;
--neutral-200: #e5e7eb;
--neutral-300: #d1d5db;
--neutral-400: #9ca3af;
--neutral-500: #6b7280;
--neutral-600: #4b5563;
--neutral-700: #374151;
--neutral-800: #1f2937;
--neutral-900: #111827;
--neutral-950: #0b0f19;
--spacing-xxs: 1px;
--spacing-xs: 2px;
--spacing-sm: 4px;
--spacing-md: 6px;
--spacing-lg: 8px;
--spacing-xl: 10px;
--spacing-xxl: 16px;
--radius-xxs: 1px;
--radius-xs: 2px;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-xxl: 22px;
--text-xxs: 9px;
--text-xs: 10px;
--text-sm: 12px;
--text-md: 14px;
--text-lg: 16px;
--text-xl: 22px;
--text-xxl: 26px;
--color-accent: var(--primary-500);
--color-accent-soft: var(--primary-50);
--background-fill-primary: white;
--background-fill-secondary: var(--neutral-50);
--border-color-accent: var(--primary-300);
--border-color-primary: var(--neutral-200);
--text-color-code-background-fill: var(--neutral-200);
--text-color-code-border: var(--border-color-primary);
--link-text-color: var(--secondary-600);
--link-text-color-active: var(--secondary-600);
--link-text-color-hover: var(--secondary-700);
--link-text-color-visited: var(--secondary-500);
--body-text-color-subdued: var(--neutral-400);
--body-background-fill: var(--background-fill-primary);
--body-text-color: var(--neutral-800);
--body-text-size: var(--text-md);
--body-text-weight: 400;
--embed-radius: var(--radius-lg);
--shadow-drop: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
--shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1),
0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-inset: rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset;
--shadow-spread: 3px;
--block-background-fill: var(--background-fill-primary);
--block-border-color: var(--border-color-primary);
--block-border-width: 1px;
--block-info-text-color: var(--body-text-color-subdued);
--block-info-text-size: var(--text-sm);
--block-info-text-weight: 400;
--block-label-background-fill: var(--background-fill-primary);
--block-label-border-color: var(--border-color-primary);
--block-label-border-width: 1px;
--block-label-text-color: var(--neutral-500);
--block-label-icon-color: var(--block-label-text-color);
--block-label-margin: 0;
--block-label-padding: var(--spacing-sm) var(--spacing-lg);
--block-label-radius: calc(var(--radius-lg) - 1px) 0
calc(var(--radius-lg) - 1px) 0;
--block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0
calc(var(--radius-lg) - 1px);
--block-label-text-size: var(--text-sm);
--block-label-text-weight: 400;
--block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
--block-radius: var(--radius-lg);
--block-shadow: var(--shadow-drop);
--block-title-background-fill: none;
--block-title-border-color: none;
--block-title-border-width: 0px;
--block-title-text-color: var(--neutral-500);
--block-title-padding: 0;
--block-title-radius: none;
--block-title-text-size: var(--text-md);
--block-title-text-weight: 400;
--container-radius: var(--radius-lg);
--form-gap-width: 1px;
--layout-gap: var(--spacing-xxl);
--panel-background-fill: var(--background-fill-secondary);
--panel-border-color: var(--border-color-primary);
--panel-border-width: 0;
--section-header-text-size: var(--text-md);
--section-header-text-weight: 400;
--checkbox-background-color: var(--background-fill-primary);
--checkbox-background-color-focus: var(--background-fill-primary);
--checkbox-background-color-hover: var(--background-fill-primary);
--checkbox-background-color-selected: var(--secondary-600);
--checkbox-border-color: var(--neutral-300);
--checkbox-border-color-focus: var(--secondary-500);
--checkbox-border-color-hover: var(--neutral-300);
--checkbox-border-color-selected: var(--secondary-600);
--checkbox-border-radius: var(--radius-sm);
--checkbox-border-width: var(--input-border-width);
--checkbox-label-background-fill: linear-gradient(
to top,
var(--neutral-50),
white
);
--checkbox-label-background-fill-hover: linear-gradient(
to top,
var(--neutral-100),
white
);
--checkbox-label-background-fill-selected: var(
--checkbox-label-background-fill
);
--checkbox-label-border-color: var(--border-color-primary);
--checkbox-label-border-color-hover: var(--border-color-primary);
--checkbox-label-border-width: var(--input-border-width);
--checkbox-label-gap: var(--spacing-lg);
--checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
--checkbox-label-shadow: var(--shadow-drop);
--checkbox-label-text-size: var(--text-md);
--checkbox-label-text-weight: 400;
--checkbox-shadow: var(--input-shadow);
--checkbox-label-text-color: var(--body-text-color);
--checkbox-label-text-color-selected: var(--checkbox-label-text-color);
--error-background-fill: linear-gradient(
to right,
#fee2e2,
var(--background-fill-secondary)
);
--error-border-color: #fecaca;
--error-border-width: 1px;
--error-text-color: #ef4444;
--prose-header-text-weight: 600;
--input-background-fill: white;
--input-background-fill-focus: var(--secondary-500);
--input-background-fill-hover: var(--input-background-fill);
--input-border-color: var(--border-color-primary);
--input-border-color-focus: var(--secondary-300);
--input-border-color-hover: var(--border-color-primary);
--input-border-width: 1px;
--input-padding: var(--spacing-xl);
--input-placeholder-color: var(--neutral-400);
--input-radius: var(--radius-lg);
--input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset);
--input-shadow-focus: 0 0 0 var(--shadow-spread) var(--secondary-50),
var(--shadow-inset);
--input-text-size: var(--text-md);
--input-text-weight: 400;
--loader-color: var(--color-accent);
--prose-text-size: var(--text-md);
--prose-text-weight: 400;
--stat-background-fill: linear-gradient(
to right,
var(--primary-400),
var(--primary-200)
);
--table-border-color: var(--neutral-300);
--table-even-background-fill: white;
--table-odd-background-fill: var(--neutral-50);
--table-radius: var(--radius-lg);
--table-row-focus: var(--color-accent-soft);
--button-border-width: var(--input-border-width);
--button-cancel-background-fill: linear-gradient(
to bottom right,
#fee2e2,
#fecaca
);
--button-cancel-background-fill-hover: linear-gradient(
to bottom right,
#fee2e2,
#fee2e2
);
--button-cancel-border-color: #fecaca;
--button-cancel-border-color-hover: var(--button-cancel-border-color);
--button-cancel-text-color: #dc2626;
--button-cancel-text-color-hover: var(--button-cancel-text-color);
--button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg));
--button-large-radius: var(--radius-lg);
--button-large-text-size: var(--text-lg);
--button-large-text-weight: 600;
--button-primary-background-fill: linear-gradient(
to bottom right,
var(--primary-100),
var(--primary-300)
);
--button-primary-background-fill-hover: linear-gradient(
to bottom right,
var(--primary-100),
var(--primary-200)
);
--button-primary-border-color: var(--primary-200);
--button-primary-border-color-hover: var(--button-primary-border-color);
--button-primary-text-color: var(--primary-600);
--button-primary-text-color-hover: var(--button-primary-text-color);
--button-secondary-background-fill: linear-gradient(
to bottom right,
var(--neutral-100),
var(--neutral-200)
);
--button-secondary-background-fill-hover: linear-gradient(
to bottom right,
var(--neutral-100),
var(--neutral-100)
);
--button-secondary-border-color: var(--neutral-200);
--button-secondary-border-color-hover: var(--button-secondary-border-color);
--button-secondary-text-color: var(--neutral-700);
--button-secondary-text-color-hover: var(--button-secondary-text-color);
--button-shadow: var(--shadow-drop);
--button-shadow-active: var(--shadow-inset);
--button-shadow-hover: var(--shadow-drop-lg);
--button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
--button-small-radius: var(--radius-lg);
--button-small-text-size: var(--text-md);
--button-small-text-weight: 400;
--button-transition: none;
}
.dark {
--color-accent-soft: var(--neutral-900);
--background-fill-primary: var(--neutral-950);
--background-fill-secondary: var(--neutral-900);
--border-color-accent: var(--neutral-600);
--border-color-primary: var(--neutral-700);
--text-color-code-background-fill: var(--neutral-800);
--link-text-color-active: var(--secondary-500);
--link-text-color: var(--secondary-500);
--link-text-color-hover: var(--secondary-400);
--link-text-color-visited: var(--secondary-600);
--body-text-color-subdued: var(--neutral-400);
--body-background-fill: var(--background-fill-primary);
--body-text-color: var(--neutral-100);
--shadow-spread: 1px;
--block-background-fill: var(--neutral-800);
--block-border-color: var(--border-color-primary);
--block-border-width: 1px;
--block-info-text-color: var(--body-text-color-subdued);
--block-label-background-fill: var(--background-fill-secondary);
--block-label-border-color: var(--border-color-primary);
--block-label-border-width: 1px;
--block-label-text-color: var(--neutral-200);
--block-shadow: none;
--block-title-background-fill: none;
--block-title-border-color: none;
--block-title-border-width: 0px;
--block-title-text-color: var(--neutral-200);
--panel-background-fill: var(--background-fill-secondary);
--panel-border-color: var(--border-color-primary);
--checkbox-background-color: var(--neutral-800);
--checkbox-background-color-focus: var(--checkbox-background-color);
--checkbox-background-color-hover: var(--checkbox-background-color);
--checkbox-background-color-selected: var(--secondary-600);
--checkbox-border-color: var(--neutral-700);
--checkbox-border-color-focus: var(--secondary-500);
--checkbox-border-color-hover: var(--neutral-600);
--checkbox-border-color-selected: var(--secondary-600);
--checkbox-label-background-fill: linear-gradient(
to top,
var(--neutral-900),
var(--neutral-800)
);
--checkbox-label-background-fill-hover: linear-gradient(
to top,
var(--neutral-900),
var(--neutral-800)
);
--checkbox-label-background-fill-selected: var(
--checkbox-label-background-fill
);
--checkbox-label-border-color: var(--border-color-primary);
--checkbox-label-border-color-hover: var(--border-color-primary);
--checkbox-label-text-color: var(--body-text-color);
--checkbox-label-text-color-selected: var(--checkbox-label-text-color);
--error-background-fill: var(--background-fill-primary);
--error-border-color: var(--border-color-primary);
--error-border-width: var(--error-border-width);
--error-text-color: #ef4444;
--input-background-fill: var(--neutral-800);
--input-background-fill-focus: var(--secondary-600);
--input-background-fill-hover: var(--input-background-fill);
--input-border-color: var(--border-color-primary);
--input-border-color-focus: var(--neutral-700);
--input-border-color-hover: var(--border-color-primary);
--input-placeholder-color: var(--neutral-500);
--input-shadow: var(--input-shadow);
--input-shadow-focus: 0 0 0 var(--shadow-spread) var(--neutral-700),
var(--shadow-inset);
--loader-color: var(--loader-color);
--stat-background-fill: linear-gradient(
to right,
var(--primary-400),
var(--primary-600)
);
--table-border-color: var(--neutral-700);
--table-even-background-fill: var(--neutral-950);
--table-odd-background-fill: var(--neutral-900);
--table-row-focus: var(--color-accent-soft);
--button-cancel-background-fill: linear-gradient(
to bottom right,
#dc2626,
#b91c1c
);
--button-cancel-background-fill-hover: linear-gradient(
to bottom right,
#dc2626,
#dc2626
);
--button-cancel-border-color: #dc2626;
--button-cancel-border-color-hover: var(--button-cancel-border-color);
--button-cancel-text-color: white;
--button-cancel-text-color-hover: var(--button-cancel-text-color);
--button-primary-background-fill: linear-gradient(
to bottom right,
var(--primary-600),
var(--primary-700)
);
--button-primary-background-fill-hover: linear-gradient(
to bottom right,
var(--primary-600),
var(--primary-600)
);
--button-primary-border-color: var(--primary-600);
--button-primary-border-color-hover: var(--button-primary-border-color);
--button-primary-text-color: white;
--button-primary-text-color-hover: var(--button-primary-text-color);
--button-secondary-background-fill: linear-gradient(
to bottom right,
var(--neutral-600),
var(--neutral-700)
);
--button-secondary-background-fill-hover: linear-gradient(
to bottom right,
var(--neutral-600),
var(--neutral-600)
);
--button-secondary-border-color: var(--neutral-600);
--button-secondary-border-color-hover: var(--button-secondary-border-color);
--button-secondary-text-color: white;
--button-secondary-text-color-hover: var(--button-secondary-text-color);
}
</style>

View File

@@ -409,6 +409,8 @@ declare module "litegraph.js" {
version: typeof LiteGraph.VERSION;
};
export type LConnectionKind = LiteGraph.INPUT | LiteGraph.OUTPUT;
export declare class LGraph {
static supported_types: string[];
static STATUS_STOPPED: 1;
@@ -436,7 +438,7 @@ declare module "litegraph.js" {
starttime: number;
status: typeof LGraph.STATUS_RUNNING | typeof LGraph.STATUS_STOPPED;
/* private */ _nodes: LGraphNode[];
private _nodes: LGraphNode[];
private _groups: LGraphGroup[];
private _nodes_by_id: Record<number, LGraphNode>;
/** nodes that are executable sorted in execution order */
@@ -514,11 +516,35 @@ declare module "litegraph.js" {
* @param node the instance of the node
*/
add(node: LGraphNode, skip_compute_order?: boolean): void;
/**
* Called before the graph is changed
*/
onBeforeChange(graph: LGraph, info: any): void;
/**
* Called after the graph is changed
*/
onAfterChange(graph: LGraph, info: any): void;
/**
* Called when a new node is added
* @param node the instance of the node
*/
onNodeAdded(node: LGraphNode): void;
/**
* Called when a node is removed
* @param node the instance of the node
*/
onNodeRemoved(node: LGraphNode): void;
/**
* Called when a node's connection is changed
* @param node the instance of the node
*/
onNodeConnectionChange(kind: LConnectionKind,
node: LGraphNode,
slot: INodeSlot,
target_node: LGraphNode,
target_slot: INodeSlot): void;
/** Called by `LGraph.configure` */
onConfigure?(o: SerializedLGraphNode): void;
/** Removes a node from the graph */
remove(node: LGraphNode): void;
/** Returns a node by its id. */
@@ -677,6 +703,7 @@ declare module "litegraph.js" {
id: number;
widgets: IWidget[] | null | undefined;
widgets_values?: IWidget["value"][];
//inputs available: array of inputs
inputs: INodeInputSlot[];
@@ -1270,6 +1297,8 @@ declare module "litegraph.js" {
node_over: LGraphNode | null;
node_title_color: string;
node_widget: [LGraphNode, IWidget] | null;
/** Called by `LGraphCanvas.clear` */
onClear?(): void;
/** Called by `LGraphCanvas.drawBackCanvas` */
onDrawBackground:
| ((ctx: CanvasRenderingContext2D, visibleArea: Vector4) => void)