Workflows can receive images from other workflows/historical prompts
This commit is contained in:
@@ -1,57 +1,39 @@
|
||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType, type INodeInputSlot, type NodeID, type NodeTypeSpec, type NodeTypeOpts, type SlotIndex, type UUID } from "@litegraph-ts/core";
|
||||
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
||||
import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyPromptRequest, type ComfyNodeID, type PromptID, type QueueItemType } from "$lib/api"
|
||||
import { importA1111, parsePNGMetadata } from "$lib/pnginfo";
|
||||
import EventEmitter from "events";
|
||||
import type TypedEmitter from "typed-emitter";
|
||||
import ComfyAPI, { type ComfyAPIStatusResponse, type ComfyBoxPromptExtraData, type ComfyNodeID, type ComfyPromptRequest, type PromptID, type QueueItemType } from "$lib/api";
|
||||
import { parsePNGMetadata } from "$lib/pnginfo";
|
||||
import { BuiltInSlotType, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type INodeInputSlot, type LGraphNodeConstructor, type NodeID, type NodeTypeOpts, type SerializedLGraph, type SlotIndex } from "@litegraph-ts/core";
|
||||
import A1111PromptModal from "./modal/A1111PromptModal.svelte";
|
||||
import ConfirmConvertWithMissingNodeTypesModal from "./modal/ConfirmConvertWithMissingNodeTypesModal.svelte";
|
||||
import MissingNodeTypesModal from "./modal/MissingNodeTypesModal.svelte";
|
||||
import WorkflowLoadErrorModal from "./modal/WorkflowLoadErrorModal.svelte";
|
||||
import ConfirmConvertWithMissingNodeTypesModal from "./modal/ConfirmConvertWithMissingNodeTypesModal.svelte";
|
||||
|
||||
import * as nodes from "$lib/nodes/index";
|
||||
|
||||
// Import nodes
|
||||
import "@litegraph-ts/nodes-basic"
|
||||
import "@litegraph-ts/nodes-events"
|
||||
import "@litegraph-ts/nodes-logic"
|
||||
import "@litegraph-ts/nodes-math"
|
||||
import "@litegraph-ts/nodes-strings"
|
||||
import "$lib/nodes/index"
|
||||
import "$lib/nodes/widgets/index"
|
||||
import "$lib/nodes/actions/index"
|
||||
import * as nodes from "$lib/nodes/index"
|
||||
import * as widgets from "$lib/nodes/widgets/index"
|
||||
|
||||
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { type SvelteComponentDev } from "svelte/internal";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import { defaultWorkflowAttributes, type LayoutState, type SerializedLayoutState, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { tick } from "svelte";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { basename, capitalize, download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
import configState from "$lib/stores/configState";
|
||||
import { blankGraph } from "$lib/defaultGraph";
|
||||
import type { SerializedPromptOutput } 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";
|
||||
import ComfyBoxStdPromptSerializer from "$lib/ComfyBoxStdPromptSerializer";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import layoutStates from "$lib/stores/layoutStates";
|
||||
import { ComfyWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||
import workflowState from "$lib/stores/workflowState";
|
||||
import convertVanillaWorkflow, { type ComfyVanillaWorkflow } from "$lib/convertVanillaWorkflow";
|
||||
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
||||
import { isBackendNodeDefInputType, iterateNodeDefInputs, iterateNodeDefOutputs, type ComfyNodeDef } from "$lib/ComfyNodeDef";
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import convertVanillaWorkflow from "$lib/convertVanillaWorkflow";
|
||||
import { blankGraph } from "$lib/defaultGraph";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import { ComfyComboNode } from "$lib/nodes/widgets";
|
||||
import notify from "$lib/notify";
|
||||
import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import configState from "$lib/stores/configState";
|
||||
import layoutStates, { defaultWorkflowAttributes, type SerializedLayoutState } from "$lib/stores/layoutStates";
|
||||
import modalState from "$lib/stores/modalState";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import workflowState, { ComfyWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState";
|
||||
import type { SerializedPromptOutput } from "$lib/utils";
|
||||
import { basename, capitalize, download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range } from "$lib/utils";
|
||||
import { tick } from "svelte";
|
||||
import { type SvelteComponentDev } from "svelte/internal";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import ComfyPromptSerializer, { isActiveBackendNode, UpstreamNodeLocator } from "./ComfyPromptSerializer";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -556,9 +538,14 @@ export default class ComfyApp {
|
||||
this.ctrlDown = e.ctrlKey;
|
||||
|
||||
// Queue prompt using ctrl or command + enter
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) {
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.code === "Enter" || e.keyCode === 10)) {
|
||||
e.preventDefault();
|
||||
this.runDefaultQueueAction();
|
||||
}
|
||||
else if ((e.ctrlKey) && (e.key === "s" || e.code === "KeyS")) {
|
||||
e.preventDefault();
|
||||
this.saveStateToLocalStorage();
|
||||
}
|
||||
});
|
||||
window.addEventListener("keyup", (e) => {
|
||||
this.shiftDown = e.shiftKey;
|
||||
|
||||
@@ -223,8 +223,13 @@
|
||||
expandAll = false
|
||||
}
|
||||
|
||||
$: if(!showModal)
|
||||
selectedPrompt = null;
|
||||
function closeModal() {
|
||||
selectedPrompt = null
|
||||
selectedImages = []
|
||||
showModal = false;
|
||||
expandAll = false;
|
||||
console.warn("CLOSEMODAL")
|
||||
}
|
||||
|
||||
let queued = false
|
||||
$: queued = Boolean($queueState.runningNodeID || $queueState.progress);
|
||||
@@ -238,9 +243,11 @@
|
||||
<div slot="header" class="prompt-modal-header">
|
||||
<h1 style="padding-bottom: 1rem;">Prompt Details</h1>
|
||||
</div>
|
||||
{#if selectedPrompt}
|
||||
<PromptDisplay prompt={selectedPrompt} images={selectedImages} {expandAll} />
|
||||
{/if}
|
||||
<svelte:fragment let:closeDialog>
|
||||
{#if selectedPrompt}
|
||||
<PromptDisplay closeModal={() => { closeModal(); closeDialog(); }} {app} prompt={selectedPrompt} images={selectedImages} {expandAll} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div slot="buttons" let:closeDialog>
|
||||
<Button variant="secondary" on:click={closeDialog}>
|
||||
Close
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
top:0;
|
||||
right:0.5rem;
|
||||
margin: 0.5rem;
|
||||
z-index: 1000000000;
|
||||
z-index: var(--layer-1);
|
||||
|
||||
opacity: 70%;
|
||||
background: var(--neutral-700);
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
import modalState, { type ModalButton, type ModalData } from "$lib/stores/modalState";
|
||||
import { Button } from "@gradio/button";
|
||||
import Modal from "./Modal.svelte";
|
||||
import { SvelteComponentDev } from "svelte/internal";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
function onClose(modal: ModalData | null) {
|
||||
if (modal == null)
|
||||
return;
|
||||
|
||||
if (modal.onClose)
|
||||
modal.onClose()
|
||||
|
||||
modalState.closeModal(modal.id)
|
||||
}
|
||||
|
||||
function onButtonClicked(button: ModalButton, closeDialog: Function) {
|
||||
button.onClick();
|
||||
function onButtonClicked(modal: ModalData, button: ModalButton, closeDialog: Function) {
|
||||
button.onClick(modal);
|
||||
|
||||
if (button.closeOnClick !== false) {
|
||||
closeDialog()
|
||||
@@ -31,13 +30,13 @@
|
||||
</div>
|
||||
<svelte:fragment>
|
||||
{#if modal != null && modal.svelteComponent != null}
|
||||
<svelte:component this={modal.svelteComponent} {...modal.svelteProps}/>
|
||||
<svelte:component this={modal.svelteComponent} {...modal.svelteProps} _modal={modal}/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div slot="buttons" class="buttons" let:closeDialog>
|
||||
{#if modal != null && modal.buttons?.length > 0}
|
||||
{#each modal.buttons as button}
|
||||
<Button variant={button.variant} on:click={() => onButtonClicked(button, closeDialog)}>
|
||||
<Button variant={button.variant} on:click={() => onButtonClicked(modal, button, closeDialog)}>
|
||||
{button.name}
|
||||
</Button>
|
||||
{/each}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
export let showModal; // boolean
|
||||
export let closeOnClick = true; // boolean
|
||||
export const closeDialog = _ => doClose();
|
||||
export const closeDialog = () => doClose();
|
||||
|
||||
let dialog; // HTMLDialogElement
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
>
|
||||
<div on:click|stopPropagation>
|
||||
<slot name="header" />
|
||||
<slot />
|
||||
<slot {closeDialog} />
|
||||
<div class="button-row">
|
||||
<slot name="buttons" {closeDialog}>
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
|
||||
@@ -8,14 +8,23 @@
|
||||
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
import { countNewLines } from "$lib/utils";
|
||||
import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines } from "$lib/utils";
|
||||
import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
|
||||
import workflowState, { type ComfyWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||
import type ComfyApp from "./ComfyApp";
|
||||
|
||||
const splitLength = 50;
|
||||
|
||||
export let prompt: SerializedPromptInputsAll;
|
||||
export let images: string[] = [];
|
||||
export let images: string[] = []; // list of image URLs to ComfyUI's /view? endpoint
|
||||
export let isMobile: boolean = false;
|
||||
export let expandAll: boolean = false;
|
||||
export let closeModal: () => void;
|
||||
export let app: ComfyApp;
|
||||
let isPromptOpen = expandAll;
|
||||
|
||||
let selected_image: number | null = null;
|
||||
|
||||
let galleryStyle: Styles = {
|
||||
grid_cols: [2],
|
||||
@@ -23,6 +32,32 @@
|
||||
height: "var(--size-96)"
|
||||
}
|
||||
|
||||
let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
||||
let comfyBoxImages = []
|
||||
let litegraphType = "(none)"
|
||||
|
||||
$: if (images.length > 0) {
|
||||
// since the image links come from gradio, have to parse the URL for the
|
||||
// ComfyImageLocation params
|
||||
comfyBoxImages = images.map(comfyURLToComfyFile)
|
||||
.map(comfyFileToComfyBoxMetadata);
|
||||
}
|
||||
else {
|
||||
comfyBoxImages = []
|
||||
}
|
||||
|
||||
$: if (comfyBoxImages.length > 0) {
|
||||
if (selected_image != null)
|
||||
litegraphType = "COMFYBOX_IMAGE"
|
||||
else
|
||||
litegraphType = "COMFYBOX_IMAGES"
|
||||
receiveTargets = workflowState.findReceiveOutputTargets(litegraphType)
|
||||
}
|
||||
else {
|
||||
litegraphType = "(none)"
|
||||
receiveTargets = []
|
||||
}
|
||||
|
||||
function isInputLink(input: SerializedPromptInput): boolean {
|
||||
return Array.isArray(input)
|
||||
&& input.length === 2
|
||||
@@ -65,56 +100,98 @@
|
||||
// TODO dialog renders over it
|
||||
// ImageViewer.instance.showLightbox(e.detail)
|
||||
}
|
||||
|
||||
function sendOutput(workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode) {
|
||||
if (workflow == null || targetNode == null)
|
||||
return
|
||||
|
||||
let value = null;
|
||||
if (targetNode.properties.type === "COMFYBOX_IMAGE") {
|
||||
if (selected_image != null)
|
||||
value = comfyBoxImages[selected_image]
|
||||
else
|
||||
value = comfyBoxImages[0]
|
||||
}
|
||||
else if (targetNode.properties.type === "COMFYBOX_IMAGES") {
|
||||
value = comfyBoxImages
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
targetNode.receiveOutput(value);
|
||||
workflowState.setActiveWorkflow(app.lCanvas, workflow.id)
|
||||
|
||||
closeModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="prompt-display">
|
||||
<div class="scroll-container">
|
||||
<div class="prompt-and-sends">
|
||||
<Block>
|
||||
{#each Object.entries(prompt) as [nodeID, inputs], i}
|
||||
{@const classType = inputs.class_type}
|
||||
{@const filtered = Object.entries(inputs.inputs).filter((i) => !isInputLink(i[1]))}
|
||||
{#if filtered.length > 0}
|
||||
<div class="accordion">
|
||||
<Block padding={true}>
|
||||
<Accordion label="Node {i+1}: {classType}" open={expandAll}>
|
||||
{#each filtered as [inputName, input]}
|
||||
<Block>
|
||||
<button class="copy-button" on:click={() => handleCopy(nodeID, inputName, input)}>
|
||||
{#if copiedNodeID === nodeID && copiedInputName === inputName}
|
||||
<span class="copied-icon">
|
||||
<Check />
|
||||
</span>
|
||||
{:else}
|
||||
<span class="copy-text"><Copy /></span>
|
||||
{/if}
|
||||
</button>
|
||||
<div>
|
||||
{#if isInputLink(input)}
|
||||
Link {input[0]} -> {input[1]}
|
||||
{:else if typeof input === "object"}
|
||||
<Accordion label="Prompt" open={isPromptOpen}>
|
||||
<div class="scroll-container">
|
||||
<Block>
|
||||
{#each Object.entries(prompt) as [nodeID, inputs], i}
|
||||
{@const classType = inputs.class_type}
|
||||
{@const filtered = Object.entries(inputs.inputs).filter((i) => !isInputLink(i[1]))}
|
||||
{#if filtered.length > 0}
|
||||
<div class="accordion">
|
||||
<Block padding={true}>
|
||||
<Accordion label="Node {i+1}: {classType}" open={expandAll}>
|
||||
{#each filtered as [inputName, input]}
|
||||
<Block>
|
||||
<BlockLabel
|
||||
Icon={JSONIcon}
|
||||
show_label={true}
|
||||
label={inputName}
|
||||
float={true}
|
||||
/>
|
||||
<JSONComponent value={input} />
|
||||
<button class="copy-button" on:click={() => handleCopy(nodeID, inputName, input)}>
|
||||
{#if copiedNodeID === nodeID && copiedInputName === inputName}
|
||||
<span class="copied-icon">
|
||||
<Check />
|
||||
</span>
|
||||
{:else}
|
||||
<span class="copy-text"><Copy /></span>
|
||||
{/if}
|
||||
</button>
|
||||
<div>
|
||||
{#if isInputLink(input)}
|
||||
Link {input[0]} -> {input[1]}
|
||||
{:else if typeof input === "object"}
|
||||
<Block>
|
||||
<BlockLabel
|
||||
Icon={JSONIcon}
|
||||
show_label={true}
|
||||
label={inputName}
|
||||
float={true}
|
||||
/>
|
||||
<JSONComponent value={input} />
|
||||
</Block>
|
||||
{:else if isMultiline(input)}
|
||||
{@const lines = Math.max(countNewLines(input), input.length / splitLength)}
|
||||
<TextBox label={inputName} value={formatInput(input)} {lines} max_lines={lines} />
|
||||
{:else}
|
||||
<TextBox label={inputName} value={formatInput(input)} lines={1} max_lines={1} />
|
||||
{/if}
|
||||
</div>
|
||||
</Block>
|
||||
{:else if isMultiline(input)}
|
||||
{@const lines = Math.max(countNewLines(input), input.length / splitLength)}
|
||||
<TextBox label={inputName} value={formatInput(input)} {lines} max_lines={lines} />
|
||||
{:else}
|
||||
<TextBox label={inputName} value={formatInput(input)} lines={1} max_lines={1} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</Accordion>
|
||||
</Block>
|
||||
{/each}
|
||||
</Accordion>
|
||||
</Block>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</Block>
|
||||
</div>
|
||||
</Accordion>
|
||||
</Block>
|
||||
<Block>
|
||||
<Accordion label="Send Outputs To..." open={true}>
|
||||
<Block>
|
||||
<BlockTitle>Output type: {litegraphType}</BlockTitle>
|
||||
{#if receiveTargets.length > 0}
|
||||
<ReceiveOutputTargets {receiveTargets} on:select={(e) => sendOutput(e.detail.workflow, e.detail.targetNode)} />
|
||||
{:else}
|
||||
<div class="outputs-message">No receive output targets found across all workflows.</div>
|
||||
{/if}
|
||||
</Block>
|
||||
</Accordion>
|
||||
</Block>
|
||||
</div>
|
||||
{#if images.length > 0}
|
||||
@@ -128,6 +205,7 @@
|
||||
root={""}
|
||||
root_url={""}
|
||||
on:clicked={onGalleryImageClicked}
|
||||
bind:selected_image
|
||||
/>
|
||||
</Block>
|
||||
</div>
|
||||
@@ -148,6 +226,10 @@
|
||||
@media (min-width: 1600px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-and-sends {
|
||||
width: 50%;
|
||||
|
||||
.scroll-container {
|
||||
position: relative;
|
||||
@@ -155,21 +237,6 @@
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
max-width: 30vw;
|
||||
|
||||
> :global(.block) {
|
||||
height: 100%;
|
||||
|
||||
:global(> .preview) {
|
||||
height: 100%;
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@@ -220,4 +287,23 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.outputs-message {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
width: 50%;
|
||||
|
||||
> :global(.block) {
|
||||
height: 100%;
|
||||
|
||||
:global(> .preview) {
|
||||
height: 100%;
|
||||
max-height: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -104,7 +104,6 @@
|
||||
.accordion {
|
||||
background: var(--panel-background-fill);
|
||||
|
||||
|
||||
:global(> .block .block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
|
||||
79
src/lib/components/modal/ReceiveOutputTargets.svelte
Normal file
79
src/lib/components/modal/ReceiveOutputTargets.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||
import type { ComfyWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import { Button } from "@gradio/button";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
select: { workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode };
|
||||
}>();
|
||||
|
||||
export let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
||||
|
||||
function onSelected( workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode ) {
|
||||
dispatch("select", {
|
||||
workflow,
|
||||
targetNode
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="scroll-container">
|
||||
{#if receiveTargets.length > 0}
|
||||
{#each receiveTargets as { workflow, targetNodes }}
|
||||
<Block>
|
||||
<BlockTitle>{workflow.attrs.title}</BlockTitle>
|
||||
{#each targetNodes as targetNode}
|
||||
<Block>
|
||||
<div class="target">
|
||||
<div class="target-name-and-desc">
|
||||
<div class="target-name">➤ {targetNode.properties.name}</div>
|
||||
{#if targetNode.properties.description}
|
||||
<div class="target-desc">{targetNode.properties.description}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="send-button">
|
||||
<Button variant="primary" on:click={() => onSelected(workflow, targetNode)}>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Block>
|
||||
{/each}
|
||||
</Block>
|
||||
{/each}
|
||||
{:else}
|
||||
<div>(No receive targets found.)</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.scroll-container {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
height: 100%;
|
||||
|
||||
> :global(.block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
}
|
||||
|
||||
.target {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
text-align: left;
|
||||
|
||||
.target-name-and-desc {
|
||||
margin: auto auto auto 0;
|
||||
left: 0px;
|
||||
|
||||
.target-desc {
|
||||
opacity: 65%;
|
||||
font-size: 11pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
79
src/lib/components/modal/SendOutputModal.svelte
Normal file
79
src/lib/components/modal/SendOutputModal.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<script lang="ts" context="module">
|
||||
export type SendOutputModalResult = {
|
||||
workflow?: ComfyWorkflow,
|
||||
targetNode?: ComfyReceiveOutputNode,
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { ModalData, ModalState } from "$lib/stores/modalState";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import type { SlotType } from "@litegraph-ts/core";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { StaticImage } from "$lib/components/gradio/image";
|
||||
import type { ComfyWorkflow, WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
|
||||
import { comfyBoxImageToComfyURL } from "$lib/utils";
|
||||
import { Button } from "@gradio/button";
|
||||
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
|
||||
import ReceiveOutputTargets from "./ReceiveOutputTargets.svelte";
|
||||
|
||||
export let value: any;
|
||||
export let type: SlotType;
|
||||
export let receiveTargets: WorkflowReceiveOutputTargets[] = [];
|
||||
|
||||
export let _modal: ModalData;
|
||||
|
||||
let images = []
|
||||
|
||||
if (type === "COMFYBOX_IMAGE") {
|
||||
images = [comfyBoxImageToComfyURL(value)];
|
||||
}
|
||||
|
||||
function sendOutput(workflow: ComfyWorkflow, targetNode: ComfyReceiveOutputNode) {
|
||||
const result: SendOutputModalResult = {
|
||||
workflow,
|
||||
targetNode
|
||||
}
|
||||
_modal.state.set(result)
|
||||
_modal.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="send-output-modal">
|
||||
<div class="targets-container">
|
||||
<Block>
|
||||
<span>Type: {type}</span>
|
||||
</Block>
|
||||
<ReceiveOutputTargets {receiveTargets} on:select={(e) => sendOutput(e.detail.workflow, e.detail.targetNode)} />
|
||||
</div>
|
||||
<div class="output-display">
|
||||
<Block>
|
||||
{#if type === "COMFYBOX_IMAGE"}
|
||||
<StaticImage show_label={false} label="Image" value={images[0]} />
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.send-output-modal {
|
||||
width: 60vw;
|
||||
height: 70vh;
|
||||
color: none;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-y: none;
|
||||
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.targets-container {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.output-display {
|
||||
width: 40vw;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user