History fixes

This commit is contained in:
space-nuko
2023-06-03 20:11:24 -05:00
parent 9054d257af
commit 94d96375cb
11 changed files with 125 additions and 56 deletions

View File

@@ -1113,7 +1113,7 @@ export default class ComfyApp {
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) { if (journeyNode != null) {
journeyNode.promptID = response.promptID; targetWorkflow.journey.afterQueued(journeyNode, response.promptID);
} }
} }
} catch (err) { } catch (err) {

View File

@@ -20,7 +20,7 @@
import selectionState from '$lib/stores/selectionState'; import selectionState from '$lib/stores/selectionState';
import { Checkbox } from '@gradio/form'; import { Checkbox } from '@gradio/form';
import modalState from '$lib/stores/modalState'; 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 PromptDisplay from "$lib/components/PromptDisplay.svelte"
import { getQueueEntryImages } from '$lib/stores/uiQueueState'; import { getQueueEntryImages } from '$lib/stores/uiQueueState';
import { SvelteComponent } from 'svelte'; import { SvelteComponent } from 'svelte';
@@ -91,23 +91,25 @@
return; return;
} }
const promptID = journeyNode.promptID; // pick first resolved prompt
if (promptID != null) { const queueEntry: QueueEntry | null =
const queueEntry = queueState.getQueueEntry(journeyNode.promptID) Array.from(journeyNode.promptIDs)
if (queueEntry?.prompt != null) { .map(id => queueState.getQueueEntry(id))
modalState.pushModal({ .find(qe => qe?.prompt != null);
title: "Prompt Details",
svelteComponent: PromptDisplay, if (queueEntry) {
svelteProps: { modalState.pushModal({
prompt: queueEntry.prompt, title: "Prompt Details",
workflow: queueEntry.extraData?.extra_pnginfo?.comfyBoxWorkflow, svelteComponent: PromptDisplay,
images: getQueueEntryImages(queueEntry), svelteProps: {
closeModal: () => modalState.closeAllModals(), prompt: queueEntry.prompt,
expandAll: false, workflow: queueEntry.extraData?.extra_pnginfo?.comfyBoxWorkflow,
app images: getQueueEntryImages(queueEntry),
}, closeModal: () => modalState.closeAllModals(),
}) expandAll: false,
} app
},
})
} }
else { else {
notify("This journey entry has no prompts yet.", { type: "warning" }) notify("This journey entry has no prompts yet.", { type: "warning" })

View File

@@ -50,23 +50,31 @@
return a[1].name > b[1].name ? 1 : -1 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 = "" let line = ""
switch (source.nodeType) { switch (source.nodeType) {
case "ui/text": case "ui/text":
line = `${source.name} (changed)` line = `${source.name}: (changed)`
break; break;
default: default:
const prevValue = prev[nodeID]; const prevValue = prev[nodeID];
let prevValueStr = "???" let prevValueStr = "???"
if (prevValue) if (prevValue)
prevValueStr = prevValue.finalValue prevValueStr = prevValue.finalValue
line = `${source.name}: ${prevValueStr} -> ${source.finalValue}` line = `${source.name}: ${prevValueStr} ${source.finalValue}`
break; break;
} }
lines.push(line) lines.push(line)
} }
if (leftover > 0) {
lines.push(`(+ ${leftover} more)`)
}
return lines.join("\n") return lines.join("\n")
} }
@@ -128,8 +136,6 @@
const patchText = makePatchText(patchNode.patch, prev); const patchText = makePatchText(patchNode.patch, prev);
const patchNodeHeight = countNewLines(patchText) * 11 + 22; const patchNodeHeight = countNewLines(patchText) * 11 + 22;
console.debug("[JourneyRenderer] Patch text", prev, patchText);
nodes.push({ nodes.push({
data: { data: {
id: midNodeID, id: midNodeID,
@@ -224,8 +230,8 @@
const journeyNode = $journey.nodesByID[nodeID] const journeyNode = $journey.nodesByID[nodeID]
if (journeyNode) { if (journeyNode) {
if (journeyNode.promptID != null) { if (journeyNode.promptIDs) {
const queueEntry = queueState.getQueueEntry(journeyNode.promptID) const queueEntry = Array.from(journeyNode.promptIDs).map(id => queueState.getQueueEntry(id)).find(Boolean);
if (queueEntry) { if (queueEntry) {
const outputs = getQueueEntryImages(queueEntry); const outputs = getQueueEntryImages(queueEntry);

View File

@@ -8,7 +8,7 @@
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte"; import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
import { ImageViewer } from "$lib/ImageViewer"; import { ImageViewer } from "$lib/ImageViewer";
import type { Styles } from "@gradio/utils"; 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 ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
import RestoreParamsTable from "./modal/RestoreParamsTable.svelte"; import RestoreParamsTable from "./modal/RestoreParamsTable.svelte";
import workflowState, { type ComfyBoxWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState"; import workflowState, { type ComfyBoxWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
@@ -41,6 +41,7 @@
// TODO other sources than serialized workflow // TODO other sources than serialized workflow
if (workflow != null) { if (workflow != null) {
const workflowParams = getWorkflowRestoreParamsUsingLayout(workflow.workflow, workflow.layout) const workflowParams = getWorkflowRestoreParamsUsingLayout(workflow.workflow, workflow.layout)
console.error("GETPARMS", workflowParams)
restoreParams = concatRestoreParams(restoreParams, workflowParams); restoreParams = concatRestoreParams(restoreParams, workflowParams);
} }
@@ -100,10 +101,6 @@
&& typeof input[1] === "number" && typeof input[1] === "number"
} }
function isMultiline(input: any): boolean {
return typeof input === "string" && (input.length > splitLength || countNewLines(input) > 1);
}
function formatInput(input: any): string { function formatInput(input: any): string {
if (typeof input === "string") if (typeof input === "string")
return input return input

View File

@@ -68,8 +68,9 @@ const styles: Stylesheet[] = [
"text-valign": "center", "text-valign": "center",
"text-wrap": "wrap", "text-wrap": "wrap",
"text-max-width": "140", "text-max-width": "140",
"background-color": "#333", "line-height": "1.5",
"border-color": "#black", "background-color": "#374151",
"border-color": "#1f2937",
"border-width": "1", "border-width": "1",
"color": "white", "color": "white",
} }

View File

@@ -1,13 +1,16 @@
<script lang="ts"> <script lang="ts">
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions"; import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
import type { ComfyWidgetNode } from "$lib/nodes/widgets"; import type { ComfyWidgetNode } from "$lib/nodes/widgets";
import type { RestoreParamTargets } from "$lib/restoreParameters"; import type { RestoreParamSource, RestoreParamTargets } from "$lib/restoreParameters";
import { isComfyWidgetNode } from "$lib/stores/layoutStates"; import { isComfyWidgetNode, type WidgetLayout } from "$lib/stores/layoutStates";
import type { ComfyBoxWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState"; import type { ComfyBoxWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
import workflowState from "$lib/stores/workflowState"; import workflowState from "$lib/stores/workflowState";
import { Block, BlockTitle } from "@gradio/atoms"; import { Block, BlockTitle } from "@gradio/atoms";
import { Button } from "@gradio/button"; import { Button } from "@gradio/button";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import deepEqual from "deep-equal";
import { capitalize, countNewLines, isMultiline } from "$lib/utils";
import { TextBox } from "@gradio/form";
type UIRestoreParam = { type UIRestoreParam = {
node: ComfyWidgetNode, node: ComfyWidgetNode,
@@ -32,14 +35,21 @@
if (node == null || !isComfyWidgetNode(node)) if (node == null || !isComfyWidgetNode(node))
continue; continue;
const nodeValue = node.getValue();
const foundSources = sources.filter(s => !deepEqual(nodeValue, s.finalValue));
if (foundSources.length === 0)
continue;
const widget = node.dragItem; const widget = node.dragItem;
if (widget == null) { if (widget == null) {
console.error("[RestoreParamsTable] Node missing layoutState widget!!!", node) console.error("[RestoreParamsTable] Node missing layoutState widget!!!", node)
} }
result.push({ node, widget, sources }) result.push({ node, widget, sources: foundSources })
} }
console.warn("RESTORE PARAMS", restoreParams, "->", result)
return result return result
} }
@@ -48,7 +58,7 @@
function doRestore(e: MouseEvent) { function doRestore(e: MouseEvent) {
dispatch("restore", {}) dispatch("restore", {})
} }
</script> </script>
<div class="scroll-container"> <div class="scroll-container">
@@ -57,7 +67,7 @@
{:else if Object.keys(uiRestoreParams).length === 0} {:else if Object.keys(uiRestoreParams).length === 0}
<div> <div>
<p>No parameters to restore found in this workflow.</p> <p>No parameters to restore found in this workflow.</p>
<p>(TODO: Only parameters compatible with the currently active workflow can be restored right now)</p> <p>(Either prompt is unchanged from active workflow, or the workflow the parameters were saved from was different)</p>
</div> </div>
{:else} {:else}
<Block> <Block>
@@ -69,11 +79,22 @@
</Block> </Block>
{#each uiRestoreParams as { node, widget, sources }} {#each uiRestoreParams as { node, widget, sources }}
<Block> <Block>
<BlockTitle>{widget.attrs.title || node.title}</BlockTitle> <div class="target-name">{widget.attrs.title || node.title}</div>
{#each sources as source} {#each sources as source}
{@const value = String(source.finalValue)}
<div class="target"> <div class="target">
<div class="target-name-and-desc"> <div class="target-name-and-desc">
<div class="target-name">{source.type}</div> <Block>
<BlockTitle>{capitalize(source.type)}</BlockTitle>
<div>
{#if isMultiline(value, 20)}
{@const lines = Math.max(countNewLines(value), value.length / 20)}
<TextBox show_label={false} label={''} {value} {lines} max_lines={lines} />
{:else}
<TextBox show_label={false} label={''} {value} lines={1} max_lines={1} />
{/if}
</div>
</Block>
</div> </div>
</div> </div>
{/each} {/each}
@@ -95,20 +116,25 @@
} }
} }
.target-name {
padding-bottom: 0.5rem;
}
.target { .target {
display: flex;
flex-direction: row;
justify-content: center;
text-align: left;
.target-name-and-desc { .target-name-and-desc {
margin: auto auto auto 0; :global(.block) {
left: 0px; background: var(--panel-background-fill);
}
.target-desc { .target-desc {
opacity: 65%; opacity: 65%;
font-size: 11pt; font-size: 11pt;
} }
} }
pre {
@include json-view;
}
} }
</style> </style>

View File

@@ -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 { SerializedPrompt } from "./components/ComfyApp";
import type { ComfyWidgetNode } from "./nodes/widgets"; import type { ComfyWidgetNode } from "./nodes/widgets";
import type { SerializedComfyWidgetNode } from "./nodes/widgets/ComfyWidgetNode"; import type { SerializedComfyWidgetNode } from "./nodes/widgets/ComfyWidgetNode";
@@ -242,15 +242,29 @@ export function getWorkflowRestoreParams(serGraph: SerializedLGraph, workflow?:
return result return result
} }
function* iterateSerializedNodesRecursive(serGraph: SerializedLGraph): Iterable<SerializedLGraphNode> {
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 { export function getWorkflowRestoreParamsUsingLayout(serGraph: SerializedLGraph, layout?: SerializedLayoutState, noExclude: boolean = false): RestoreParamWorkflowNodeTargets {
const result = {} const result = {}
for (const serNode of serGraph.nodes) { for (const serNode of iterateSerializedNodesRecursive(serGraph)) {
if (!isSerializedComfyWidgetNode(serNode)) if (!isSerializedComfyWidgetNode(serNode)) {
continue; continue;
}
if (!noExclude && serNode.properties.excludeFromJourney) if (!noExclude && serNode.properties.excludeFromJourney) {
continue; continue;
}
let name = null; let name = null;
const serWidget = Array.from(Object.values(layout?.allItems || {})).find(di => di.dragItem.type === "widget" && di.dragItem.nodeId === serNode.id) const serWidget = Array.from(Object.values(layout?.allItems || {})).find(di => di.dragItem.type === "widget" && di.dragItem.nodeId === serNode.id)

View File

@@ -20,7 +20,7 @@ export interface JourneyNode {
id: JourneyNodeID, id: JourneyNodeID,
type: JourneyNodeType, type: JourneyNodeType,
children: JourneyPatchNode[], children: JourneyPatchNode[],
promptID?: PromptID, promptIDs: Set<PromptID>,
images?: string[] images?: string[]
} }
@@ -101,6 +101,7 @@ export function calculateWorkflowParamsPatch(parent: JourneyNode, newParams: Res
export type JourneyState = { export type JourneyState = {
root: JourneyRootNode | null, root: JourneyRootNode | null,
nodesByID: Record<JourneyNodeID, JourneyNode>, nodesByID: Record<JourneyNodeID, JourneyNode>,
nodesByPromptID: Record<PromptID, JourneyNode>,
activeNodeID: JourneyNodeID | null, activeNodeID: JourneyNodeID | null,
/* /*
@@ -117,6 +118,7 @@ type JourneyStateOps = {
iterateBreadthFirst: (id?: JourneyNodeID | null) => Iterable<JourneyNode>, iterateBreadthFirst: (id?: JourneyNodeID | null) => Iterable<JourneyNode>,
iterateLinearPath: (id: JourneyNodeID) => Iterable<JourneyNode>, iterateLinearPath: (id: JourneyNodeID) => Iterable<JourneyNode>,
pushPatchOntoActive: (workflow: ComfyBoxWorkflow, activeNode?: JourneyNode, showNotification?: boolean) => JourneyNode | null 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 onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput, queueEntry: QueueEntry) => void
} }
@@ -127,6 +129,7 @@ function create() {
{ {
root: null, root: null,
nodesByID: {}, nodesByID: {},
nodesByPromptID: {},
activeNodeID: null, activeNodeID: null,
version: 0 version: 0
}) })
@@ -135,6 +138,7 @@ function create() {
store.set({ store.set({
root: null, root: null,
nodesByID: {}, nodesByID: {},
nodesByPromptID: {},
activeNodeID: null, activeNodeID: null,
version: 0 version: 0
}) })
@@ -173,6 +177,7 @@ function create() {
id: uuidv4(), id: uuidv4(),
type: "root", type: "root",
children: [], children: [],
promptIDs: new Set(),
base: { ...params } base: { ...params }
} }
s.root = _node s.root = _node
@@ -183,6 +188,7 @@ function create() {
type: "patch", type: "patch",
parent: parentNode, parent: parentNode,
children: [], children: [],
promptIDs: new Set(),
patch: params, patch: params,
} }
parentNode.children.push(_node); parentNode.children.push(_node);
@@ -311,8 +317,16 @@ function create() {
return path; 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) { 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) if (journeyNode == null)
return; return;
@@ -333,7 +347,8 @@ function create() {
selectNode, selectNode,
iterateBreadthFirst, iterateBreadthFirst,
iterateLinearPath, iterateLinearPath,
onExecuted afterQueued,
onExecuted,
} }
} }

View File

@@ -36,7 +36,7 @@ type QueueStateOps = {
export type QueueEntry = { export type QueueEntry = {
/*** Data preserved on page refresh ***/ /*** 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, number: number,
queuedAt?: Date, queuedAt?: Date,
finishedAt?: Date, finishedAt?: Date,

View File

@@ -132,6 +132,10 @@ function updateFromQueue(queuePending: QueueEntry[], queueRunning: QueueEntry[])
// newest entries appear at the top // newest entries appear at the top
s.queuedEntries = queuePending.map((e) => convertPendingEntry(e, "pending")).reverse(); s.queuedEntries = queuePending.map((e) => convertPendingEntry(e, "pending")).reverse();
s.runningEntries = queueRunning.map((e) => convertPendingEntry(e, "running")).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); s.queueUIEntries = s.queuedEntries.concat(s.runningEntries);
console.warn("[ComfyQueue] BUILDQUEUE", s.queuedEntries.length, s.runningEntries.length) console.warn("[ComfyQueue] BUILDQUEUE", s.queuedEntries.length, s.runningEntries.length)
return s; return s;

View File

@@ -745,3 +745,7 @@ const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].
export function isMobileBrowser(userAgent: string): boolean { export function isMobileBrowser(userAgent: string): boolean {
return MOBILE_USER_AGENTS.some(a => userAgent.match(a)) 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);
}