Mobile queue
This commit is contained in:
@@ -403,6 +403,9 @@ export default class ComfyApp {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
const workflows = state.workflows as SerializedAppState[];
|
const workflows = state.workflows as SerializedAppState[];
|
||||||
|
if (workflows.length === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
await Promise.all(workflows.map(w => {
|
await Promise.all(workflows.map(w => {
|
||||||
return this.openWorkflow(w, { refreshCombos: defs, warnMissingNodeTypes: false, setActive: false }).catch(error => {
|
return this.openWorkflow(w, { refreshCombos: defs, warnMissingNodeTypes: false, setActive: false }).catch(error => {
|
||||||
console.error("Failed restoring previous workflow", error)
|
console.error("Failed restoring previous workflow", error)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import notify from "$lib/notify";
|
|||||||
import workflowState from "$lib/stores/workflowState";
|
import workflowState from "$lib/stores/workflowState";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import type ComfyApp from "$lib/components/ComfyApp";
|
import type ComfyApp from "$lib/components/ComfyApp";
|
||||||
|
import interfaceState from "$lib/stores/interfaceState";
|
||||||
|
|
||||||
export interface ComfySendOutputActionProperties extends ComfyGraphNodeProperties {
|
export interface ComfySendOutputActionProperties extends ComfyGraphNodeProperties {
|
||||||
}
|
}
|
||||||
@@ -41,36 +42,8 @@ export default class ComfySendOutputAction extends ComfyGraphNode {
|
|||||||
|
|
||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
|
|
||||||
const doSend = (modal: ModalData) => {
|
interfaceState.querySendOutput(value, type, receiveTargets, () => {
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
|
|
||||||
const { workflow, targetNode } = get(modal.state) as SendOutputModalResult;
|
|
||||||
console.warn("send", workflow, targetNode);
|
|
||||||
|
|
||||||
if (workflow == null || targetNode == null)
|
|
||||||
return
|
|
||||||
|
|
||||||
const app = (window as any).app as ComfyApp;
|
|
||||||
if (app == null) {
|
|
||||||
console.error("Couldn't get app!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targetNode.receiveOutput(value);
|
|
||||||
workflowState.setActiveWorkflow(app.lCanvas, workflow.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
modalState.pushModal({
|
|
||||||
title: "Send Output",
|
|
||||||
closeOnClick: true,
|
|
||||||
showCloseButton: true,
|
|
||||||
svelteComponent: SendOutputModal,
|
|
||||||
svelteProps: {
|
|
||||||
value,
|
|
||||||
type,
|
|
||||||
receiveTargets
|
|
||||||
},
|
|
||||||
onClose: doSend
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ function notifyf7(text: string, options: NotifyOptions) {
|
|||||||
if (!f7)
|
if (!f7)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
console.error(options)
|
|
||||||
let closeTimeout = options.timeout
|
let closeTimeout = options.timeout
|
||||||
if (closeTimeout === undefined)
|
if (closeTimeout === undefined)
|
||||||
closeTimeout = 3000;
|
closeTimeout = 3000;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { debounce } from '$lib/utils';
|
import { debounce, isMobileBrowser } from '$lib/utils';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
import type { Readable, Writable } from 'svelte/store';
|
import type { Readable, Writable } from 'svelte/store';
|
||||||
import type { WorkflowInstID } from './workflowState';
|
import type { WorkflowInstID, WorkflowReceiveOutputTargets } from './workflowState';
|
||||||
|
import modalState, { type ModalData } from './modalState';
|
||||||
|
import type { SlotType } from '@litegraph-ts/core';
|
||||||
|
import type ComfyApp from '$lib/components/ComfyApp';
|
||||||
|
import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte";
|
||||||
|
import workflowState from './workflowState';
|
||||||
|
|
||||||
export type InterfaceState = {
|
export type InterfaceState = {
|
||||||
// Show a large indicator of the currently editing number value for mobile
|
// Show a large indicator of the currently editing number value for mobile
|
||||||
@@ -15,13 +20,16 @@ export type InterfaceState = {
|
|||||||
isJumpingToNode: boolean,
|
isJumpingToNode: boolean,
|
||||||
|
|
||||||
selectedWorkflowIndex: number | null
|
selectedWorkflowIndex: number | null
|
||||||
showingWorkflow: boolean
|
showingWorkflow: boolean,
|
||||||
|
selectedTab: number,
|
||||||
|
showSheet: boolean,
|
||||||
|
|
||||||
isDarkMode: boolean
|
isDarkMode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceStateOps = {
|
type InterfaceStateOps = {
|
||||||
showIndicator: (pointerX: number, pointerY: number, value: any) => void,
|
showIndicator: (pointerX: number, pointerY: number, value: any) => void,
|
||||||
|
querySendOutput: (value: any, type: SlotType, receiveTargets: WorkflowReceiveOutputTargets[], cb: (modal: ModalData) => void) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WritableInterfaceStateStore = Writable<InterfaceState> & InterfaceStateOps;
|
export type WritableInterfaceStateStore = Writable<InterfaceState> & InterfaceStateOps;
|
||||||
@@ -34,6 +42,8 @@ const store: Writable<InterfaceState> = writable(
|
|||||||
|
|
||||||
graphTransitioning: false,
|
graphTransitioning: false,
|
||||||
isJumpingToNode: false,
|
isJumpingToNode: false,
|
||||||
|
selectedTab: 1,
|
||||||
|
showSheet: false,
|
||||||
|
|
||||||
selectedWorkflowIndex: null,
|
selectedWorkflowIndex: null,
|
||||||
showingWorkflow: false,
|
showingWorkflow: false,
|
||||||
@@ -57,9 +67,49 @@ function showIndicator(pointerX: number, pointerY: number, value: any) {
|
|||||||
debounceDrag();
|
debounceDrag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function querySendOutput(value: any, type: SlotType, receiveTargets: WorkflowReceiveOutputTargets[], cb: (modal: ModalData) => void) {
|
||||||
|
if (isMobileBrowser(navigator.userAgent)) {
|
||||||
|
store.update(s => { s.showSheet = true; return s; })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const doSend = (modal: ModalData) => {
|
||||||
|
cb(modal)
|
||||||
|
|
||||||
|
const { workflow, targetNode } = get(modal.state) as SendOutputModalResult;
|
||||||
|
console.warn("send", workflow, targetNode);
|
||||||
|
|
||||||
|
if (workflow == null || targetNode == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
const app = (window as any).app as ComfyApp;
|
||||||
|
if (app == null) {
|
||||||
|
console.error("Couldn't get app!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetNode.receiveOutput(value);
|
||||||
|
workflowState.setActiveWorkflow(app.lCanvas, workflow.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.pushModal({
|
||||||
|
title: "Send Output",
|
||||||
|
closeOnClick: true,
|
||||||
|
showCloseButton: true,
|
||||||
|
svelteComponent: SendOutputModal,
|
||||||
|
svelteProps: {
|
||||||
|
value,
|
||||||
|
type,
|
||||||
|
receiveTargets
|
||||||
|
},
|
||||||
|
onClose: doSend
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const interfaceStateStore: WritableInterfaceStateStore =
|
const interfaceStateStore: WritableInterfaceStateStore =
|
||||||
{
|
{
|
||||||
...store,
|
...store,
|
||||||
showIndicator
|
showIndicator,
|
||||||
|
querySendOutput
|
||||||
}
|
}
|
||||||
export default interfaceStateStore;
|
export default interfaceStateStore;
|
||||||
|
|||||||
@@ -445,7 +445,6 @@ function queueCleared(type: QueueItemType) {
|
|||||||
store.update(s => {
|
store.update(s => {
|
||||||
if (type === "queue") {
|
if (type === "queue") {
|
||||||
s.queuePending.set([]);
|
s.queuePending.set([]);
|
||||||
s.queueRunning.set([]);
|
|
||||||
s.queueRemaining = 0;
|
s.queueRemaining = 0;
|
||||||
s.runningNodeID = null;
|
s.runningNodeID = null;
|
||||||
s.progress = null;
|
s.progress = null;
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import type { FileData as GradioFileData } from "@gradio/upload";
|
|||||||
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, type NodeID, type SlotType, type Vector4, type SerializedLGraphNode } from "@litegraph-ts/core";
|
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, type NodeID, type SlotType, type Vector4, type SerializedLGraphNode } from "@litegraph-ts/core";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import type { ComfyNodeID } from "./api";
|
import type { ComfyNodeID } from "./api";
|
||||||
import { type SerializedPrompt } from "./components/ComfyApp";
|
import ComfyApp, { type SerializedPrompt } from "./components/ComfyApp";
|
||||||
import workflowState from "./stores/workflowState";
|
import workflowState, { type WorkflowReceiveOutputTargets } from "./stores/workflowState";
|
||||||
import { ImageViewer } from "./ImageViewer";
|
import { ImageViewer } from "./ImageViewer";
|
||||||
import configState from "$lib/stores/configState";
|
import configState from "$lib/stores/configState";
|
||||||
|
import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte";
|
||||||
|
|
||||||
export function clamp(n: number, min: number, max: number): number {
|
export function clamp(n: number, min: number, max: number): number {
|
||||||
if (max <= min)
|
if (max <= min)
|
||||||
@@ -734,3 +735,9 @@ export function partition<T>(myArray: T[], chunkSize: number): T[] {
|
|||||||
|
|
||||||
return tempArray;
|
return tempArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].map(a => new RegExp(a, "i"))
|
||||||
|
|
||||||
|
export function isMobileBrowser(userAgent: string): boolean {
|
||||||
|
return MOBILE_USER_AGENTS.some(a => userAgent.match(a))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
const params = new URLSearchParams(window.location.search)
|
import { isMobileBrowser } from "$lib/utils"
|
||||||
|
|
||||||
const MOBILE_USER_AGENTS = ["iPhone", "iPad", "Android", "BlackBerry", "WebOs"].map(a => new RegExp(a, "i"))
|
|
||||||
|
|
||||||
function isMobileBrowser(userAgent: string): boolean {
|
|
||||||
return MOBILE_USER_AGENTS.some(a => userAgent.match(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMobile = isMobileBrowser(navigator.userAgent);
|
const isMobile = isMobileBrowser(navigator.userAgent);
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
if (params.get("desktop") !== "true") {
|
if (params.get("desktop") !== "true") {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
window.location.href = "/mobile/"
|
window.location.href = "/mobile/"
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||||
import queueState from "$lib/stores/queueState";
|
|
||||||
import workflowState, { ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
import workflowState, { ComfyBoxWorkflow } from "$lib/stores/workflowState";
|
||||||
import { getNodeInfo } from "$lib/utils"
|
|
||||||
import { Image, LayoutTextSidebarReverse } from "svelte-bootstrap-icons";
|
|
||||||
|
|
||||||
import { Link, Toolbar } from "framework7-svelte"
|
import { Link, Toolbar } from "framework7-svelte"
|
||||||
import ProgressBar from "$lib/components/ProgressBar.svelte";
|
|
||||||
import Progressbar from "$lib/components/f7/progressbar.svelte";
|
|
||||||
import Indicator from "./Indicator.svelte";
|
|
||||||
import interfaceState from "$lib/stores/interfaceState";
|
|
||||||
import type { WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
|
||||||
|
|
||||||
export let subworkflowID: number = -1;
|
export let subworkflowID: number = -1;
|
||||||
export let app: ComfyApp = undefined;
|
export let app: ComfyApp = undefined;
|
||||||
let layoutState: WritableLayoutStateStore = null;
|
|
||||||
let fileInput: HTMLInputElement = undefined;
|
|
||||||
let workflow: ComfyBoxWorkflow | null = null;
|
let workflow: ComfyBoxWorkflow | null = null;
|
||||||
|
|
||||||
$: workflow = $workflowState.activeWorkflow;
|
$: workflow = $workflowState.activeWorkflow;
|
||||||
@@ -24,41 +14,16 @@
|
|||||||
navigator.vibrate(20)
|
navigator.vibrate(20)
|
||||||
app.runDefaultQueueAction()
|
app.runDefaultQueueAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshCombos() {
|
|
||||||
navigator.vibrate(20)
|
|
||||||
await app.refreshComboInNodes()
|
|
||||||
}
|
|
||||||
|
|
||||||
function doLoad(): void {
|
|
||||||
if (!fileInput)
|
|
||||||
return;
|
|
||||||
|
|
||||||
navigator.vibrate(20)
|
|
||||||
fileInput.value = null;
|
|
||||||
fileInput.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadWorkflow(): void {
|
|
||||||
app.handleFile(fileInput.files[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSaveLocal(): void {
|
|
||||||
navigator.vibrate(20)
|
|
||||||
app.saveStateToLocalStorage();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Toolbar bottom color="red" style="bottom: calc(var(--f7-toolbar-height))">
|
<Toolbar bottom color="red" style="bottom: calc(var(--f7-toolbar-height))">
|
||||||
{#if workflow != null && workflow.attrs.queuePromptButtonName != ""}
|
{#if workflow != null && workflow.attrs.queuePromptButtonName != ""}
|
||||||
|
<div style:width="100%">
|
||||||
<Link on:click={queuePrompt}>
|
<Link on:click={queuePrompt}>
|
||||||
{workflow.attrs.queuePromptButtonName}
|
{workflow.attrs.queuePromptButtonName}
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Link on:click={refreshCombos}>🔄</Link>
|
|
||||||
<Link on:click={doSaveLocal}>Save Local</Link>
|
|
||||||
<Link on:click={doLoad}>Load</Link>
|
|
||||||
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -66,6 +31,10 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.toolbar) {
|
||||||
|
--f7-toolbar-font-size: 13pt;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.dark .toolbar.color-red) {
|
:global(.dark .toolbar.color-red) {
|
||||||
background: var(--neutral-700) !important;
|
background: var(--neutral-700) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,8 @@
|
|||||||
$: toolbarCount = $interfaceState.showingWorkflow ? 2 : 1;
|
$: toolbarCount = $interfaceState.showingWorkflow ? 2 : 1;
|
||||||
|
|
||||||
const ICON_SIZE = "1.5rem";
|
const ICON_SIZE = "1.5rem";
|
||||||
|
|
||||||
|
let selectedTab = 1;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bottom" style:--toolbarCount={toolbarCount}>
|
<div class="bottom" style:--toolbarCount={toolbarCount}>
|
||||||
@@ -108,13 +110,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Toolbar bottom tabbar color="blue" class={toolbarCount > 1 ? "hasGenToolbar" : ""}>
|
<Toolbar bottom tabbar color="blue" class={toolbarCount > 1 ? "hasGenToolbar" : ""}>
|
||||||
<Link transition="f7-dive" href="/queue/">
|
<Link transition="f7-dive" href="/queue/" tabLinkActive={$interfaceState.selectedTab === 0}>
|
||||||
<LayoutTextSidebarReverse width={ICON_SIZE} height={ICON_SIZE} />
|
<LayoutTextSidebarReverse width={ICON_SIZE} height={ICON_SIZE} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link transition="f7-dive" href={centerHref} tabLinkActive>
|
<Link transition="f7-dive" href={centerHref} tabLinkActive={$interfaceState.selectedTab === 1}>
|
||||||
<Image width={ICON_SIZE} height={ICON_SIZE} />
|
<Image width={ICON_SIZE} height={ICON_SIZE} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link transition="f7-dive" href="/gallery/">
|
<Link transition="f7-dive" href="/gallery/" tabLinkActive={$interfaceState.selectedTab === 2}>
|
||||||
<Grid width={ICON_SIZE} height={ICON_SIZE} />
|
<Grid width={ICON_SIZE} height={ICON_SIZE} />
|
||||||
</Link>
|
</Link>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
@@ -143,6 +145,11 @@
|
|||||||
border-top: 2px solid var(--neutral-600);
|
border-top: 2px solid var(--neutral-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.dark .tab-link-active) {
|
||||||
|
--f7-tabbar-link-active-color: var(--secondary-500);
|
||||||
|
--f7-tabbar-link-active-bg-color: #283547;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
--toolbarCount: 1;
|
--toolbarCount: 1;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte"
|
import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link, f7 } from "framework7-svelte"
|
||||||
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
||||||
import type ComfyApp from "$lib/components/ComfyApp";
|
import type ComfyApp from "$lib/components/ComfyApp";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import { partition, showLightbox } from "$lib/utils";
|
import { partition, showLightbox } from "$lib/utils";
|
||||||
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
|
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
|
||||||
import { showMobileLightbox } from "$lib/components/utils";
|
import { showMobileLightbox } from "$lib/components/utils";
|
||||||
|
import notify from "$lib/notify";
|
||||||
|
|
||||||
export let app: ComfyApp
|
export let app: ComfyApp
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@
|
|||||||
|
|
||||||
let gridCols = 3;
|
let gridCols = 3;
|
||||||
|
|
||||||
|
function onPageBeforeIn() {
|
||||||
|
$interfaceState.selectedTab = 2;
|
||||||
|
}
|
||||||
|
|
||||||
$: buildImageList(_entries);
|
$: buildImageList(_entries);
|
||||||
|
|
||||||
function buildImageList(entries: ReadonlyArray<QueueUIEntry>) {
|
function buildImageList(entries: ReadonlyArray<QueueUIEntry>) {
|
||||||
@@ -39,14 +44,16 @@
|
|||||||
showMobileLightbox(allImages, index)
|
showMobileLightbox(allImages, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function clearHistory() {
|
async function clearHistory() {
|
||||||
|
f7.dialog.confirm("Are you sure you want to clear the current history?", async () => {
|
||||||
await app.clearQueue("history");
|
await app.clearQueue("history");
|
||||||
uiQueueState.updateEntries(true)
|
uiQueueState.updateEntries(true)
|
||||||
|
notify("History cleared!")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page name="gallery">
|
<Page name="gallery" on:pageBeforeIn={onPageBeforeIn}>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<NavLeft></NavLeft>
|
<NavLeft></NavLeft>
|
||||||
<NavTitle>Gallery</NavTitle>
|
<NavTitle>Gallery</NavTitle>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<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, List, ListItem, Card, CardHeader, CardContent, CardFooter, f7 } from "framework7-svelte"
|
||||||
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
||||||
import type ComfyApp from "$lib/components/ComfyApp";
|
import type ComfyApp from "$lib/components/ComfyApp";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
@@ -8,10 +8,108 @@
|
|||||||
import interfaceState from "$lib/stores/interfaceState";
|
import interfaceState from "$lib/stores/interfaceState";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import GenToolbar from '../GenToolbar.svelte'
|
import GenToolbar from '../GenToolbar.svelte'
|
||||||
|
import { partition, showLightbox, truncateString } from "$lib/utils";
|
||||||
|
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
|
||||||
|
import { showMobileLightbox } from "$lib/components/utils";
|
||||||
|
import queueState from "$lib/stores/queueState";
|
||||||
|
import notify from "$lib/notify";
|
||||||
|
|
||||||
|
export let app: ComfyApp
|
||||||
|
|
||||||
|
let _entries: ReadonlyArray<QueueUIEntry> = []
|
||||||
|
$: _entries = $uiQueueState.queueUIEntries;
|
||||||
|
|
||||||
|
function onPageBeforeIn() {
|
||||||
|
$interfaceState.selectedTab = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function interrupt() {
|
||||||
|
await app.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearQueue() {
|
||||||
|
f7.dialog.confirm("Are you sure you want to clear the current queue?", async () => {
|
||||||
|
await app.clearQueue("queue");
|
||||||
|
uiQueueState.updateEntries(true)
|
||||||
|
notify("Queue cleared!")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPrompt(entry: QueueUIEntry): string {
|
||||||
|
let s = ""
|
||||||
|
for (const inputs of Object.values(entry.entry.prompt)) {
|
||||||
|
if (inputs.class_type === "CLIPTextEncode") {
|
||||||
|
for (const [key, value] of Object.entries(inputs.inputs)) {
|
||||||
|
if (key === "text") {
|
||||||
|
s += value + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return truncateString(s, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doCancel(entry: QueueUIEntry) {
|
||||||
|
if ($queueState.isInterrupting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO support interrupting from multiple running items!
|
||||||
|
if (entry.status === "running") {
|
||||||
|
await app.interrupt();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await app.deleteQueueItem("queue", entry.entry.promptID);
|
||||||
|
}
|
||||||
|
|
||||||
|
notify("Queue item canceled.")
|
||||||
|
uiQueueState.updateEntries(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCardImage(entry: QueueUIEntry): string {
|
||||||
|
if (entry.images.length > 0)
|
||||||
|
return entry.images[0]
|
||||||
|
return "https://cdn.framework7.io/placeholder/nature-1000x600-3.jpg"
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page name="queue">
|
<Page name="queue" on:pageBeforeIn={onPageBeforeIn}>
|
||||||
<Navbar title="Queue" />
|
<Navbar>
|
||||||
|
<NavLeft></NavLeft>
|
||||||
|
<NavTitle>Queue</NavTitle>
|
||||||
|
<NavRight>
|
||||||
|
<Link on:click={interrupt}>🛑️</Link>
|
||||||
|
<Link on:click={clearQueue}>🗑️</Link>
|
||||||
|
</NavRight>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
|
<Block>
|
||||||
|
<List>
|
||||||
|
{#each _entries as entry, i}
|
||||||
|
{@const prompt = findPrompt(entry)}
|
||||||
|
<ListItem>
|
||||||
|
<Card outlineMd class="demo-card-header-pic">
|
||||||
|
<CardHeader valign="bottom"
|
||||||
|
style="background-image: url({getCardImage(entry)})">
|
||||||
|
{entry.message}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p class="list-entry-message">{entry.submessage}</p>
|
||||||
|
<p>
|
||||||
|
{prompt}
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Link on:click={() => doCancel(entry)}>Cancel</Link>
|
||||||
|
{#if entry.date}
|
||||||
|
<p>{entry.date}</p>
|
||||||
|
{/if}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</ListItem>
|
||||||
|
{/each}
|
||||||
|
</List>
|
||||||
|
</Block>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -31,4 +129,27 @@
|
|||||||
:global(.root-container.mobile > .block > .v-pane) {
|
:global(.root-container.mobile > .block > .v-pane) {
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-entry-image {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-bottom: var(--f7-grid-gap);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
filter: brightness(120%) contrast(120%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-entry-message {
|
||||||
|
font-size: 15pt;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Page, Navbar, Tabs, Tab, NavLeft, NavTitle, NavRight, Link } from "framework7-svelte"
|
import { Page, Navbar, Tabs, Tab, NavLeft, NavTitle, NavRight, Link, Actions, ActionsGroup, ActionsButton, ActionsLabel, Sheet, Toolbar, PageContent, Block } from "framework7-svelte"
|
||||||
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
||||||
import type ComfyApp from "$lib/components/ComfyApp";
|
import type ComfyApp from "$lib/components/ComfyApp";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
import { MenuUp } from 'svelte-bootstrap-icons';
|
||||||
import type { IDragItem, WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
import type { IDragItem, WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||||
import workflowState, { type ComfyBoxWorkflow, type WorkflowInstID } from "$lib/stores/workflowState";
|
import workflowState, { type ComfyBoxWorkflow, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||||
import interfaceState from "$lib/stores/interfaceState";
|
import interfaceState from "$lib/stores/interfaceState";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import GenToolbar from '../GenToolbar.svelte'
|
import GenToolbar from '../GenToolbar.svelte'
|
||||||
|
import { onDestroy } from "svelte";
|
||||||
|
|
||||||
export let workflowIndex: number;
|
export let workflowIndex: number;
|
||||||
export let app: ComfyApp
|
export let app: ComfyApp
|
||||||
@@ -15,6 +17,7 @@
|
|||||||
let workflow: ComfyBoxWorkflow;
|
let workflow: ComfyBoxWorkflow;
|
||||||
let root: IDragItem | null;
|
let root: IDragItem | null;
|
||||||
let title = ""
|
let title = ""
|
||||||
|
let actionsOpened = false;
|
||||||
|
|
||||||
function onPageBeforeIn() {
|
function onPageBeforeIn() {
|
||||||
workflow = $workflowState.openedWorkflows[workflowIndex-1]
|
workflow = $workflowState.openedWorkflows[workflowIndex-1]
|
||||||
@@ -23,12 +26,23 @@
|
|||||||
}
|
}
|
||||||
$interfaceState.selectedWorkflowIndex = workflowIndex
|
$interfaceState.selectedWorkflowIndex = workflowIndex
|
||||||
$interfaceState.showingWorkflow = true;
|
$interfaceState.showingWorkflow = true;
|
||||||
|
$interfaceState.selectedTab = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPageBeforeOut() {
|
function onPageBeforeOut() {
|
||||||
$interfaceState.showingWorkflow = false;
|
$interfaceState.showingWorkflow = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshCombos() {
|
||||||
|
navigator.vibrate(20)
|
||||||
|
await app.refreshComboInNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSaveLocal(): void {
|
||||||
|
navigator.vibrate(20)
|
||||||
|
app.saveStateToLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
$: layoutState = workflow?.layout;
|
$: layoutState = workflow?.layout;
|
||||||
$: title = workflow?.attrs?.title || `Workflow: ${workflow?.id || workflowIndex}`;
|
$: title = workflow?.attrs?.title || `Workflow: ${workflow?.id || workflowIndex}`;
|
||||||
|
|
||||||
@@ -47,7 +61,9 @@
|
|||||||
<NavLeft backLink="Back" backLinkUrl="/workflows/" backLinkForce={true}></NavLeft>
|
<NavLeft backLink="Back" backLinkUrl="/workflows/" backLinkForce={true}></NavLeft>
|
||||||
<NavTitle>{title}</NavTitle>
|
<NavTitle>{title}</NavTitle>
|
||||||
<NavRight>
|
<NavRight>
|
||||||
<Link icon="icon-bars" panelOpen="right"></Link>
|
<Link icon="icon-bars" on:click={() => {actionsOpened = true;}}>
|
||||||
|
<MenuUp />
|
||||||
|
</Link>
|
||||||
</NavRight>
|
</NavRight>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
@@ -62,6 +78,16 @@
|
|||||||
Workflow not found.
|
Workflow not found.
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<Actions bind:opened={actionsOpened}>
|
||||||
|
<ActionsGroup>
|
||||||
|
<ActionsLabel>Actions</ActionsLabel>
|
||||||
|
<ActionsButton strong on:click={refreshCombos}>Refresh Dropdowns</ActionsButton>
|
||||||
|
<ActionsButton strong on:click={doSaveLocal}>Save to Local Storage</ActionsButton>
|
||||||
|
<!-- <ActionsButton>Button 2</ActionsButton> -->
|
||||||
|
<ActionsButton color="red">Cancel</ActionsButton>
|
||||||
|
</ActionsGroup>
|
||||||
|
</Actions>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -81,4 +107,8 @@
|
|||||||
:global(.root-container.mobile > .block > .v-pane) {
|
:global(.root-container.mobile > .block > .v-pane) {
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.demo-sheet-push {
|
||||||
|
bottom: calc(var(--f7-toolbar-height) * 3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
import { Page, Navbar, Button, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||||
|
|
||||||
export let app: ComfyApp | null = null;
|
export let app: ComfyApp | null = null;
|
||||||
|
let fileInput: HTMLInputElement = undefined;
|
||||||
|
|
||||||
async function doLoadDefault() {
|
async function doLoadDefault() {
|
||||||
f7.dialog.confirm("Would you like to load the default workflow in a new tab?", async () => {
|
f7.dialog.confirm("Would you like to load the default workflow in a new tab?", async () => {
|
||||||
@@ -26,8 +27,22 @@
|
|||||||
app.saveStateToLocalStorage(false);
|
app.saveStateToLocalStorage(false);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
function doLoad(): void {
|
||||||
|
if (!fileInput)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigator.vibrate(20)
|
||||||
|
fileInput.value = null;
|
||||||
|
fileInput.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadWorkflow(): void {
|
||||||
|
app.handleFile(fileInput.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
function onPageBeforeIn() {
|
function onPageBeforeIn() {
|
||||||
$interfaceState.selectedWorkflowIndex = null;
|
$interfaceState.selectedWorkflowIndex = null;
|
||||||
|
$interfaceState.selectedTab = 1;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -50,6 +65,10 @@
|
|||||||
(No workflows opened.)
|
(No workflows opened.)
|
||||||
{/if}
|
{/if}
|
||||||
<Block strong outlineIos>
|
<Block strong outlineIos>
|
||||||
<Button fill={true} onClick={doLoadDefault}>Load Default Graph</Button>
|
<p class="grid grid-cols-2 grid-gap">
|
||||||
|
<Button outline onClick={doLoadDefault}>Load default graph</Button>
|
||||||
|
<Button outline onClick={doLoad}>Load from file...</Button>
|
||||||
|
</p>
|
||||||
</Block>
|
</Block>
|
||||||
|
<input bind:this={fileInput} id="comfy-file-input" style:display="none" type="file" accept=".json" on:change={loadWorkflow} />
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
Reference in New Issue
Block a user