This commit is contained in:
space-nuko
2023-06-02 19:51:18 -05:00
parent c8a3276336
commit 59aad9183d
3 changed files with 127 additions and 64 deletions

View File

@@ -9,6 +9,7 @@
import workflowState from '$lib/stores/workflowState'; import workflowState from '$lib/stores/workflowState';
import type { WritableJourneyStateStore } from '$lib/stores/journeyStates'; import type { WritableJourneyStateStore } from '$lib/stores/journeyStates';
import JourneyRenderer from './JourneyRenderer.svelte'; import JourneyRenderer from './JourneyRenderer.svelte';
import { Plus } from "svelte-bootstrap-icons";
export let app: ComfyApp; export let app: ComfyApp;
@@ -21,11 +22,42 @@
<div class="journey-view"> <div class="journey-view">
<JourneyRenderer {workflow} {journey} /> <JourneyRenderer {workflow} {journey} />
<div class="bottom">
<button class="mode-button ternary"
title={"Add new"}
on:click={() => {}}>
<Plus width="100%" height="100%" />
</button>
</div>
</div> </div>
<style lang="scss"> <style lang="scss">
$button-height: 2.5rem;
.journey-view { .journey-view {
width: 100%; width: 100%;
height: calc(100% - $button-height);
}
.bottom {
height: $button-height;
display: flex;
flex-direction: row;
color: var(--comfy-accent-soft);
.mode-button {
height: 100%; height: 100%;
width: 100%;
padding: 0.5rem;
@include square-button;
&:hover {
color: var(--body-text-color);
}
&.selected {
background-color: var(--panel-background-fill);
}
}
} }
</style> </style>

View File

@@ -29,10 +29,10 @@ export interface RestoreParamSource<T extends RestoreParamType = any> {
*/ */
export interface RestoreParamSourceWorkflowNode extends RestoreParamSource<"workflow"> { export interface RestoreParamSourceWorkflowNode extends RestoreParamSource<"workflow"> {
type: "workflow", type: "workflow",
sourceNode: SerializedComfyWidgetNode
} }
export type RestoreParamWorkflowNodeTargets = Record<NodeID, RestoreParamSourceWorkflowNode>
/* /*
* A value received by the ComfyUI *backend* that corresponds to a value that * 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 * 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. * 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<T, K extends keyof T> extends RestoreParamSource<"stdPrompt"> { export interface RestoreParamSourceStdPrompt<T, K extends keyof T> extends RestoreParamSource<"stdPrompt"> {
type: "stdPrompt", type: "stdPrompt",
@@ -102,19 +105,7 @@ export interface RestoreParamSourceStdPrompt<T, K extends keyof T> extends Resto
finalValue: any finalValue: any
} }
export type RestoreParamTarget = { export type RestoreParamTargets = Record<NodeID, RestoreParamSource[]>
/*
* 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<NodeID, RestoreParamTarget>
function isSerializedComfyWidgetNode(param: any): param is SerializedComfyWidgetNode { function isSerializedComfyWidgetNode(param: any): param is SerializedComfyWidgetNode {
return param != null && typeof param === "object" && "id" in param && "comfyValue" in param 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) => { const addSource = (result: RestoreParamTargets, targetNode: ComfyWidgetNode, source: RestoreParamSource) => {
result[targetNode.id] ||= { targetNode, sources: [] } result[targetNode.id] ||= []
result[targetNode.id].sources.push(source); result[targetNode.id].push(source);
} }
const mergeSources = (a: RestoreParamTargets, b: RestoreParamTargets) => { export function concatRestoreParams(a: RestoreParamTargets, b: Record<NodeID, RestoreParamSource>): RestoreParamTargets {
for (const [k, vs] of Object.entries(b)) { for (const [targetNodeID, source] of Object.entries(b)) {
a[vs.targetNode.id] ||= { targetNode: vs.targetNode, sources: [] } a[targetNodeID] ||= []
for (const source of vs.sources) { a[targetNodeID].push(source);
a[vs.targetNode.id].sources.push(source);
}
} }
return a;
} }
export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets { 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): RestoreParamWorkflowNodeTargets {
const result = {} const result = {}
const graph = workflow.graph; 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) { for (const serNode of prompt.workflow.nodes) {
const foundNode = graph.getNodeByIdRecursive(serNode.id); const foundNode = graph.getNodeByIdRecursive(serNode.id);
if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) { if (isComfyWidgetNode(foundNode) && foundNode.type === serNode.type) {
@@ -172,9 +174,8 @@ export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: Ser
const source: RestoreParamSourceWorkflowNode = { const source: RestoreParamSourceWorkflowNode = {
type: "workflow", type: "workflow",
finalValue, finalValue,
sourceNode: serNode
} }
addSource(result, foundNode, source) result[foundNode.id] = source;
} }
} }
} }
@@ -182,11 +183,14 @@ export function getWorkflowRestoreParams(workflow: ComfyBoxWorkflow, prompt: Ser
return result return result
} }
export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets { export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): Record<NodeID, RestoreParamSourceBackendNodeInput[]> {
const result = {} const result = {}
const graph = workflow.graph; 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)) { for (const [serNodeID, inputs] of Object.entries(prompt.output)) {
const serNode = prompt.workflow.nodes.find(sn => sn.id === serNodeID) const serNode = prompt.workflow.nodes.find(sn => sn.id === serNodeID)
if (serNode == null) if (serNode == null)
@@ -223,16 +227,11 @@ export function getBackendRestoreParams(workflow: ComfyBoxWorkflow, prompt: Seri
export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets { export default function restoreParameters(workflow: ComfyBoxWorkflow, prompt: SerializedPrompt): RestoreParamTargets {
const result = {} 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); 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); const backendParams = getBackendRestoreParams(workflow, prompt);
mergeSources(result, backendParams); concatRestoreParams2(result, backendParams);
// Step 3: Extract the standard prompt from the workflow and use that to // Step 3: Extract the standard prompt from the workflow and use that to
// infer parameter types // infer parameter types

View File

@@ -1,7 +1,66 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store'; import type { Readable, Writable } from 'svelte/store';
import type { DragItemID, IDragItem } from './layoutStates'; 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 * 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. * jump between past and present sets of parameters.
*/ */
export type JourneyState = { export type JourneyState = {
/* tree: JourneyNode,
* Selected drag items. nodesByID: Record<JourneyNodeID, JourneyNode>
* NOTE: Order is important, for node grouping actions.
*/
currentJourney: DragItemID[],
/*
* Hovered drag items.
*/
currentHovered: Set<DragItemID>,
/*
* Selected LGraphNodes inside the litegraph canvas.
* NOTE: Order is important, for node grouping actions.
*/
currentJourneyNodes: LGraphNode[],
/*
* Currently hovered nodes.
*/
currentHoveredNodes: Set<NodeID>
} }
type JourneyStateOps = { type JourneyStateOps = {
@@ -41,18 +81,10 @@ export type WritableJourneyStateStore = Writable<JourneyState> & JourneyStateOps
function create() { function create() {
const store: Writable<JourneyState> = writable( const store: Writable<JourneyState> = writable(
{ {
currentJourney: [],
currentJourneyNodes: [],
currentHovered: new Set(),
currentHoveredNodes: new Set(),
}) })
function clear() { function clear() {
store.set({ store.set({
currentJourney: [],
currentJourneyNodes: [],
currentHovered: new Set(),
currentHoveredNodes: new Set(),
}) })
} }