Thumbnail option for gallery/queue images
This commit is contained in:
@@ -1,18 +1,3 @@
|
|||||||
<script lang="ts" context="module">
|
|
||||||
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
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import queueState, { type CompletedQueueEntry, type QueueEntry, type QueueEntryStatus } 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";
|
||||||
@@ -20,7 +5,7 @@
|
|||||||
import Spinner from "./Spinner.svelte";
|
import Spinner from "./Spinner.svelte";
|
||||||
import PromptDisplay from "./PromptDisplay.svelte";
|
import PromptDisplay from "./PromptDisplay.svelte";
|
||||||
import { List, ListUl, Grid } from "svelte-bootstrap-icons";
|
import { List, ListUl, Grid } from "svelte-bootstrap-icons";
|
||||||
import { convertComfyOutputToComfyURL, convertFilenameToComfyURL, getNodeInfo, truncateString } from "$lib/utils"
|
import { getNodeInfo, type ComfyImageLocation } 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 { Button } from "@gradio/button";
|
import { Button } from "@gradio/button";
|
||||||
@@ -31,7 +16,7 @@
|
|||||||
import ComfyQueueListDisplay from "./ComfyQueueListDisplay.svelte";
|
import ComfyQueueListDisplay from "./ComfyQueueListDisplay.svelte";
|
||||||
import ComfyQueueGridDisplay from "./ComfyQueueGridDisplay.svelte";
|
import ComfyQueueGridDisplay from "./ComfyQueueGridDisplay.svelte";
|
||||||
import { WORKFLOWS_VIEW } from "./ComfyBoxWorkflowsView.svelte";
|
import { WORKFLOWS_VIEW } from "./ComfyBoxWorkflowsView.svelte";
|
||||||
import uiQueueState from "$lib/stores/uiQueueState";
|
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
|
||||||
|
|
||||||
export let app: ComfyApp;
|
export let app: ComfyApp;
|
||||||
|
|
||||||
@@ -125,7 +110,7 @@
|
|||||||
let showModal = false;
|
let showModal = false;
|
||||||
let expandAll = false;
|
let expandAll = false;
|
||||||
let selectedPrompt = null;
|
let selectedPrompt = null;
|
||||||
let selectedImages = [];
|
let selectedImages: ComfyImageLocation[] = [];
|
||||||
function showPrompt(entry: QueueUIEntry) {
|
function showPrompt(entry: QueueUIEntry) {
|
||||||
if (entry.error != null) {
|
if (entry.error != null) {
|
||||||
showModal = false;
|
showModal = false;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { QueueItemType } from "$lib/api";
|
import type { QueueItemType } from "$lib/api";
|
||||||
import { showLightbox } from "$lib/utils";
|
import { convertComfyOutputToComfyURL, showLightbox } from "$lib/utils";
|
||||||
import type { QueueUIEntry } from "./ComfyQueue.svelte";
|
import type { QueueUIEntry } from "./ComfyQueue.svelte";
|
||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
allEntries = []
|
allEntries = []
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
for (const image of entry.images) {
|
for (const image of entry.images) {
|
||||||
allEntries.push([entry, image]);
|
allEntries.push([entry, convertComfyOutputToComfyURL(image, true)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allImages = allEntries.map(p => p[1]);
|
allImages = allEntries.map(p => p[1]);
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
<img class="grid-entry-image"
|
<img class="grid-entry-image"
|
||||||
on:click={(e) => handleClick(e, entry, i)}
|
on:click={(e) => handleClick(e, entry, i)}
|
||||||
src={image}
|
src={image}
|
||||||
|
loading="lazy"
|
||||||
alt="thumbnail" />
|
alt="thumbnail" />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -130,6 +131,8 @@
|
|||||||
.grid-entry-image {
|
.grid-entry-image {
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
max-width: unset;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { QueueItemType } from "$lib/api";
|
import type { QueueItemType } from "$lib/api";
|
||||||
import { showLightbox, truncateString } from "$lib/utils";
|
import { convertComfyOutputToComfyURL, showLightbox, truncateString } from "$lib/utils";
|
||||||
import type { QueueUIEntry } from "./ComfyQueue.svelte";
|
|
||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
|
import type { QueueUIEntry } from "$lib/stores/uiQueueState";
|
||||||
|
|
||||||
export let entries: QueueUIEntry[] = [];
|
export let entries: QueueUIEntry[] = [];
|
||||||
export let showPrompt: (entry: QueueUIEntry) => void;
|
export let showPrompt: (entry: QueueUIEntry) => void;
|
||||||
@@ -39,11 +39,13 @@
|
|||||||
<div class="list-entry-images"
|
<div class="list-entry-images"
|
||||||
style="--cols: {Math.ceil(Math.sqrt(Math.min(entry.images.length, 4)))}" >
|
style="--cols: {Math.ceil(Math.sqrt(Math.min(entry.images.length, 4)))}" >
|
||||||
{#each entry.images.slice(0, 4) as image, i}
|
{#each entry.images.slice(0, 4) as image, i}
|
||||||
|
{@const imageURL = convertComfyOutputToComfyURL(image, true)}
|
||||||
<div>
|
<div>
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<img class="list-entry-image"
|
<img class="list-entry-image"
|
||||||
on:click={(e) => showLightbox(entry.images, i, e)}
|
on:click={(e) => showLightbox(entry.images, i, e)}
|
||||||
src={image}
|
src={imageURL}
|
||||||
|
loading="lazy"
|
||||||
alt="thumbnail" />
|
alt="thumbnail" />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
|
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
|
||||||
import { ImageViewer } from "$lib/ImageViewer";
|
import { ImageViewer } from "$lib/ImageViewer";
|
||||||
import type { Styles } from "@gradio/utils";
|
import type { Styles } from "@gradio/utils";
|
||||||
import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines } from "$lib/utils";
|
import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines, type ComfyImageLocation, convertComfyOutputToComfyURL } from "$lib/utils";
|
||||||
import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
|
import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
|
||||||
import workflowState, { type ComfyBoxWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
import workflowState, { type ComfyBoxWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
const splitLength = 50;
|
const splitLength = 50;
|
||||||
|
|
||||||
export let prompt: SerializedPromptInputsAll;
|
export let prompt: SerializedPromptInputsAll;
|
||||||
export let images: string[] = []; // list of image URLs to ComfyUI's /view? endpoint
|
export let images: ComfyImageLocation[] = [];
|
||||||
export let isMobile: boolean = false;
|
export let isMobile: boolean = false;
|
||||||
export let expandAll: boolean = false;
|
export let expandAll: boolean = false;
|
||||||
export let closeModal: () => void;
|
export let closeModal: () => void;
|
||||||
@@ -36,10 +36,7 @@
|
|||||||
let litegraphType = "(none)"
|
let litegraphType = "(none)"
|
||||||
|
|
||||||
$: if (images.length > 0) {
|
$: if (images.length > 0) {
|
||||||
// since the image links come from gradio, have to parse the URL for the
|
comfyBoxImages = images.map(comfyFileToComfyBoxMetadata);
|
||||||
// ComfyImageLocation params
|
|
||||||
comfyBoxImages = images.map(comfyURLToComfyFile)
|
|
||||||
.map(comfyFileToComfyBoxMetadata);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
comfyBoxImages = []
|
comfyBoxImages = []
|
||||||
@@ -199,7 +196,7 @@
|
|||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<Block>
|
<Block>
|
||||||
<Gallery
|
<Gallery
|
||||||
value={images}
|
value={images.map(convertComfyOutputToComfyURL)}
|
||||||
label=""
|
label=""
|
||||||
show_label={false}
|
show_label={false}
|
||||||
style={galleryStyle}
|
style={galleryStyle}
|
||||||
|
|||||||
@@ -119,6 +119,36 @@ const defNotifications: ConfigDefEnum<"notifications", NotificationState> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum OutputThumbnailsMode {
|
||||||
|
Auto,
|
||||||
|
AlwaysThumbnail,
|
||||||
|
AlwaysFullSize
|
||||||
|
}
|
||||||
|
|
||||||
|
const defOutputThumbnails: ConfigDefEnum<"outputThumbnails", OutputThumbnailsMode> = {
|
||||||
|
name: "outputThumbnails",
|
||||||
|
type: "enum",
|
||||||
|
defaultValue: OutputThumbnailsMode.Auto,
|
||||||
|
category: "ui",
|
||||||
|
description: "If enabled, send back smaller sized output image thumbnails for gallery/queue/history. Enable if you have slow network or are using Colab.",
|
||||||
|
options: {
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
value: OutputThumbnailsMode.Auto,
|
||||||
|
label: "Autodetect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: OutputThumbnailsMode.AlwaysThumbnail,
|
||||||
|
label: "Always use thumbnails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: OutputThumbnailsMode.AlwaysFullSize,
|
||||||
|
label: "Always use full size"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const defAlwaysStripUserState: ConfigDefBoolean<"alwaysStripUserState"> = {
|
const defAlwaysStripUserState: ConfigDefBoolean<"alwaysStripUserState"> = {
|
||||||
name: "alwaysStripUserState",
|
name: "alwaysStripUserState",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
@@ -207,6 +237,7 @@ export const CONFIG_DEFS = [
|
|||||||
defComfyUIHostname,
|
defComfyUIHostname,
|
||||||
defComfyUIPort,
|
defComfyUIPort,
|
||||||
defNotifications,
|
defNotifications,
|
||||||
|
defOutputThumbnails,
|
||||||
defAlwaysStripUserState,
|
defAlwaysStripUserState,
|
||||||
defPromptForWorkflowName,
|
defPromptForWorkflowName,
|
||||||
defConfirmWhenUnloadingUnsavedChanges,
|
defConfirmWhenUnloadingUnsavedChanges,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { PromptID, QueueItemType } from '$lib/api';
|
import type { PromptID, QueueItemType } from '$lib/api';
|
||||||
|
import type { ComfyImageLocation } 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 queueState, { type CompletedQueueEntry, type QueueEntry } from './queueState';
|
import queueState, { QueueEntryStatus, type CompletedQueueEntry, type QueueEntry } from './queueState';
|
||||||
import type { WorkflowError } from './workflowState';
|
import type { WorkflowError } from './workflowState';
|
||||||
import { convertComfyOutputToComfyURL } from '$lib/utils';
|
import { convertComfyOutputToComfyURL } from '$lib/utils';
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export type QueueUIEntry = {
|
|||||||
submessage: string,
|
submessage: string,
|
||||||
date?: string,
|
date?: string,
|
||||||
status: QueueUIEntryStatus,
|
status: QueueUIEntryStatus,
|
||||||
images?: string[], // URLs
|
images?: ComfyImageLocation[], // URLs
|
||||||
details?: string, // shown in a tooltip on hover
|
details?: string, // shown in a tooltip on hover
|
||||||
error?: WorkflowError
|
error?: WorkflowError
|
||||||
}
|
}
|
||||||
@@ -94,13 +95,12 @@ function convertPendingEntry(entry: QueueEntry, status: QueueUIEntryStatus): Que
|
|||||||
|
|
||||||
const thumbnails = entry.extraData?.thumbnails
|
const thumbnails = entry.extraData?.thumbnails
|
||||||
if (thumbnails) {
|
if (thumbnails) {
|
||||||
result.images = thumbnails.map(convertComfyOutputToComfyURL);
|
result.images = [...thumbnails]
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputs = Object.values(entry.outputs)
|
const outputs = Object.values(entry.outputs)
|
||||||
.filter(o => o.images)
|
.filter(o => o.images)
|
||||||
.flatMap(o => o.images)
|
.flatMap(o => o.images)
|
||||||
.map(convertComfyOutputToComfyURL);
|
|
||||||
if (outputs) {
|
if (outputs) {
|
||||||
result.images = result.images.concat(outputs)
|
result.images = result.images.concat(outputs)
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,6 @@ function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry {
|
|||||||
const images = Object.values(entry.entry.outputs)
|
const images = Object.values(entry.entry.outputs)
|
||||||
.filter(o => o.images)
|
.filter(o => o.images)
|
||||||
.flatMap(o => o.images)
|
.flatMap(o => o.images)
|
||||||
.map(convertComfyOutputToComfyURL);
|
|
||||||
result.images = images
|
result.images = images
|
||||||
|
|
||||||
if (entry.message)
|
if (entry.message)
|
||||||
@@ -132,6 +131,8 @@ function updateFromQueue(queuePending: QueueEntry[], queueRunning: QueueEntry[])
|
|||||||
// newest entries appear at the top
|
// newest entries appear at the top
|
||||||
s.queuedEntries = queuePending.map((e) => convertPendingEntry(e, "pending")).reverse();
|
s.queuedEntries = queuePending.map((e) => convertPendingEntry(e, "pending")).reverse();
|
||||||
s.runningEntries = queueRunning.map((e) => convertPendingEntry(e, "running")).reverse();
|
s.runningEntries = queueRunning.map((e) => convertPendingEntry(e, "running")).reverse();
|
||||||
|
s.queuedEntries.sort((a, b) => a.entry.number - b.entry.number)
|
||||||
|
s.runningEntries.sort((a, b) => a.entry.number - b.entry.number)
|
||||||
s.queueUIEntries = s.queuedEntries.concat(s.runningEntries);
|
s.queueUIEntries = s.queuedEntries.concat(s.runningEntries);
|
||||||
console.warn("[ComfyQueue] BUILDQUEUE", s.queuedEntries.length, s.runningEntries.length)
|
console.warn("[ComfyQueue] BUILDQUEUE", s.queuedEntries.length, s.runningEntries.length)
|
||||||
return s;
|
return s;
|
||||||
|
|||||||
139
src/lib/utils.ts
139
src/lib/utils.ts
@@ -9,6 +9,7 @@ import workflowState, { type WorkflowReceiveOutputTargets } from "./stores/workf
|
|||||||
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";
|
import SendOutputModal, { type SendOutputModalResult } from "$lib/components/modal/SendOutputModal.svelte";
|
||||||
|
import { OutputThumbnailsMode } from "./stores/configDefs";
|
||||||
|
|
||||||
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)
|
||||||
@@ -300,31 +301,80 @@ export function convertComfyOutputToGradio(output: SerializedPromptOutput): Grad
|
|||||||
|
|
||||||
export function convertComfyOutputEntryToGradio(r: ComfyImageLocation): GradioFileData {
|
export function convertComfyOutputEntryToGradio(r: ComfyImageLocation): GradioFileData {
|
||||||
const url = configState.getBackendURL();
|
const url = configState.getBackendURL();
|
||||||
const params = new URLSearchParams(r)
|
|
||||||
const fileData: GradioFileData = {
|
const fileData: GradioFileData = {
|
||||||
name: r.filename,
|
name: r.filename,
|
||||||
orig_name: r.filename,
|
orig_name: r.filename,
|
||||||
is_file: false,
|
is_file: false,
|
||||||
data: url + "/view?" + params
|
data: convertComfyOutputToComfyURL(r)
|
||||||
}
|
}
|
||||||
return fileData
|
return fileData
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertComfyOutputToComfyURL(output: string | ComfyImageLocation): string {
|
function convertComfyPreviewTypeToString(preview: ComfyImagePreviewType): string {
|
||||||
|
const arr = []
|
||||||
|
switch (preview.format) {
|
||||||
|
case ComfyImagePreviewFormat.JPEG:
|
||||||
|
arr.push("jpeg")
|
||||||
|
break;
|
||||||
|
case ComfyImagePreviewFormat.WebP:
|
||||||
|
default:
|
||||||
|
arr.push("webp")
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.push(String(preview.quality))
|
||||||
|
|
||||||
|
return arr.join(";")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertComfyOutputToComfyURL(output: string | ComfyImageLocation, thumbnail: boolean = false): string {
|
||||||
if (typeof output === "string")
|
if (typeof output === "string")
|
||||||
return output;
|
return output;
|
||||||
|
|
||||||
const params = new URLSearchParams(output)
|
const paramsObj = {
|
||||||
|
filename: output.filename,
|
||||||
|
subfolder: output.subfolder,
|
||||||
|
type: output.type
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnail) {
|
||||||
|
let doThumbnail: boolean;
|
||||||
|
|
||||||
|
switch (get(configState).outputThumbnails) {
|
||||||
|
case OutputThumbnailsMode.AlwaysFullSize:
|
||||||
|
doThumbnail = false;
|
||||||
|
break;
|
||||||
|
case OutputThumbnailsMode.AlwaysThumbnail:
|
||||||
|
doThumbnail = true;
|
||||||
|
break;
|
||||||
|
case OutputThumbnailsMode.Auto:
|
||||||
|
default:
|
||||||
|
// TODO detect colab, etc.
|
||||||
|
if (isMobileBrowser(navigator.userAgent)) {
|
||||||
|
doThumbnail = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doThumbnail = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doThumbnail) {
|
||||||
|
output.preview = {
|
||||||
|
format: ComfyImagePreviewFormat.WebP,
|
||||||
|
quality: 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.preview != null)
|
||||||
|
paramsObj["preview"] = convertComfyPreviewTypeToString(output.preview)
|
||||||
|
|
||||||
|
const params = new URLSearchParams(paramsObj)
|
||||||
const url = configState.getBackendURL();
|
const url = configState.getBackendURL();
|
||||||
return url + "/view?" + params
|
return url + "/view?" + params
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertGradioFileDataToComfyURL(image: GradioFileData, type: ComfyUploadImageType = "input"): string {
|
|
||||||
const baseUrl = configState.getBackendURL();
|
|
||||||
const params = new URLSearchParams({ filename: image.name, subfolder: "", type })
|
|
||||||
return `${baseUrl}/view?${params}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertGradioFileDataToComfyOutput(fileData: GradioFileData, type: ComfyUploadImageType = "input"): ComfyImageLocation {
|
export function convertGradioFileDataToComfyOutput(fileData: GradioFileData, type: ComfyUploadImageType = "input"): ComfyImageLocation {
|
||||||
if (!fileData.is_file)
|
if (!fileData.is_file)
|
||||||
throw "Can't convert blob data to comfy output!"
|
throw "Can't convert blob data to comfy output!"
|
||||||
@@ -336,18 +386,6 @@ export function convertGradioFileDataToComfyOutput(fileData: GradioFileData, typ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertFilenameToComfyURL(filename: string,
|
|
||||||
subfolder: string = "",
|
|
||||||
type: "input" | "output" | "temp" = "output"): string {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
filename,
|
|
||||||
subfolder,
|
|
||||||
type
|
|
||||||
})
|
|
||||||
const url = configState.getBackendURL();
|
|
||||||
return url + "/view?" + params
|
|
||||||
}
|
|
||||||
|
|
||||||
export function jsonToJsObject(json: string): string {
|
export function jsonToJsObject(json: string): string {
|
||||||
// Try to parse, to see if it's real JSON
|
// Try to parse, to see if it's real JSON
|
||||||
JSON.parse(json);
|
JSON.parse(json);
|
||||||
@@ -413,6 +451,16 @@ export interface SerializedPromptOutput {
|
|||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ComfyImagePreviewFormat {
|
||||||
|
WebP = "webp",
|
||||||
|
JPEG = "jpeg",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ComfyImagePreviewType = {
|
||||||
|
format: ComfyImagePreviewFormat,
|
||||||
|
quality: number
|
||||||
|
}
|
||||||
|
|
||||||
/** Raw output entry as received from ComfyUI's backend */
|
/** Raw output entry as received from ComfyUI's backend */
|
||||||
export type ComfyImageLocation = {
|
export type ComfyImageLocation = {
|
||||||
/* Filename with extension in the subfolder. */
|
/* Filename with extension in the subfolder. */
|
||||||
@@ -420,7 +468,19 @@ export type ComfyImageLocation = {
|
|||||||
/* Subfolder in the containing folder. */
|
/* Subfolder in the containing folder. */
|
||||||
subfolder: string,
|
subfolder: string,
|
||||||
/* Base ComfyUI folder where the image is located. */
|
/* Base ComfyUI folder where the image is located. */
|
||||||
type: ComfyUploadImageType
|
type: ComfyUploadImageType,
|
||||||
|
/*
|
||||||
|
* Preview information
|
||||||
|
*
|
||||||
|
* "format;quality"
|
||||||
|
*
|
||||||
|
* ex)
|
||||||
|
* webp;50 -> webp, quality 50
|
||||||
|
* webp;50 -> webp, quality 50
|
||||||
|
* jpeg;80 -> rgb, jpeg, quality 80
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
preview?: ComfyImagePreviewType
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -544,27 +604,54 @@ export function comfyBoxImageToComfyURL(image: ComfyBoxImageMetadata): string {
|
|||||||
return convertComfyOutputToComfyURL(image.comfyUIFile)
|
return convertComfyOutputToComfyURL(image.comfyUIFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseComfyUIPreviewType(previewStr: string): ComfyImagePreviewType {
|
||||||
|
let split = previewStr.split(";")
|
||||||
|
let format = ComfyImagePreviewFormat.WebP;
|
||||||
|
if (split[0] === "webp")
|
||||||
|
format = ComfyImagePreviewFormat.WebP;
|
||||||
|
else if (split[0] === "jpeg")
|
||||||
|
format = ComfyImagePreviewFormat.JPEG;
|
||||||
|
|
||||||
|
let quality = parseInt(split[0])
|
||||||
|
if (isNaN(quality))
|
||||||
|
quality = 80
|
||||||
|
|
||||||
|
return { format, quality }
|
||||||
|
}
|
||||||
|
|
||||||
export function comfyURLToComfyFile(urlString: string): ComfyImageLocation | null {
|
export function comfyURLToComfyFile(urlString: string): ComfyImageLocation | null {
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
const params = new URLSearchParams(url.search);
|
const params = new URLSearchParams(url.search);
|
||||||
const filename = params.get("filename")
|
const filename = params.get("filename")
|
||||||
const type = params.get("type") as ComfyUploadImageType;
|
const type = params.get("type") as ComfyUploadImageType;
|
||||||
const subfolder = params.get("subfolder") || ""
|
const subfolder = params.get("subfolder") || ""
|
||||||
|
const previewStr = params.get("preview") || null;
|
||||||
|
let preview = null
|
||||||
|
|
||||||
|
if (previewStr != null) {
|
||||||
|
preview = parseComfyUIPreviewType(preview);
|
||||||
|
}
|
||||||
|
|
||||||
// If at least filename and type exist then we're good
|
// If at least filename and type exist then we're good
|
||||||
if (filename != null && type != null) {
|
if (filename != null && type != null) {
|
||||||
return { filename, type, subfolder }
|
return { filename, type, subfolder, preview }
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showLightbox(images: string[], index: number, e: Event) {
|
export function showLightbox(images: ComfyImageLocation[] | string[], index: number, e: Event) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!images)
|
if (!images)
|
||||||
return
|
return
|
||||||
|
|
||||||
ImageViewer.instance.showModal(images, index);
|
let images_: string[]
|
||||||
|
if (typeof images[0] === "object")
|
||||||
|
images_ = (images as ComfyImageLocation[]).map(v => convertComfyOutputToComfyURL(v))
|
||||||
|
else
|
||||||
|
images_ = (images as string[])
|
||||||
|
|
||||||
|
ImageViewer.instance.showModal(images_, index);
|
||||||
|
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Page, Navbar, Block, Tabs, Tab, NavLeft, NavTitle, NavRight, Link, f7 } 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 type ComfyApp from "$lib/components/ComfyApp";
|
import type ComfyApp from "$lib/components/ComfyApp";
|
||||||
import { writable, type Writable } from "svelte/store";
|
|
||||||
import type { IDragItem, WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
|
||||||
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 { convertComfyOutputToComfyURL, partition, showLightbox } from "$lib/utils";
|
||||||
import GenToolbar from '../GenToolbar.svelte'
|
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
|
||||||
import { partition, showLightbox } from "$lib/utils";
|
import { showMobileLightbox } from "$lib/components/utils";
|
||||||
import uiQueueState, { type QueueUIEntry } from "$lib/stores/uiQueueState";
|
import notify from "$lib/notify";
|
||||||
import { showMobileLightbox } from "$lib/components/utils";
|
|
||||||
import notify from "$lib/notify";
|
|
||||||
|
|
||||||
export let app: ComfyApp
|
export let app: ComfyApp
|
||||||
|
|
||||||
@@ -33,7 +27,7 @@
|
|||||||
const _allEntries = []
|
const _allEntries = []
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
for (const image of entry.images) {
|
for (const image of entry.images) {
|
||||||
_allEntries.push([entry, image]);
|
_allEntries.push([entry, convertComfyOutputToComfyURL(image, true)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allEntries = partition(_allEntries, gridCols);
|
allEntries = partition(_allEntries, gridCols);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
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 { convertComfyOutputToComfyURL, partition, showLightbox, truncateString } 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 queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
function getCardImage(entry: QueueUIEntry): string {
|
function getCardImage(entry: QueueUIEntry): string {
|
||||||
if (entry.images.length > 0)
|
if (entry.images.length > 0)
|
||||||
return entry.images[0]
|
return convertComfyOutputToComfyURL(entry.images[0])
|
||||||
return "https://cdn.framework7.io/placeholder/nature-1000x600-3.jpg"
|
return "https://cdn.framework7.io/placeholder/nature-1000x600-3.jpg"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user