diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 008966d..323660f 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -1113,7 +1113,7 @@ export default class ComfyApp { queueState.afterQueued(workflow.id, response.promptID, response.number, p.output, extraData) workflowState.afterQueued(workflow.id, response.promptID) if (journeyNode != null) { - journeyNode.promptID = response.promptID; + targetWorkflow.journey.afterQueued(journeyNode, response.promptID); } } } catch (err) { diff --git a/src/lib/components/ComfyJourneyView.svelte b/src/lib/components/ComfyJourneyView.svelte index e4492eb..8004451 100644 --- a/src/lib/components/ComfyJourneyView.svelte +++ b/src/lib/components/ComfyJourneyView.svelte @@ -20,7 +20,7 @@ import selectionState from '$lib/stores/selectionState'; import { Checkbox } from '@gradio/form'; import modalState from '$lib/stores/modalState'; - import queueState from '$lib/stores/queueState'; + import queueState, { type QueueEntry } from '$lib/stores/queueState'; import PromptDisplay from "$lib/components/PromptDisplay.svelte" import { getQueueEntryImages } from '$lib/stores/uiQueueState'; import { SvelteComponent } from 'svelte'; @@ -91,23 +91,25 @@ return; } - const promptID = journeyNode.promptID; - if (promptID != null) { - const queueEntry = queueState.getQueueEntry(journeyNode.promptID) - if (queueEntry?.prompt != null) { - modalState.pushModal({ - title: "Prompt Details", - svelteComponent: PromptDisplay, - svelteProps: { - prompt: queueEntry.prompt, - workflow: queueEntry.extraData?.extra_pnginfo?.comfyBoxWorkflow, - images: getQueueEntryImages(queueEntry), - closeModal: () => modalState.closeAllModals(), - expandAll: false, - app - }, - }) - } + // pick first resolved prompt + const queueEntry: QueueEntry | null = + Array.from(journeyNode.promptIDs) + .map(id => queueState.getQueueEntry(id)) + .find(qe => qe?.prompt != null); + + if (queueEntry) { + modalState.pushModal({ + title: "Prompt Details", + svelteComponent: PromptDisplay, + svelteProps: { + prompt: queueEntry.prompt, + workflow: queueEntry.extraData?.extra_pnginfo?.comfyBoxWorkflow, + images: getQueueEntryImages(queueEntry), + closeModal: () => modalState.closeAllModals(), + expandAll: false, + app + }, + }) } else { notify("This journey entry has no prompts yet.", { type: "warning" }) diff --git a/src/lib/components/JourneyRenderer.svelte b/src/lib/components/JourneyRenderer.svelte index a6f440d..a8c7fa9 100644 --- a/src/lib/components/JourneyRenderer.svelte +++ b/src/lib/components/JourneyRenderer.svelte @@ -50,23 +50,31 @@ return a[1].name > b[1].name ? 1 : -1 }) - for (const [nodeID, source] of sorted) { + const MAX_ENTRIES = 5 + const entries = sorted.slice(0, MAX_ENTRIES) + const leftover = sorted.length - MAX_ENTRIES + + for (const [nodeID, source] of entries) { let line = "" switch (source.nodeType) { case "ui/text": - line = `${source.name} (changed)` + line = `${source.name}: (changed)` break; default: const prevValue = prev[nodeID]; let prevValueStr = "???" if (prevValue) prevValueStr = prevValue.finalValue - line = `${source.name}: ${prevValueStr} -> ${source.finalValue}` + line = `${source.name}: ${prevValueStr} → ${source.finalValue}` break; } lines.push(line) } + if (leftover > 0) { + lines.push(`(+ ${leftover} more)`) + } + return lines.join("\n") } @@ -128,8 +136,6 @@ const patchText = makePatchText(patchNode.patch, prev); const patchNodeHeight = countNewLines(patchText) * 11 + 22; - console.debug("[JourneyRenderer] Patch text", prev, patchText); - nodes.push({ data: { id: midNodeID, @@ -224,8 +230,8 @@ const journeyNode = $journey.nodesByID[nodeID] if (journeyNode) { - if (journeyNode.promptID != null) { - const queueEntry = queueState.getQueueEntry(journeyNode.promptID) + if (journeyNode.promptIDs) { + const queueEntry = Array.from(journeyNode.promptIDs).map(id => queueState.getQueueEntry(id)).find(Boolean); if (queueEntry) { const outputs = getQueueEntryImages(queueEntry); diff --git a/src/lib/components/PromptDisplay.svelte b/src/lib/components/PromptDisplay.svelte index d435fbb..8d9ef13 100644 --- a/src/lib/components/PromptDisplay.svelte +++ b/src/lib/components/PromptDisplay.svelte @@ -8,7 +8,7 @@ import Gallery from "$lib/components/gradio/gallery/Gallery.svelte"; import { ImageViewer } from "$lib/ImageViewer"; import type { Styles } from "@gradio/utils"; - import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines } from "$lib/utils"; + import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines, isMultiline } from "$lib/utils"; import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte"; import RestoreParamsTable from "./modal/RestoreParamsTable.svelte"; import workflowState, { type ComfyBoxWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState"; @@ -41,6 +41,7 @@ // TODO other sources than serialized workflow if (workflow != null) { const workflowParams = getWorkflowRestoreParamsUsingLayout(workflow.workflow, workflow.layout) + console.error("GETPARMS", workflowParams) restoreParams = concatRestoreParams(restoreParams, workflowParams); } @@ -100,10 +101,6 @@ && typeof input[1] === "number" } - function isMultiline(input: any): boolean { - return typeof input === "string" && (input.length > splitLength || countNewLines(input) > 1); - } - function formatInput(input: any): string { if (typeof input === "string") return input diff --git a/src/lib/components/graph/GraphStyles.ts b/src/lib/components/graph/GraphStyles.ts index 325ca4b..d724c1d 100644 --- a/src/lib/components/graph/GraphStyles.ts +++ b/src/lib/components/graph/GraphStyles.ts @@ -68,8 +68,9 @@ const styles: Stylesheet[] = [ "text-valign": "center", "text-wrap": "wrap", "text-max-width": "140", - "background-color": "#333", - "border-color": "#black", + "line-height": "1.5", + "background-color": "#374151", + "border-color": "#1f2937", "border-width": "1", "color": "white", } diff --git a/src/lib/components/modal/RestoreParamsTable.svelte b/src/lib/components/modal/RestoreParamsTable.svelte index fc0f0b8..6b4bb55 100644 --- a/src/lib/components/modal/RestoreParamsTable.svelte +++ b/src/lib/components/modal/RestoreParamsTable.svelte @@ -1,13 +1,16 @@
@@ -57,7 +67,7 @@ {:else if Object.keys(uiRestoreParams).length === 0}

No parameters to restore found in this workflow.

-

(TODO: Only parameters compatible with the currently active workflow can be restored right now)

+

(Either prompt is unchanged from active workflow, or the workflow the parameters were saved from was different)

{:else} @@ -69,11 +79,22 @@ {#each uiRestoreParams as { node, widget, sources }} - {widget.attrs.title || node.title} +
➤ {widget.attrs.title || node.title}
{#each sources as source} + {@const value = String(source.finalValue)}
-
➤ {source.type}
+ + {capitalize(source.type)} +
+ {#if isMultiline(value, 20)} + {@const lines = Math.max(countNewLines(value), value.length / 20)} + + {:else} + + {/if} +
+
{/each} @@ -95,20 +116,25 @@ } } + .target-name { + padding-bottom: 0.5rem; + } + .target { - display: flex; - flex-direction: row; - justify-content: center; - text-align: left; .target-name-and-desc { - margin: auto auto auto 0; - left: 0px; + :global(.block) { + background: var(--panel-background-fill); + } .target-desc { opacity: 65%; font-size: 11pt; } } + + pre { + @include json-view; + } } diff --git a/src/lib/restoreParameters.ts b/src/lib/restoreParameters.ts index a29be11..9a95ab4 100644 --- a/src/lib/restoreParameters.ts +++ b/src/lib/restoreParameters.ts @@ -1,4 +1,4 @@ -import type { INodeInputSlot, NodeID, SerializedLGraph } from "@litegraph-ts/core"; +import type { INodeInputSlot, NodeID, SerializedLGraph, SerializedLGraphNode } from "@litegraph-ts/core"; import type { SerializedPrompt } from "./components/ComfyApp"; import type { ComfyWidgetNode } from "./nodes/widgets"; import type { SerializedComfyWidgetNode } from "./nodes/widgets/ComfyWidgetNode"; @@ -242,15 +242,29 @@ export function getWorkflowRestoreParams(serGraph: SerializedLGraph, workflow?: return result } +function* iterateSerializedNodesRecursive(serGraph: SerializedLGraph): Iterable { + for (const serNode of serGraph.nodes) { + yield serNode; + + if (serNode.type === "graph/subgraph") { + for (const childNode of iterateSerializedNodesRecursive((serNode as any).subgraph)) { + yield childNode; + } + } + } +} + export function getWorkflowRestoreParamsUsingLayout(serGraph: SerializedLGraph, layout?: SerializedLayoutState, noExclude: boolean = false): RestoreParamWorkflowNodeTargets { const result = {} - for (const serNode of serGraph.nodes) { - if (!isSerializedComfyWidgetNode(serNode)) + for (const serNode of iterateSerializedNodesRecursive(serGraph)) { + if (!isSerializedComfyWidgetNode(serNode)) { continue; + } - if (!noExclude && serNode.properties.excludeFromJourney) + if (!noExclude && serNode.properties.excludeFromJourney) { continue; + } let name = null; const serWidget = Array.from(Object.values(layout?.allItems || {})).find(di => di.dragItem.type === "widget" && di.dragItem.nodeId === serNode.id) diff --git a/src/lib/stores/journeyStates.ts b/src/lib/stores/journeyStates.ts index 31fdddf..ae27bc4 100644 --- a/src/lib/stores/journeyStates.ts +++ b/src/lib/stores/journeyStates.ts @@ -20,7 +20,7 @@ export interface JourneyNode { id: JourneyNodeID, type: JourneyNodeType, children: JourneyPatchNode[], - promptID?: PromptID, + promptIDs: Set, images?: string[] } @@ -101,6 +101,7 @@ export function calculateWorkflowParamsPatch(parent: JourneyNode, newParams: Res export type JourneyState = { root: JourneyRootNode | null, nodesByID: Record, + nodesByPromptID: Record, activeNodeID: JourneyNodeID | null, /* @@ -117,6 +118,7 @@ type JourneyStateOps = { iterateBreadthFirst: (id?: JourneyNodeID | null) => Iterable, iterateLinearPath: (id: JourneyNodeID) => Iterable, pushPatchOntoActive: (workflow: ComfyBoxWorkflow, activeNode?: JourneyNode, showNotification?: boolean) => JourneyNode | null + afterQueued: (journeyNode: JourneyNode, promptID: PromptID) => void, onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput, queueEntry: QueueEntry) => void } @@ -127,6 +129,7 @@ function create() { { root: null, nodesByID: {}, + nodesByPromptID: {}, activeNodeID: null, version: 0 }) @@ -135,6 +138,7 @@ function create() { store.set({ root: null, nodesByID: {}, + nodesByPromptID: {}, activeNodeID: null, version: 0 }) @@ -173,6 +177,7 @@ function create() { id: uuidv4(), type: "root", children: [], + promptIDs: new Set(), base: { ...params } } s.root = _node @@ -183,6 +188,7 @@ function create() { type: "patch", parent: parentNode, children: [], + promptIDs: new Set(), patch: params, } parentNode.children.push(_node); @@ -311,8 +317,16 @@ function create() { return path; } + function afterQueued(journeyNode: JourneyNode, promptID: PromptID) { + journeyNode.promptIDs.add(promptID); + store.update(s => { + s.nodesByPromptID[promptID] = journeyNode; + return s; + }) + } + function onExecuted(promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput, queueEntry: QueueEntry) { - const journeyNode = Array.from(iterateBreadthFirst()).find(j => j.promptID === promptID); + const journeyNode = get(store).nodesByPromptID[promptID]; if (journeyNode == null) return; @@ -333,7 +347,8 @@ function create() { selectNode, iterateBreadthFirst, iterateLinearPath, - onExecuted + afterQueued, + onExecuted, } } diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index 9e45774..f93982c 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -36,7 +36,7 @@ type QueueStateOps = { export type QueueEntry = { /*** Data preserved on page refresh ***/ - /** Priority of the prompt. -1 means to queue at the front. */ + /** Priority of the prompt. Lower/negative numbers get higher priority. */ number: number, queuedAt?: Date, finishedAt?: Date, diff --git a/src/lib/stores/uiQueueState.ts b/src/lib/stores/uiQueueState.ts index a295478..f712db0 100644 --- a/src/lib/stores/uiQueueState.ts +++ b/src/lib/stores/uiQueueState.ts @@ -132,6 +132,10 @@ function updateFromQueue(queuePending: QueueEntry[], queueRunning: QueueEntry[]) // newest entries appear at the top s.queuedEntries = queuePending.map((e) => convertPendingEntry(e, "pending")).reverse(); s.runningEntries = queueRunning.map((e) => convertPendingEntry(e, "running")).reverse(); + + s.queuedEntries.sort((a, b) => a.entry.number - b.entry.number) + s.runningEntries.sort((a, b) => a.entry.number - b.entry.number) + s.queueUIEntries = s.queuedEntries.concat(s.runningEntries); console.warn("[ComfyQueue] BUILDQUEUE", s.queuedEntries.length, s.runningEntries.length) return s; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 75808b0..1a4c924 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -745,3 +745,7 @@ const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"]. export function isMobileBrowser(userAgent: string): boolean { return MOBILE_USER_AGENTS.some(a => userAgent.match(a)) } + +export function isMultiline(input: any, splitLength: number = 50): boolean { + return typeof input === "string" && (input.length > splitLength || countNewLines(input) > 1); +}