Workflow properties
This commit is contained in:
@@ -3,6 +3,7 @@ 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";
|
||||||
|
|
||||||
export type SerializedGraphCanvasState = {
|
export type SerializedGraphCanvasState = {
|
||||||
offset: Vector2,
|
offset: Vector2,
|
||||||
@@ -230,6 +231,13 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onNodeSelected(node: LGraphNode) {
|
||||||
|
const ls = get(layoutState)
|
||||||
|
ls.currentSelectionNodes = [node]
|
||||||
|
ls.currentSelection = []
|
||||||
|
layoutState.set(ls)
|
||||||
|
}
|
||||||
|
|
||||||
override onNodeMoved(node: LGraphNode) {
|
override onNodeMoved(node: LGraphNode) {
|
||||||
if (super.onNodeMoved)
|
if (super.onNodeMoved)
|
||||||
super.onNodeMoved(node);
|
super.onNodeMoved(node);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { BlockTitle } from "@gradio/atoms";
|
import { BlockTitle } from "@gradio/atoms";
|
||||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||||
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
||||||
import { Checkbox } from "@gradio/form"
|
import { Checkbox, TextBox } from "@gradio/form"
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import layoutState from "$lib/stores/layoutState";
|
import layoutState from "$lib/stores/layoutState";
|
||||||
import { ImageViewer } from "$lib/ImageViewer";
|
import { ImageViewer } from "$lib/ImageViewer";
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
let containerElem: HTMLDivElement;
|
let containerElem: HTMLDivElement;
|
||||||
let resizeTimeout: NodeJS.Timeout | null;
|
let resizeTimeout: NodeJS.Timeout | null;
|
||||||
let hasShownUIHelpToast: boolean = false;
|
let hasShownUIHelpToast: boolean = false;
|
||||||
|
let uiTheme: string = "";
|
||||||
|
|
||||||
let debugLayout: boolean = false;
|
let debugLayout: boolean = false;
|
||||||
|
|
||||||
@@ -46,7 +47,10 @@
|
|||||||
|
|
||||||
function queuePrompt() {
|
function queuePrompt() {
|
||||||
console.log("Queuing!");
|
console.log("Queuing!");
|
||||||
app.queuePrompt(0, 1);
|
let subworkflow = $uiState.subWorkflow;
|
||||||
|
if (subworkflow === "")
|
||||||
|
subworkflow = null
|
||||||
|
app.queuePrompt(0, 1, subworkflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (app?.lCanvas) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
|
$: if (app?.lCanvas) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
|
||||||
@@ -164,6 +168,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
{#if uiTheme === "anapnoe"}
|
||||||
|
<link rel="stylesheet" href="/src/scss/ux.scss">
|
||||||
|
{/if}
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="dropzone" class="dropzone"></div>
|
<div id="dropzone" class="dropzone"></div>
|
||||||
<div id="container" bind:this={containerElem}>
|
<div id="container" bind:this={containerElem}>
|
||||||
@@ -220,16 +230,24 @@
|
|||||||
<Button variant="secondary" on:click={doRefreshCombos}>
|
<Button variant="secondary" on:click={doRefreshCombos}>
|
||||||
🔄
|
🔄
|
||||||
</Button>
|
</Button>
|
||||||
<Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
<!-- <Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
||||||
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/>
|
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/> -->
|
||||||
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
||||||
|
<TextBox bind:value={$uiState.subWorkflow} label="Subworkflow" show_label={true} lines={1} max_lines={1}/>
|
||||||
<label class="label" for="enable-ui-editing">
|
<label class="label" for="enable-ui-editing">
|
||||||
<BlockTitle>Enable UI Editing</BlockTitle>
|
<BlockTitle>Enable UI Editing</BlockTitle>
|
||||||
</label>
|
|
||||||
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="widgets">Widgets</option>
|
<option value="widgets">Widgets</option>
|
||||||
</select>
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="label" for="ui-theme">
|
||||||
|
<BlockTitle>Theme</BlockTitle>
|
||||||
|
<select id="ui-theme" name="ui-theme" bind:value={uiTheme}>
|
||||||
|
<option value="">None</option>
|
||||||
|
<option value="anapnoe">Anapnoe</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<LightboxModal />
|
<LightboxModal />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import ComfyGraph from "$lib/ComfyGraph";
|
|||||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import { promptToGraphVis, toGraphVis } from "$lib/utils";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
|
|
||||||
@@ -54,9 +55,11 @@ export type SerializedPromptInputs = {
|
|||||||
class_type: string
|
class_type: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SerializedPromptOutput = Record<string, SerializedPromptInputs>
|
||||||
|
|
||||||
export type SerializedPrompt = {
|
export type SerializedPrompt = {
|
||||||
workflow: SerializedLGraph,
|
workflow: SerializedLGraph,
|
||||||
output: Record<string, SerializedPromptInputs>
|
output: SerializedPromptOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Progress = {
|
export type Progress = {
|
||||||
@@ -420,7 +423,7 @@ export default class ComfyApp {
|
|||||||
* Converts the current graph workflow for sending to the API
|
* Converts the current graph workflow for sending to the API
|
||||||
* @returns The workflow and node links
|
* @returns The workflow and node links
|
||||||
*/
|
*/
|
||||||
async graphToPrompt(): Promise<SerializedPrompt> {
|
async graphToPrompt(tag: string | null = null): Promise<SerializedPrompt> {
|
||||||
// Run frontend-only logic
|
// Run frontend-only logic
|
||||||
this.lGraph.runStep(1)
|
this.lGraph.runStep(1)
|
||||||
|
|
||||||
@@ -438,6 +441,11 @@ export default class ComfyApp {
|
|||||||
|
|
||||||
const node = node_ as ComfyBackendNode;
|
const node = node_ as ComfyBackendNode;
|
||||||
|
|
||||||
|
if (tag && node.tags.indexOf(tag) === -1) {
|
||||||
|
console.debug("Skipping tagged node", tag, node.tags)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (node.mode === NodeMode.NEVER) {
|
if (node.mode === NodeMode.NEVER) {
|
||||||
// Don't serialize muted nodes
|
// Don't serialize muted nodes
|
||||||
continue;
|
continue;
|
||||||
@@ -451,6 +459,11 @@ export default class ComfyApp {
|
|||||||
const inp = node.inputs[i];
|
const inp = node.inputs[i];
|
||||||
const inputLink = node.getInputLink(i)
|
const inputLink = node.getInputLink(i)
|
||||||
const inputNode = node.getInputNode(i)
|
const inputNode = node.getInputNode(i)
|
||||||
|
|
||||||
|
if (inputNode && tag && "tags" in inputNode && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!inputLink || !inputNode) {
|
if (!inputLink || !inputNode) {
|
||||||
if ("config" in inp) {
|
if ("config" in inp) {
|
||||||
const defaultValue = (inp as IComfyInputSlot).config?.defaultValue
|
const defaultValue = (inp as IComfyInputSlot).config?.defaultValue
|
||||||
@@ -489,17 +502,36 @@ export default class ComfyApp {
|
|||||||
if (parent) {
|
if (parent) {
|
||||||
const seen = {}
|
const seen = {}
|
||||||
let link = node.getInputLink(i);
|
let link = node.getInputLink(i);
|
||||||
while (parent && !parent.isBackendNode) {
|
|
||||||
|
const isValidParent = (parent: ComfyGraphNode) => {
|
||||||
|
if (!parent || parent.isBackendNode)
|
||||||
|
return false;
|
||||||
|
if ("tags" in parent && (parent.tags as string[]).indexOf(tag) === -1)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isValidParent(parent)) {
|
||||||
link = parent.getInputLink(link.origin_slot);
|
link = parent.getInputLink(link.origin_slot);
|
||||||
if (link && !seen[link.id]) {
|
if (link && !seen[link.id]) {
|
||||||
seen[link.id] = true
|
seen[link.id] = true
|
||||||
parent = parent.getInputNode(link.origin_slot) as ComfyGraphNode;
|
const inputNode = parent.getInputNode(link.origin_slot) as ComfyGraphNode;
|
||||||
|
if (inputNode && "tags" in inputNode && tag && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||||
|
console.debug("Skipping tagged parent node", tag, node.tags)
|
||||||
|
parent = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parent = inputNode;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
parent = null;
|
parent = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (link && parent && parent.isBackendNode) {
|
if (link && parent && parent.isBackendNode) {
|
||||||
|
if ("tags" in parent && tag && (parent.tags as string[]).indexOf(tag) === -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
const input = node.inputs[i]
|
const input = node.inputs[i]
|
||||||
// TODO can null be a legitimate value in some cases?
|
// TODO can null be a legitimate value in some cases?
|
||||||
// Nodes like CLIPLoader will never have a value in the frontend, hence "null".
|
// Nodes like CLIPLoader will never have a value in the frontend, hence "null".
|
||||||
@@ -522,15 +554,19 @@ export default class ComfyApp {
|
|||||||
if (Array.isArray(output[o].inputs[i])
|
if (Array.isArray(output[o].inputs[i])
|
||||||
&& output[o].inputs[i].length === 2
|
&& output[o].inputs[i].length === 2
|
||||||
&& !output[output[o].inputs[i][0]]) {
|
&& !output[output[o].inputs[i][0]]) {
|
||||||
|
console.debug("Prune removed node link", o, i, output[o].inputs[i])
|
||||||
delete output[o].inputs[i];
|
delete output[o].inputs[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.warn({ workflow, output })
|
||||||
|
console.warn(promptToGraphVis({ workflow, output }))
|
||||||
|
|
||||||
return { workflow, output };
|
return { workflow, output };
|
||||||
}
|
}
|
||||||
|
|
||||||
async queuePrompt(num: number, batchCount: number = 1) {
|
async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) {
|
||||||
this.queueItems.push({ num, batchCount });
|
this.queueItems.push({ num, batchCount });
|
||||||
|
|
||||||
// Only have one action process the items so each one gets a unique seed correctly
|
// Only have one action process the items so each one gets a unique seed correctly
|
||||||
@@ -545,7 +581,7 @@ export default class ComfyApp {
|
|||||||
console.log(`Queue get! ${num} ${batchCount}`);
|
console.log(`Queue get! ${num} ${batchCount}`);
|
||||||
|
|
||||||
for (let i = 0; i < batchCount; i++) {
|
for (let i = 0; i < batchCount; i++) {
|
||||||
const p = await this.graphToPrompt();
|
const p = await this.graphToPrompt(tag);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.queuePrompt(num, p);
|
await this.api.queuePrompt(num, p);
|
||||||
@@ -626,12 +662,8 @@ export default class ComfyApp {
|
|||||||
if ("config" in input) {
|
if ("config" in input) {
|
||||||
const comfyInput = input as IComfyInputSlot;
|
const comfyInput = input as IComfyInputSlot;
|
||||||
|
|
||||||
console.warn("RefreshCombo", comfyInput.defaultWidgetNode, comfyInput)
|
|
||||||
|
|
||||||
if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) {
|
if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) {
|
||||||
comfyInput.config.values = def["input"]["required"][comfyInput.name][0];
|
comfyInput.config.values = def["input"]["required"][comfyInput.name][0];
|
||||||
|
|
||||||
console.warn("RefreshCombo", comfyInput.config.values, def["input"]["required"][comfyInput.name])
|
|
||||||
const inputNode = node.getInputNode(index)
|
const inputNode = node.getInputNode(index)
|
||||||
|
|
||||||
if (inputNode && "doAutoConfig" in inputNode) {
|
if (inputNode && "doAutoConfig" in inputNode) {
|
||||||
|
|||||||
54
src/lib/components/ComfyComboProperty.svelte
Normal file
54
src/lib/components/ComfyComboProperty.svelte
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { BlockTitle } from "@gradio/atoms";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
export let value: string = "";
|
||||||
|
export let values: string[] = [""];
|
||||||
|
export let name: string = "";
|
||||||
|
let value_: string = ""
|
||||||
|
|
||||||
|
$: handleChange(value);
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
change: string;
|
||||||
|
submit: undefined;
|
||||||
|
blur: undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function handleChange(val: string) {
|
||||||
|
if (val != value_)
|
||||||
|
dispatch("change", val);
|
||||||
|
value_ = val
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="select-wrapper">
|
||||||
|
<BlockTitle>{name}</BlockTitle>
|
||||||
|
<div class="select">
|
||||||
|
<select on:blur bind:value>
|
||||||
|
{#each values as value}
|
||||||
|
<option {value}>
|
||||||
|
{value}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.select-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.select {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-title {
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/lib/components/ComfyNumberProperty.svelte
Normal file
45
src/lib/components/ComfyNumberProperty.svelte
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { BlockTitle } from "@gradio/atoms";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
export let value: number = 0;
|
||||||
|
export let step: number = 1;
|
||||||
|
export let name: string = "";
|
||||||
|
let value_: number = 0;
|
||||||
|
|
||||||
|
$: value;
|
||||||
|
$: handleChange(value);
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
change: number;
|
||||||
|
submit: undefined;
|
||||||
|
blur: undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function handleChange(val: number) {
|
||||||
|
if (val != value_)
|
||||||
|
dispatch("change", val);
|
||||||
|
value_ = val
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="number-wrapper">
|
||||||
|
<BlockTitle>{name}</BlockTitle>
|
||||||
|
<div class="number">
|
||||||
|
<input type="number" bind:value {step}>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.number-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
import { Block, BlockTitle } from "@gradio/atoms";
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
import { TextBox, Checkbox } from "@gradio/form";
|
import { TextBox, Checkbox } from "@gradio/form";
|
||||||
import { LGraphNode } from "@litegraph-ts/core"
|
import { LGraphNode } from "@litegraph-ts/core"
|
||||||
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES } from "$lib/stores/layoutState"
|
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
|
||||||
import type { ComfyWidgetNode } from "$lib/nodes";
|
import type { ComfyWidgetNode } from "$lib/nodes";
|
||||||
|
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
||||||
|
import ComfyComboProperty from "./ComfyComboProperty.svelte";
|
||||||
|
|
||||||
let target: IDragItem | null = null;
|
let target: IDragItem | null = null;
|
||||||
let node: LGraphNode | null = null;
|
let node: LGraphNode | null = null;
|
||||||
@@ -20,6 +21,10 @@
|
|||||||
node = null;
|
node = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ($layoutState.currentSelectionNodes.length > 0) {
|
||||||
|
target = null;
|
||||||
|
node = $layoutState.currentSelectionNodes[0]
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
target = null
|
target = null
|
||||||
node = null;
|
node = null;
|
||||||
@@ -29,14 +34,14 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (node != null)
|
if (node != null)
|
||||||
targetType = node.type || "Widget"
|
targetType = node.type || "Node"
|
||||||
else if (target)
|
else if (target)
|
||||||
targetType = "group"
|
targetType = "Group"
|
||||||
else
|
else
|
||||||
targetType = "???"
|
targetType = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAttribute(entry: any, value: any) {
|
function updateAttribute(entry: AttributesSpec, value: any) {
|
||||||
if (target) {
|
if (target) {
|
||||||
const name = entry.name
|
const name = entry.name
|
||||||
console.warn("updateAttribute", name, value)
|
console.warn("updateAttribute", name, value)
|
||||||
@@ -51,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProperty(entry: any, value: any) {
|
function updateProperty(entry: AttributesSpec, value: any) {
|
||||||
if (node) {
|
if (node) {
|
||||||
const name = entry.name
|
const name = entry.name
|
||||||
console.warn("updateProperty", name, value)
|
console.warn("updateProperty", name, value)
|
||||||
@@ -64,15 +69,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getVar(node: LGraphNode, entry: AttributesSpec) {
|
||||||
|
let value = node[entry.name]
|
||||||
|
if (entry.serialize)
|
||||||
|
value = entry.serialize(value)
|
||||||
|
console.debug("[ComfyProperties] getVar", entry, value, node)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVar(entry: any, value: any) {
|
||||||
|
if (node) {
|
||||||
|
const name = entry.name
|
||||||
|
console.warn("updateProperty", name, value)
|
||||||
|
|
||||||
|
if (entry.deserialize)
|
||||||
|
value = entry.deserialize(value)
|
||||||
|
|
||||||
|
console.debug("[ComfyProperties] updateVar", entry, value, name, node)
|
||||||
|
node[name] = value;
|
||||||
|
|
||||||
|
if ("propsChanged" in node) {
|
||||||
|
const comfyNode = node as ComfyWidgetNode
|
||||||
|
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWorkflowAttribute(entry: AttributesSpec, value: any) {
|
||||||
|
const name = entry.name
|
||||||
|
console.warn("updateWorkflowAttribute", name, value)
|
||||||
|
|
||||||
|
$layoutState.attrs[name] = value
|
||||||
|
$layoutState = $layoutState
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="props">
|
<div class="props">
|
||||||
{#if target}
|
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div class="target-name">
|
<div class="target-name">
|
||||||
<span>
|
<span>
|
||||||
<span class="title">{target.attrs.title}</span>
|
<span class="title">{target?.attrs?.title || node?.title || "Workflow"}<span>
|
||||||
|
{#if targetType !== ""}
|
||||||
<span class="type">({targetType})</span>
|
<span class="type">({targetType})</span>
|
||||||
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +124,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{#each category.specs as spec(spec.name)}
|
{#each category.specs as spec(spec.name)}
|
||||||
{#if spec.location === "widget" && spec.name in target.attrs}
|
{#if spec.location === "widget" && target && spec.name in target.attrs}
|
||||||
<div class="props-entry">
|
<div class="props-entry">
|
||||||
{#if spec.type === "string"}
|
{#if spec.type === "string"}
|
||||||
<TextBox
|
<TextBox
|
||||||
@@ -101,33 +141,19 @@
|
|||||||
label={spec.name}
|
label={spec.name}
|
||||||
/>
|
/>
|
||||||
{:else if spec.type === "number"}
|
{:else if spec.type === "number"}
|
||||||
<label class="number-wrapper">
|
<ComfyNumberProperty
|
||||||
<BlockTitle>{spec.name}</BlockTitle>
|
name={spec.name}
|
||||||
<div class="number">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={target.attrs[spec.name]}
|
value={target.attrs[spec.name]}
|
||||||
step={1}
|
step={1}
|
||||||
on:change={(e) => updateAttribute(spec, e.currentTarget.valueAsNumber)}
|
on:change={(e) => updateAttribute(spec, e.detail)}
|
||||||
on:input={(e) => updateAttribute(spec, e.currentTarget.valueAsNumber)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
{:else if spec.type === "enum"}
|
{:else if spec.type === "enum"}
|
||||||
<label class="select-wrapper">
|
<ComfyComboProperty
|
||||||
<BlockTitle>{spec.name}</BlockTitle>
|
name={spec.name}
|
||||||
<div class="select">
|
|
||||||
<select
|
|
||||||
value={target.attrs[spec.name]}
|
value={target.attrs[spec.name]}
|
||||||
on:change={(e) => updateAttribute(spec, e.currentTarget.options[e.currentTarget.selectedIndex].value)}>
|
values={spec.values}
|
||||||
{#each spec.values as value}
|
on:changed={(e) => updateAttribute(spec, e.detail)}
|
||||||
<option value={value}>
|
/>
|
||||||
{value}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if node}
|
{:else if node}
|
||||||
@@ -144,45 +170,94 @@
|
|||||||
{:else if spec.type === "boolean"}
|
{:else if spec.type === "boolean"}
|
||||||
<Checkbox
|
<Checkbox
|
||||||
value={node.properties[spec.name]}
|
value={node.properties[spec.name]}
|
||||||
|
label={spec.name}
|
||||||
on:change={(e) => updateProperty(spec, e.detail)}
|
on:change={(e) => updateProperty(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "number"}
|
||||||
|
<ComfyNumberProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={node.properties[spec.name]}
|
||||||
|
step={1}
|
||||||
|
on:change={(e) => updateProperty(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "enum"}
|
||||||
|
<ComfyComboProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={node.properties[spec.name]}
|
||||||
|
values={spec.values}
|
||||||
|
on:changed={(e) => updateProperty(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else if spec.location === "nodeVars" && spec.name in node}
|
||||||
|
<div class="props-entry">
|
||||||
|
{#if spec.type === "string"}
|
||||||
|
<TextBox
|
||||||
|
value={getVar(node, spec)}
|
||||||
|
on:change={(e) => updateVar(spec, e.detail)}
|
||||||
|
on:input={(e) => updateVar(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
max_lines={1}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "boolean"}
|
||||||
|
<Checkbox
|
||||||
|
value={getVar(node, spec)}
|
||||||
|
on:change={(e) => updateVar(spec, e.detail)}
|
||||||
label={spec.name}
|
label={spec.name}
|
||||||
/>
|
/>
|
||||||
{:else if spec.type === "number"}
|
{:else if spec.type === "number"}
|
||||||
<label class="number-wrapper">
|
<ComfyNumberProperty
|
||||||
<BlockTitle>{spec.name}</BlockTitle>
|
name={spec.name}
|
||||||
<div class="number">
|
value={getVar(node, spec)}
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={node.properties[spec.name]}
|
|
||||||
step={1}
|
step={1}
|
||||||
on:change={(e) => updateProperty(spec, e.currentTarget.valueAsNumber)}
|
on:change={(e) => updateVar(spec, e.detail)}
|
||||||
on:input={(e) => updateProperty(spec, e.currentTarget.valueAsNumber)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
{:else if spec.type === "enum"}
|
{:else if spec.type === "enum"}
|
||||||
<label class="select-wrapper">
|
<ComfyComboProperty
|
||||||
<BlockTitle>{spec.name}</BlockTitle>
|
name={spec.name}
|
||||||
<div class="select">
|
value={getVar(node, spec)}
|
||||||
<select
|
values={spec.values}
|
||||||
value={node.properties[spec.name]}
|
on:changed={(e) => updateVar(spec, e.detail)}
|
||||||
on:change={(e) => updateProperty(spec, e.currentTarget.options[e.currentTarget.selectedIndex].value)}>
|
/>
|
||||||
{#each spec.values as value}
|
|
||||||
<option value={value}>
|
|
||||||
{value}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else if spec.location === "workflow" && spec.name in $layoutState.attrs}
|
||||||
|
<div class="props-entry">
|
||||||
|
{#if spec.type === "string"}
|
||||||
|
<TextBox
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
on:input={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
max_lines={1}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "boolean"}
|
||||||
|
<Checkbox
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "number"}
|
||||||
|
<ComfyNumberProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
step={1}
|
||||||
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "enum"}
|
||||||
|
<ComfyComboProperty
|
||||||
|
name={spec.name}
|
||||||
|
value={$layoutState.attrs[spec.name]}
|
||||||
|
values={spec.values}
|
||||||
|
on:changed={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -218,34 +293,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.number-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.number {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.select {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
select {
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-title {
|
|
||||||
padding: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
/* width: 100%;
|
/* width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import ComfyGraphNode from "./ComfyGraphNode";
|
|||||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||||
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
||||||
import { toast } from '@zerodevx/svelte-toast'
|
import { toast } from '@zerodevx/svelte-toast'
|
||||||
|
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||||
|
|
||||||
export interface ComfyAfterQueuedEventProperties extends Record<any, any> {
|
export interface ComfyAfterQueuedEventProperties extends Record<any, any> {
|
||||||
prompt: SerializedPrompt
|
prompt: SerializedPrompt
|
||||||
@@ -49,10 +50,12 @@ LiteGraph.registerNodeType({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export interface ComfyOnExecutedEventProperties extends Record<any, any> {
|
export interface ComfyOnExecutedEventProperties extends Record<any, any> {
|
||||||
|
images: GalleryOutput | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
||||||
override properties: ComfyOnExecutedEventProperties = {
|
override properties: ComfyOnExecutedEventProperties = {
|
||||||
|
images: null
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -60,17 +63,20 @@ export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
|||||||
{ name: "images", type: "IMAGE" }
|
{ name: "images", type: "IMAGE" }
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
|
{ name: "images", type: "IMAGE" },
|
||||||
{ name: "onExecuted", type: BuiltInSlotType.EVENT },
|
{ name: "onExecuted", type: BuiltInSlotType.EVENT },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
private _output: any = null;
|
override onExecute() {
|
||||||
|
if (this.properties.images !== null)
|
||||||
|
this.setOutputData(0, this.properties.images)
|
||||||
|
}
|
||||||
|
|
||||||
override receiveOutput(output: any) {
|
override receiveOutput(output: any) {
|
||||||
if (this._output !== output) {
|
if (output && "images" in output) {
|
||||||
console.error(output)
|
this.setProperty("images", output as GalleryOutput)
|
||||||
this.triggerSlot(0, "bang")
|
this.triggerSlot(1, "bang")
|
||||||
this._output = output
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
|
|||||||
import ComfyGraphNode from "./ComfyGraphNode";
|
import ComfyGraphNode from "./ComfyGraphNode";
|
||||||
import ComfyWidgets from "$lib/widgets"
|
import ComfyWidgets from "$lib/widgets"
|
||||||
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
||||||
|
import type { SerializedLGraphNode } from "@litegraph-ts/core";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Base class for any node with configuration sent by the backend.
|
* Base class for any node with configuration sent by the backend.
|
||||||
@@ -29,6 +30,12 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tags this node belongs to
|
||||||
|
* Allows you to run subsections of the graph
|
||||||
|
*/
|
||||||
|
tags: string[] = []
|
||||||
|
|
||||||
private setup(nodeData: any) {
|
private setup(nodeData: any) {
|
||||||
var inputs = nodeData["input"]["required"];
|
var inputs = nodeData["input"]["required"];
|
||||||
if (nodeData["input"]["optional"] != undefined) {
|
if (nodeData["input"]["optional"] != undefined) {
|
||||||
@@ -74,6 +81,16 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
|||||||
// app.#invokeExtensionsAsync("nodeCreated", this);
|
// app.#invokeExtensionsAsync("nodeCreated", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
|
super.onSerialize(o);
|
||||||
|
(o as any).tags = this.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
override onConfigure(o: SerializedLGraphNode) {
|
||||||
|
super.onConfigure(o);
|
||||||
|
this.tags = (o as any).tags || []
|
||||||
|
}
|
||||||
|
|
||||||
override onExecuted(outputData: any) {
|
override onExecuted(outputData: any) {
|
||||||
console.warn("onExecuted outputs", outputData)
|
console.warn("onExecuted outputs", outputData)
|
||||||
for (let index = 0; index < this.outputs.length; index++) {
|
for (let index = 0; index < this.outputs.length; index++) {
|
||||||
|
|||||||
@@ -407,7 +407,8 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
inputs: [
|
inputs: [
|
||||||
{ name: "images", type: "IMAGE" }
|
{ name: "images", type: "IMAGE" },
|
||||||
|
{ name: "store", type: BuiltInSlotType.ACTION }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +426,19 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override onAction() {
|
||||||
|
const link = this.getInputLink(0)
|
||||||
|
if (link.data && "images" in link.data) {
|
||||||
|
const data = link.data as GalleryOutput
|
||||||
|
console.debug("[ComfyGalleryNode] Received output!", data)
|
||||||
|
|
||||||
|
const galleryItems: GradioFileData[] = this.convertItems(link.data)
|
||||||
|
|
||||||
|
const currentValue = get(this.value)
|
||||||
|
this.setValue(currentValue.concat(galleryItems))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override formatValue(value: GradioFileData[] | null): string {
|
override formatValue(value: GradioFileData[] | null): string {
|
||||||
return `Images: ${value?.length || 0}`
|
return `Images: ${value?.length || 0}`
|
||||||
}
|
}
|
||||||
@@ -449,19 +463,6 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
|||||||
super.setValue([])
|
super.setValue([])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveOutput() {
|
|
||||||
const link = this.getInputLink(0)
|
|
||||||
if (link.data && "images" in link.data) {
|
|
||||||
const data = link.data as GalleryOutput
|
|
||||||
console.debug("[ComfyGalleryNode] Received output!", data)
|
|
||||||
|
|
||||||
const galleryItems: GradioFileData[] = this.convertItems(link.data)
|
|
||||||
|
|
||||||
const currentValue = get(this.value)
|
|
||||||
this.setValue(currentValue.concat(galleryItems))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
LiteGraph.registerNodeType({
|
||||||
|
|||||||
@@ -11,14 +11,20 @@ type DragItemEntry = {
|
|||||||
parent: IDragItem | null
|
parent: IDragItem | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LayoutAttributes = {
|
||||||
|
defaultWorkflow: string
|
||||||
|
}
|
||||||
|
|
||||||
export type LayoutState = {
|
export type LayoutState = {
|
||||||
root: IDragItem | null,
|
root: IDragItem | null,
|
||||||
allItems: Record<DragItemID, DragItemEntry>,
|
allItems: Record<DragItemID, DragItemEntry>,
|
||||||
allItemsByNode: Record<number, DragItemEntry>,
|
allItemsByNode: Record<number, DragItemEntry>,
|
||||||
currentId: number,
|
currentId: number,
|
||||||
currentSelection: DragItemID[],
|
currentSelection: DragItemID[],
|
||||||
|
currentSelectionNodes: LGraphNode[],
|
||||||
isConfiguring: boolean,
|
isConfiguring: boolean,
|
||||||
isMenuOpen: boolean
|
isMenuOpen: boolean,
|
||||||
|
attrs: LayoutAttributes
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Attributes = {
|
export type Attributes = {
|
||||||
@@ -34,11 +40,14 @@ export type Attributes = {
|
|||||||
export type AttributesSpec = {
|
export type AttributesSpec = {
|
||||||
name: string,
|
name: string,
|
||||||
type: string,
|
type: string,
|
||||||
location: "widget" | "nodeProps"
|
location: "widget" | "nodeProps" | "nodeVars" | "workflow"
|
||||||
editable: boolean,
|
editable: boolean,
|
||||||
|
|
||||||
values?: string[],
|
values?: string[],
|
||||||
hidden?: boolean
|
hidden?: boolean,
|
||||||
|
|
||||||
|
serialize?: (arg: any) => string,
|
||||||
|
deserialize?: (arg: string) => any,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttributesCategorySpec = {
|
export type AttributesCategorySpec = {
|
||||||
@@ -95,6 +104,18 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
{
|
{
|
||||||
categoryName: "behavior",
|
categoryName: "behavior",
|
||||||
specs: [
|
specs: [
|
||||||
|
{
|
||||||
|
name: "tags",
|
||||||
|
type: "string",
|
||||||
|
location: "nodeVars",
|
||||||
|
editable: true,
|
||||||
|
serialize: (arg: string[]) => arg.join(","),
|
||||||
|
deserialize: (arg: string) => {
|
||||||
|
if (arg === "")
|
||||||
|
return []
|
||||||
|
return arg.split(",").map(s => s.trim())
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "min",
|
name: "min",
|
||||||
type: "number",
|
type: "number",
|
||||||
@@ -113,6 +134,12 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
location: "nodeProps",
|
location: "nodeProps",
|
||||||
editable: true,
|
editable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "defaultWorkflow",
|
||||||
|
type: "string",
|
||||||
|
location: "workflow",
|
||||||
|
editable: true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@@ -161,8 +188,12 @@ const store: Writable<LayoutState> = writable({
|
|||||||
allItemsByNode: {},
|
allItemsByNode: {},
|
||||||
currentId: 0,
|
currentId: 0,
|
||||||
currentSelection: [],
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: true
|
isConfiguring: true,
|
||||||
|
attrs: {
|
||||||
|
defaultWorkflow: ""
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function findDefaultContainerForInsertion(): ContainerLayout | null {
|
function findDefaultContainerForInsertion(): ContainerLayout | null {
|
||||||
@@ -425,6 +456,7 @@ function initDefaultLayout() {
|
|||||||
allItemsByNode: {},
|
allItemsByNode: {},
|
||||||
currentId: 0,
|
currentId: 0,
|
||||||
currentSelection: [],
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false
|
isConfiguring: false
|
||||||
})
|
})
|
||||||
@@ -444,6 +476,7 @@ export type SerializedLayoutState = {
|
|||||||
root: DragItemID | null,
|
root: DragItemID | null,
|
||||||
allItems: Record<DragItemID, SerializedDragEntry>,
|
allItems: Record<DragItemID, SerializedDragEntry>,
|
||||||
currentId: number,
|
currentId: number,
|
||||||
|
attrs: LayoutAttributes
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SerializedDragEntry = {
|
export type SerializedDragEntry = {
|
||||||
@@ -481,6 +514,7 @@ function serialize(): SerializedLayoutState {
|
|||||||
root: state.root?.id,
|
root: state.root?.id,
|
||||||
allItems,
|
allItems,
|
||||||
currentId: state.currentId,
|
currentId: state.currentId,
|
||||||
|
attrs: state.attrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,8 +570,10 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
allItemsByNode,
|
allItemsByNode,
|
||||||
currentId: data.currentId,
|
currentId: data.currentId,
|
||||||
currentSelection: [],
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false
|
isConfiguring: false,
|
||||||
|
attrs: data.attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("[layoutState] deserialize", data, state)
|
console.debug("[layoutState] deserialize", data, state)
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export type UIState = {
|
|||||||
nodesLocked: boolean,
|
nodesLocked: boolean,
|
||||||
graphLocked: boolean,
|
graphLocked: boolean,
|
||||||
autoAddUI: boolean,
|
autoAddUI: boolean,
|
||||||
uiEditMode: UIEditMode
|
uiEditMode: UIEditMode,
|
||||||
|
subWorkflow: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WritableUIStateStore = Writable<UIState>;
|
export type WritableUIStateStore = Writable<UIState>;
|
||||||
@@ -19,7 +20,8 @@ const store: WritableUIStateStore = writable(
|
|||||||
graphLocked: false,
|
graphLocked: false,
|
||||||
nodesLocked: false,
|
nodesLocked: false,
|
||||||
autoAddUI: true,
|
autoAddUI: true,
|
||||||
uiEditMode: "disabled"
|
uiEditMode: "disabled",
|
||||||
|
subWorkflow: "default"
|
||||||
})
|
})
|
||||||
|
|
||||||
const uiStateStore: WritableUIStateStore =
|
const uiStateStore: WritableUIStateStore =
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import ComfyApp from "./components/ComfyApp";
|
import ComfyApp, { type SerializedPrompt } from "./components/ComfyApp";
|
||||||
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
||||||
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
||||||
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
||||||
import { get } from "svelte/store"
|
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";
|
||||||
|
|
||||||
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)
|
||||||
@@ -49,8 +50,48 @@ export function startDrag(evt: MouseEvent) {
|
|||||||
else {
|
else {
|
||||||
ls.currentSelection = [item.id]
|
ls.currentSelection = [item.id]
|
||||||
}
|
}
|
||||||
|
ls.currentSelectionNodes = [];
|
||||||
|
|
||||||
layoutState.set(ls)
|
layoutState.set(ls)
|
||||||
};
|
};
|
||||||
|
|
||||||
export function stopDrag(evt: MouseEvent) {
|
export function stopDrag(evt: MouseEvent) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function workflowToGraphVis(workflow: SerializedLGraph): string {
|
||||||
|
let out = "digraph {\n"
|
||||||
|
|
||||||
|
for (const link of workflow.links) {
|
||||||
|
const nodeA = workflow.nodes.find(n => n.id === link[1])
|
||||||
|
const nodeB = workflow.nodes.find(n => n.id === link[3])
|
||||||
|
out += `"${link[1]}_${nodeA.title}" -> "${link[3]}_${nodeB.title}"\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
out += "}"
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
export function promptToGraphVis(prompt: SerializedPrompt): string {
|
||||||
|
let out = "digraph {\n"
|
||||||
|
|
||||||
|
for (const pair of Object.entries(prompt.output)) {
|
||||||
|
const [id, o] = pair;
|
||||||
|
const outNode = prompt.workflow.nodes.find(n => n.id == id)
|
||||||
|
for (const pair2 of Object.entries(o.inputs)) {
|
||||||
|
const [inpName, i] = pair2;
|
||||||
|
|
||||||
|
if (Array.isArray(i) && i.length === 2 && typeof i[0] === "string" && typeof i[1] === "number") {
|
||||||
|
// Link
|
||||||
|
const inpNode = prompt.workflow.nodes.find(n => n.id == i[0])
|
||||||
|
out += `"${inpNode.title}" -> "${outNode.title}"\n`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Value
|
||||||
|
out += `"${id}-${inpName}-${typeof i}" -> "${outNode.title}"\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out += "}"
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
@import "gradio";
|
@import "gradio";
|
||||||
@import "ux";
|
|
||||||
|
|||||||
@@ -202,6 +202,10 @@ body {
|
|||||||
object-fit: contain !important;
|
object-fit: contain !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--ae-input-color);
|
||||||
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
background: var(--ae-main-bg-color);
|
background: var(--ae-main-bg-color);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user