View parsed A1111 prompt info
This commit is contained in:
114
src/lib/components/A1111PromptDisplay.svelte
Normal file
114
src/lib/components/A1111PromptDisplay.svelte
Normal file
@@ -0,0 +1,114 @@
|
||||
<script lang="ts">
|
||||
import ComfyBoxStdPrompt from "$lib/ComfyBoxStdPrompt";
|
||||
import type { A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import { TextBox } from "@gradio/form";
|
||||
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||
import type { A1111PromptAndInfo } from "./ComfyApp";
|
||||
import { StaticImage } from "./gradio/image";
|
||||
|
||||
export let prompt: A1111PromptAndInfo | null = null;
|
||||
|
||||
let json: any = {}
|
||||
let a1111: A1111ParsedInfotext | null = null
|
||||
let infotext: string = ""
|
||||
let image: string | null = null;
|
||||
|
||||
$: if (prompt) {
|
||||
infotext = prompt.infotext;
|
||||
a1111 = prompt.parsedInfotext;
|
||||
json = prompt.stdPrompt;
|
||||
image = URL.createObjectURL(prompt.imageFile);
|
||||
}
|
||||
else {
|
||||
infotext = ""
|
||||
a1111 = null;
|
||||
json = {}
|
||||
image = null;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if prompt != null}
|
||||
<div class="a1111-prompt-display">
|
||||
<div class="prompt-container">
|
||||
<Block>
|
||||
<TextBox label="Infotext" show_label={true} value={infotext} lines={5} max_lines={20}/>
|
||||
</Block>
|
||||
<div class="scroll-container">
|
||||
{#if a1111}
|
||||
{#if Object.keys(a1111.extraParams).length > 0}
|
||||
<Block>
|
||||
<BlockTitle>Unused Parameters</BlockTitle>
|
||||
<div class="json">
|
||||
<JsonView json={a1111.extraParams} />
|
||||
</div>
|
||||
</Block>
|
||||
{/if}
|
||||
{/if}
|
||||
<Block>
|
||||
<BlockTitle>Converted Prompt</BlockTitle>
|
||||
<div class="json">
|
||||
<JsonView {json} />
|
||||
</div>
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
<Block>
|
||||
<StaticImage show_label={false} label="Image" value={image} />
|
||||
</Block>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.a1111-prompt-display {
|
||||
width: 70vw;
|
||||
height: 70vh;
|
||||
color: none;
|
||||
|
||||
--jsonPaddingLeft: 1rem;
|
||||
--jsonBorderLeft: 1px dotted var(--neutral-600);
|
||||
--jsonBracketColor: currentcolor;
|
||||
--jsonBracketHoverBackground: var(--neutral-100);
|
||||
--jsonSeparatorColor: currentcolor;
|
||||
--jsonKeyColor: var(--body-text-color);
|
||||
--jsonValColor: var(--body-text-color-subdued);
|
||||
--jsonValStringColor: var(--color-green-500);
|
||||
--jsonValNumberColor: var(--color-blue-500);
|
||||
--jsonValBooleanColor: var(--color-red-500);
|
||||
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-y: none;
|
||||
|
||||
flex-direction: row;
|
||||
|
||||
.prompt-container {
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
.json {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:global(> .block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
|
||||
.accordion {
|
||||
background: var(--panel-background-fill);
|
||||
|
||||
|
||||
:global(> .block .block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,13 +5,12 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
||||
import ComfyApp, { type A1111PromptAndInfo, type SerializedAppState } from "./ComfyApp";
|
||||
import { Checkbox, TextBox } from "@gradio/form"
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import type { ComfyAPIStatus } from "$lib/api";
|
||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
||||
|
||||
import { LGraph } from "@litegraph-ts/core";
|
||||
@@ -20,14 +19,18 @@
|
||||
import ComfyProperties from "./ComfyProperties.svelte";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
|
||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||
import { download, jsonToJsObject } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||
import { download, jsonToJsObject } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
import Modal from "./Modal.svelte";
|
||||
import ComfyBoxStdPrompt from "$lib/ComfyBoxStdPrompt";
|
||||
import A1111PromptDisplay from "./A1111PromptDisplay.svelte";
|
||||
import type { A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
|
||||
export let app: ComfyApp = undefined;
|
||||
let queue: ComfyQueue = undefined;
|
||||
let alreadySetup: Writable<boolean> = writable(false);
|
||||
let a1111Prompt: Writable<A1111PromptAndInfo | null> = writable(null);
|
||||
let mainElem: HTMLDivElement;
|
||||
let uiPane: ComfyUIPane = undefined;
|
||||
let props: ComfyProperties = undefined;
|
||||
let containerElem: HTMLDivElement;
|
||||
let resizeTimeout: NodeJS.Timeout | null;
|
||||
@@ -44,6 +47,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if(app) {
|
||||
alreadySetup = app.alreadySetup;
|
||||
a1111Prompt = app.a1111Prompt;
|
||||
}
|
||||
|
||||
function refreshView(event?: Event) {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(app.resizeCanvas.bind(app), 250);
|
||||
@@ -188,6 +196,10 @@
|
||||
else {
|
||||
document.getElementById("app-root").classList.remove("dark")
|
||||
}
|
||||
|
||||
let showModal: boolean = false;
|
||||
|
||||
$: showModal = $a1111Prompt != null
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -196,8 +208,19 @@
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<Modal bind:showModal on:close={() => ($a1111Prompt = null)}>
|
||||
<div slot="header" class="prompt-modal-header">
|
||||
<h1 style="padding-bottom: 1rem;">A1111 Prompt Details</h1>
|
||||
</div>
|
||||
<A1111PromptDisplay prompt={$a1111Prompt} />
|
||||
<div slot="buttons" let:closeDialog>
|
||||
<Button variant="secondary" on:click={closeDialog}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<div id="main" class:dark={uiTheme === "gradio-dark"}>
|
||||
<div id="dropzone" class="dropzone"></div>
|
||||
<div id="container" bind:this={containerElem}>
|
||||
<Splitpanes theme="comfy" on:resize={refreshView}>
|
||||
<Pane bind:size={propsSidebarSize}>
|
||||
@@ -208,7 +231,7 @@
|
||||
<Pane>
|
||||
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
||||
<Pane>
|
||||
<ComfyUIPane bind:this={uiPane} {app} />
|
||||
<ComfyUIPane {app} />
|
||||
</Pane>
|
||||
<Pane bind:size={graphSize}>
|
||||
<ComfyGraphView {app} transitioning={graphTransitioning} />
|
||||
@@ -217,7 +240,7 @@
|
||||
</Pane>
|
||||
<Pane bind:size={queueSidebarSize}>
|
||||
<div class="sidebar-wrapper pane-wrapper">
|
||||
<ComfyQueue bind:this={queue} />
|
||||
<ComfyQueue />
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
@@ -225,35 +248,35 @@
|
||||
<div id="bottombar">
|
||||
<div class="left">
|
||||
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
||||
<Button variant="primary" on:click={queuePrompt}>
|
||||
<Button variant="primary" disabled={!$alreadySetup} on:click={queuePrompt}>
|
||||
{$layoutState.attrs.queuePromptButtonName}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button variant="secondary" on:click={toggleGraph}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleGraph}>
|
||||
Toggle Graph
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={toggleProps}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleProps}>
|
||||
Toggle Props
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={toggleQueue}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleQueue}>
|
||||
Toggle Queue
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doSave}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSave}>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doSaveLocal}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSaveLocal}>
|
||||
Save Local
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doLoad}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoad}>
|
||||
Load
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doClear}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doClear}>
|
||||
Clear
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doLoadDefault}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoadDefault}>
|
||||
Load Default
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doRefreshCombos}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doRefreshCombos}>
|
||||
🔄
|
||||
</Button>
|
||||
<!-- <Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
||||
@@ -325,19 +348,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
background: #60a7dc80;
|
||||
border: 4px dashed #60a7dc;
|
||||
}
|
||||
|
||||
:global(html, body) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -26,7 +26,7 @@ import layoutState from "$lib/stores/layoutState";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import { get } from "svelte/store";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { tick } from "svelte";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
||||
@@ -37,6 +37,9 @@ import type { ComfyExecutionResult } from "$lib/utils";
|
||||
import ComfyPromptSerializer, { UpstreamNodeLocator, isActiveBackendNode } from "./ComfyPromptSerializer";
|
||||
import { iterateNodeDefInputs, type ComfyNodeDef, isBackendNodeDefInputType, iterateNodeDefOutputs } from "$lib/ComfyNodeDef";
|
||||
import { ComfyComboNode } from "$lib/nodes/widgets";
|
||||
import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import type { ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -55,6 +58,13 @@ type QueueItem = {
|
||||
batchCount: number
|
||||
}
|
||||
|
||||
export type A1111PromptAndInfo = {
|
||||
infotext: string,
|
||||
parsedInfotext: A1111ParsedInfotext,
|
||||
stdPrompt: ComfyBoxStdPrompt,
|
||||
imageFile: File
|
||||
}
|
||||
|
||||
/*
|
||||
* Represents a single workflow that can be loaded into the program from JSON.
|
||||
*/
|
||||
@@ -128,10 +138,11 @@ export default class ComfyApp {
|
||||
shiftDown: boolean = false;
|
||||
ctrlDown: boolean = false;
|
||||
selectedGroupMoving: boolean = false;
|
||||
alreadySetup: Writable<boolean> = writable(false);
|
||||
a1111Prompt: Writable<A1111PromptAndInfo | null> = writable(null);
|
||||
|
||||
private queueItems: QueueItem[] = [];
|
||||
private processingQueue: boolean = false;
|
||||
private alreadySetup = false;
|
||||
private promptSerializer: ComfyPromptSerializer;
|
||||
|
||||
constructor() {
|
||||
@@ -140,7 +151,7 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
async setup(): Promise<void> {
|
||||
if (this.alreadySetup) {
|
||||
if (get(this.alreadySetup)) {
|
||||
console.error("Already setup")
|
||||
return;
|
||||
}
|
||||
@@ -183,7 +194,6 @@ export default class ComfyApp {
|
||||
// setInterval(this.saveStateToLocalStorage.bind(this), 1000);
|
||||
|
||||
this.addApiUpdateHandlers();
|
||||
this.addDropHandler();
|
||||
this.addPasteHandler();
|
||||
this.addKeyboardHandler();
|
||||
|
||||
@@ -197,7 +207,7 @@ export default class ComfyApp {
|
||||
|
||||
this.requestPermissions();
|
||||
|
||||
this.alreadySetup = true;
|
||||
this.alreadySetup.set(true);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -318,70 +328,28 @@ export default class ComfyApp {
|
||||
}
|
||||
}
|
||||
|
||||
private showDropZone() {
|
||||
if (this.dropZone)
|
||||
this.dropZone.style.display = "block";
|
||||
}
|
||||
|
||||
private hideDropZone() {
|
||||
if (this.dropZone)
|
||||
this.dropZone.style.display = "none";
|
||||
}
|
||||
|
||||
private allowDrag(event: DragEvent) {
|
||||
if (event.dataTransfer.items?.length > 0) {
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
this.showDropZone();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDrop(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.hideDropZone();
|
||||
|
||||
if (event.dataTransfer.files.length > 0) {
|
||||
await this.handleFile(event.dataTransfer.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private addDropHandler() {
|
||||
// this.dropZone = document.getElementById("dropzone");
|
||||
|
||||
// if (this.dropZone) {
|
||||
// window.addEventListener('dragenter', this.allowDrag.bind(this));
|
||||
// this.dropZone.addEventListener('dragover', this.allowDrag.bind(this));
|
||||
// this.dropZone.addEventListener('dragleave', this.hideDropZone.bind(this));
|
||||
// this.dropZone.addEventListener('drop', this.handleDrop.bind(this));
|
||||
// }
|
||||
// else {
|
||||
// console.warn("No dropzone detected (probably on mobile).")
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler on paste that extracts and loads workflows from pasted JSON data
|
||||
*/
|
||||
private addPasteHandler() {
|
||||
// document.addEventListener("paste", (e) => {
|
||||
// let data = (e.clipboardData || (window as any).clipboardData).getData("text/plain");
|
||||
// let workflow;
|
||||
// try {
|
||||
// data = data.slice(data.indexOf("{"));
|
||||
// workflow = JSON.parse(data);
|
||||
// } catch (err) {
|
||||
// try {
|
||||
// data = data.slice(data.indexOf("workflow\n"));
|
||||
// data = data.slice(data.indexOf("{"));
|
||||
// workflow = JSON.parse(data);
|
||||
// } catch (error) { }
|
||||
// }
|
||||
document.addEventListener("paste", (e) => {
|
||||
let data = (e.clipboardData || (window as any).clipboardData).getData("text/plain");
|
||||
let workflow;
|
||||
try {
|
||||
data = data.slice(data.indexOf("{"));
|
||||
workflow = JSON.parse(data);
|
||||
} catch (err) {
|
||||
try {
|
||||
data = data.slice(data.indexOf("workflow\n"));
|
||||
data = data.slice(data.indexOf("{"));
|
||||
workflow = JSON.parse(data);
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// if (workflow && workflow.version && workflow.nodes && workflow.extra) {
|
||||
// this.loadGraphData(workflow);
|
||||
// }
|
||||
// });
|
||||
if (workflow && workflow.version && workflow.nodes && workflow.extra) {
|
||||
this.loadGraphData(workflow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -745,8 +713,18 @@ export default class ComfyApp {
|
||||
if (pngInfo.comfyBoxConfig) {
|
||||
this.deserialize(JSON.parse(pngInfo.comfyBoxConfig));
|
||||
} else if (pngInfo.parameters) {
|
||||
throw "TODO A111 import!"
|
||||
// importA1111(this.lGraph, pngInfo.parameters, this.api);
|
||||
const parsed = parseA1111(pngInfo.parameters)
|
||||
if ("error" in parsed) {
|
||||
notify(`Couldn't parse webui prompt: ${parsed.error}`, { type: "error" })
|
||||
return;
|
||||
}
|
||||
const converted = convertA1111ToStdPrompt(parsed)
|
||||
this.a1111Prompt.set({
|
||||
infotext: pngInfo.parameters,
|
||||
parsedInfotext: parsed,
|
||||
stdPrompt: converted,
|
||||
imageFile: file
|
||||
})
|
||||
}
|
||||
else {
|
||||
console.error("No metadata found in image file.", pngInfo)
|
||||
@@ -890,5 +868,6 @@ export default class ComfyApp {
|
||||
*/
|
||||
clean() {
|
||||
this.nodeOutputs = {};
|
||||
this.a1111Prompt.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import layoutState, { type ContainerLayout, type DragItem, type IDragItem } from "$lib/stores/layoutState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import DropZone from "./DropZone.svelte";
|
||||
|
||||
import Menu from './menu/Menu.svelte';
|
||||
import MenuOption from './menu/MenuOption.svelte';
|
||||
@@ -150,6 +151,7 @@
|
||||
</script>
|
||||
|
||||
<div id="comfy-ui-panes" on:contextmenu={onRightClick}>
|
||||
<DropZone {app} />
|
||||
<WidgetContainer bind:dragItem={root} classes={["root-container"]} />
|
||||
</div>
|
||||
|
||||
|
||||
78
src/lib/components/DropZone.svelte
Normal file
78
src/lib/components/DropZone.svelte
Normal file
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import type ComfyApp from "./ComfyApp";
|
||||
|
||||
export let app: ComfyApp;
|
||||
let a1111Prompt: Writable<any | null> = writable(null);
|
||||
let dropZone: HTMLDivElement | null = null;
|
||||
let disabled = false;
|
||||
|
||||
$: a1111Prompt = app.a1111Prompt;
|
||||
|
||||
$: disabled = a1111Prompt && $a1111Prompt;
|
||||
|
||||
$: if (disabled) {
|
||||
hideDropZone();
|
||||
}
|
||||
|
||||
function showDropZone() {
|
||||
if (dropZone && !disabled)
|
||||
dropZone.style.display = "block";
|
||||
}
|
||||
|
||||
function hideDropZone() {
|
||||
if (dropZone)
|
||||
dropZone.style.display = "none";
|
||||
}
|
||||
|
||||
function allowDrag(event: DragEvent) {
|
||||
if (disabled)
|
||||
return
|
||||
|
||||
if (event.dataTransfer != null && event.dataTransfer.items?.length > 0) {
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
showDropZone();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDrop(event: DragEvent) {
|
||||
if (disabled)
|
||||
return
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
hideDropZone();
|
||||
|
||||
if (event.dataTransfer != null && event.dataTransfer.files.length > 0) {
|
||||
await app.handleFile(event.dataTransfer.files[0]);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:dragenter={showDropZone} />
|
||||
|
||||
{#if !disabled}
|
||||
<div id="dropzone"
|
||||
class="dropzone"
|
||||
bind:this={dropZone}
|
||||
on:dragover={allowDrag}
|
||||
on:dragleave={hideDropZone}
|
||||
on:drop={handleDrop}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.dropzone {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
background: #60a7dc80;
|
||||
border: 4px dashed #60a7dc;
|
||||
}
|
||||
</style>
|
||||
@@ -94,5 +94,6 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-sm);
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user