Prompt travel
This commit is contained in:
@@ -40,6 +40,7 @@ import { deserializeTemplateFromSVG, type SerializedComfyBoxTemplate } from "$li
|
|||||||
import templateState from "$lib/stores/templateState";
|
import templateState from "$lib/stores/templateState";
|
||||||
import { formatValidationError, type ComfyAPIPromptErrorResponse, formatExecutionError, type ComfyExecutionError } from "$lib/apiErrors";
|
import { formatValidationError, type ComfyAPIPromptErrorResponse, formatExecutionError, type ComfyExecutionError } from "$lib/apiErrors";
|
||||||
import systemState from "$lib/stores/systemState";
|
import systemState from "$lib/stores/systemState";
|
||||||
|
import type { JourneyNode } from "$lib/stores/journeyStates";
|
||||||
|
|
||||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||||
|
|
||||||
@@ -610,6 +611,7 @@ export default class ComfyApp {
|
|||||||
if (node?.onExecuted) {
|
if (node?.onExecuted) {
|
||||||
node.onExecuted(output);
|
node.onExecuted(output);
|
||||||
}
|
}
|
||||||
|
workflow.journey.onExecuted(promptID, nodeID, output, queueEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1028,6 +1030,15 @@ export default class ComfyApp {
|
|||||||
notify("Prompt queued.", { type: "info", showOn: "web" });
|
notify("Prompt queued.", { type: "info", showOn: "web" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let journeyNode: JourneyNode | null;
|
||||||
|
|
||||||
|
if (get(uiState).autoPushJourney) {
|
||||||
|
const activeNode = targetWorkflow.journey.getActiveNode();
|
||||||
|
if (activeNode != null) {
|
||||||
|
journeyNode = targetWorkflow.journey.pushPatchOntoActive(targetWorkflow, activeNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.processingQueue = true;
|
this.processingQueue = true;
|
||||||
let workflow: ComfyBoxWorkflow;
|
let workflow: ComfyBoxWorkflow;
|
||||||
|
|
||||||
@@ -1097,6 +1108,9 @@ export default class ComfyApp {
|
|||||||
else {
|
else {
|
||||||
queueState.afterQueued(workflow.id, response.promptID, response.number, p.output, extraData)
|
queueState.afterQueued(workflow.id, response.promptID, response.number, p.output, extraData)
|
||||||
workflowState.afterQueued(workflow.id, response.promptID)
|
workflowState.afterQueued(workflow.id, response.promptID)
|
||||||
|
if (journeyNode != null) {
|
||||||
|
journeyNode.promptID = response.promptID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMes = err?.toString();
|
errorMes = err?.toString();
|
||||||
|
|||||||
@@ -7,12 +7,14 @@
|
|||||||
import type ComfyApp from './ComfyApp';
|
import type ComfyApp from './ComfyApp';
|
||||||
import type { ComfyBoxWorkflow } from '$lib/stores/workflowState';
|
import type { ComfyBoxWorkflow } from '$lib/stores/workflowState';
|
||||||
import workflowState from '$lib/stores/workflowState';
|
import workflowState from '$lib/stores/workflowState';
|
||||||
import { calculateWorkflowParamsPatch, resolvePatch, type JourneyPatchNode, type WritableJourneyStateStore } from '$lib/stores/journeyStates';
|
import uiState from '$lib/stores/uiState';
|
||||||
|
import { calculateWorkflowParamsPatch, resolvePatch, type JourneyPatchNode, type WritableJourneyStateStore, diffParams } from '$lib/stores/journeyStates';
|
||||||
import JourneyRenderer from './JourneyRenderer.svelte';
|
import JourneyRenderer from './JourneyRenderer.svelte';
|
||||||
import { Plus } from "svelte-bootstrap-icons";
|
import { Plus } from "svelte-bootstrap-icons";
|
||||||
import { getWorkflowRestoreParams, getWorkflowRestoreParamsFromWorkflow } from '$lib/restoreParameters';
|
import { getWorkflowRestoreParams, getWorkflowRestoreParamsFromWorkflow } from '$lib/restoreParameters';
|
||||||
import notify from '$lib/notify';
|
import notify from '$lib/notify';
|
||||||
import selectionState from '$lib/stores/selectionState';
|
import selectionState from '$lib/stores/selectionState';
|
||||||
|
import { Checkbox } from '@gradio/form';
|
||||||
|
|
||||||
export let app: ComfyApp;
|
export let app: ComfyApp;
|
||||||
|
|
||||||
@@ -30,32 +32,7 @@
|
|||||||
|
|
||||||
const workflowParams = getWorkflowRestoreParamsFromWorkflow(workflow)
|
const workflowParams = getWorkflowRestoreParamsFromWorkflow(workflow)
|
||||||
const activeNode = journey.getActiveNode();
|
const activeNode = journey.getActiveNode();
|
||||||
|
journey.pushPatchOntoActive(workflow, activeNode, true)
|
||||||
let journeyNode
|
|
||||||
|
|
||||||
if (activeNode == null) {
|
|
||||||
// add root node
|
|
||||||
if ($journey.root != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
journeyNode = journey.addNode(workflowParams, null);
|
|
||||||
notify("Pushed a new base workflow state.", { type: "info" })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// add patch node
|
|
||||||
const patch = calculateWorkflowParamsPatch(activeNode, workflowParams);
|
|
||||||
const patchedCount = Object.keys(patch).length;
|
|
||||||
if (patchedCount === 0) {
|
|
||||||
notify("No changes were made to active parameters yet.", { type: "warning" })
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
journeyNode = journey.addNode(patch, activeNode);
|
|
||||||
notify(`Pushed new state with ${patchedCount} changes.`, { type: "info" })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (journeyNode != null) {
|
|
||||||
journey.selectNode(journeyNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelectNode(e: CustomEvent<{ cyto: cytoscape.Core, node: cytoscape.NodeSingular }>) {
|
function onSelectNode(e: CustomEvent<{ cyto: cytoscape.Core, node: cytoscape.NodeSingular }>) {
|
||||||
@@ -85,12 +62,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (journeyNode.type === "patch") {
|
const patch = resolvePatch(journeyNode);
|
||||||
$selectionState.currentPatchHoveredNodes = new Set(Object.keys((journeyNode as JourneyPatchNode).patch))
|
const workflowParams = getWorkflowRestoreParamsFromWorkflow(workflow);
|
||||||
}
|
const diff = diffParams(patch, workflowParams);
|
||||||
else {
|
|
||||||
$selectionState.currentPatchHoveredNodes = new Set();
|
$selectionState.currentPatchHoveredNodes = new Set(Object.keys(diff));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHoverNodeOut(e: CustomEvent<{ cyto: cytoscape.Core, node: cytoscape.NodeSingular }>) {
|
function onHoverNodeOut(e: CustomEvent<{ cyto: cytoscape.Core, node: cytoscape.NodeSingular }>) {
|
||||||
@@ -104,6 +80,9 @@
|
|||||||
on:hover_node={onHoverNode}
|
on:hover_node={onHoverNode}
|
||||||
on:hover_node_out={onHoverNodeOut}
|
on:hover_node_out={onHoverNodeOut}
|
||||||
/>
|
/>
|
||||||
|
<div class="bottom" style:border-top="1px solid var(--panel-border-color)">
|
||||||
|
<Checkbox label="Auto-Push" disabled={$journey.root == null} bind:value={$uiState.autoPushJourney}/>
|
||||||
|
</div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<button class="mode-button ternary"
|
<button class="mode-button ternary"
|
||||||
title={"Add new"}
|
title={"Add new"}
|
||||||
@@ -119,7 +98,7 @@
|
|||||||
|
|
||||||
.journey-view {
|
.journey-view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - $button-height);
|
height: calc(100% - $button-height * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
@@ -127,6 +106,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
color: var(--comfy-accent-soft);
|
color: var(--comfy-accent-soft);
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
.mode-button {
|
.mode-button {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import type { NodeDataDefinition, EdgeDataDefinition } from 'cytoscape';
|
import type { NodeDataDefinition, EdgeDataDefinition } from 'cytoscape';
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
import selectionState from '$lib/stores/selectionState';
|
import selectionState from '$lib/stores/selectionState';
|
||||||
|
import uiQueueState from '$lib/stores/uiQueueState';
|
||||||
|
|
||||||
export let workflow: ComfyBoxWorkflow | null = null
|
export let workflow: ComfyBoxWorkflow | null = null
|
||||||
export let journey: WritableJourneyStateStore | null = null
|
export let journey: WritableJourneyStateStore | null = null
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
const patchNode = node as JourneyPatchNode;
|
const patchNode = node as JourneyPatchNode;
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: patchNode.id,
|
id: patchNode.id,
|
||||||
label: "N",
|
label: "P",
|
||||||
selected: node.id === journeyState.activeNodeID,
|
selected: node.id === journeyState.activeNodeID,
|
||||||
locked: true
|
locked: true
|
||||||
|
|
||||||
@@ -101,9 +102,22 @@
|
|||||||
const { cyto } = e.detail;
|
const { cyto } = e.detail;
|
||||||
|
|
||||||
for (const node of cyto.nodes().components()) {
|
for (const node of cyto.nodes().components()) {
|
||||||
if (node.id() === lastSelected) {
|
const nodeID = node.id()
|
||||||
|
if (nodeID === lastSelected) {
|
||||||
// why doesn't passing `selected` work in the ctor?
|
// why doesn't passing `selected` work in the ctor?
|
||||||
node.select();
|
node.select();
|
||||||
|
cyto.zoom(1.25);
|
||||||
|
cyto.center(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
const journeyNode = $journey.nodesByID[nodeID]
|
||||||
|
if (journeyNode) {
|
||||||
|
if (journeyNode.promptID != null) {
|
||||||
|
const queueEntry = $uiQueueState.historyUIEntries.find(e => e.entry.promptID === journeyNode.promptID)
|
||||||
|
if (queueEntry != null && queueEntry.images) {
|
||||||
|
node.data("bgImage", queueEntry.images[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +128,6 @@
|
|||||||
.on("select", onNodeSelected)
|
.on("select", onNodeSelected)
|
||||||
.on("mouseover", onNodeHovered)
|
.on("mouseover", onNodeHovered)
|
||||||
.on("mouseout", onNodeHoveredOut)
|
.on("mouseout", onNodeHoveredOut)
|
||||||
|
|
||||||
const nodes = Array.from(journey.iterateBreadthFirst());
|
|
||||||
if (nodes.length > 0) {
|
|
||||||
const lastNode = nodes[nodes.length - 1]
|
|
||||||
const start = cyto.$(`#${lastNode.id}`)
|
|
||||||
cyto.center(start)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
container: refElement,
|
container: refElement,
|
||||||
style: GraphStyles,
|
style: GraphStyles,
|
||||||
wheelSensitivity: 0.1,
|
wheelSensitivity: 0.1,
|
||||||
maxZoom: 1,
|
maxZoom: 3,
|
||||||
minZoom: 0.5,
|
minZoom: 0.5,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ const styles: Stylesheet[] = [
|
|||||||
{
|
{
|
||||||
selector: "node",
|
selector: "node",
|
||||||
style: {
|
style: {
|
||||||
"width": "50",
|
"width": "100",
|
||||||
"height": "50",
|
"height": "100",
|
||||||
|
"shape": "round-rectangle",
|
||||||
"font-family": "Arial",
|
"font-family": "Arial",
|
||||||
"font-size": "18",
|
"font-size": "18",
|
||||||
"font-weight": "normal",
|
"font-weight": "normal",
|
||||||
@@ -33,6 +34,16 @@ const styles: Stylesheet[] = [
|
|||||||
"color": "#1d3660"
|
"color": "#1d3660"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: "node[bgImage]",
|
||||||
|
style: {
|
||||||
|
"label": "",
|
||||||
|
"background-image": "data(bgImage)",
|
||||||
|
"background-image-containment": "over",
|
||||||
|
"background-fit": "cover",
|
||||||
|
"color": "transparent"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
selector: "node:selected",
|
selector: "node:selected",
|
||||||
style: {
|
style: {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default class ComfyButtonNode extends ComfyWidgetNode<boolean> {
|
|||||||
override properties: ComfyButtonProperties = {
|
override properties: ComfyButtonProperties = {
|
||||||
tags: [],
|
tags: [],
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
|
excludeFromJourney: true,
|
||||||
param: "bang"
|
param: "bang"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default class ComfyCheckboxNode extends ComfyWidgetNode<boolean> {
|
|||||||
override properties: ComfyCheckboxProperties = {
|
override properties: ComfyCheckboxProperties = {
|
||||||
tags: [],
|
tags: [],
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
|
excludeFromJourney: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export default class ComfyComboNode extends ComfyWidgetNode<string> {
|
|||||||
tags: [],
|
tags: [],
|
||||||
defaultValue: "A",
|
defaultValue: "A",
|
||||||
values: ["A", "B", "C", "D"],
|
values: ["A", "B", "C", "D"],
|
||||||
convertValueToLabelCode: ""
|
convertValueToLabelCode: "",
|
||||||
|
excludeFromJourney: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export default class ComfyGalleryNode extends ComfyWidgetNode<ComfyBoxImageMetad
|
|||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
index: 0,
|
index: 0,
|
||||||
updateMode: "replace",
|
updateMode: "replace",
|
||||||
autoSelectOnUpdate: true
|
autoSelectOnUpdate: true,
|
||||||
|
excludeFromJourney: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
@@ -130,8 +131,6 @@ export default class ComfyGalleryNode extends ComfyWidgetNode<ComfyBoxImageMetad
|
|||||||
|
|
||||||
const meta = parseWhateverIntoImageMetadata(param) || [];
|
const meta = parseWhateverIntoImageMetadata(param) || [];
|
||||||
|
|
||||||
console.debug("[ComfyGalleryNode] Received output!", param)
|
|
||||||
|
|
||||||
if (updateMode === "append") {
|
if (updateMode === "append") {
|
||||||
const currentValue = get(this.value)
|
const currentValue = get(this.value)
|
||||||
if (meta.length > 0 && (selectedIndex != null || this.properties.autoSelectOnUpdate)) {
|
if (meta.length > 0 && (selectedIndex != null || this.properties.autoSelectOnUpdate)) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
|
|||||||
import type { ComfyWidgetProperties } from "./ComfyWidgetNode";
|
import type { ComfyWidgetProperties } from "./ComfyWidgetNode";
|
||||||
import ComfyWidgetNode from "./ComfyWidgetNode";
|
import ComfyWidgetNode from "./ComfyWidgetNode";
|
||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import { type LineGroup } from "$lib/components/MaskCanvas.svelte"
|
|
||||||
|
|
||||||
export interface ComfyImageUploadNodeProperties extends ComfyWidgetProperties {
|
export interface ComfyImageUploadNodeProperties extends ComfyWidgetProperties {
|
||||||
maskCount: number
|
maskCount: number
|
||||||
@@ -16,7 +15,8 @@ export default class ComfyImageUploadNode extends ComfyWidgetNode<ComfyBoxImageM
|
|||||||
properties: ComfyImageUploadNodeProperties = {
|
properties: ComfyImageUploadNodeProperties = {
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
maskCount: 0
|
maskCount: 0,
|
||||||
|
excludeFromJourney: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default class ComfyMarkdownNode extends ComfyWidgetNode<string> {
|
|||||||
override properties: ComfyMarkdownProperties = {
|
override properties: ComfyMarkdownProperties = {
|
||||||
tags: [],
|
tags: [],
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
|
excludeFromJourney: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export default class ComfyMultiRegionNode extends ComfyWidgetNode<BoundingBox[]>
|
|||||||
canvasWidth: 512,
|
canvasWidth: 512,
|
||||||
canvasHeight: 512,
|
canvasHeight: 512,
|
||||||
canvasImageURL: null,
|
canvasImageURL: null,
|
||||||
inputType: "size"
|
inputType: "size",
|
||||||
|
excludeFromJourney: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export default class ComfyNumberNode extends ComfyWidgetNode<number> {
|
|||||||
min: 0,
|
min: 0,
|
||||||
max: 10,
|
max: 10,
|
||||||
step: 1,
|
step: 1,
|
||||||
precision: 1
|
precision: 1,
|
||||||
|
excludeFromJourney: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
override svelteComponentType = NumberWidget
|
override svelteComponentType = NumberWidget
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default class ComfyRadioNode extends ComfyWidgetNode<string> {
|
|||||||
tags: [],
|
tags: [],
|
||||||
choices: ["Choice A", "Choice B", "Choice C"],
|
choices: ["Choice A", "Choice B", "Choice C"],
|
||||||
defaultValue: "Choice A",
|
defaultValue: "Choice A",
|
||||||
|
excludeFromJourney: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default class ComfyTextNode extends ComfyWidgetNode<string> {
|
|||||||
multiline: false,
|
multiline: false,
|
||||||
lines: 5,
|
lines: 5,
|
||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
|
excludeFromJourney: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
static slotLayout: SlotLayout = {
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ export type SerializedComfyWidgetNode = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ComfyWidgetProperties extends ComfyGraphNodeProperties {
|
export interface ComfyWidgetProperties extends ComfyGraphNodeProperties {
|
||||||
defaultValue: any
|
defaultValue: any,
|
||||||
|
excludeFromJourney: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShownOutputProperty = {
|
export type ShownOutputProperty = {
|
||||||
|
|||||||
@@ -162,13 +162,16 @@ export function concatRestoreParams2(a: RestoreParamTargets, b: RestoreParamTarg
|
|||||||
/*
|
/*
|
||||||
* Like getWorkflowRestoreParams but applies to an instanced (non-serialized) workflow
|
* Like getWorkflowRestoreParams but applies to an instanced (non-serialized) workflow
|
||||||
*/
|
*/
|
||||||
export function getWorkflowRestoreParamsFromWorkflow(workflow: ComfyBoxWorkflow): RestoreParamWorkflowNodeTargets {
|
export function getWorkflowRestoreParamsFromWorkflow(workflow: ComfyBoxWorkflow, noExclude: boolean = false): RestoreParamWorkflowNodeTargets {
|
||||||
const result = {}
|
const result = {}
|
||||||
|
|
||||||
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||||
if (!isComfyWidgetNode(node))
|
if (!isComfyWidgetNode(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!noExclude && node.properties.excludeFromJourney)
|
||||||
|
continue;
|
||||||
|
|
||||||
const finalValue = node.getValue();
|
const finalValue = node.getValue();
|
||||||
if (finalValue != null) {
|
if (finalValue != null) {
|
||||||
const source: RestoreParamSourceWorkflowNode = {
|
const source: RestoreParamSourceWorkflowNode = {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
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 { DragItemID, IDragItem } from './layoutStates';
|
import { isComfyWidgetNode, type DragItemID, type IDragItem } from './layoutStates';
|
||||||
import { LiteGraph, type LGraphNode, type NodeID, type UUID } from '@litegraph-ts/core';
|
import { LiteGraph, type LGraphNode, type NodeID, type UUID } from '@litegraph-ts/core';
|
||||||
import type { SerializedAppState } from '$lib/components/ComfyApp';
|
import type { SerializedAppState } from '$lib/components/ComfyApp';
|
||||||
import type { RestoreParamTargets, RestoreParamWorkflowNodeTargets } from '$lib/restoreParameters';
|
import { getWorkflowRestoreParamsFromWorkflow, type RestoreParamSourceWorkflowNode, type RestoreParamTargets, type RestoreParamWorkflowNodeTargets } from '$lib/restoreParameters';
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import deepEqual from "deep-equal";
|
import deepEqual from "deep-equal";
|
||||||
|
import notify from '$lib/notify';
|
||||||
|
import type { ComfyBoxWorkflow } from './workflowState';
|
||||||
|
import type { ComfyNodeID, PromptID } from '$lib/api';
|
||||||
|
import type { SerializedPromptOutput } from '$lib/utils';
|
||||||
|
import type { QueueEntry } from './queueState';
|
||||||
|
|
||||||
export type JourneyNodeType = "root" | "patch";
|
export type JourneyNodeType = "root" | "patch";
|
||||||
|
|
||||||
@@ -14,7 +19,9 @@ export type JourneyNodeID = UUID;
|
|||||||
export interface JourneyNode {
|
export interface JourneyNode {
|
||||||
id: JourneyNodeID,
|
id: JourneyNodeID,
|
||||||
type: JourneyNodeType,
|
type: JourneyNodeType,
|
||||||
children: JourneyPatchNode[]
|
children: JourneyPatchNode[],
|
||||||
|
promptID?: PromptID,
|
||||||
|
images?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JourneyRootNode extends JourneyNode {
|
export interface JourneyRootNode extends JourneyNode {
|
||||||
@@ -52,7 +59,7 @@ export function resolvePatch(node: JourneyNode): RestoreParamWorkflowNodeTargets
|
|||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
function diffParams(base: RestoreParamWorkflowNodeTargets, updated: RestoreParamWorkflowNodeTargets): RestoreParamWorkflowNodeTargets {
|
export function diffParams(base: RestoreParamWorkflowNodeTargets, updated: RestoreParamWorkflowNodeTargets): RestoreParamWorkflowNodeTargets {
|
||||||
const result = {}
|
const result = {}
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(updated)) {
|
for (const [k, v] of Object.entries(updated)) {
|
||||||
@@ -89,9 +96,11 @@ export type JourneyState = {
|
|||||||
type JourneyStateOps = {
|
type JourneyStateOps = {
|
||||||
clear: () => void,
|
clear: () => void,
|
||||||
getActiveNode: () => JourneyNode | null,
|
getActiveNode: () => JourneyNode | null,
|
||||||
addNode: (params: RestoreParamWorkflowNodeTargets, parent?: JourneyNodeID | JourneyNode) => JourneyNode,
|
// addNode: (params: RestoreParamWorkflowNodeTargets, parent?: JourneyNodeID | JourneyNode) => JourneyNode,
|
||||||
selectNode: (id?: JourneyNodeID | JourneyNode) => void,
|
selectNode: (id?: JourneyNodeID | JourneyNode) => void,
|
||||||
iterateBreadthFirst: (id?: JourneyNodeID | null) => Iterable<JourneyNode>
|
iterateBreadthFirst: (id?: JourneyNodeID | null) => Iterable<JourneyNode>,
|
||||||
|
pushPatchOntoActive: (workflow: ComfyBoxWorkflow, activeNode?: JourneyNode, showNotification?: boolean) => JourneyNode | null
|
||||||
|
onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput, queueEntry: QueueEntry) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WritableJourneyStateStore = Writable<JourneyState> & JourneyStateOps;
|
export type WritableJourneyStateStore = Writable<JourneyState> & JourneyStateOps;
|
||||||
@@ -131,6 +140,7 @@ function create() {
|
|||||||
*/
|
*/
|
||||||
function addNode(params: RestoreParamWorkflowNodeTargets, parent?: JourneyNodeID | JourneyNode): JourneyNode {
|
function addNode(params: RestoreParamWorkflowNodeTargets, parent?: JourneyNodeID | JourneyNode): JourneyNode {
|
||||||
let _node: JourneyRootNode | JourneyPatchNode;
|
let _node: JourneyRootNode | JourneyPatchNode;
|
||||||
|
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
let parentNode: JourneyNode | null = null
|
let parentNode: JourneyNode | null = null
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
@@ -167,6 +177,41 @@ function create() {
|
|||||||
return _node;
|
return _node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pushPatchOntoActive(workflow: ComfyBoxWorkflow, activeNode?: JourneyNode, showNotification: boolean = false): JourneyNode | null {
|
||||||
|
const workflowParams = getWorkflowRestoreParamsFromWorkflow(workflow)
|
||||||
|
|
||||||
|
let journeyNode
|
||||||
|
|
||||||
|
if (activeNode == null) {
|
||||||
|
// add root node
|
||||||
|
if (get(store).root != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
journeyNode = addNode(workflowParams, null);
|
||||||
|
if (showNotification)
|
||||||
|
notify("Pushed a new base workflow state.", { type: "info" })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// add patch node
|
||||||
|
const patch = calculateWorkflowParamsPatch(activeNode, workflowParams);
|
||||||
|
const patchedCount = Object.keys(patch).length;
|
||||||
|
if (patchedCount === 0) {
|
||||||
|
if (showNotification)
|
||||||
|
notify("No changes were made to active parameters yet.", { type: "warning" })
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
journeyNode = addNode(patch, activeNode);
|
||||||
|
if (showNotification)
|
||||||
|
notify(`Pushed new state with ${patchedCount} changes.`, { type: "info" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (journeyNode != null) {
|
||||||
|
selectNode(journeyNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return journeyNode;
|
||||||
|
}
|
||||||
|
|
||||||
function selectNode(obj?: JourneyNodeID | JourneyNode) {
|
function selectNode(obj?: JourneyNodeID | JourneyNode) {
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
if (typeof obj === "string")
|
if (typeof obj === "string")
|
||||||
@@ -217,13 +262,23 @@ function create() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onExecuted(promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput, queueEntry: QueueEntry) {
|
||||||
|
const journeyNode = Array.from(iterateBreadthFirst()).find(j => j.promptID === promptID);
|
||||||
|
if (journeyNode === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...store,
|
...store,
|
||||||
getActiveNode,
|
getActiveNode,
|
||||||
clear,
|
clear,
|
||||||
addNode,
|
// addNode,
|
||||||
|
pushPatchOntoActive,
|
||||||
selectNode,
|
selectNode,
|
||||||
iterateBreadthFirst
|
iterateBreadthFirst,
|
||||||
|
onExecuted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -512,6 +512,14 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
serialize: serializeStringArray,
|
serialize: serializeStringArray,
|
||||||
deserialize: deserializeStringArray
|
deserialize: deserializeStringArray
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "excludeFromJourney",
|
||||||
|
type: "boolean",
|
||||||
|
location: "nodeProps",
|
||||||
|
editable: true,
|
||||||
|
defaultValue: false,
|
||||||
|
canShow: isComfyWidgetNode
|
||||||
|
},
|
||||||
|
|
||||||
// Container tags are contained in the widget attributes
|
// Container tags are contained in the widget attributes
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export type UIState = {
|
|||||||
forceSaveUserState: boolean | null,
|
forceSaveUserState: boolean | null,
|
||||||
|
|
||||||
activeError: PromptID | null
|
activeError: PromptID | null
|
||||||
|
|
||||||
|
autoPushJourney: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type UIStateOps = {
|
type UIStateOps = {
|
||||||
@@ -34,7 +36,9 @@ const store: Writable<UIState> = writable(
|
|||||||
reconnecting: false,
|
reconnecting: false,
|
||||||
forceSaveUserState: null,
|
forceSaveUserState: null,
|
||||||
|
|
||||||
activeError: null
|
activeError: null,
|
||||||
|
|
||||||
|
autoPushJourney: true
|
||||||
})
|
})
|
||||||
|
|
||||||
function reconnecting() {
|
function reconnecting() {
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ export class ComfyBoxWorkflow {
|
|||||||
console.error("[applyParamsPatch] Node was not ComfyWidgetNode!!", nodeId, source)
|
console.error("[applyParamsPatch] Node was not ComfyWidgetNode!!", nodeId, source)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
node.setValue(source.finalValue);
|
node.value.set(source.finalValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,4 +68,32 @@ export default class journeyStateTests extends UnitTest {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test__patches_exclusions() {
|
||||||
|
const [workflow, layoutState] = ComfyBoxWorkflow.create()
|
||||||
|
const { graph, journey } = workflow;
|
||||||
|
layoutState.initDefaultLayout() // adds 3 containers
|
||||||
|
|
||||||
|
const widget1 = LiteGraph.createNode(ComfyNumberNode);
|
||||||
|
const widget2 = LiteGraph.createNode(ComfyNumberNode);
|
||||||
|
const watch1 = LiteGraph.createNode(Watch);
|
||||||
|
const watch2 = LiteGraph.createNode(Watch);
|
||||||
|
|
||||||
|
graph.add(widget1)
|
||||||
|
graph.add(watch1)
|
||||||
|
|
||||||
|
widget1.properties.excludeFromJourney = true;
|
||||||
|
widget1.connect(0, watch1, 0);
|
||||||
|
widget1.setValue(0)
|
||||||
|
|
||||||
|
let workflowParams = getWorkflowRestoreParamsFromWorkflow(workflow)
|
||||||
|
const root = journey.addNode(workflowParams, null);
|
||||||
|
|
||||||
|
expect(root).toEqual({
|
||||||
|
id: root.id,
|
||||||
|
type: "root",
|
||||||
|
children: [],
|
||||||
|
base: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user