Refactor UI queue state
This commit is contained in:
@@ -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<QueueUIEntry> = []
|
||||
$: 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() {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
207
src/lib/stores/uiQueueState.ts
Normal file
207
src/lib/stores/uiQueueState.ts
Normal file
@@ -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<UIQueueState> & UIQueueStateOps;
|
||||
const store: Writable<UIQueueState> = 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;
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Page, Navbar, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte"
|
||||
import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte"
|
||||
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
||||
import type ComfyApp from "$lib/components/ComfyApp";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
@@ -12,6 +12,34 @@
|
||||
|
||||
<Page name="gallery">
|
||||
<Navbar title="Gallery" />
|
||||
|
||||
<Block>
|
||||
<div class="grid grid-cols-3 grid-gap">
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 grid-gap">
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 grid-gap">
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 grid-gap">
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 grid-gap">
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
<div>3 cols</div>
|
||||
</div>
|
||||
</Block>
|
||||
</Page>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user