From 3cd623fd20806acc8aa7844f0f8de1927318c16c Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Wed, 31 May 2023 17:34:14 -0500 Subject: [PATCH] Refactor UI queue state --- src/lib/components/ComfyQueue.svelte | 140 ++---------------- src/lib/stores/queueState.ts | 1 + src/lib/stores/uiQueueState.ts | 207 +++++++++++++++++++++++++++ src/mobile/routes/gallery.svelte | 30 +++- 4 files changed, 252 insertions(+), 126 deletions(-) create mode 100644 src/lib/stores/uiQueueState.ts diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index e71a73e..2b8c40d 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -30,6 +30,7 @@ import ComfyQueueListDisplay from "./ComfyQueueListDisplay.svelte"; import ComfyQueueGridDisplay from "./ComfyQueueGridDisplay.svelte"; import { WORKFLOWS_VIEW } from "./ComfyBoxWorkflowsView.svelte"; + import uiQueueState from "$lib/stores/uiQueueState"; export let app: ComfyApp; @@ -52,46 +53,34 @@ let displayMode: DisplayModeType = "list"; let imageSize: number = 40; let gridColumns: number = 3; - let changed = true; function switchMode(newMode: QueueItemType) { - changed = mode !== newMode + const changed = mode !== newMode mode = newMode if (changed) { - _queuedEntries = [] - _runningEntries = [] - _entries = [] + uiQueueState.updateEntries(); } } function switchDisplayMode(newDisplayMode: DisplayModeType) { - // changed = displayMode !== newDisplayMode displayMode = newDisplayMode - // if (changed) { - // _queuedEntries = [] - // _runningEntries = [] - // _entries = [] - // } } - let _queuedEntries: QueueUIEntry[] = [] - let _runningEntries: QueueUIEntry[] = [] - let _entries: QueueUIEntry[] = [] - - $: if (mode === "queue" && (changed || $queuePending.length != _queuedEntries.length || $queueRunning.length != _runningEntries.length)) { + let _entries: ReadonlyArray = [] + $: if(mode === "queue") { + _entries = $uiQueueState.queueUIEntries updateFromQueue(); - changed = false; } - else if (mode === "history" && (changed || $queueCompleted.length != _entries.length)) { + else { + _entries = $uiQueueState.historyUIEntries; updateFromHistory(); - changed = false; } $: if (mode === "queue" && !$queuePending && !$queueRunning) { - _queuedEntries = [] - _runningEntries = [] - _entries = []; - changed = true + uiQueueState.clearQueue(); + } + else if (mode === "history" && !$queueCompleted) { + uiQueueState.clearHistory(); } async function deleteEntry(entry: QueueUIEntry, event: MouseEvent) { @@ -106,125 +95,26 @@ await app.deleteQueueItem(mode, entry.entry.promptID); } - if (mode === "queue") { - _queuedEntries = [] - _runningEntries = [] - } - - _entries = []; - changed = true; + uiQueueState.updateEntries(true) } async function clearQueue() { await app.clearQueue(mode); - - if (mode === "queue") { - _queuedEntries = [] - _runningEntries = [] - } - - _entries = []; - changed = true; - } - - function formatDate(date: Date): string { - const time = date.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }); - const day = date.toLocaleString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }).replace(',', ''); - return [time, day].join(", ") - } - - function convertEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { - let date = entry.finishedAt || entry.queuedAt; - let dateStr = null; - if (date) { - dateStr = formatDate(date); - } - - const subgraphs: string[] | null = entry.extraData?.extra_pnginfo?.comfyBoxPrompt?.subgraphs; - - let message = "Prompt"; - if (entry.extraData?.workflowTitle != null) { - message = `${entry.extraData.workflowTitle}` - } - - if (subgraphs && subgraphs.length > 0) { - const subgraphsString = subgraphs.join(', ') - message += ` (${subgraphsString})` - } - - let submessage = `Nodes: ${Object.keys(entry.prompt).length}` - - if (Object.keys(entry.outputs).length > 0) { - const imageCount = Object.values(entry.outputs).filter(o => o.images).flatMap(o => o.images).length - submessage = `Images: ${imageCount}` - } - - return { - entry, - message, - submessage, - date: dateStr, - status, - images: [] - } - } - - function convertPendingEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { - const result = convertEntry(entry, status); - - const thumbnails = entry.extraData?.thumbnails - if (thumbnails) { - result.images = thumbnails.map(convertComfyOutputToComfyURL); - } - - const outputs = Object.values(entry.outputs) - .filter(o => o.images) - .flatMap(o => o.images) - .map(convertComfyOutputToComfyURL); - if (outputs) { - result.images = result.images.concat(outputs) - } - - return result; - } - - function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry { - const result = convertEntry(entry.entry, entry.status); - - const images = Object.values(entry.entry.outputs) - .filter(o => o.images) - .flatMap(o => o.images) - .map(convertComfyOutputToComfyURL); - result.images = images - - if (entry.message) - result.submessage = entry.message - else if (entry.status === "interrupted" || entry.status === "all_cached") - result.submessage = "Prompt was interrupted." - if (entry.error) - result.error = entry.error - - return result; + uiQueueState.updateEntries(true) } async function updateFromQueue() { - // newest entries appear at the top - _queuedEntries = $queuePending.map((e) => convertPendingEntry(e, "pending")).reverse(); - _runningEntries = $queueRunning.map((e) => convertPendingEntry(e, "running")).reverse(); - _entries = [..._queuedEntries, ..._runningEntries] if (queueList) { await tick(); // Wait for list size to be recalculated queueList.scroll({ top: queueList.scrollHeight }) } - console.warn("[ComfyQueue] BUILDQUEUE", _entries.length, $queuePending.length, $queueRunning.length) } async function updateFromHistory() { - _entries = $queueCompleted.map(convertCompletedEntry).reverse(); if (queueList) { + await tick(); // Wait for list size to be recalculated queueList.scrollTo(0, 0); } - console.warn("[ComfyQueue] BUILDHISTORY", _entries.length, $queueCompleted.length) } async function interrupt() { diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index 56cf239..eda8da3 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -8,6 +8,7 @@ import { get, writable, type Writable } from "svelte/store"; import { v4 as uuidv4 } from "uuid"; import workflowState, { type WorkflowError, type WorkflowExecutionError, type WorkflowInstID, type WorkflowValidationError } from "./workflowState"; import configState from "./configState"; +import uiQueueState from "./uiQueueState"; export type QueueEntryStatus = "success" | "validation_failed" | "error" | "interrupted" | "all_cached" | "unknown"; diff --git a/src/lib/stores/uiQueueState.ts b/src/lib/stores/uiQueueState.ts new file mode 100644 index 0000000..a361c22 --- /dev/null +++ b/src/lib/stores/uiQueueState.ts @@ -0,0 +1,207 @@ +import type { PromptID, QueueItemType } from '$lib/api'; +import { get, writable } from 'svelte/store'; +import type { Readable, Writable } from 'svelte/store'; +import queueState, { type CompletedQueueEntry, type QueueEntry } from './queueState'; +import type { WorkflowError } from './workflowState'; +import { convertComfyOutputToComfyURL } from '$lib/utils'; + +export type QueueUIEntryStatus = QueueEntryStatus | "pending" | "running"; + +export type QueueUIEntry = { + entry: QueueEntry, + message: string, + submessage: string, + date?: string, + status: QueueUIEntryStatus, + images?: string[], // URLs + details?: string, // shown in a tooltip on hover + error?: WorkflowError +} + +export type UIQueueState = { + mode: QueueItemType, + + queuedEntries: QueueUIEntry[], + runningEntries: QueueUIEntry[], + + queueUIEntries: QueueUIEntry[], + historyUIEntries: QueueUIEntry[], +} + +type UIQueueStateOps = { + updateEntries: (force?: boolean) => void + clearAll: () => void + clearQueue: () => void + clearHistory: () => void +} + +export type WritableUIQueueStateStore = Writable & UIQueueStateOps; +const store: Writable = writable( + { + mode: "queue", + queuedEntries: [], + runningEntries: [], + completedEntries: [], + + queueUIEntries: [], + historyUIEntries: [], + }) + +function formatDate(date: Date): string { + const time = date.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }); + const day = date.toLocaleString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }).replace(',', ''); + return [time, day].join(", ") +} + +function convertEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { + let date = entry.finishedAt || entry.queuedAt; + let dateStr = null; + if (date) { + dateStr = formatDate(date); + } + + const subgraphs: string[] | null = entry.extraData?.extra_pnginfo?.comfyBoxPrompt?.subgraphs; + + let message = "Prompt"; + if (entry.extraData?.workflowTitle != null) { + message = `${entry.extraData.workflowTitle}` + } + + if (subgraphs && subgraphs.length > 0) { + const subgraphsString = subgraphs.join(', ') + message += ` (${subgraphsString})` + } + + let submessage = `Nodes: ${Object.keys(entry.prompt).length}` + + if (Object.keys(entry.outputs).length > 0) { + const imageCount = Object.values(entry.outputs).filter(o => o.images).flatMap(o => o.images).length + submessage = `Images: ${imageCount}` + } + + return { + entry, + message, + submessage, + date: dateStr, + status, + images: [] + } +} + +function convertPendingEntry(entry: QueueEntry, status: QueueUIEntryStatus): QueueUIEntry { + const result = convertEntry(entry, status); + + const thumbnails = entry.extraData?.thumbnails + if (thumbnails) { + result.images = thumbnails.map(convertComfyOutputToComfyURL); + } + + const outputs = Object.values(entry.outputs) + .filter(o => o.images) + .flatMap(o => o.images) + .map(convertComfyOutputToComfyURL); + if (outputs) { + result.images = result.images.concat(outputs) + } + + return result; +} + +function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry { + const result = convertEntry(entry.entry, entry.status); + + const images = Object.values(entry.entry.outputs) + .filter(o => o.images) + .flatMap(o => o.images) + .map(convertComfyOutputToComfyURL); + result.images = images + + if (entry.message) + result.submessage = entry.message + else if (entry.status === "interrupted" || entry.status === "all_cached") + result.submessage = "Prompt was interrupted." + if (entry.error) + result.error = entry.error + + return result; +} + +function updateFromQueue(queuePending: QueueEntry[], queueRunning: QueueEntry[]) { + store.update(s => { + // 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.queueUIEntries = s.queuedEntries.concat(s.runningEntries); + console.warn("[ComfyQueue] BUILDQUEUE", s.queuedEntries.length, s.runningEntries.length) + return s; + }) +} + +function updateFromHistory(queueCompleted: CompletedQueueEntry[]) { + store.update(s => { + s.historyUIEntries = queueCompleted.map(convertCompletedEntry).reverse(); + console.warn("[ComfyQueue] BUILDHISTORY", s.historyUIEntries.length) + return s + }) +} + +function updateEntries(force: boolean = false) { + const state = get(store) + const qs = get(queueState) + const queuePending = qs.queuePending + const queueRunning = qs.queueRunning + const queueCompleted = qs.queueCompleted + + const queueChanged = get(queuePending).length != state.queuedEntries.length + || get(queueRunning).length != state.runningEntries.length; + const historyChanged = get(queueCompleted).length != state.historyUIEntries.length; + + if (queueChanged || force) { + updateFromQueue(get(queuePending), get(queueRunning)); + } + if (historyChanged || force) { + updateFromHistory(get(queueCompleted)); + } +} + +function clearAll() { + store.update(s => { + s.queuedEntries = [] + s.runningEntries = [] + s.historyUIEntries = [] + return s + }) + updateEntries(true); +} + +function clearQueue() { + store.update(s => { + s.queuedEntries = [] + s.runningEntries = [] + return s + }) + updateEntries(true); +} + +function clearHistory() { + store.update(s => { + s.historyUIEntries = [] + return s + }) + updateEntries(true); +} + +queueState.subscribe(s => { + updateEntries(); +}) + +const uiStateStore: WritableUIQueueStateStore = +{ + ...store, + updateEntries, + clearAll, + clearQueue, + clearHistory, +} +export default uiStateStore; diff --git a/src/mobile/routes/gallery.svelte b/src/mobile/routes/gallery.svelte index 27bd613..2f1b79a 100644 --- a/src/mobile/routes/gallery.svelte +++ b/src/mobile/routes/gallery.svelte @@ -1,5 +1,5 @@