diff --git a/src/lib/components/ComfyJourneyView.svelte b/src/lib/components/ComfyJourneyView.svelte
index 7404245..02a67ba 100644
--- a/src/lib/components/ComfyJourneyView.svelte
+++ b/src/lib/components/ComfyJourneyView.svelte
@@ -9,6 +9,7 @@
import workflowState from '$lib/stores/workflowState';
import type { WritableJourneyStateStore } from '$lib/stores/journeyStates';
import JourneyRenderer from './JourneyRenderer.svelte';
+ import { Plus } from "svelte-bootstrap-icons";
export let app: ComfyApp;
@@ -21,11 +22,42 @@
diff --git a/src/lib/restoreParameters.ts b/src/lib/restoreParameters.ts
index ed6f54e..3a1de88 100644
--- a/src/lib/restoreParameters.ts
+++ b/src/lib/restoreParameters.ts
@@ -29,10 +29,10 @@ export interface RestoreParamSource {
*/
export interface RestoreParamSourceWorkflowNode extends RestoreParamSource<"workflow"> {
type: "workflow",
-
- sourceNode: SerializedComfyWidgetNode
}
+export type RestoreParamWorkflowNodeTargets = Record
+
/*
* A value received by the ComfyUI *backend* that corresponds to a value that
* was held in a ComfyWidgetNode. These may not necessarily be one-to-one
@@ -59,6 +59,9 @@ export interface RestoreParamSourceBackendNodeInput extends RestoreParamSource<"
/*
* A value contained in the standard prompt extracted from the saved workflow.
+ *
+ * This should only be necessary to fall back on if one workflow's parameters
+ * are to be used in a completely separate workflow's.
*/
export interface RestoreParamSourceStdPrompt extends RestoreParamSource<"stdPrompt"> {
type: "stdPrompt",
@@ -102,19 +105,7 @@ export interface RestoreParamSourceStdPrompt extends Resto
finalValue: any
}
-export type RestoreParamTarget = {
- /*
- * Node that will receive the parameter from the prompt
- */
- targetNode: ComfyWidgetNode;
-
- /*
- * Possible sources of values to insert into the target node
- */
- sources: RestoreParamSource[]
-}
-
-export type RestoreParamTargets = Record
+export type RestoreParamTargets = Record
function isSerializedComfyWidgetNode(param: any): param is SerializedComfyWidgetNode {
return param != null && typeof param === "object" && "id" in param && "comfyValue" in param
@@ -146,24 +137,35 @@ function findUpstreamSerializedWidgetNode(prompt: SerializedPrompt, input: INode
}
const addSource = (result: RestoreParamTargets, targetNode: ComfyWidgetNode, source: RestoreParamSource) => {
- result[targetNode.id] ||= { targetNode, sources: [] }
- result[targetNode.id].sources.push(source);
+ result[targetNode.id] ||= []
+ result[targetNode.id].push(source);
}
-const mergeSources = (a: RestoreParamTargets, b: RestoreParamTargets) => {
- for (const [k, vs] of Object.entries(b)) {
- a[vs.targetNode.id] ||= { targetNode: vs.targetNode, sources: [] }
- for (const source of vs.sources) {
- a[vs.targetNode.id].sources.push(source);
+export function concatRestoreParams(a: RestoreParamTargets, b: Record): RestoreParamTargets {
+ for (const [targetNodeID, source] of Object.entries(b)) {
+ a[targetNodeID] ||= []
+ a[targetNodeID].push(source);
+ }
+ return a;
+}
+
+export function concatRestoreParams2(a: RestoreParamTargets, b: RestoreParamTargets): RestoreParamTargets {
+ for (const [targetNodeID, vs] of Object.entries(b)) {
+ a[targetNodeID] ||= []
+ for (const source of vs) {
+ a[targetNodeID].push(source);
}
}
+ return a;
}
-export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
+export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamWorkflowNodeTargets {
const result = {}
const graph = workflow.graph;
+ // Find nodes that correspond to *this* workflow exactly, since we can
+ // easily match up the nodes between each (their IDs will be the same)
for (const serNode of prompt.workflow.nodes) {
const foundNode = graph.getNodeByIdRecursive(serNode.id);
if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) {
@@ -172,9 +174,8 @@ export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: Ser
const source: RestoreParamSourceWorkflowNode = {
type: "workflow",
finalValue,
- sourceNode: serNode
}
- addSource(result, foundNode, source)
+ result[foundNode.id] = source;
}
}
}
@@ -182,11 +183,14 @@ export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: Ser
return result
}
-export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
+export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): Record {
const result = {}
const graph = workflow.graph;
+ // Figure out what parameters the backend received. If there was a widget
+ // node attached to a backend node's input upstream, then we can use that
+ // value.
for (const [serNodeID, inputs] of Object.entries(prompt.output)) {
const serNode = prompt.workflow.nodes.find(sn => sn.id === serNodeID)
if (serNode == null)
@@ -223,16 +227,11 @@ export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: Seri
export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
const result = {}
- // Step 1: Find nodes that correspond to *this* workflow exactly, since we
- // can easily match up the nodes between each (their IDs will be the same)
const workflowParams = getWorkflowRestoreParams(workflow, prompt);
- mergeSources(result, workflowParams);
+ concatRestoreParams(result, workflowParams);
- // Step 2: Figure out what parameters the backend received. If there was a
- // widget node attached to a backend node's input upstream, then we can
- // use that value.
const backendParams = getBackendRestoreParams(workflow, prompt);
- mergeSources(result, backendParams);
+ concatRestoreParams2(result, backendParams);
// Step 3: Extract the standard prompt from the workflow and use that to
// infer parameter types
diff --git a/src/lib/stores/journeyStates.ts b/src/lib/stores/journeyStates.ts
index f5d7888..a609eab 100644
--- a/src/lib/stores/journeyStates.ts
+++ b/src/lib/stores/journeyStates.ts
@@ -1,7 +1,66 @@
import { writable } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
import type { DragItemID, IDragItem } from './layoutStates';
-import type { LGraphNode, NodeID } from '@litegraph-ts/core';
+import type { LGraphNode, NodeID, UUID } from '@litegraph-ts/core';
+import type { SerializedAppState } from '$lib/components/ComfyApp';
+import type { RestoreParamTargets, RestoreParamWorkflowNodeTargets } from '$lib/restoreParameters';
+
+export type JourneyNodeType = "root" | "patch";
+
+export type JourneyNodeID = UUID;
+
+export interface JourneyNode {
+ id: JourneyNodeID,
+ type: JourneyNodeType,
+ children: JourneyPatchNode[]
+}
+
+export interface JourneyRootNode extends JourneyNode {
+ type: "root"
+
+ /*
+ * This contains all the values of the workflow to set
+ */
+ base: RestoreParamWorkflowNodeTargets
+}
+
+export interface JourneyPatchNode extends JourneyNode {
+ type: "patch"
+
+ parent: JourneyNode,
+
+ /*
+ * This contains only the subset of parameters that were changed from the
+ * parent
+ */
+ patch: RestoreParamWorkflowNodeTargets
+}
+
+export function resolvePatch(node: JourneyNode): RestoreParamWorkflowNodeTargets {
+ if (node.type === "root") {
+ return { ...(node as JourneyRootNode).base }
+ }
+
+ const patchNode = (node as JourneyPatchNode);
+ const patch = { ...patchNode.patch };
+ const base = resolvePatch(patchNode.parent);
+ for (const [k, v] of Object.entries(patch)) {
+ base[k] = v;
+ }
+ return base;
+}
+
+function diffParams(base: RestoreParamWorkflowNodeTargets, updated: RestoreParamWorkflowNodeTargets): RestoreParamWorkflowNodeTargets {
+ const result = {}
+
+ for (const [k, v] of Object.entries(updated)) {
+ if (!(k in base) || base[k].finalValue !== v) {
+ result[k] = v
+ }
+ }
+
+ return result;
+}
/*
* A "journey" is like browser history for prompts, except organized in a
@@ -9,27 +68,8 @@ import type { LGraphNode, NodeID } from '@litegraph-ts/core';
* jump between past and present sets of parameters.
*/
export type JourneyState = {
- /*
- * Selected drag items.
- * NOTE: Order is important, for node grouping actions.
- */
- currentJourney: DragItemID[],
-
- /*
- * Hovered drag items.
- */
- currentHovered: Set,
-
- /*
- * Selected LGraphNodes inside the litegraph canvas.
- * NOTE: Order is important, for node grouping actions.
- */
- currentJourneyNodes: LGraphNode[],
-
- /*
- * Currently hovered nodes.
- */
- currentHoveredNodes: Set
+ tree: JourneyNode,
+ nodesByID: Record
}
type JourneyStateOps = {
@@ -41,18 +81,10 @@ export type WritableJourneyStateStore = Writable & JourneyStateOps
function create() {
const store: Writable = writable(
{
- currentJourney: [],
- currentJourneyNodes: [],
- currentHovered: new Set(),
- currentHoveredNodes: new Set(),
})
function clear() {
store.set({
- currentJourney: [],
- currentJourneyNodes: [],
- currentHovered: new Set(),
- currentHoveredNodes: new Set(),
})
}