Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cb2af3dc3 | ||
|
|
9d999f92a9 | ||
|
|
01e8668d1a | ||
|
|
8e58d962a7 | ||
|
|
c59c308d32 | ||
|
|
4560f2cca9 | ||
|
|
872752b820 | ||
|
|
97bb2b69c5 | ||
|
|
08fe87c8af | ||
|
|
d43e5fcefc | ||
|
|
65492a108c |
+25
-4
@@ -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
@@ -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 |
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user