From f9ed8145d2b856dd06dfbe1e1f73b503965621c8 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Mon, 11 Dec 2023 10:49:37 +0800 Subject: [PATCH] feat: add new ws realtime event --- routes.py | 41 ++++++++++++ web/bun.lockb | Bin 274531 -> 275443 bytes web/package.json | 4 +- web/src/app/[workflow_id]/page.tsx | 17 ++--- web/src/app/api/create-run/route.ts | 89 +------------------------ web/src/components/MachinesWS.tsx | 86 ++++++++++++++++++++++++ web/src/components/NavbarRight.tsx | 2 +- web/src/components/RunDisplay.tsx | 27 ++++++++ web/src/components/VersionSelect.tsx | 17 +++-- web/src/server/createRun.ts | 95 +++++++++++++++++++++++++++ web/src/server/curdMachine.ts | 2 +- 11 files changed, 272 insertions(+), 108 deletions(-) create mode 100644 web/src/components/MachinesWS.tsx create mode 100644 web/src/components/RunDisplay.tsx create mode 100644 web/src/server/createRun.ts diff --git a/routes.py b/routes.py index 5e6fe9d..6db1603 100644 --- a/routes.py +++ b/routes.py @@ -20,6 +20,9 @@ import atexit import logging from enum import Enum +import aiohttp +from aiohttp import web + api = None api_task = None prompt_metadata = {} @@ -101,6 +104,41 @@ async def comfy_deploy_run(request): return web.json_response(res, status=status) +sockets = dict() + +@server.PromptServer.instance.routes.get('/comfy-deploy/ws') +async def websocket_handler(request): + ws = web.WebSocketResponse() + await ws.prepare(request) + sid = request.rel_url.query.get('clientId', '') + if sid: + # Reusing existing session, remove old + sockets.pop(sid, None) + else: + sid = uuid.uuid4().hex + + sockets[sid] = ws + + try: + # Send initial state to the new client + await send("status", { 'sid': sid }, sid) + + async for msg in ws: + if msg.type == aiohttp.WSMsgType.ERROR: + print('ws connection closed with exception %s' % ws.exception()) + finally: + sockets.pop(sid, None) + return ws + +async def send(event, data, sid=None): + if sid: + ws = sockets.get(sid) + if ws: + await ws.send_json({ 'event': event, 'data': data }) + else: + for ws in sockets.values(): + await ws.send_json({ 'event': event, 'data': data }) + logging.basicConfig(level=logging.INFO) prompt_server = server.PromptServer.instance @@ -111,6 +149,9 @@ async def send_json_override(self, event, data, sid=None): prompt_id = data.get('prompt_id') + # now we send everything + await send(event, data) + if event == 'execution_start': update_run(prompt_id, Status.RUNNING) diff --git a/web/bun.lockb b/web/bun.lockb index 1fa660a822bdc1780df72c242682cd8b27c3c7ec..58976711facafb1e3371c3250b45b091dca0f8b7 100755 GIT binary patch delta 53413 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%0A(Ya#s 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 + + @@ -109,26 +112,20 @@ async function RunsTable(props: { workflow_id: string }) { Version Machine Time + Live Status Status {allRuns.map((run) => ( - - {run.version.version} - {run.machine.name} - {getRelativeTime(run.created_at)} - - - - + ))} ); } -function StatusBadge({ +export function StatusBadge({ run, }: { run: Awaited>[0]; diff --git a/web/src/app/api/create-run/route.ts b/web/src/app/api/create-run/route.ts index b563491..aab477e 100644 --- a/web/src/app/api/create-run/route.ts +++ b/web/src/app/api/create-run/route.ts @@ -1,9 +1,5 @@ import { parseDataSafe } from "../../../lib/parseDataSafe"; -import { db } from "@/db/db"; -import { workflowRunsTable } from "@/db/schema"; -import { eq } from "drizzle-orm"; -import { revalidatePath } from "next/cache"; -import { NextResponse } from "next/server"; +import { createRun } from "../../../server/createRun"; import { z } from "zod"; const Request = z.object({ @@ -12,7 +8,7 @@ const Request = z.object({ machine_id: z.string(), }); -const ComfyAPI_Run = z.object({ +export const ComfyAPI_Run = z.object({ prompt_id: z.string(), number: z.number(), node_errors: z.any(), @@ -26,84 +22,5 @@ export async function POST(request: Request) { const { workflow_version_id, machine_id } = data; - const machine = await db.query.machinesTable.findFirst({ - where: eq(workflowRunsTable.id, machine_id), - }); - - if (!machine) { - return new Response("Machine not found", { - status: 404, - }); - } - - const workflow_version_data = - // workflow_version_id - // ? - await db.query.workflowVersionTable.findFirst({ - where: eq(workflowRunsTable.id, workflow_version_id), - }); - // : workflow_version != undefined - // ? await db.query.workflowVersionTable.findFirst({ - // where: and( - // eq(workflowVersionTable.version, workflow_version), - // eq(workflowVersionTable.workflow_id) - // ), - // }) - // : null; - - if (!workflow_version_data) { - return new Response("Workflow version not found", { - status: 404, - }); - } - - const comfyui_endpoint = `${machine.endpoint}/comfy-deploy/run`; - - // Sending to comfyui - const result = await fetch(comfyui_endpoint, { - method: "POST", - // headers: { - // "Content-Type": "application/json", - // }, - body: JSON.stringify({ - workflow_api: workflow_version_data.workflow_api, - status_endpoint: `${origin}/api/update-run`, - }), - }) - .then(async (res) => ComfyAPI_Run.parseAsync(await res.json())) - .catch((error) => { - console.error(error); - return new Response(error.details, { - status: 500, - }); - }); - - console.log(result); - - // return the error - if (result instanceof Response) { - return result; - } - - // Add to our db - const workflow_run = await db - .insert(workflowRunsTable) - .values({ - id: result.prompt_id, - workflow_id: workflow_version_data.workflow_id, - workflow_version_id: workflow_version_data.id, - machine_id, - }) - .returning(); - - revalidatePath(`./${workflow_version_data.workflow_id}`); - - return NextResponse.json( - { - workflow_run_id: workflow_run[0].id, - }, - { - status: 200, - } - ); + return await createRun(origin, workflow_version_id, machine_id); } diff --git a/web/src/components/MachinesWS.tsx b/web/src/components/MachinesWS.tsx new file mode 100644 index 0000000..9d2ea04 --- /dev/null +++ b/web/src/components/MachinesWS.tsx @@ -0,0 +1,86 @@ +"use client"; + +import type { getMachines } from "@/server/curdMachine"; +import React, { useEffect } from "react"; +import useWebSocket, { ReadyState } from "react-use-websocket"; +import { create } from "zustand"; + +type State = { + data: { + id: string; + json: { + event: string; + data: any; + }; + }[]; + addData: ( + id: string, + json: { + event: string; + data: any; + } + ) => void; +}; + +export const useStore = create((set) => ({ + data: [], + addData: (id, json) => + set((state) => ({ + ...state, + data: [...state.data, { id, json }], + })), +})); + +export function MachinesWSMain(props: { + machines: Awaited>; +}) { + return ( +
+ Machine Status + {props.machines.map((x) => ( + + ))} +
+ ); +} + +function MachineWS({ + machine, +}: { + machine: Awaited>[0]; +}) { + const { addData } = useStore(); + const wsEndpoint = machine.endpoint.replace(/^http/, "ws"); + const { lastMessage, readyState } = useWebSocket( + `${wsEndpoint}/comfy-deploy/ws`, + { + reconnectAttempts: 10, + reconnectInterval: 1000, + } + ); + + const connectionStatus = { + [ReadyState.CONNECTING]: "Connecting", + [ReadyState.OPEN]: "Open", + [ReadyState.CLOSING]: "Closing", + [ReadyState.CLOSED]: "Closed", + [ReadyState.UNINSTANTIATED]: "Uninstantiated", + }[readyState]; + + useEffect(() => { + if (!lastMessage?.data) return; + + const message = JSON.parse(lastMessage.data); + console.log(message.event, message); + + if (message.data?.prompt_id) { + addData(message.data.prompt_id, message); + } + }, [lastMessage]); + + return ( +
+ {machine.name} - {connectionStatus} +
+ ); +} diff --git a/web/src/components/NavbarRight.tsx b/web/src/components/NavbarRight.tsx index 91127e9..14364ca 100644 --- a/web/src/components/NavbarRight.tsx +++ b/web/src/components/NavbarRight.tsx @@ -1,6 +1,6 @@ "use client"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { usePathname } from "next/navigation"; import { useRouter } from "next/navigation"; diff --git a/web/src/components/RunDisplay.tsx b/web/src/components/RunDisplay.tsx new file mode 100644 index 0000000..4f47465 --- /dev/null +++ b/web/src/components/RunDisplay.tsx @@ -0,0 +1,27 @@ +"use client"; + +import type { findAllRuns } from "../app/[workflow_id]/page"; +import { StatusBadge } from "../app/[workflow_id]/page"; +import { useStore } from "@/components/MachinesWS"; +import { TableCell, TableRow } from "@/components/ui/table"; +import { getRelativeTime } from "@/lib/getRelativeTime"; + +export function RunDisplay({ + run, +}: { + run: Awaited>[0]; +}) { + const data = useStore((state) => state.data.find((x) => x.id === run.id)); + + return ( + + {run.version.version} + {run.machine.name} + {getRelativeTime(run.created_at)} + {data ? data.json.event : "-"} + + + + + ); +} diff --git a/web/src/components/VersionSelect.tsx b/web/src/components/VersionSelect.tsx index 2f901ed..f2cb7d2 100644 --- a/web/src/components/VersionSelect.tsx +++ b/web/src/components/VersionSelect.tsx @@ -12,6 +12,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { createRun } from "@/server/createRun"; import type { getMachines } from "@/server/curdMachine"; import { Play } from "lucide-react"; import { parseAsInteger, useQueryState } from "next-usequerystate"; @@ -101,17 +102,15 @@ export function RunWorkflowButton({ className="gap-2" disabled={isLoading} onClick={async () => { + const workflow_version_id = workflow?.versions.find( + (x) => x.version === version + )?.id; + if (!workflow_version_id) return; + setIsLoading(true); try { - await fetch(`/api/create-run`, { - method: "POST", - body: JSON.stringify({ - workflow_version_id: workflow?.versions.find( - (x) => x.version === version - )?.id, - machine_id: machine, - }), - }); + const origin = window.location.origin; + await createRun(origin, workflow_version_id, machine); setIsLoading(false); } catch (error) { setIsLoading(false); diff --git a/web/src/server/createRun.ts b/web/src/server/createRun.ts new file mode 100644 index 0000000..bfd922f --- /dev/null +++ b/web/src/server/createRun.ts @@ -0,0 +1,95 @@ +"use server"; + +import { ComfyAPI_Run } from "../app/api/create-run/route"; +import { db } from "@/db/db"; +import { workflowRunsTable } from "@/db/schema"; +import { eq } from "drizzle-orm"; +import { revalidatePath } from "next/cache"; +import { NextResponse } from "next/server"; +import "server-only"; + +export async function createRun( + origin: string, + workflow_version_id: string, + machine_id: string +) { + const machine = await db.query.machinesTable.findFirst({ + where: eq(workflowRunsTable.id, machine_id), + }); + + if (!machine) { + return new Response("Machine not found", { + status: 404, + }); + } + + const workflow_version_data = + // workflow_version_id + // ? + await db.query.workflowVersionTable.findFirst({ + where: eq(workflowRunsTable.id, workflow_version_id), + }); + // : workflow_version != undefined + // ? await db.query.workflowVersionTable.findFirst({ + // where: and( + // eq(workflowVersionTable.version, workflow_version), + // eq(workflowVersionTable.workflow_id) + // ), + // }) + // : null; + if (!workflow_version_data) { + return new Response("Workflow version not found", { + status: 404, + }); + } + + const comfyui_endpoint = `${machine.endpoint}/comfy-deploy/run`; + + // Sending to comfyui + const result = await fetch(comfyui_endpoint, { + method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + body: JSON.stringify({ + workflow_api: workflow_version_data.workflow_api, + status_endpoint: `${origin}/api/update-run`, + }), + }) + .then(async (res) => ComfyAPI_Run.parseAsync(await res.json())) + .catch((error) => { + console.error(error); + return new Response(error.details, { + status: 500, + }); + }); + + console.log(result); + + // return the error + if (result instanceof Response) { + return result; + } + + // Add to our db + const workflow_run = await db + .insert(workflowRunsTable) + .values({ + id: result.prompt_id, + workflow_id: workflow_version_data.workflow_id, + workflow_version_id: workflow_version_data.id, + machine_id, + }) + .returning(); + + revalidatePath(`/${workflow_version_data.workflow_id}`); + + return NextResponse.json( + { + workflow_run_id: workflow_run[0].id, + }, + { + status: 200, + } + ); +} diff --git a/web/src/server/curdMachine.ts b/web/src/server/curdMachine.ts index 10a1aec..53917ab 100644 --- a/web/src/server/curdMachine.ts +++ b/web/src/server/curdMachine.ts @@ -1,7 +1,7 @@ "use server"; import { db } from "@/db/db"; -import { machinesTable, workflowTable } from "@/db/schema"; +import { machinesTable } from "@/db/schema"; import { auth } from "@clerk/nextjs"; import { eq } from "drizzle-orm"; import { revalidatePath } from "next/cache";