Refactor image upload widget
This commit is contained in:
@@ -7,10 +7,10 @@
|
||||
import UploadText from "$lib/components/gradio/app/UploadText.svelte";
|
||||
import { tick } from "svelte";
|
||||
import notify from "$lib/notify";
|
||||
import type { ComfyUploadImageAPIResponse } from "$lib/utils";
|
||||
import { convertComfyOutputToComfyURL, type ComfyUploadImageAPIResponse, converGradioFileDataToComfyURL } from "$lib/utils";
|
||||
import type { GalleryOutputEntry, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
||||
|
||||
export let value: GradioFileData[] | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
export let value: GalleryOutputEntry[] | null = null;
|
||||
export let imgWidth: number = 0;
|
||||
export let imgHeight: number = 0;
|
||||
export let imgElem: HTMLImageElement | null = null
|
||||
@@ -21,19 +21,23 @@
|
||||
// let propsChanged: Writable<number> | null = null;
|
||||
let dragging = false;
|
||||
let pending_upload = false;
|
||||
let old_value: Array<GradioFileData> | null = null;
|
||||
let old_value: GradioFileData[] | null = null;
|
||||
|
||||
let _value: GradioFileData[] | null = null;
|
||||
const root = "comf"
|
||||
const root_url = "https//ComfyUI!"
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: GradioFileData[];
|
||||
change: GalleryOutputEntry[];
|
||||
upload: undefined;
|
||||
clear: undefined;
|
||||
}>();
|
||||
|
||||
$: _value = normalise_file(value, root, root_url);
|
||||
if (value) {
|
||||
_value = null
|
||||
if (imgElem)
|
||||
imgElem.src = convertComfyOutputToComfyURL(value[0])
|
||||
}
|
||||
|
||||
$: if (!(_value && _value.length > 0 && imgElem)) {
|
||||
imgWidth = 1
|
||||
@@ -41,7 +45,6 @@
|
||||
}
|
||||
|
||||
function onChange() {
|
||||
value = _value || []
|
||||
dispatch("change", value)
|
||||
}
|
||||
|
||||
@@ -55,7 +58,7 @@
|
||||
|
||||
interface GradioUploadResponse {
|
||||
error?: string;
|
||||
files?: Array<string>;
|
||||
files?: Array<GalleryOutputEntry>;
|
||||
}
|
||||
|
||||
async function upload_files(root: string, files: Array<File>): Promise<GradioUploadResponse> {
|
||||
@@ -85,7 +88,12 @@
|
||||
}
|
||||
else {
|
||||
// bare filename of image
|
||||
files.push((r as ComfyUploadImageAPIResponse).name)
|
||||
const resp = r as ComfyUploadImageAPIResponse;
|
||||
files.push({
|
||||
filename: resp.name,
|
||||
subfolder: "",
|
||||
type: "input"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,21 +141,12 @@
|
||||
}
|
||||
|
||||
pending_upload = false;
|
||||
_value.forEach(
|
||||
(file_data: GradioFileData, i: number) => {
|
||||
if (response.files) {
|
||||
file_data.orig_name = file_data.name;
|
||||
file_data.name = response.files[i];
|
||||
file_data.is_file = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
notify(response.error, { type: "error" })
|
||||
}
|
||||
|
||||
value = normalise_file(_value, root, root_url) as GradioFileData[];
|
||||
value = response.files;
|
||||
onChange();
|
||||
onUpload();
|
||||
});
|
||||
@@ -164,14 +163,13 @@
|
||||
|
||||
function handle_clear(_e: CustomEvent<null>) {
|
||||
_value = null;
|
||||
value = [];
|
||||
onChange();
|
||||
onClear();
|
||||
}
|
||||
|
||||
function getImageUrl(image: GradioFileData) {
|
||||
const baseUrl = `http://${location.hostname}:8188` // TODO make configurable
|
||||
const params = new URLSearchParams({ filename: image.name, subfolder: "", type: "input" })
|
||||
return `${baseUrl}/view?${params}`
|
||||
function convertGradioUpload(e: CustomEvent<GradioFileData[]>) {
|
||||
_value = e.detail
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -194,7 +192,7 @@
|
||||
{#if _value && _value.length > 0 && !pending_upload}
|
||||
{@const firstImage = _value[0]}
|
||||
<ModifyUpload on:clear={handle_clear} absolute />
|
||||
<img src={getImageUrl(firstImage)}
|
||||
<img src={converGradioFileDataToComfyURL(firstImage)}
|
||||
alt={firstImage.orig_name}
|
||||
bind:this={imgElem}
|
||||
bind:naturalWidth={imgWidth}
|
||||
@@ -204,13 +202,13 @@
|
||||
<Upload
|
||||
file_count={fileCount}
|
||||
filetype="image/*"
|
||||
on:change={({ detail }) => (value = detail)}
|
||||
on:change={convertGradioUpload}
|
||||
on:load={handle_upload}
|
||||
bind:dragging
|
||||
on:clear
|
||||
on:select
|
||||
parse_to_data_url={false}
|
||||
>
|
||||
>
|
||||
<UploadText type="file" />
|
||||
</Upload>
|
||||
{/if}
|
||||
|
||||
@@ -890,7 +890,7 @@ export interface ComfyImageUploadProperties extends ComfyWidgetProperties {
|
||||
fileCount: "single" | "multiple" // gradio File component format
|
||||
}
|
||||
|
||||
export class ComfyImageUploadNode extends ComfyWidgetNode<Array<GradioFileData>> {
|
||||
export class ComfyImageUploadNode extends ComfyWidgetNode<GalleryOutputEntry[]> {
|
||||
override properties: ComfyImageUploadProperties = {
|
||||
defaultValue: [],
|
||||
tags: [],
|
||||
@@ -923,24 +923,38 @@ export class ComfyImageUploadNode extends ComfyWidgetNode<Array<GradioFileData>>
|
||||
super(name, [])
|
||||
}
|
||||
|
||||
override parseValue(value: any): GradioFileData[] {
|
||||
override parseValue(value: any): GalleryOutputEntry[] {
|
||||
if (value == null)
|
||||
return []
|
||||
|
||||
if (typeof value === "string" && value !== "") { // Single filename
|
||||
return [{ name: value, data: value, orig_name: value, is_file: true }]
|
||||
const isComfyImageSpec = (value: any): boolean => {
|
||||
return value && typeof value === "object" && "filename" in value && "type" in value
|
||||
}
|
||||
else {
|
||||
return []
|
||||
|
||||
if (typeof value === "string") {
|
||||
// Single filename
|
||||
return [{ filename: value, subfolder: "", type: "input" }]
|
||||
}
|
||||
else if (isComfyImageSpec(value)) {
|
||||
// Single ComfyUI file
|
||||
return [value]
|
||||
}
|
||||
else if (Array.isArray(value)) {
|
||||
if (value.every(v => typeof v === "string"))
|
||||
return value.map(filename => { return { filename, subfolder: "", type: "input" } })
|
||||
else if (value.every(isComfyImageSpec))
|
||||
return value
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
override onExecute(param: any, options: object) {
|
||||
super.onExecute(param, options);
|
||||
|
||||
const value = get(this.value)
|
||||
if (value.length > 0 && value[0].name) {
|
||||
this.setOutputData(0, value[0].name) // TODO when ComfyUI LoadImage supports loading an image batch
|
||||
if (value.length > 0) {
|
||||
this.setOutputData(0, value[0].filename) // TODO when ComfyUI LoadImage supports loading an image batch
|
||||
this.setOutputData(1, this.imageSize[0])
|
||||
this.setOutputData(2, this.imageSize[1])
|
||||
this.setOutputData(3, value.length)
|
||||
@@ -953,7 +967,7 @@ export class ComfyImageUploadNode extends ComfyWidgetNode<Array<GradioFileData>>
|
||||
}
|
||||
}
|
||||
|
||||
override formatValue(value: GradioFileData[]): string {
|
||||
override formatValue(value: GalleryOutputEntry[]): string {
|
||||
return `Images: ${value?.length || 0}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { get } from "svelte/store"
|
||||
import layoutState from "$lib/stores/layoutState"
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
import type { SerializedLGraph } from "@litegraph-ts/core";
|
||||
import type { GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes";
|
||||
import type { FileNameOrGalleryData, GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
|
||||
export function clamp(n: number, min: number, max: number): number {
|
||||
@@ -139,12 +139,32 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat
|
||||
});
|
||||
}
|
||||
|
||||
export function convertComfyOutputToComfyURL(output: GalleryOutputEntry): string {
|
||||
export function convertComfyOutputToComfyURL(output: FileNameOrGalleryData): string {
|
||||
if (typeof output === "string")
|
||||
return output;
|
||||
|
||||
const params = new URLSearchParams(output)
|
||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
||||
return url + "/view?" + params
|
||||
}
|
||||
|
||||
export function converGradioFileDataToComfyURL(image: GradioFileData, type: "input" | "output" | "temp" = "input"): string {
|
||||
const baseUrl = `http://${location.hostname}:8188` // TODO make configurable
|
||||
const params = new URLSearchParams({ filename: image.name, subfolder: "", type })
|
||||
return `${baseUrl}/view?${params}`
|
||||
}
|
||||
|
||||
export function convertGradioFileDataToComfyOutput(fileData: GradioFileData, type: "input" | "output" | "temp" = "input"): GalleryOutputEntry {
|
||||
if (!fileData.is_file)
|
||||
throw "Can't convert blob data to comfy output!"
|
||||
|
||||
return {
|
||||
filename: fileData.name,
|
||||
subfolder: "",
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
export function convertFilenameToComfyURL(filename: string,
|
||||
subfolder: string = "",
|
||||
type: "input" | "output" | "temp" = "output"): string {
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
import { get, type Writable, writable } from "svelte/store";
|
||||
import Modal from "$lib/components/Modal.svelte";
|
||||
import { Button } from "@gradio/button";
|
||||
import type { ComfyImageEditorNode, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import type { ComfyImageEditorNode, GalleryOutputEntry, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import { Embed as Klecks, KL, KlApp, klHistory, type KlAppOptionsEmbed } from "klecks";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
|
||||
import "klecks/style/style.scss";
|
||||
import ImageUpload from "$lib/components/ImageUpload.svelte";
|
||||
import { uploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
@@ -17,6 +21,10 @@
|
||||
let leftUrl: string = ""
|
||||
let rightUrl: string = ""
|
||||
|
||||
let imgElem: HTMLImageElement | null = null
|
||||
let imgWidth: number = 0;
|
||||
let imgHeight: number = 0;
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
function setNodeValue(widget: WidgetLayout) {
|
||||
@@ -65,8 +73,32 @@
|
||||
showModal = false;
|
||||
}
|
||||
|
||||
function submitKlecksToComfyUI(onSuccess: () => void, onError: () => void) {
|
||||
const data = kl.getPNG();
|
||||
const FILENAME: string = "ComfyUITemp.png";
|
||||
const SUBFOLDER: string = "ComfyBox_Editor";
|
||||
|
||||
async function submitKlecksToComfyUI(onSuccess: () => void, onError: () => void) {
|
||||
const blob = kl.getPNG();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("image", blob, FILENAME);
|
||||
|
||||
const entry: GalleryOutputEntry = {
|
||||
filename: FILENAME,
|
||||
subfolder: SUBFOLDER,
|
||||
type: "input"
|
||||
}
|
||||
|
||||
await uploadImageToComfyUI(entry)
|
||||
.then((resp: ComfyUploadImageAPIResponse) => {
|
||||
entry.filename = resp.name;
|
||||
$nodeValue = [entry]
|
||||
onSuccess();
|
||||
})
|
||||
.catch(err => {
|
||||
notify(`Failed to upload image from editor: ${err}`, { type: "error", timeout: 10000 })
|
||||
$nodeValue = []
|
||||
onError();
|
||||
})
|
||||
}
|
||||
|
||||
function generateBlankImage(fill: string = "#fff"): HTMLCanvasElement {
|
||||
@@ -110,6 +142,10 @@
|
||||
kl.klApp?.out("yo");
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function onUploadChanged(e: CustomEvent<GradioFileData[]>) {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper comfy-image-editor">
|
||||
@@ -123,6 +159,17 @@
|
||||
<div class="image-editor-root" bind:this={editorRoot} />
|
||||
</Modal>
|
||||
<div class="comfy-image-editor-panel">
|
||||
<ImageUpload value={$nodeValue}
|
||||
{isMobile}
|
||||
bind:imgWidth
|
||||
bind:imgHeight
|
||||
bind:imgElem
|
||||
fileCount={"single"}
|
||||
elem_classes={[]}
|
||||
style={""}
|
||||
label={"Image"}
|
||||
on:change={onUploadChanged}
|
||||
/>
|
||||
<Block>
|
||||
<BlockTitle>Image editor.</BlockTitle>
|
||||
<Button variant="secondary" on:click={openImageEditor}>
|
||||
|
||||
@@ -1,37 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
import { File as FileIcon } from "@gradio/icons";
|
||||
import ImageUpload from "$lib/components/ImageUpload.svelte"
|
||||
import type { WidgetLayout } from "$lib/stores/layoutState";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { ComfyGalleryNode, ComfyImageUploadNode, GalleryOutputEntry } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
import UploadText from "$lib/components/gradio/app/UploadText.svelte";
|
||||
import { tick } from "svelte";
|
||||
import notify from "$lib/notify";
|
||||
import type { Vector2 } from "@litegraph-ts/core";
|
||||
import type { ComfyGalleryNode, ComfyImageUploadNode, GalleryOutputEntry, MultiImageData } from "$lib/nodes/ComfyWidgetNodes";
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyImageUploadNode | null = null;
|
||||
let nodeValue: Writable<Array<GradioFileData>> | null = null;
|
||||
let nodeValue: Writable<GalleryOutputEntry[]> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
let dragging = false;
|
||||
let pending_upload = false;
|
||||
let old_value: Array<GradioFileData> | null = null;
|
||||
let imgWidth: number = 1;
|
||||
let imgHeight: number = 1;
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
$: if (!(node && $nodeValue && $nodeValue.length > 0)) {
|
||||
node.imageSize = [1, 1]
|
||||
node.imageSize = [0, 0]
|
||||
}
|
||||
else if (imgWidth > 1 || imgHeight > 1) {
|
||||
else if (imgWidth > 0 && imgHeight > 0) {
|
||||
node.imageSize = [imgWidth, imgHeight]
|
||||
}
|
||||
else {
|
||||
node.imageSize = [1, 1]
|
||||
node.imageSize = [0, 0]
|
||||
}
|
||||
|
||||
function setNodeValue(widget: WidgetLayout) {
|
||||
@@ -42,22 +32,28 @@
|
||||
}
|
||||
};
|
||||
|
||||
function onChange(e: CustomEvent<GradioFileData[]>) {
|
||||
function onChange(e: CustomEvent<GalleryOutputEntry[]>) {
|
||||
console.warn("ONCHANGE!!!", e.detail)
|
||||
$nodeValue = e.detail || []
|
||||
}
|
||||
|
||||
function onClear(e: CustomEvent<GalleryOutputEntry[]>) {
|
||||
console.warn("ONCLEAR!!!", e.detail)
|
||||
$nodeValue = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper gradio-file comfy-image-upload" style={widget.attrs.style}>
|
||||
{#if widget && node && nodeValue}
|
||||
<ImageUpload value={$nodeValue}
|
||||
{isMobile}
|
||||
bind:imgWidth
|
||||
bind:imgHeight
|
||||
bind:fileCount={node.properties.fileCount}
|
||||
elem_classes={widget.attrs.classes.split(",")}
|
||||
style={widget.attrs.style}
|
||||
label={widget.attrs.title}
|
||||
on:change={onChange}/>
|
||||
on:change={onChange}
|
||||
on:clear={onClear}/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user