Tha gallery widget
This commit is contained in:
@@ -52,6 +52,10 @@
|
||||
let graphResizeTimer: typeof Timer = -1;
|
||||
|
||||
function doAutosave(graph: LGraph): void {
|
||||
// We will merge in the state of the frontend instead of what's inside the
|
||||
// LGraph structure.
|
||||
const frontendState = get(widgetState);
|
||||
|
||||
const serializedGraph = graph.serialize()
|
||||
const serializedPaneOrder = uiPane.serialize()
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ import * as basic from "@litegraph-ts/nodes-basic"
|
||||
import * as nodes from "$lib/nodes/index"
|
||||
import ComfyGraphCanvas from "$lib/ComfyGraphCanvas";
|
||||
import type ComfyGraphNode from "$lib/ComfyGraphNode";
|
||||
import type { WidgetStateStore, WidgetUIState } from "$lib/stores/widgetState";
|
||||
import * as widgets from "$lib/widgets/index"
|
||||
import type ComfyWidget from "$lib/widgets/ComfyWidget";
|
||||
|
||||
LiteGraph.catch_exceptions = false;
|
||||
|
||||
@@ -87,10 +90,13 @@ export default class ComfyApp {
|
||||
|
||||
LiteGraph.release_link_on_empty_shows_menu = true;
|
||||
LiteGraph.alt_drag_do_clone_nodes = true;
|
||||
LiteGraph.ignore_all_widget_events = true;
|
||||
|
||||
this.lGraph.start();
|
||||
|
||||
// await this.#invokeExtensionsAsync("init");
|
||||
this.registerNodeTypeOverrides();
|
||||
this.registerWidgetTypeOverrides();
|
||||
await this.registerNodes();
|
||||
|
||||
// Load previous workflow
|
||||
@@ -120,6 +126,7 @@ export default class ComfyApp {
|
||||
this.addPasteHandler();
|
||||
this.addKeyboardHandler();
|
||||
|
||||
|
||||
// await this.#invokeExtensionsAsync("setup");
|
||||
|
||||
// Ensure the canvas fills the window
|
||||
@@ -187,6 +194,17 @@ export default class ComfyApp {
|
||||
this.lCanvas.onClear = this.canvasOnClear.bind(this);
|
||||
}
|
||||
|
||||
static node_type_overrides: Record<string, typeof ComfyGraphNode> = {}
|
||||
static widget_type_overrides: Record<string, Function> = {}
|
||||
|
||||
private registerNodeTypeOverrides() {
|
||||
ComfyApp.node_type_overrides["SaveImage"] = nodes.ComfySaveImageNode;
|
||||
}
|
||||
|
||||
private registerWidgetTypeOverrides() {
|
||||
ComfyApp.widget_type_overrides["comfy/gallery"] = widgets.ComfyGalleryWidget_Svelte;
|
||||
}
|
||||
|
||||
private async registerNodes() {
|
||||
const app = this;
|
||||
|
||||
@@ -206,7 +224,12 @@ export default class ComfyApp {
|
||||
for (const nodeId in defs) {
|
||||
const nodeData = defs[nodeId];
|
||||
|
||||
const ctor = class extends LGraphNode {
|
||||
const typeOverride = ComfyApp.node_type_overrides[nodeId]
|
||||
if (typeOverride)
|
||||
console.debug("Attaching custom type to received node:", nodeId, typeOverride)
|
||||
const baseClass: typeof LGraphNode = typeOverride || LGraphNode;
|
||||
|
||||
const ctor = class extends baseClass {
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
this.type = nodeId; // XXX: workaround dependency in LGraphNode.addInput()
|
||||
@@ -432,13 +455,13 @@ export default class ComfyApp {
|
||||
* Converts the current graph workflow for sending to the API
|
||||
* @returns The workflow and node links
|
||||
*/
|
||||
async graphToPrompt(frontendState: Record<number, any[]> = {}) {
|
||||
async graphToPrompt(frontendState: WidgetStateStore = {}) {
|
||||
const workflow = this.lGraph.serialize();
|
||||
|
||||
const output = {};
|
||||
// Process nodes in order of execution
|
||||
for (const node of this.lGraph.computeExecutionOrder<ComfyGraphNodeExecutable>(false, null)) {
|
||||
const fromFrontend = frontendState[node.id];
|
||||
const fromFrontend: WidgetUIState[] | null = frontendState[node.id];
|
||||
|
||||
const n = workflow.nodes.find((n) => n.id === node.id);
|
||||
|
||||
@@ -465,7 +488,7 @@ export default class ComfyApp {
|
||||
const widget = widgets[i];
|
||||
if (!widget.options || widget.options.serialize !== false) {
|
||||
let value = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
|
||||
if (fromFrontend) {
|
||||
if (fromFrontend && !fromFrontend[i].isVirtual) {
|
||||
value = fromFrontend[i].value;
|
||||
}
|
||||
inputs[widget.name] = value
|
||||
@@ -514,7 +537,7 @@ export default class ComfyApp {
|
||||
return { workflow, output };
|
||||
}
|
||||
|
||||
async queuePrompt(num: number, batchCount: number = 1, frontendState: Record<number, any[]> = {}) {
|
||||
async queuePrompt(num: number, batchCount: number = 1, frontendState: WidgetStateStore = {}) {
|
||||
this.queueItems.push({ num, batchCount });
|
||||
|
||||
// Only have one action process the items so each one gets a unique seed correctly
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import { onDestroy } from "svelte";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import { Move } from 'radix-icons-svelte';
|
||||
import ComboWidget from "./widgets/ComboWidget.svelte";
|
||||
import RangeWidget from "./widgets/RangeWidget.svelte";
|
||||
import TextWidget from "./widgets/TextWidget.svelte";
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
||||
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
||||
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
||||
import widgetState, { type WidgetUIState } from "$lib/stores/widgetState";
|
||||
|
||||
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME } from 'svelte-dnd-action';
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// notice - fade in works fine but don't add svelte's fade-out (known issue)
|
||||
import {cubicIn} from 'svelte/easing';
|
||||
import { flip } from 'svelte/animate';
|
||||
import ComfyApp from "./ComfyApp";
|
||||
|
||||
export let dragItems = [];
|
||||
let dragDisabled = true;
|
||||
@@ -40,6 +41,30 @@
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
function getComponentForWidgetState(item: WidgetUIState): any {
|
||||
let ctor: any = null;
|
||||
|
||||
// custom widgets with TypeScript sources
|
||||
if (item.isVirtual) {
|
||||
let override = ComfyApp.widget_type_overrides[item.widget.type]
|
||||
if (override) {
|
||||
return override;
|
||||
}
|
||||
}
|
||||
|
||||
// litegraph.ts built-in widgets
|
||||
switch (item.widget.type) {
|
||||
case "combo":
|
||||
return ComboWidget;
|
||||
case "number":
|
||||
return RangeWidget;
|
||||
case "text":
|
||||
return TextWidget;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -59,14 +84,8 @@
|
||||
<label for={id}>
|
||||
<BlockTitle>{node.title}</BlockTitle>
|
||||
</label>
|
||||
{#each $widgetState[id] as item, i}
|
||||
{#if item.widget.type == "combo"}
|
||||
<ComboWidget {item} />
|
||||
{:else if item.widget.type == "number"}
|
||||
<RangeWidget {item} />
|
||||
{:else if item.widget.type == "text"}
|
||||
<TextWidget {item} />
|
||||
{/if}
|
||||
{#each $widgetState[id] as item}
|
||||
<svelte:component this={getComponentForWidgetState(item)} {item} />
|
||||
{#if dragItem[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
{/if}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetUIState } from "$lib/stores/widgetState";
|
||||
import { Dropdown } from "@gradio/form";
|
||||
export let item: WidgetUIState | null = null;
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
{#if item}
|
||||
<Dropdown
|
||||
bind:value={item.value}
|
||||
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
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetUIState } from "$lib/stores/widgetState";
|
||||
import { Range } from "@gradio/form";
|
||||
export let item: WidgetUIState | null = null;
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
{#if item}
|
||||
<Range
|
||||
bind:value={item.value}
|
||||
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
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetUIState } from "$lib/stores/widgetState";
|
||||
import { TextBox } from "@gradio/form";
|
||||
export let item: WidgetUIState | null = null;
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
{#if item}
|
||||
<TextBox
|
||||
bind:value={item.value}
|
||||
label={item.widget.name}
|
||||
lines={item.widget.options.multiline ? 5 : 1}
|
||||
max_lines={item.widget.options.multiline ? 5 : 1}
|
||||
show_label={true}
|
||||
on:change
|
||||
on:submit
|
||||
on:blur
|
||||
on:select
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user