Switch between workflows
This commit is contained in:
@@ -200,6 +200,10 @@ export default class ComfyApp {
|
|||||||
await this.initDefaultGraph();
|
await this.initDefaultGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workflowState.createNewWorkflow(this.lCanvas);
|
||||||
|
workflowState.createNewWorkflow(this.lCanvas);
|
||||||
|
workflowState.createNewWorkflow(this.lCanvas);
|
||||||
|
|
||||||
// Save current workflow automatically
|
// Save current workflow automatically
|
||||||
// setInterval(this.saveStateToLocalStorage.bind(this), 1000);
|
// setInterval(this.saveStateToLocalStorage.bind(this), 1000);
|
||||||
|
|
||||||
@@ -503,7 +507,7 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
this.clean();
|
this.clean();
|
||||||
|
|
||||||
const workflow = workflowState.openWorkflow(data);
|
const workflow = workflowState.openWorkflow(this.lCanvas, data);
|
||||||
|
|
||||||
// Restore canvas offset/zoom
|
// Restore canvas offset/zoom
|
||||||
this.lCanvas.deserialize(data.canvas)
|
this.lCanvas.deserialize(data.canvas)
|
||||||
@@ -514,13 +518,7 @@ export default class ComfyApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setActiveWorkflow(index: number) {
|
setActiveWorkflow(index: number) {
|
||||||
const workflow = workflowState.setActiveWorkflow(index);
|
workflowState.setActiveWorkflow(this.lCanvas, index);
|
||||||
|
|
||||||
if (workflow != null) {
|
|
||||||
workflow.start("app", this.lCanvas);
|
|
||||||
this.lCanvas.deserialize(workflow.canvases["app"].state)
|
|
||||||
}
|
|
||||||
|
|
||||||
selectionState.clear();
|
selectionState.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +540,7 @@ export default class ComfyApp {
|
|||||||
this.clean();
|
this.clean();
|
||||||
|
|
||||||
this.lCanvas.closeAllSubgraphs();
|
this.lCanvas.closeAllSubgraphs();
|
||||||
workflowState.closeAllWorkflows();
|
workflowState.closeAllWorkflows(this.lCanvas);
|
||||||
uiState.update(s => {
|
uiState.update(s => {
|
||||||
s.uiUnlocked = true;
|
s.uiUnlocked = true;
|
||||||
s.uiEditMode = "widgets";
|
s.uiEditMode = "widgets";
|
||||||
|
|||||||
@@ -187,7 +187,9 @@
|
|||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
<div id="workflow-tabs">
|
<div id="workflow-tabs">
|
||||||
{#each $workflowState.openedWorkflows as workflow, index}
|
{#each $workflowState.openedWorkflows as workflow, index}
|
||||||
<button class="workflow-tab" class:selected={index === $workflowState.activeWorkflowIdx}>
|
<button class="workflow-tab"
|
||||||
|
class:selected={index === $workflowState.activeWorkflowIdx}
|
||||||
|
on:click={() => app.setActiveWorkflow(index)}>
|
||||||
{workflow.title}
|
{workflow.title}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import ComfyGraph from '$lib/ComfyGraph';
|
|||||||
import { LGraphCanvas, LiteGraph, Subgraph } from '@litegraph-ts/core';
|
import { LGraphCanvas, LiteGraph, Subgraph } from '@litegraph-ts/core';
|
||||||
import layoutStates from './stores/layoutStates';
|
import layoutStates from './stores/layoutStates';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
import workflowState from './stores/workflowState';
|
||||||
|
|
||||||
export function configureLitegraph(isMobile: boolean = false) {
|
export function configureLitegraph(isMobile: boolean = false) {
|
||||||
LiteGraph.catch_exceptions = false;
|
LiteGraph.catch_exceptions = false;
|
||||||
@@ -28,5 +29,7 @@ export function configureLitegraph(isMobile: boolean = false) {
|
|||||||
|
|
||||||
(window as any).LiteGraph = LiteGraph;
|
(window as any).LiteGraph = LiteGraph;
|
||||||
(window as any).LGraphCanvas = LGraphCanvas;
|
(window as any).LGraphCanvas = LGraphCanvas;
|
||||||
(window as any).layoutStates = get(layoutStates)
|
(window as any).layoutStates = layoutStates;
|
||||||
|
(window as any).workflowState = workflowState;
|
||||||
|
(window as any).svelteGet = get;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ export class ComfySetNodeModeAdvancedAction extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of Object.values(get(layoutState).allItems)) {
|
for (const entry of Object.values(get(this.layoutState).allItems)) {
|
||||||
if (entry.dragItem.type === "container") {
|
if (entry.dragItem.type === "container") {
|
||||||
const container = entry.dragItem;
|
const container = entry.dragItem;
|
||||||
const hasTag = container.attrs.tags.indexOf(action.tag) != -1;
|
const hasTag = container.attrs.tags.indexOf(action.tag) != -1;
|
||||||
@@ -532,7 +532,7 @@ export class ComfySetNodeModeAdvancedAction extends ComfyGraphNode {
|
|||||||
this.graph.getNodeByIdRecursive(nodeId).changeMode(newMode);
|
this.graph.getNodeByIdRecursive(nodeId).changeMode(newMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
const layout = get(layoutState);
|
const layout = get(this.layoutState);
|
||||||
for (const [dragItemID, isHidden] of Object.entries(widgetChanges)) {
|
for (const [dragItemID, isHidden] of Object.entries(widgetChanges)) {
|
||||||
const container = layout.allItems[dragItemID].dragItem
|
const container = layout.allItems[dragItemID].dragItem
|
||||||
container.attrs.hidden = isHidden;
|
container.attrs.hidden = isHidden;
|
||||||
|
|||||||
@@ -1233,7 +1233,14 @@ function getLayoutByNode(node: LGraphNode): WritableLayoutStateStore | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type LayoutStateStores = {
|
export type LayoutStateStores = {
|
||||||
|
/*
|
||||||
|
* Layouts associated with opened workflows
|
||||||
|
*/
|
||||||
all: Record<WorkflowInstID, WritableLayoutStateStore>,
|
all: Record<WorkflowInstID, WritableLayoutStateStore>,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Increment to force Svelte to re-render the props panel
|
||||||
|
*/
|
||||||
refreshPropsPanel: number
|
refreshPropsPanel: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1249,10 +1256,6 @@ export type WritableLayoutStateStores = Writable<LayoutStateStores> & LayoutStat
|
|||||||
|
|
||||||
const store = writable({
|
const store = writable({
|
||||||
all: {},
|
all: {},
|
||||||
|
|
||||||
/*
|
|
||||||
* Increment to force Svelte to re-render the props panel
|
|
||||||
*/
|
|
||||||
refreshPropsPanel: 0
|
refreshPropsPanel: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ function onExecuted(promptID: PromptID, nodeID: ComfyNodeID, outputs: ComfyExecu
|
|||||||
entry_ = entry;
|
entry_ = entry;
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
return entry;
|
return entry_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queueStateStore: WritableQueueStateStore =
|
const queueStateStore: WritableQueueStateStore =
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { SerializedGraphCanvasState } from '$lib/ComfyGraphCanvas';
|
import type { SerializedGraphCanvasState } from '$lib/ComfyGraphCanvas';
|
||||||
import type { LGraphCanvas, SerializedLGraph, UUID } from '@litegraph-ts/core';
|
import type { LGraphCanvas, NodeID, SerializedLGraph, UUID } from '@litegraph-ts/core';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
import type { Readable, Writable } from 'svelte/store';
|
import type { Readable, Writable } from 'svelte/store';
|
||||||
import type { SerializedLayoutState, WritableLayoutStateStore } from './layoutStates';
|
import type { SerializedLayoutState, WritableLayoutStateStore } from './layoutStates';
|
||||||
@@ -36,13 +36,24 @@ export class ComfyWorkflow {
|
|||||||
* Used for uniquely identifying the instance of the opened workflow in the frontend.
|
* Used for uniquely identifying the instance of the opened workflow in the frontend.
|
||||||
*/
|
*/
|
||||||
id: WorkflowInstID;
|
id: WorkflowInstID;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Human-readable name on the tab
|
||||||
|
*/
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Graph of this workflow, whose nodes are bound to the UI layout
|
||||||
|
*/
|
||||||
graph: ComfyGraph;
|
graph: ComfyGraph;
|
||||||
|
|
||||||
get layout(): WritableLayoutStateStore | null {
|
get layout(): WritableLayoutStateStore | null {
|
||||||
return layoutStates.getLayout(this.id)
|
return layoutStates.getLayout(this.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Graph canvases attached to the graph of this workflow
|
||||||
|
*/
|
||||||
canvases: Record<string, ActiveCanvas> = {};
|
canvases: Record<string, ActiveCanvas> = {};
|
||||||
|
|
||||||
constructor(title: string) {
|
constructor(title: string) {
|
||||||
@@ -150,12 +161,13 @@ export type WorkflowState = {
|
|||||||
|
|
||||||
type WorkflowStateOps = {
|
type WorkflowStateOps = {
|
||||||
getWorkflow: (id: WorkflowInstID) => ComfyWorkflow | null
|
getWorkflow: (id: WorkflowInstID) => ComfyWorkflow | null
|
||||||
|
getWorkflowByNodeID: (id: NodeID) => ComfyWorkflow | null
|
||||||
getActiveWorkflow: () => ComfyWorkflow | null
|
getActiveWorkflow: () => ComfyWorkflow | null
|
||||||
createNewWorkflow: () => ComfyWorkflow,
|
createNewWorkflow: (canvas: ComfyGraphCanvas, setActive?: boolean) => ComfyWorkflow,
|
||||||
openWorkflow: (data: SerializedAppState) => ComfyWorkflow,
|
openWorkflow: (canvas: ComfyGraphCanvas, data: SerializedAppState) => ComfyWorkflow,
|
||||||
closeWorkflow: (index: number) => void,
|
closeWorkflow: (canvas: ComfyGraphCanvas, index: number) => void,
|
||||||
closeAllWorkflows: () => void,
|
closeAllWorkflows: (canvas: ComfyGraphCanvas) => void,
|
||||||
setActiveWorkflow: (index: number) => ComfyWorkflow | null
|
setActiveWorkflow: (canvas: ComfyGraphCanvas, index: number) => ComfyWorkflow | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WritableWorkflowStateStore = Writable<WorkflowState> & WorkflowStateOps;
|
export type WritableWorkflowStateStore = Writable<WorkflowState> & WorkflowStateOps;
|
||||||
@@ -163,13 +175,20 @@ const store: Writable<WorkflowState> = writable(
|
|||||||
{
|
{
|
||||||
openedWorkflows: [],
|
openedWorkflows: [],
|
||||||
openedWorkflowsByID: {},
|
openedWorkflowsByID: {},
|
||||||
activeWorkflowIdx: -1
|
activeWorkflowIdx: -1,
|
||||||
|
activeWorkflow: null
|
||||||
})
|
})
|
||||||
|
|
||||||
function getWorkflow(id: WorkflowInstID): ComfyWorkflow | null {
|
function getWorkflow(id: WorkflowInstID): ComfyWorkflow | null {
|
||||||
return get(store).openedWorkflowsByID[id];
|
return get(store).openedWorkflowsByID[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWorkflowByNodeID(id: NodeID): ComfyWorkflow | null {
|
||||||
|
return Object.values(get(store).openedWorkflows).find(w => {
|
||||||
|
return w.graph.getNodeByIdRecursive(id) != null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getActiveWorkflow(): ComfyWorkflow | null {
|
function getActiveWorkflow(): ComfyWorkflow | null {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
if (state.activeWorkflowIdx === -1)
|
if (state.activeWorkflowIdx === -1)
|
||||||
@@ -177,34 +196,36 @@ function getActiveWorkflow(): ComfyWorkflow | null {
|
|||||||
return state.openedWorkflows[state.activeWorkflowIdx];
|
return state.openedWorkflows[state.activeWorkflowIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewWorkflow(): ComfyWorkflow {
|
function createNewWorkflow(canvas: ComfyGraphCanvas, setActive: boolean = false): ComfyWorkflow {
|
||||||
const workflow = new ComfyWorkflow("Workflow X");
|
const workflow = new ComfyWorkflow("Workflow X");
|
||||||
const layoutState = layoutStates.create(workflow);
|
const layoutState = layoutStates.create(workflow);
|
||||||
workflow.deserialize(layoutState, { graph: blankGraph.workflow, layout: blankGraph.layout })
|
layoutState.initDefaultLayout();
|
||||||
|
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
this.openedWorkflows.push(workflow);
|
state.openedWorkflows.push(workflow);
|
||||||
setActiveWorkflow(state.openedWorkflows.length - 1)
|
|
||||||
|
if (setActive)
|
||||||
|
setActiveWorkflow(canvas, state.openedWorkflows.length - 1)
|
||||||
|
|
||||||
store.set(state)
|
store.set(state)
|
||||||
|
|
||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openWorkflow(data: SerializedAppState): ComfyWorkflow {
|
function openWorkflow(canvas: ComfyGraphCanvas, data: SerializedAppState): ComfyWorkflow {
|
||||||
const [workflow, layoutState] = ComfyWorkflow.create("Workflow X")
|
const [workflow, layoutState] = ComfyWorkflow.create("Workflow X")
|
||||||
workflow.deserialize(layoutState, { graph: data.workflow, layout: data.layout })
|
workflow.deserialize(layoutState, { graph: data.workflow, layout: data.layout })
|
||||||
|
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
state.openedWorkflows.push(workflow);
|
state.openedWorkflows.push(workflow);
|
||||||
setActiveWorkflow(state.openedWorkflows.length - 1)
|
setActiveWorkflow(canvas, state.openedWorkflows.length - 1)
|
||||||
|
|
||||||
store.set(state)
|
store.set(state)
|
||||||
|
|
||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeWorkflow(index: number) {
|
function closeWorkflow(canvas: ComfyGraphCanvas, index: number) {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
|
|
||||||
if (index < 0 || index >= state.openedWorkflows.length)
|
if (index < 0 || index >= state.openedWorkflows.length)
|
||||||
@@ -213,19 +234,21 @@ function closeWorkflow(index: number) {
|
|||||||
const workflow = state.openedWorkflows[index];
|
const workflow = state.openedWorkflows[index];
|
||||||
workflow.stopAll();
|
workflow.stopAll();
|
||||||
|
|
||||||
|
layoutStates.remove(workflow.id)
|
||||||
|
|
||||||
state.openedWorkflows.splice(index, 1)
|
state.openedWorkflows.splice(index, 1)
|
||||||
setActiveWorkflow(0);
|
setActiveWorkflow(canvas, 0);
|
||||||
|
|
||||||
store.set(state);
|
store.set(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllWorkflows() {
|
function closeAllWorkflows(canvas: ComfyGraphCanvas) {
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
while (state.openedWorkflows.length > 0)
|
while (state.openedWorkflows.length > 0)
|
||||||
closeWorkflow(0)
|
closeWorkflow(canvas, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setActiveWorkflow(index: number): ComfyWorkflow | null {
|
function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number): ComfyWorkflow | null {
|
||||||
const state = get(store);
|
const state = get(store);
|
||||||
|
|
||||||
if (state.openedWorkflows.length === 0) {
|
if (state.openedWorkflows.length === 0) {
|
||||||
@@ -244,6 +267,11 @@ function setActiveWorkflow(index: number): ComfyWorkflow | null {
|
|||||||
state.activeWorkflowIdx = index;
|
state.activeWorkflowIdx = index;
|
||||||
state.activeWorkflow = workflow;
|
state.activeWorkflow = workflow;
|
||||||
|
|
||||||
|
workflow.start("app", canvas);
|
||||||
|
canvas.deserialize(workflow.canvases["app"].state)
|
||||||
|
|
||||||
|
store.set(state)
|
||||||
|
|
||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +279,7 @@ const workflowStateStore: WritableWorkflowStateStore =
|
|||||||
{
|
{
|
||||||
...store,
|
...store,
|
||||||
getWorkflow,
|
getWorkflow,
|
||||||
|
getWorkflowByNodeID,
|
||||||
getActiveWorkflow,
|
getActiveWorkflow,
|
||||||
createNewWorkflow,
|
createNewWorkflow,
|
||||||
openWorkflow,
|
openWorkflow,
|
||||||
|
|||||||
@@ -203,14 +203,15 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeInfo(nodeId: ComfyNodeID): string {
|
export function getNodeInfo(nodeId: ComfyNodeID): string {
|
||||||
let app = (window as any).app;
|
const workflow = workflowState.getWorkflowByNodeID(nodeId);
|
||||||
if (!app?.activeGraph)
|
if (workflow == null)
|
||||||
return String(nodeId);
|
return nodeId;
|
||||||
|
|
||||||
|
const title = workflow.graph?.getNodeByIdRecursive(nodeId)?.title;
|
||||||
|
if (title == null)
|
||||||
|
return nodeId;
|
||||||
|
|
||||||
const displayNodeID = nodeId ? (nodeId.split("-")[0]) : String(nodeId);
|
const displayNodeID = nodeId ? (nodeId.split("-")[0]) : String(nodeId);
|
||||||
|
|
||||||
const workflow = workflowState.getActiveWorkflow();
|
|
||||||
const title = workflow?.graph?.getNodeByIdRecursive(nodeId)?.title || String(nodeId);
|
|
||||||
return title + " (" + displayNodeID + ")"
|
return title + " (" + displayNodeID + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user