Queue/history bar

This commit is contained in:
space-nuko
2023-05-12 13:46:42 -05:00
parent 3bf774ba0c
commit f64db2035a
11 changed files with 321 additions and 166 deletions

View File

@@ -1,4 +1,3 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@@ -9,7 +8,7 @@
<meta name="theme-color" content="#2196f3"> <meta name="theme-color" content="#2196f3">
</head> </head>
<body> <body>
<div id="app"> <div id="app" class="mobile">
<script type="module" src='/src/main-mobile.ts'></script> <script type="module" src='/src/main-mobile.ts'></script>
</body> </body>
</html> </html>

View File

@@ -1,14 +1,15 @@
import type { Progress, SerializedPrompt, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp"; import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp";
import type TypedEmitter from "typed-emitter"; import type TypedEmitter from "typed-emitter";
import EventEmitter from "events"; import EventEmitter from "events";
import type { GalleryOutput } from "./nodes/ComfyWidgetNodes"; import type { GalleryOutput } from "./nodes/ComfyWidgetNodes";
import type { SerializedLGraph } from "@litegraph-ts/core";
type PromptRequestBody = { export type ComfyPromptRequest = {
client_id: string, client_id?: string,
prompt: any, prompt: SerializedPromptInputsAll,
extra_data: any, extra_data: ComfyPromptExtraData,
front: boolean, front?: boolean,
number: number | undefined number?: number
} }
export type QueueItemType = "queue" | "history"; export type QueueItemType = "queue" | "history";
@@ -34,8 +35,8 @@ export type PromptID = string; // UUID
export type ComfyAPIHistoryItem = [ export type ComfyAPIHistoryItem = [
number, // prompt number number, // prompt number
PromptID, PromptID,
SerializedPrompt, SerializedPromptInputsAll,
any, // extra data ComfyPromptExtraData,
NodeID[] // good outputs NodeID[] // good outputs
] ]
@@ -54,6 +55,16 @@ export type ComfyAPIHistoryResponse = {
error?: string error?: string
} }
export type ComfyPromptPNGInfo = {
workflow: SerializedLGraph
}
export type ComfyPromptExtraData = {
extra_pnginfo?: ComfyPromptPNGInfo,
client_id?: string, // UUID
subgraphs: string[]
}
type ComfyAPIEvents = { type ComfyAPIEvents = {
status: (status: ComfyAPIStatusResponse | null, error?: Error | null) => void, status: (status: ComfyAPIStatusResponse | null, error?: Error | null) => void,
progress: (progress: Progress) => void, progress: (progress: Progress) => void,
@@ -219,19 +230,11 @@ export default class ComfyAPI {
* @param {number} number The index at which to queue the prompt, passing -1 will insert the prompt at the front of the queue * @param {number} number The index at which to queue the prompt, passing -1 will insert the prompt at the front of the queue
* @param {object} prompt The prompt data to queue * @param {object} prompt The prompt data to queue
*/ */
async queuePrompt(number: number, { output, workflow }, extra_data: any): Promise<ComfyAPIPromptResponse> { async queuePrompt(body: ComfyPromptRequest): Promise<ComfyAPIPromptResponse> {
const body: PromptRequestBody = { body.client_id = this.clientId;
client_id: this.clientId,
prompt: output,
extra_data,
front: false,
number: number
};
if (number === -1) { if (body.number === -1) {
body.front = true; body.front = true;
} else if (number != 0) {
body.number = number;
} }
let postBody = null; let postBody = null;

View File

@@ -78,7 +78,7 @@
} }
} }
let propsSidebarSize = 15; //15; let propsSidebarSize = 0;
function toggleProps() { function toggleProps() {
if (propsSidebarSize == 0) { if (propsSidebarSize == 0) {
@@ -90,11 +90,11 @@
} }
} }
let queueSidebarSize = 15; let queueSidebarSize = 20;
function toggleQueue() { function toggleQueue() {
if (queueSidebarSize == 0) { if (queueSidebarSize == 0) {
queueSidebarSize = 15; queueSidebarSize = 20;
app.resizeCanvas(); app.resizeCanvas();
} }
else { else {

View File

@@ -1,6 +1,6 @@
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType, type INodeInputSlot } from "@litegraph-ts/core"; import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType, type INodeInputSlot } from "@litegraph-ts/core";
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core"; import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
import ComfyAPI, { type ComfyAPIStatusResponse, type NodeID, type PromptID } from "$lib/api" import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyPromptExtraData, type ComfyPromptRequest, type NodeID, type PromptID } from "$lib/api"
import { getPngMetadata, importA1111 } from "$lib/pnginfo"; import { getPngMetadata, importA1111 } from "$lib/pnginfo";
import EventEmitter from "events"; import EventEmitter from "events";
import type TypedEmitter from "typed-emitter"; import type TypedEmitter from "typed-emitter";
@@ -385,6 +385,7 @@ export default class ComfyApp {
const queue = await this.api.getQueue(); const queue = await this.api.getQueue();
const history = await this.api.getHistory(); const history = await this.api.getHistory();
queueState.queueUpdated(queue); queueState.queueUpdated(queue);
queueState.historyUpdated(history);
} }
private requestPermissions() { private requestPermissions() {
@@ -739,13 +740,24 @@ export default class ComfyApp {
const p = await this.graphToPrompt(tag); const p = await this.graphToPrompt(tag);
console.debug(promptToGraphVis(p)) console.debug(promptToGraphVis(p))
const extra_data = { extra_pnginfo: { workflow: p.workflow } } const extraData: ComfyPromptExtraData = {
extra_pnginfo: {
workflow: p.workflow
},
subgraphs: [tag]
}
let error = null; let error = null;
let promptID = null; let promptID = null;
const request: ComfyPromptRequest = {
number: num,
extra_data: extraData,
prompt: p.output
}
try { try {
const response = await this.api.queuePrompt(num, p, extra_data); const response = await this.api.queuePrompt(request);
promptID = response.promptID; promptID = response.promptID;
error = response.error; error = response.error;
} catch (error) { } catch (error) {
@@ -768,7 +780,7 @@ export default class ComfyApp {
} }
this.lCanvas.draw(true, true); this.lCanvas.draw(true, true);
queueState.afterQueued(promptID, num, p, extra_data) queueState.afterQueued(promptID, num, p.output, extraData)
} }
} }
} finally { } finally {

View File

@@ -1,74 +1,113 @@
<script lang="ts"> <script lang="ts">
import queueState, { type QueueEntry } from "$lib/stores/queueState"; import queueState, { type CompletedQueueEntry, type QueueEntry, type QueueEntryStatus } from "$lib/stores/queueState";
import ProgressBar from "./ProgressBar.svelte"; import ProgressBar from "./ProgressBar.svelte";
import { getNodeInfo } from "$lib/utils" import Spinner from "./Spinner.svelte";
import type { Writable } from "svelte/store"; import { convertComfyOutputToComfyURL, convertFilenameToComfyURL, getNodeInfo } from "$lib/utils"
import type { Writable } from "svelte/store";
import type { QueueItemType } from "$lib/api";
let queuePending: Writable<QueueEntry[]> | null = null; let queuePending: Writable<QueueEntry[]> | null = null;
let queueRunning: Writable<QueueEntry[]> | null = null; let queueRunning: Writable<QueueEntry[]> | null = null;
let queueCompleted: Writable<CompletedQueueEntry[]> | null = null;
type QueueUIEntry = { type QueueUIEntry = {
message: string, message: string,
submessage: string, submessage: string,
date?: string, date?: string,
status: "success" | "error" | "pending" | "running" | "all_cached", status: QueueEntryStatus | "pending" | "running",
images?: string[] // URLs images?: string[], // URLs
error?: string
} }
$: if ($queueState) { $: if ($queueState) {
queuePending = $queueState.queuePending queuePending = $queueState.queuePending
queueRunning = $queueState.queueRunning queueRunning = $queueState.queueRunning
queueCompleted = $queueState.queueCompleted
}
let mode: QueueItemType = "queue";
function switchMode(newMode: QueueItemType) {
console.warn("SwitchMode", newMode)
const changed = mode !== newMode
mode = newMode
if (changed)
_entries = []
} }
let _entries: QueueUIEntry[] = [] let _entries: QueueUIEntry[] = []
$: if ($queuePending && $queuePending.length != _entries.length) { $: if (mode === "queue" && $queuePending && $queuePending.length != _entries.length) {
_entries = [] updateFromQueue();
for (const entry of $queuePending) {
// for (const outputs of Object.values(entry.outputs)) {
// const allImages = outputs.images.map(r => {
// // TODO configure backend URL
// const url = "http://localhost:8188/view?"
// const params = new URLSearchParams(r)
// return url + params
// });
//
// _entries.push({ allImages, name: "Output" })
// }
let date = null;
if (entry.queuedAt) {
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: 'numeric'
};
const dateTimeFormat = new Intl.DateTimeFormat('en-US', options);
date = dateTimeFormat.format(entry.queuedAt);
}
_entries.push({
message: "Prompt",
submessage: ".......",
date,
status: "pending",
images: null
})
}
console.error("BUILDENTRIES", _entries, $queuePending)
} }
else if (mode === "history" && $queueCompleted && $queueCompleted.length != _entries.length) {
updateFromHistory();
}
function convertEntry(entry: QueueEntry): QueueUIEntry {
const images = Object.values(entry.outputs).flatMap(o => o.images)
.map(convertComfyOutputToComfyURL);
let date = null;
if (entry.queuedAt) {
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: 'numeric'
};
const dateTimeFormat = new Intl.DateTimeFormat('en-US', options);
date = dateTimeFormat.format(entry.queuedAt);
}
let message = "Prompt";
if (entry.extraData.subgraphs)
message = `Prompt: ${entry.extraData.subgraphs.join(', ')}`
let submessage = `Nodes: ${Object.keys(entry.prompt).length}`
return {
message,
submessage,
date,
status: "pending",
images
}
}
function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry {
const result = convertEntry(entry.entry);
result.status = entry.status;
result.error = entry.error;
return result;
}
function updateFromQueue() {
_entries = $queuePending.map(convertEntry);
console.warn("[ComfyQueue] BUILDQUEUE", _entries, $queuePending)
}
function updateFromHistory() {
_entries = $queueCompleted.map(convertCompletedEntry);
console.warn("[ComfyQueue] BUILDHISTORY", _entries, $queueCompleted)
}
let queued = false
$: queued = Boolean($queueState.runningNodeID || $queueState.progress);
let inProgress = false;
$: inProgress = typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0;
</script> </script>
<div class="queue"> <div class="queue">
<div class="queue-entries"> <div class="queue-entries">
{#each _entries as entry} {#each _entries as entry}
<div class="queue-entry {entry.status}"> <div class="queue-entry {entry.status}">
{#if entry.images} {#if entry.images.length > 0}
<img class="queue-entry-image" src={entry.images[0]} alt="thumbnail" /> <img class="queue-entry-image" src={entry.images[0]} alt="thumbnail" />
{:else} {:else}
<div class="queue-entry-image-placeholder" /> <!-- <div class="queue-entry-image-placeholder" /> -->
{/if} {/if}
<div class="queue-entry-details"> <div class="queue-entry-details">
<div class="queue-entry-message"> <div class="queue-entry-message">
@@ -88,68 +127,79 @@
</div> </div>
{/each} {/each}
</div> </div>
<div class="mode-buttons">
<div class="mode-button"
on:click={() => switchMode("queue")}
class:mode-selected={mode === "queue"}>
Queue
</div>
<div class="mode-button"
on:click={() => switchMode("history")}
class:mode-selected={mode === "history"}>
History
</div>
</div>
<div class="bottom"> <div class="bottom">
{#if $queueState.runningNodeID || $queueState.progress} <div class="queue-remaining" class:queued class:in-progress={inProgress}>
{#if inProgress}
<Spinner />
<div class="status">
Queued prompts: {$queueState.queueRemaining}
</div>
{:else}
<div>
Nothing queued.
</div>
{/if}
</div>
{#if queued}
<div class="node-name"> <div class="node-name">
<span>Node: {getNodeInfo($queueState.runningNodeID)}</span> <span>Node: {getNodeInfo($queueState.runningNodeID)}</span>
</div> </div>
<div> <div>
<ProgressBar value={$queueState.progress?.value} max={$queueState.progress?.max} styles="height: 30px;" /> <ProgressBar value={$queueState.progress?.value} max={$queueState.progress?.max} />
</div>
{/if}
{#if typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0}
<div class="queue-remaining in-progress">
<div>
Queued prompts: {$queueState.queueRemaining}.
</div>
</div>
{:else}
<div class="queue-remaining done">
<div>
Nothing queued.
</div>
</div> </div>
{/if} {/if}
</div> </div>
</div> </div>
<style lang="scss"> <style lang="scss">
$pending-height: 300px; $pending-height: 200px;
$bottom-bar-height: 70px;
$mode-buttons-height: 30px;
$queue-height: calc(100vh - #{$pending-height} - #{$mode-buttons-height} - #{$bottom-bar-height});
.queue { .queue {
} color: var(--body-text-color);
.queue-remaining {
height: 5em;
width: 100%;
text-align: center;
border: 5px solid #CCC;
position: relative;
display: flex;
justify-content: center;
align-items: center;
} }
.queue-entries { .queue-entries {
overflow-y: scroll; overflow-y: scroll;
max-height: calc(100vh - $pending-height); height: $queue-height;
max-height: $queue-height;
} }
.queue-entry { .queue-entry {
padding: 0.5rem; padding: 1.0rem;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
border-top: 2px solid var(--neutral-200); border-bottom: 1px solid var(--panel-border-color);
border-bottom: 1px solid var(--neutral-400); background: var(--panel-background-fill);
&.success { &.success {
background: green /* background: green; */
} }
&.error { &.error {
background: red background: red;
} }
&.cached { &.all_cached {
background: grey background: grey;
}
&.running {
/* background: lightblue; */
}
&.pending, &.unknown {
/* background: orange; */
} }
} }
@@ -174,54 +224,86 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
white-space: nowrap;
} }
.queue-entry-message { .queue-entry-message {
width: var(--size-20); font-size: 15px;
font-size: large;
} }
.queue-entry-submessage { .queue-entry-submessage {
width: var(--size-20); font-size: 12px;
} }
.queue-entry-queued-at { .queue-entry-queued-at {
width: auto; width: auto;
font-size: 14px; font-size: 10px;
position:absolute; position:absolute;
right: 0px; right: 0px;
bottom:0px; bottom:0px;
padding: 0.0rem 0.4rem; padding: 0.0rem 0.4rem;
/* color: var(--neutral-600) */ color: var(--body-text-color);
color: var(--neutral-600);
} }
.node-name { .mode-buttons {
border: 5px solid #CCC; height: calc($mode-buttons-height);
background-color: var(--color-red-300);
padding: 0.2em;
display: flex; display: flex;
flex-direction: row;
text-align: center;
justify-content: center; justify-content: center;
align-items: center;
.mode-button {
padding: 0.2rem;
width: 100%;
height: 100%;
border: 1px solid var(--panel-border-color);
font-weight: bold;
background: var(--button-secondary-background-fill);
&:hover {
background: var(--button-secondary-background-fill-hover);
filter: brightness(120%);
}
&:active {
filter: brightness(50%)
}
&.mode-selected {
filter: brightness(150%)
}
}
} }
.bottom { .bottom {
width: 100%; width: 100%;
height: $pending-height; height: calc($pending-height);
position: absolute; position: absolute;
} border: 2px solid var(--panel-border-color);
.in-progress { .node-name {
background-color: var(--secondary-300); background-color: var(--comfy-node-name-background);
} color: var(--comfy-node-name-foreground);
.done { padding: 0.2em;
background-color: var(--color-grey-200); display: flex;
} justify-content: center;
align-items: center;
}
.queue-item { .queue-remaining {
height: 1.5em; height: calc($pending-height - $bottom-bar-height - 50px);
width: 10em; width: 100%;
text-align: center; text-align: center;
border: 1px solid black; position: relative;
display: flex;
justify-content: space-evenly;
align-items: center;
background: var(--panel-background-fill);
> .status {
}
&.queued {
height: calc($pending-height - $bottom-bar-height);
}
}
} }
</style> </style>

View File

@@ -24,16 +24,16 @@
<style> <style>
.progress { .progress {
width: 100%; width: 100%;
height: 100%; height: 30px;
text-align: center; text-align: center;
background-color: lightgrey; background: var(--comfy-progress-bar-background);
padding: 0px; padding: 0px;
position: relative; position: relative;
} }
.bar { .bar {
height: 100%; height: 100%;
background-color: #B3D8A9; background: var(--comfy-progress-bar-foreground);
} }
.label { .label {

View File

@@ -0,0 +1,24 @@
<span class="loader" {...$$restProps}/>
<style lang="scss">
$border-bottom-color_1: var(--comfy-spinner-accent-color);
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loader {
width: 48px;
height: 48px;
border: 5px solid var(--comfy-spinner-main-color);
border-bottom-color: $border-bottom-color_1;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 2s linear infinite;
}
</style>

View File

@@ -1,5 +1,5 @@
import type { ComfyAPIHistoryItem, ComfyAPIQueueResponse, ComfyAPIStatusResponse, NodeID, PromptID } from "$lib/api"; import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyPromptExtraData, NodeID, PromptID } from "$lib/api";
import type { Progress, SerializedPrompt, SerializedPromptOutputs } from "$lib/components/ComfyApp"; import type { Progress, SerializedPrompt, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp";
import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes"; import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes";
import { get, writable, type Writable } from "svelte/store"; import { get, writable, type Writable } from "svelte/store";
@@ -7,33 +7,39 @@ export type QueueItem = {
name: string name: string
} }
export type QueueEntryStatus = "success" | "error" | "all_cached" | "unknown";
type QueueStateOps = { type QueueStateOps = {
queueUpdated: (queue: ComfyAPIQueueResponse) => void, queueUpdated: (resp: ComfyAPIQueueResponse) => void,
historyUpdated: (resp: ComfyAPIHistoryResponse) => void,
statusUpdated: (status: ComfyAPIStatusResponse | null) => void, statusUpdated: (status: ComfyAPIStatusResponse | null) => void,
executingUpdated: (promptID: PromptID | null, runningNodeID: NodeID | null) => void, executingUpdated: (promptID: PromptID | null, runningNodeID: NodeID | null) => void,
executionCached: (promptID: PromptID, nodes: NodeID[]) => void, executionCached: (promptID: PromptID, nodes: NodeID[]) => void,
executionError: (promptID: PromptID, message: string) => void, executionError: (promptID: PromptID, message: string) => void,
progressUpdated: (progress: Progress) => void progressUpdated: (progress: Progress) => void
afterQueued: (promptID: PromptID, number: number, prompt: SerializedPrompt, extraData: any) => void afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
onExecuted: (promptID: PromptID, nodeID: NodeID, output: GalleryOutput) => void onExecuted: (promptID: PromptID, nodeID: NodeID, output: GalleryOutput) => void
} }
export type QueueEntry = { export type QueueEntry = {
/* Data preserved on page refresh */
number: number, number: number,
queuedAt?: Date, queuedAt?: Date,
finishedAt?: Date, finishedAt?: Date,
promptID: PromptID, promptID: PromptID,
prompt: SerializedPrompt, prompt: SerializedPromptInputsAll,
extraData: any, extraData: ComfyPromptExtraData,
goodOutputs: NodeID[], goodOutputs: NodeID[],
// Collected while the prompt is still executing /* Data not sent by Comfy's API, lost on page refresh */
/* Prompt outputs, collected while the prompt is still executing */
outputs: SerializedPromptOutputs, outputs: SerializedPromptOutputs,
} }
export type CompletedQueueEntry = { export type CompletedQueueEntry = {
entry: QueueEntry, entry: QueueEntry,
type: "success" | "error" | "all_cached", status: QueueEntryStatus,
error?: string, error?: string,
} }
@@ -70,12 +76,31 @@ function toQueueEntry(resp: ComfyAPIHistoryItem): QueueEntry {
} }
} }
function queueUpdated(queue: ComfyAPIQueueResponse) { function toCompletedQueueEntry(resp: ComfyAPIHistoryEntry): CompletedQueueEntry {
console.debug("[queueState] queueUpdated", queue.running.length, queue.pending.length) const entry = toQueueEntry(resp.prompt)
entry.outputs = resp.outputs;
return {
entry,
status: Object.values(entry.outputs).length > 0 ? "success" : "all_cached",
error: null
}
}
function queueUpdated(resp: ComfyAPIQueueResponse) {
console.debug("[queueState] queueUpdated", resp.running.length, resp.pending.length)
store.update((s) => { store.update((s) => {
s.queueRunning.set(queue.running.map(toQueueEntry)); s.queueRunning.set(resp.running.map(toQueueEntry));
s.queuePending.set(queue.pending.map(toQueueEntry)); s.queuePending.set(resp.pending.map(toQueueEntry));
s.queueRemaining = queue.pending.length; s.queueRemaining = resp.pending.length;
return s
})
}
function historyUpdated(resp: ComfyAPIHistoryResponse) {
console.debug("[queueState] historyUpdated", Object.values(resp.history).length)
store.update((s) => {
const values = Object.values(resp.history) // TODO Order by prompt finished date!
s.queueCompleted.set(values.map(toCompletedQueueEntry));
return s return s
}) })
} }
@@ -115,7 +140,7 @@ function executingUpdated(promptID: PromptID | null, runningNodeID: NodeID | nul
s.queueCompleted.update(qc => { s.queueCompleted.update(qc => {
const completed: CompletedQueueEntry = { const completed: CompletedQueueEntry = {
entry, entry,
type: "success", status: "success",
} }
qc.push(completed) qc.push(completed)
return qc return qc
@@ -142,7 +167,7 @@ function executionCached(promptID: PromptID, nodes: NodeID[]) {
s.queueCompleted.update(qc => { s.queueCompleted.update(qc => {
const completed: CompletedQueueEntry = { const completed: CompletedQueueEntry = {
entry, entry,
type: "all_cached", status: "all_cached",
} }
qc.push(completed) qc.push(completed)
return qc return qc
@@ -167,7 +192,7 @@ function executionError(promptID: PromptID, message: string) {
s.queueCompleted.update(qc => { s.queueCompleted.update(qc => {
const completed: CompletedQueueEntry = { const completed: CompletedQueueEntry = {
entry, entry,
type: "error", status: "error",
error: message error: message
} }
qc.push(completed) qc.push(completed)
@@ -180,8 +205,8 @@ function executionError(promptID: PromptID, message: string) {
}) })
} }
function afterQueued(promptID: PromptID, number: number, prompt: SerializedPrompt, extraData: any) { function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) {
console.debug("[queueState] afterQueued", promptID, Object.keys(prompt.workflow.nodes)) console.debug("[queueState] afterQueued", promptID, Object.keys(prompt.nodes))
store.update(s => { store.update(s => {
const entry: QueueEntry = { const entry: QueueEntry = {
number, number,
@@ -215,6 +240,7 @@ const queueStateStore: WritableQueueStateStore =
{ {
...store, ...store,
queueUpdated, queueUpdated,
historyUpdated,
statusUpdated, statusUpdated,
progressUpdated, progressUpdated,
executingUpdated, executingUpdated,

View File

@@ -135,6 +135,12 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat
}); });
} }
export function convertComfyOutputToComfyURL(output: GalleryOutputEntry): string {
const params = new URLSearchParams(output)
const url = `http://${location.hostname}:8188` // TODO make configurable
return url + "/view?" + params
}
export function convertFilenameToComfyURL(filename: string, export function convertFilenameToComfyURL(filename: string,
subfolder: string = "", subfolder: string = "",
type: "input" | "output" | "temp" = "output"): string { type: "input" | "output" | "temp" = "output"): string {

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- <link rel="icon" href="%sveltekit.assets%/favicon.png" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#2196f3">
</head>
<body>
<div id="app">
<script type="module" src='/src/main-mobile.ts'></script>
</body>
</html>

View File

@@ -21,6 +21,12 @@ body {
--comfy-dropdown-item-background-hover: var(--neutral-400); --comfy-dropdown-item-background-hover: var(--neutral-400);
--comfy-dropdown-item-color-active: var(--neutral-100); --comfy-dropdown-item-color-active: var(--neutral-100);
--comfy-dropdown-item-background-active: var(--secondary-600); --comfy-dropdown-item-background-active: var(--secondary-600);
--comfy-progress-bar-background: var(--secondary-300);
--comfy-progress-bar-foreground: #var(--body-text-color);
--comfy-node-name-background: var(--color-red-300);
--comfy-node-name-foreground: var(--body-text-color);
--comfy-spinner-main-color: var(--neutral-400);
--comfy-spinner-accent-color: var(--secondary-500);
} }
.dark { .dark {
@@ -34,6 +40,17 @@ body {
--comfy-dropdown-item-background-hover: var(--neutral-600); --comfy-dropdown-item-background-hover: var(--neutral-600);
--comfy-dropdown-item-background-active: var(--secondary-600); --comfy-dropdown-item-background-active: var(--secondary-600);
--comfy-dropdown-border-color: var(--neutral-600); --comfy-dropdown-border-color: var(--neutral-600);
--comfy-progress-bar-background: var(--secondary-400);
--comfy-progress-bar-foreground: #var(--body-text-color);
--comfy-node-name-background: var(--primary-500);
--comfy-node-name-foreground: var(--body-text-color);
--comfy-spinner-main-color: var(--neutral-600);
--comfy-spinner-accent-color: var(--secondary-600);
}
.mobile {
--comfy-progress-bar-background: lightgrey;
--comfy-progress-bar-foreground: #B3D8A9
} }
@mixin disable-input { @mixin disable-input {