Compare commits

..
Author SHA1 Message Date
EmmanuelMr18 0cb2af3dc3 feat(share): display muliple images 2024-02-16 22:52:32 -06:00
EmmanuelMr18 9d999f92a9 fix(workflows): user can click multiple times on run even while loading 2024-02-14 19:00:18 -06:00
bennykok 01e8668d1a chore(plugin): update comfyui json 2024-02-14 14:56:11 +08:00
Emmanuel Morales 8e58d962a7 feat(example page): add inpaint, controlnet and sdxl turbo workflows (#24) 2024-02-14 12:49:32 +08:00
bennykok c59c308d32 chore(plugin): update comfyui-json version, fixed some custom nodes detection 2024-02-12 16:01:05 +08:00
bennykok 4560f2cca9 chore(plugin): update comfyui-json version 2024-02-11 21:53:48 +08:00
Nick Kao 872752b820 Merge pull request #22 from BennyKok/fix/createRun-overwritring-ComfyUIDeployExternalText-inputs
fix(create run): ComfyUIDeployExternalText default value was overwritten
2024-02-10 11:08:03 -08:00
EmmanuelMr18 97bb2b69c5 fix(create run): ComfyUIDeployExternalText default value overwriting 2024-02-10 10:57:54 -06:00
bennykok 08fe87c8af fix(plugin): cached execution progress update 2024-02-09 22:13:43 +08:00
bennykok d43e5fcefc fix: live status not sending, add progress 2024-02-09 21:52:48 +08:00
Emmanuel Morales 65492a108c feat(example page): create initial 4 cards (#21)
* feat(example page): add new examples section in the navbar

* feat(example page): initial layout of the new page

* feat(example page): create initial 4 workflows

* style(example page): center title

* style(example page): remove comment

* fix(example page): add hyperlink for txt2img card
2024-02-09 11:15:30 +08:00
10 changed files with 137 additions and 40 deletions
+25 -4
View File
@@ -361,13 +361,30 @@ async def send_json_override(self, event, data, sid=None):
if event == 'executing' and data.get('node') is not None: if event == 'executing' and data.get('node') is not None:
node = data.get('node') node = data.get('node')
if 'prompt_id' in prompt_metadata: if prompt_id in prompt_metadata:
if 'progress' not in prompt_metadata[prompt_id]:
prompt_metadata[prompt_id]["progress"] = set()
prompt_metadata[prompt_id]["progress"].add(node)
calculated_progress = len(prompt_metadata[prompt_id]["progress"]) / len(prompt_metadata[prompt_id]['workflow_api'])
# print("calculated_progress", calculated_progress)
if 'last_updated_node' in prompt_metadata[prompt_id] and prompt_metadata[prompt_id]['last_updated_node'] == node: if 'last_updated_node' in prompt_metadata[prompt_id] and prompt_metadata[prompt_id]['last_updated_node'] == node:
return return
prompt_metadata[prompt_id]['last_updated_node'] = node prompt_metadata[prompt_id]['last_updated_node'] = node
class_type = prompt_metadata[prompt_id]['workflow_api'][node]['class_type'] class_type = prompt_metadata[prompt_id]['workflow_api'][node]['class_type']
print("updating run live status", class_type) print("updating run live status", class_type)
await update_run_live_status(prompt_id, "Executing " + class_type) await update_run_live_status(prompt_id, "Executing " + class_type, calculated_progress)
if event == 'execution_cached' and data.get('nodes') is not None:
if prompt_id in prompt_metadata:
if 'progress' not in prompt_metadata[prompt_id]:
prompt_metadata[prompt_id]["progress"] = set()
if 'nodes' in data:
for node in data.get('nodes', []):
prompt_metadata[prompt_id]["progress"].add(node)
# prompt_metadata[prompt_id]["progress"].update(data.get('nodes'))
if event == 'execution_error': if event == 'execution_error':
# Careful this might not be fully awaited. # Careful this might not be fully awaited.
@@ -391,18 +408,22 @@ class Status(Enum):
# Global variable to keep track of the last read line number # Global variable to keep track of the last read line number
last_read_line_number = 0 last_read_line_number = 0
async def update_run_live_status(prompt_id, live_status): async def update_run_live_status(prompt_id, live_status, calculated_progress: float):
if prompt_id not in prompt_metadata: if prompt_id not in prompt_metadata:
return return
print("progress", calculated_progress)
status_endpoint = prompt_metadata[prompt_id]['status_endpoint'] status_endpoint = prompt_metadata[prompt_id]['status_endpoint']
body = { body = {
"run_id": prompt_id, "run_id": prompt_id,
"live_status": live_status, "live_status": live_status,
"progress": calculated_progress
} }
# requests.post(status_endpoint, json=body) # requests.post(status_endpoint, json=body)
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
await session.post(status_endpoint, json=body) async with session.post(status_endpoint, json=body) as response:
pass
def update_run(prompt_id, status: Status): def update_run(prompt_id, status: Status):
+8 -4
View File
@@ -1,7 +1,7 @@
import { app } from "./app.js"; import { app } from "./app.js";
import { api } from "./api.js"; import { api } from "./api.js";
import { ComfyWidgets, LGraphNode } from "./widgets.js"; import { ComfyWidgets, LGraphNode } from "./widgets.js";
import { generateDependencyGraph } from "https://esm.sh/[email protected]4"; import { generateDependencyGraph } from "https://esm.sh/[email protected]9";
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/ /** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
/** @type {ComfyExtension} */ /** @type {ComfyExtension} */
@@ -182,7 +182,7 @@ function showError(title, message) {
function createDynamicUIHtml(data) { function createDynamicUIHtml(data) {
console.log(data); console.log(data);
let html = let html =
'<div style="max-width: 1024px; margin: 14px auto; display: flex; flex-direction: column; gap: 24px;">'; '<div style="min-width: 600px; max-width: 1024px; margin: 14px auto; display: flex; flex-direction: column; gap: 24px;">';
const bgcolor = "var(--comfy-input-bg)"; const bgcolor = "var(--comfy-input-bg)";
const evenBg = "var(--border-color)"; const evenBg = "var(--border-color)";
const textColor = "var(--input-text)"; const textColor = "var(--input-text)";
@@ -284,8 +284,12 @@ function addButton() {
} }
const ok = await confirmDialog.confirm( const ok = await confirmDialog.confirm(
"Confirm deployment -> " + displayName, "Confirm deployment -> " +
`A new version will be deployed, are you conform? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`, displayName +
"<br><br><small><div style='font-weight: normal;'>" +
endpoint +
"<div><br>",
`A new version will be deployed, are you confirm? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`,
); );
if (!ok) return; if (!ok) return;
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

+29 -2
View File
@@ -48,12 +48,39 @@ const exampleWorkflows: exampleWorkflow[] = [
}, },
{ {
title: "Upscale and Add Detail SDXL", title: "Upscale and Add Detail SDXL",
description: "Upscale and Add Details to your creations", description: "Upscale and Add Details to your creations.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-upscale-and-add-detail-sdxl', previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-upscale-and-add-detail-sdxl',
image: { image: {
src: '/example-workflows/upscale.webp', src: '/example-workflows/upscale.webp',
alt: 'Upscale and Add Detail SDXL', alt: 'Upscale and Add Detail SDXL',
} }
},
{
title: "Txt2Img SDXL Turbo",
description: "Try SDXL turbo and generate images since 1 step in seconds.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-txt2img-sdxl-turbo',
image: {
src: '/example-workflows/txt2img-sdxl-turbo.webp',
alt: 'Txt2Img SDXL Turbo',
}
},
{
title: "Img2Img SDXL Controlnet",
description: "This workflow uses canny. Generate lines of you original image and create variations.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-img2-img-sdxl-controlnet',
image: {
src: '/example-workflows/txt2img-controlnet.webp',
alt: 'Img2Img SDXL Controlnet',
}
},
{
title: "Automatic Inpainting (SEG)",
description: "Type what do you want to select and change that area with your prompt.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-automatic-inpainting-clip-seg',
image: {
src: '/example-workflows/automatic-inpainting-seg.webp',
alt: 'Img2Img SDXL Controlnet',
}
} }
]; ];
@@ -67,7 +94,7 @@ async function Examples() {
</h1> </h1>
<p className="max-w-[560px] text-center text-lg text-muted-foreground">Text to Image, Image to Image, IPAdapter, and more. Here are some examples that you can use to deploy your workflow.</p> <p className="max-w-[560px] text-center text-lg text-muted-foreground">Text to Image, Image to Image, IPAdapter, and more. Here are some examples that you can use to deploy your workflow.</p>
</section> </section>
<section className="flex justify-center flex-wrap gap-4"> <section className="flex justify-center flex-wrap gap-5">
{exampleWorkflows.map(workflow => { {exampleWorkflows.map(workflow => {
return <Card className="w-[350px]"> return <Card className="w-[350px]">
<CardHeader> <CardHeader>
+3 -1
View File
@@ -80,7 +80,9 @@
/* @apply rounded-lg p-2 overflow-x-scroll */ /* @apply rounded-lg p-2 overflow-x-scroll */
@apply p-2 max-w-full overflow-auto w-full @apply p-2 max-w-full overflow-auto w-full
} }
.vsc-controller{
position: absolute;
}
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
+26 -25
View File
@@ -60,6 +60,7 @@ import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher"; import fetcher from "./fetcher";
import { ButtonAction } from "@/components/ButtonActionLoader"; import { ButtonAction } from "@/components/ButtonActionLoader";
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine"; import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
import { VisualizeImagesGrid } from "@/components/VisualizeImagesGrid";
export function VersionSelect({ export function VersionSelect({
workflow, workflow,
@@ -169,19 +170,21 @@ export function useSelectedMachine(
} }
type PublicRunStore = { type PublicRunStore = {
image: string; image: {
url: string;
}[] | null;
loading: boolean; loading: boolean;
runId: string; runId: string;
status: string; status: string;
setImage: (image: string) => void; setImage: (image: { url: string; }[]) => void;
setLoading: (loading: boolean) => void; setLoading: (loading: boolean) => void;
setRunId: (runId: string) => void; setRunId: (runId: string) => void;
setStatus: (status: string) => void; setStatus: (status: string) => void;
}; };
export const publicRunStore = create<PublicRunStore>((set) => ({ export const publicRunStore = create<PublicRunStore>((set) => ({
image: "", image: null,
loading: false, loading: false,
runId: "", runId: "",
status: "", status: "",
@@ -205,7 +208,10 @@ export function PublicRunOutputs(props: {
console.log(res?.status); console.log(res?.status);
if (res) setStatus(res.status); if (res) setStatus(res.status);
if (res && res.status === "success") { if (res && res.status === "success") {
setImage(res.outputs[0]?.data.images[0].url); const imageURLs = res.outputs[0]?.data.images.map((item: { url: string; }) => {
return { url: item.url };
});
setImage(imageURLs);
setLoading(false); setLoading(false);
clearInterval(interval); clearInterval(interval);
} }
@@ -214,30 +220,25 @@ export function PublicRunOutputs(props: {
return () => clearInterval(interval); return () => clearInterval(interval);
}, [runId]); }, [runId]);
return ( if (loading) {
<div className="border border-gray-200 w-full square h-[400px] rounded-lg relative"> return (
{!loading && !image && props.preview && props.preview.length > 0 && ( <div className="border border-gray-200 w-full h-[400px] square rounded-lg relative p-4 ">
<>
<img
className="w-full h-full object-contain"
src={props.preview[0]?.url}
alt="Generated image"
/>
</>
)}
{!loading && image && (
<img
className="w-full h-full object-contain"
src={image}
alt="Generated image"
/>
)}
{loading && (
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2"> <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
{status} <LoadingIcon /> {status} <LoadingIcon />
</div> </div>
<Skeleton className="w-full h-full" />
</div>
);
}
return (
<div className="border border-gray-200 w-full min-h-[400px] square rounded-lg relative p-4 ">
{!image && props.preview && props.preview.length > 0 &&
<VisualizeImagesGrid images={props.preview} />
}
{image && (
<VisualizeImagesGrid images={image} />
)} )}
{loading && <Skeleton className="w-full h-full" />}
</div> </div>
); );
} }
@@ -328,7 +329,7 @@ export function RunWorkflowButton({
className="px-1" className="px-1"
> >
<div className="flex justify-end"> <div className="flex justify-end">
<AutoFormSubmit> <AutoFormSubmit disabled={isLoading}>
Run Run
{isLoading ? <LoadingIcon /> : <Play size={14} />} {isLoading ? <LoadingIcon /> : <Play size={14} />}
</AutoFormSubmit> </AutoFormSubmit>
@@ -0,0 +1,42 @@
type imagesType = {
url: string;
width?: number;
height?: number;
};
type VisualizeImagesGridProps = {
images: imagesType[];
layout?: 'justify-between' | 'justify-center' | 'justify-start' | 'justify-end';
};
export function VisualizeImagesGrid({ images, layout }: VisualizeImagesGridProps) {
return (
<div className={`flex gap-4 flex-wrap ${layout || 'justify-center'}`}>
<>
{images && images.length > 0 &&
images.map(item => {
if (!item) {
return;
}
if (item?.url.endsWith(".mp4") || item?.url.endsWith(".webm")) {
return (
<video key={item?.url} controls autoPlay className="rounded-xl" style={{ maxHeight: item.height || 370, maxWidth: item.width || "auto" }}>
<source src={item?.url} type="video/mp4" />
<source src={item?.url} type="video/webm" />
Your browser does not support the video tag.
</video>
);
}
return <img
key={item?.url}
className="object-contain overflow-hidden rounded-xl"
src={item?.url}
alt="Generated image"
style={{ maxHeight: item.height || 370, maxWidth: item.width || "auto" }}
/>;
})
}
</>
</div>
);
}
+4 -4
View File
@@ -90,12 +90,12 @@ export const createRun = withServerPromise(
Object.entries(workflow_api).forEach(([_, node]) => { Object.entries(workflow_api).forEach(([_, node]) => {
if (node.inputs["input_id"] === key) { if (node.inputs["input_id"] === key) {
node.inputs["input_id"] = inputs[key]; node.inputs["input_id"] = inputs[key];
// Fix for external text default value
if (node.class_type == "ComfyUIDeployExternalText") {
node.inputs["default_value"] = inputs[key];
}
} }
// Fix for external text default value
if (node.class_type == "ComfyUIDeployExternalText") {
node.inputs["default_value"] = inputs[key];
}
}); });
} }
} }