Gallery fix

This commit is contained in:
space-nuko
2023-05-12 17:11:57 -05:00
parent 4f241c17ab
commit bfbbad692f
6 changed files with 100 additions and 56 deletions

View File

@@ -43,11 +43,12 @@ export class ImageViewer {
} }
static selected_gallery_button(gallery: HTMLDivElement): [HTMLButtonElement | null, number] { static selected_gallery_button(gallery: HTMLDivElement): [HTMLButtonElement | null, number] {
var allCurrentButtons = gallery.querySelectorAll('.preview > .thumbnails > .thumbnail-item.thumbnail-small.selected'); var allCurrentButtons = gallery.querySelectorAll('.preview > .thumbnails > .thumbnail-item.thumbnail-small');
console.log(allCurrentButtons)
var visibleCurrentButton = null; var visibleCurrentButton = null;
let index = -1; let index = -1;
allCurrentButtons.forEach((elem, i) => { allCurrentButtons.forEach((elem, i) => {
if (elem.parentElement.offsetParent) { if (elem.parentElement.offsetParent && elem.classList.contains("selected")) {
visibleCurrentButton = elem; visibleCurrentButton = elem;
index = i; index = i;
} }
@@ -104,11 +105,9 @@ export class ImageViewer {
this.setModalImageSrc(selectedImageUrl) this.setModalImageSrc(selectedImageUrl)
if (this.currentGallery) { if (this.currentGallery) {
var galleryButtons = ImageViewer.all_gallery_buttons(this.currentGallery); const galleryButtons = ImageViewer.all_gallery_buttons(this.currentGallery);
var [_currentButton, index] = ImageViewer.selected_gallery_button(this.currentGallery); const nextButton = galleryButtons[this.selectedIndex];
if (nextButton) {
if (index != -1) {
const nextButton = galleryButtons[negmod((index + offset), galleryButtons.length)]
nextButton.click() nextButton.click()
} }
} }
@@ -173,6 +172,7 @@ export class ImageViewer {
let urls = ImageViewer.get_gallery_urls(galleryElem) let urls = ImageViewer.get_gallery_urls(galleryElem)
const [_currentButton, index] = ImageViewer.selected_gallery_button(galleryElem) const [_currentButton, index] = ImageViewer.selected_gallery_button(galleryElem)
console.warn("Gallery!", index, urls, galleryElem)
this.showModal(urls, index, galleryElem) this.showModal(urls, index, galleryElem)
evt.stopPropagation(); evt.stopPropagation();

View File

@@ -284,7 +284,6 @@
<LightboxModal /> <LightboxModal />
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} /> <input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
</div> </div>
<SvelteToast options={toastOptions} /> <SvelteToast options={toastOptions} />
<style lang="scss"> <style lang="scss">

View File

@@ -373,7 +373,7 @@ export default class ComfyApp {
// Queue prompt using ctrl or command + enter // Queue prompt using ctrl or command + enter
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) { if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) {
this.queuePrompt(e.shiftKey ? -1 : 0); this.runDefaultQueueAction();
} }
}); });
window.addEventListener("keyup", (e) => { window.addEventListener("keyup", (e) => {
@@ -525,7 +525,6 @@ export default class ComfyApp {
if (get(layoutState).attrs.queuePromptButtonRunWorkflow) { if (get(layoutState).attrs.queuePromptButtonRunWorkflow) {
this.queuePrompt(0, 1); this.queuePrompt(0, 1);
notify("Prompt queued.");
} }
} }
@@ -757,7 +756,13 @@ export default class ComfyApp {
try { try {
const response = await this.api.queuePrompt(request); const response = await this.api.queuePrompt(request);
// BUG: This can cause race conditions updating frontend state
// since we don't have a prompt ID until the backend
// returns!
promptID = response.promptID; promptID = response.promptID;
queueState.afterQueued(promptID, num, p.output, extraData)
error = response.error; error = response.error;
} catch (error) { } catch (error) {
error = error.toString(); error = error.toString();
@@ -779,7 +784,6 @@ export default class ComfyApp {
} }
this.lCanvas.draw(true, true); this.lCanvas.draw(true, true);
queueState.afterQueued(promptID, num, p.output, extraData)
} }
} }
} finally { } finally {

View File

@@ -6,10 +6,10 @@
import { convertComfyOutputToComfyURL, convertFilenameToComfyURL, getNodeInfo } from "$lib/utils" import { convertComfyOutputToComfyURL, convertFilenameToComfyURL, getNodeInfo } from "$lib/utils"
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import type { QueueItemType } from "$lib/api"; import type { QueueItemType } from "$lib/api";
import { ImageViewer } from "$lib/ImageViewer"; import { ImageViewer } from "$lib/ImageViewer";
import { Button } from "@gradio/button"; import { Button } from "@gradio/button";
import type ComfyApp from "./ComfyApp"; import type ComfyApp from "./ComfyApp";
import { tick } from "svelte"; import { tick } from "svelte";
let queuePending: Writable<QueueEntry[]> | null = null; let queuePending: Writable<QueueEntry[]> | null = null;
let queueRunning: Writable<QueueEntry[]> | null = null; let queueRunning: Writable<QueueEntry[]> | null = null;
@@ -22,7 +22,7 @@
date?: string, date?: string,
status: QueueEntryStatus | "pending" | "running", status: QueueEntryStatus | "pending" | "running",
images?: string[], // URLs images?: string[], // URLs
error?: string details?: string // shown in a tooltip on hover
} }
$: if ($queueState) { $: if ($queueState) {
@@ -80,17 +80,18 @@
submessage, submessage,
date, date,
status: "pending", status: "pending",
images images,
} }
} }
function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry { function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry {
const result = convertEntry(entry.entry); const result = convertEntry(entry.entry);
result.status = entry.status; result.status = entry.status;
result.error = entry.error;
if (result.status === "all_cached") if (entry.message)
result.submessage = "(Execution was cached)" result.submessage = entry.message
if (entry.error)
result.details = entry.error
return result; return result;
} }
@@ -105,10 +106,10 @@
} }
async function updateFromHistory() { async function updateFromHistory() {
_entries = $queueCompleted.map(convertCompletedEntry); _entries = $queueCompleted.map(convertCompletedEntry).reverse();
if (queueList) { if (queueList) {
await tick(); // Wait for list size to be recalculated await tick(); // Wait for list size to be recalculated
queueList.scroll({ top: queueList.scrollHeight }) queueList.scrollTo(0, 0);
} }
console.warn("[ComfyQueue] BUILDHISTORY", _entries, $queueCompleted) console.warn("[ComfyQueue] BUILDHISTORY", _entries, $queueCompleted)
} }
@@ -137,7 +138,7 @@
</script> </script>
<div class="queue"> <div class="queue">
<div class="queue-entries" bind:this={queueList}> <div class="queue-entries {mode}-mode" bind:this={queueList}>
{#if _entries.length > 0} {#if _entries.length > 0}
{#each _entries as entry} {#each _entries as entry}
<div class="queue-entry {entry.status}"> <div class="queue-entry {entry.status}">
@@ -156,13 +157,13 @@
{entry.submessage} {entry.submessage}
</div> </div>
</div> </div>
<div class="queue-entry-rest"> </div>
{#if entry.date != null} <div class="queue-entry-rest">
<span class="queue-entry-queued-at"> {#if entry.date != null}
{entry.date} <span class="queue-entry-queued-at">
</span> {entry.date}
{/if} </span>
</div> {/if}
</div> </div>
{/each} {/each}
{:else} {:else}
@@ -230,9 +231,17 @@
} }
.queue-entries { .queue-entries {
overflow-y: scroll;
height: $queue-height; height: $queue-height;
max-height: $queue-height; max-height: $queue-height;
display: flex;
overflow-y: auto;
flex-flow: column nowrap;
&.queue-mode > :first-child {
// elements stick to bottom in queue mode only
// next element in queue is on the bottom
margin-top: auto !important;
}
> .queue-empty { > .queue-empty {
display: flex; display: flex;
@@ -328,11 +337,11 @@
.queue-entry-queued-at { .queue-entry-queued-at {
width: auto; width: auto;
font-size: 10px; font-size: 12px;
position:absolute; position:absolute;
right: 0px; right: 0px;
bottom: 0px; bottom: 0px;
padding: 0.0rem 0.4rem; padding: 0.4rem 0.6rem;
color: var(--body-text-color); color: var(--body-text-color);
} }

View File

@@ -45,6 +45,11 @@ function notifyToast(text: string, options: NotifyOptions) {
'--toastBackground': 'var(--color-blue-500)', '--toastBackground': 'var(--color-blue-500)',
} }
} }
else if (options.type === "warning") {
toastOptions.theme = {
'--toastBackground': 'var(--color-yellow-500)',
}
}
else if (options.type === "error") { else if (options.type === "error") {
toastOptions.theme = { toastOptions.theme = {
'--toastBackground': 'var(--color-red-500)', '--toastBackground': 'var(--color-red-500)',

View File

@@ -1,13 +1,14 @@
import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyPromptExtraData, NodeID, PromptID } from "$lib/api"; import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyPromptExtraData, NodeID, PromptID } from "$lib/api";
import type { Progress, SerializedPrompt, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp"; import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp";
import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes"; import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes";
import notify from "$lib/notify";
import { get, writable, type Writable } from "svelte/store"; import { get, writable, type Writable } from "svelte/store";
export type QueueItem = { export type QueueItem = {
name: string name: string
} }
export type QueueEntryStatus = "success" | "error" | "all_cached" | "unknown"; export type QueueEntryStatus = "success" | "error" | "interrupted" | "all_cached" | "unknown";
type QueueStateOps = { type QueueStateOps = {
queueUpdated: (resp: ComfyAPIQueueResponse) => void, queueUpdated: (resp: ComfyAPIQueueResponse) => void,
@@ -35,11 +36,15 @@ export type QueueEntry = {
/* Prompt outputs, collected while the prompt is still executing */ /* Prompt outputs, collected while the prompt is still executing */
outputs: SerializedPromptOutputs, outputs: SerializedPromptOutputs,
/* Nodes in of the workflow that have finished running so far. */
nodesRan: Set<NodeID>,
cachedNodes: Set<NodeID>
} }
export type CompletedQueueEntry = { export type CompletedQueueEntry = {
entry: QueueEntry, entry: QueueEntry,
status: QueueEntryStatus, status: QueueEntryStatus,
message?: string,
error?: string, error?: string,
} }
@@ -72,7 +77,9 @@ function toQueueEntry(resp: ComfyAPIHistoryItem): QueueEntry {
prompt, prompt,
extraData, extraData,
goodOutputs, goodOutputs,
outputs: {} outputs: {},
nodesRan: new Set(), // TODO can ComfyUI send this too?
cachedNodes: new Set()
} }
} }
@@ -122,27 +129,28 @@ function statusUpdated(status: ComfyAPIStatusResponse | null) {
}) })
} }
function findEntryInPending(promptID: PromptID): [number, QueueEntry, Writable<QueueEntry[]>] | null { function findEntryInPending(promptID: PromptID): [number, QueueEntry | null, Writable<QueueEntry[]> | null] {
const state = get(store); const state = get(store);
let index = get(state.queuePending).findIndex(e => e.promptID === promptID) let index = get(state.queuePending).findIndex(e => e.promptID === promptID)
if (index) if (index !== -1)
return [index, get(state.queuePending)[index], state.queuePending] return [index, get(state.queuePending)[index], state.queuePending]
index = get(state.queueRunning).findIndex(e => e.promptID === promptID) index = get(state.queueRunning).findIndex(e => e.promptID === promptID)
if (index) if (index !== -1)
return [index, get(state.queueRunning)[index], state.queueRunning] return [index, get(state.queueRunning)[index], state.queueRunning]
return null return [-1, null, null]
} }
function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: QueueEntryStatus, error?: string) { function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: QueueEntryStatus, message?: string, error?: string) {
const state = get(store) const state = get(store)
const entry = get(queue)[index]; const entry = get(queue)[index];
console.debug("[queueState] Move to completed", entry.promptID, index, status, message, error)
entry.finishedAt = new Date() // Now entry.finishedAt = new Date() // Now
queue.update(qp => { qp.splice(index, 1); return qp }); queue.update(qp => { qp.splice(index, 1); return qp });
state.queueCompleted.update(qc => { state.queueCompleted.update(qc => {
const completed: CompletedQueueEntry = { entry, status, error } const completed: CompletedQueueEntry = { entry, status, message, error }
qc.push(completed) qc.push(completed)
return qc return qc
}) })
@@ -150,21 +158,36 @@ function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: Q
store.set(state) store.set(state)
} }
function executingUpdated(promptID: PromptID | null, runningNodeID: NodeID | null) { function executingUpdated(promptID: PromptID, runningNodeID: NodeID | null) {
console.debug("[queueState] executingUpdated", promptID, runningNodeID) console.debug("[queueState] executingUpdated", promptID, runningNodeID)
store.update((s) => { store.update((s) => {
s.progress = null; s.progress = null;
const [index, entry, queue] = findEntryInPending(promptID);
if (runningNodeID != null) { if (runningNodeID != null) {
if (entry != null) {
entry.nodesRan.add(runningNodeID)
}
s.runningNodeID = parseInt(runningNodeID); s.runningNodeID = parseInt(runningNodeID);
} }
else if (promptID != null) { else {
// Prompt finished executing. // Prompt finished executing.
const [index, entry, queue] = findEntryInPending(promptID); if (entry != null) {
if (entry) { const totalNodesInPrompt = Object.keys(entry.prompt).length
moveToCompleted(index, queue, "success") if (entry.cachedNodes.size >= Object.keys(entry.prompt).length) {
notify("Prompt was cached, nothing to run.", { type: "warning" })
moveToCompleted(index, queue, "all_cached", "(Execution was cached)");
}
else if (entry.nodesRan.size >= totalNodesInPrompt) {
moveToCompleted(index, queue, "success")
}
else {
notify("Interrupted prompt.")
moveToCompleted(index, queue, "interrupted", `Interrupted after ${entry.nodesRan.size}/${totalNodesInPrompt} nodes`)
}
} }
else { else {
console.error("[queueState] Could not find in pending! (executingUpdated)", promptID) console.debug("[queueState] Could not find in pending! (executingUpdated)", promptID)
} }
s.progress = null; s.progress = null;
s.runningNodeID = null; s.runningNodeID = null;
@@ -177,13 +200,14 @@ function executionCached(promptID: PromptID, nodes: NodeID[]) {
console.debug("[queueState] executionCached", promptID, nodes) console.debug("[queueState] executionCached", promptID, nodes)
store.update(s => { store.update(s => {
const [index, entry, queue] = findEntryInPending(promptID); const [index, entry, queue] = findEntryInPending(promptID);
if (entry) { if (entry != null) {
if (nodes.length >= Object.keys(entry.prompt).length) { for (const nodeID of nodes) {
moveToCompleted(index, queue, "all_cached"); entry.nodesRan.add(nodeID);
entry.cachedNodes.add(nodeID);
} }
} }
else { else {
console.error("[queueState] Could not find in pending! (executionCached)", promptID) console.error("[queueState] Could not find in pending! (executionCached)", promptID, "pending", JSON.stringify(get(get(store).queuePending).map(p => p.promptID)), "running", JSON.stringify(get(get(store).queueRunning).map(p => p.promptID)))
} }
s.progress = null; s.progress = null;
s.runningNodeID = null; s.runningNodeID = null;
@@ -195,8 +219,8 @@ function executionError(promptID: PromptID, message: string) {
console.debug("[queueState] executionError", promptID, message) console.debug("[queueState] executionError", promptID, message)
store.update(s => { store.update(s => {
const [index, entry, queue] = findEntryInPending(promptID); const [index, entry, queue] = findEntryInPending(promptID);
if (entry) { if (entry != null) {
moveToCompleted(index, queue, "error", message) moveToCompleted(index, queue, "error", "Error executing", message)
} }
else { else {
console.error("[queueState] Could not find in pending! (executionError)", promptID) console.error("[queueState] Could not find in pending! (executionError)", promptID)
@@ -218,9 +242,12 @@ function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromp
prompt, prompt,
extraData, extraData,
goodOutputs: [], goodOutputs: [],
outputs: {} outputs: {},
nodesRan: new Set(),
cachedNodes: new Set()
} }
s.queuePending.update(qp => { qp.push(entry); return qp }) s.queuePending.update(qp => { qp.push(entry); return qp })
console.debug("[queueState] ADD PROMPT", promptID)
return s return s
}) })
} }
@@ -229,7 +256,7 @@ function onExecuted(promptID: PromptID, nodeID: NodeID, output: GalleryOutput) {
console.debug("[queueState] onExecuted", promptID, nodeID, output) console.debug("[queueState] onExecuted", promptID, nodeID, output)
store.update(s => { store.update(s => {
const [index, entry, queue] = findEntryInPending(promptID) const [index, entry, queue] = findEntryInPending(promptID)
if (entry) { if (entry != null) {
entry.outputs[nodeID] = output; entry.outputs[nodeID] = output;
queue.set(get(queue)) queue.set(get(queue))
} }