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
16 changed files with 240 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:
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:
return
prompt_metadata[prompt_id]['last_updated_node'] = node
class_type = prompt_metadata[prompt_id]['workflow_api'][node]['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':
# 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
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:
return
print("progress", calculated_progress)
status_endpoint = prompt_metadata[prompt_id]['status_endpoint']
body = {
"run_id": prompt_id,
"live_status": live_status,
"progress": calculated_progress
}
# requests.post(status_endpoint, json=body)
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):
+8 -4
View File
@@ -1,7 +1,7 @@
import { app } from "./app.js";
import { api } from "./api.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*/
/** @type {ComfyExtension} */
@@ -182,7 +182,7 @@ function showError(title, message) {
function createDynamicUIHtml(data) {
console.log(data);
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 evenBg = "var(--border-color)";
const textColor = "var(--input-text)";
@@ -284,8 +284,12 @@ function addButton() {
}
const ok = await confirmDialog.confirm(
"Confirm deployment -> " + displayName,
`A new version will be deployed, are you conform? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`,
"Confirm deployment -> " +
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;
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+9
View File
@@ -0,0 +1,9 @@
"use client";
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
import { usePathname } from "next/navigation";
export default function Loading() {
const pathName = usePathname();
return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
}
+117
View File
@@ -0,0 +1,117 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import Image from "next/image";
import Link from "next/link";
export default function Page() {
return <Examples />;
}
type exampleWorkflow = {
title: string;
description: string;
previewURL: string;
image: {
src: string,
alt: string,
};
};
const exampleWorkflows: exampleWorkflow[] = [
{
title: "Txt2Img SDXL",
description: "The basic workflow, type a prompt and generate images based on that.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-txt2img-sdxl',
image: {
src: '/example-workflows/txt2img.webp',
alt: 'IPAdapter workflow',
}
},
{
title: "Txt2Img LCM SDXL",
description: "Images in a couple of seconds, increase the speed of each generation using LCM Lora.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-lcm-sdxl',
image: {
src: '/example-workflows/txt2img-lcm.webp',
alt: 'txt2img LCM SDXL',
}
},
{
title: "IPAdapter SDXL",
description: "Load images and use them as reference for new generations.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-ip-adapter-sdxl',
image: {
src: '/example-workflows/ipadapter.webp',
alt: 'IPAdapter workflow',
}
},
{
title: "Upscale and Add Detail SDXL",
description: "Upscale and Add Details to your creations.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-upscale-and-add-detail-sdxl',
image: {
src: '/example-workflows/upscale.webp',
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',
}
}
];
async function Examples() {
return (
<div className="w-full py-4">
<section className="mx-auto flex max-w-[980px] flex-col items-center gap-2 py-8 md:py-12 md:pb-8 lg:py-24 lg:pb-20">
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl text-center">
Check out some examples
</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>
</section>
<section className="flex justify-center flex-wrap gap-5">
{exampleWorkflows.map(workflow => {
return <Card className="w-[350px]">
<CardHeader>
<CardTitle>{workflow.title}</CardTitle>
<CardDescription>{workflow.description}</CardDescription>
</CardHeader>
<CardContent>
<Image src={workflow.image.src} alt={workflow.image.alt} width={350} height={230} />
</CardContent>
<CardFooter className="flex justify-end gap-2">
<Button asChild>
<Link href={workflow.previewURL}>View Workflow</Link>
</Button>
</CardFooter>
</Card>;
})}
</section>
</div>
);
}
+3 -1
View File
@@ -80,7 +80,9 @@
/* @apply rounded-lg p-2 overflow-x-scroll */
@apply p-2 max-w-full overflow-auto w-full
}
.vsc-controller{
position: absolute;
}
@layer base {
* {
@apply border-border;
+6 -2
View File
@@ -40,6 +40,10 @@ export function NavbarMenu({
name: "API Keys",
path: "/api-keys",
},
{
name: "Examples",
path: "/examples"
}
];
return (
@@ -48,9 +52,9 @@ export function NavbarMenu({
{isDesktop && (
<Tabs
defaultValue={pathname}
className="w-[300px] flex pointer-events-auto"
className="w-fit flex pointer-events-auto"
>
<TabsList className="grid w-full grid-cols-3">
<TabsList className="w-full">
{pages.map((page) => (
<TabsTrigger
key={page.name}
+26 -25
View File
@@ -60,6 +60,7 @@ import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher";
import { ButtonAction } from "@/components/ButtonActionLoader";
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
import { VisualizeImagesGrid } from "@/components/VisualizeImagesGrid";
export function VersionSelect({
workflow,
@@ -169,19 +170,21 @@ export function useSelectedMachine(
}
type PublicRunStore = {
image: string;
image: {
url: string;
}[] | null;
loading: boolean;
runId: string;
status: string;
setImage: (image: string) => void;
setImage: (image: { url: string; }[]) => void;
setLoading: (loading: boolean) => void;
setRunId: (runId: string) => void;
setStatus: (status: string) => void;
};
export const publicRunStore = create<PublicRunStore>((set) => ({
image: "",
image: null,
loading: false,
runId: "",
status: "",
@@ -205,7 +208,10 @@ export function PublicRunOutputs(props: {
console.log(res?.status);
if (res) setStatus(res.status);
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);
clearInterval(interval);
}
@@ -214,30 +220,25 @@ export function PublicRunOutputs(props: {
return () => clearInterval(interval);
}, [runId]);
return (
<div className="border border-gray-200 w-full square h-[400px] rounded-lg relative">
{!loading && !image && props.preview && props.preview.length > 0 && (
<>
<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 && (
if (loading) {
return (
<div className="border border-gray-200 w-full h-[400px] square rounded-lg relative p-4 ">
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
{status} <LoadingIcon />
</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>
);
}
@@ -328,7 +329,7 @@ export function RunWorkflowButton({
className="px-1"
>
<div className="flex justify-end">
<AutoFormSubmit>
<AutoFormSubmit disabled={isLoading}>
Run
{isLoading ? <LoadingIcon /> : <Play size={14} />}
</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]) => {
if (node.inputs["input_id"] === 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];
}
});
}
}