feat: add cold start duration display

This commit is contained in:
BennyKok 2024-01-21 11:40:06 +08:00
parent 1d2497116d
commit ca1b05fff5
8 changed files with 1144 additions and 343 deletions

View File

@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "started_at" timestamp;

View File

@ -0,0 +1,762 @@
{
"id": "1ca4fdb7-c0c4-4c39-8b47-f40282293da0",
"prevId": "db06ea66-92c2-4ebe-93c1-6cb8a90ccd8b",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@ -218,6 +218,13 @@
"when": 1705716303820, "when": 1705716303820,
"tag": "0030_kind_doorman", "tag": "0030_kind_doorman",
"breakpoints": true "breakpoints": true
},
{
"idx": 31,
"version": "5",
"when": 1705763980972,
"tag": "0031_fast_lyja",
"breakpoints": true
} }
] ]
} }

View File

@ -2,62 +2,73 @@ import { RunInputs } from "@/components/RunInputs";
import { RunOutputs } from "@/components/RunOutputs"; import { RunOutputs } from "@/components/RunOutputs";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { TableCell, TableRow } from "@/components/ui/table"; import { TableCell, TableRow } from "@/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime"; import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
import { type findAllRuns } from "@/server/findAllRuns"; import { type findAllRuns } from "@/server/findAllRuns";
import { Suspense } from "react"; import { Suspense } from "react";
import { LiveStatus } from "./LiveStatus"; import { LiveStatus } from "./LiveStatus";
export async function RunDisplay({ export async function RunDisplay({
run, run,
}: { }: {
run: Awaited<ReturnType<typeof findAllRuns>>[0]; run: Awaited<ReturnType<typeof findAllRuns>>[0];
}) { }) {
return ( return (
<Dialog> <Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer"> <DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow> <TableRow>
<TableCell>{run.number}</TableCell> <TableCell>{run.number}</TableCell>
<TableCell className="font-medium truncate"> <TableCell className="font-medium truncate">
{run.machine?.name} {run.machine?.name}
</TableCell> </TableCell>
<TableCell className="truncate"> <TableCell className="truncate">
{getRelativeTime(run.created_at)} {getRelativeTime(run.created_at)}
</TableCell> </TableCell>
<TableCell>{run.version?.version}</TableCell> <TableCell>{run.version?.version}</TableCell>
<TableCell> <TableCell>
<Badge variant="outline" className="truncate"> <Badge variant="outline" className="truncate">
{run.origin} {run.origin}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell className="truncate"> <TableCell className="truncate">
{getDuration(run.duration)} <Tooltip>
</TableCell> <TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
<LiveStatus run={run} /> <TooltipContent>
</TableRow> <div>Cold start: {getDuration(run.cold_start_duration)}</div>
</DialogTrigger> <div>Run duration: {getDuration(run.run_duration)}</div>
<DialogContent className="max-w-3xl"> </TooltipContent>
<DialogHeader> </Tooltip>
<DialogTitle>Run outputs</DialogTitle> </TableCell>
<DialogDescription> <LiveStatus run={run} />
You can view your run&apos;s outputs here </TableRow>
</DialogDescription> </DialogTrigger>
</DialogHeader> <DialogContent className="max-w-3xl">
<div className="max-h-96 overflow-y-scroll"> <DialogHeader>
<RunInputs run={run} /> <DialogTitle>Run outputs</DialogTitle>
<Suspense> <DialogDescription>
<RunOutputs run_id={run.id} /> You can view your run&apos;s outputs here
</Suspense> </DialogDescription>
</div> </DialogHeader>
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */} <div className="max-h-96 overflow-y-scroll">
</DialogContent> <RunInputs run={run} />
</Dialog> <Suspense>
); <RunOutputs run_id={run.id} />
</Suspense>
</div>
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
</DialogContent>
</Dialog>
);
} }

View File

@ -1,10 +1,3 @@
import {
findAllDeployments,
findAllRunsWithCounts,
} from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
import { PaginationControl } from "./PaginationControl";
import { RunDisplay } from "./RunDisplay";
import { import {
Table, Table,
TableBody, TableBody,
@ -15,6 +8,13 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import { parseAsInteger } from "next-usequerystate"; import { parseAsInteger } from "next-usequerystate";
import { headers } from "next/headers"; import { headers } from "next/headers";
import {
findAllDeployments,
findAllRunsWithCounts,
} from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
import { PaginationControl } from "./PaginationControl";
import { RunDisplay } from "./RunDisplay";
const itemPerPage = 6; const itemPerPage = 6;
const pageParser = parseAsInteger.withDefault(1); const pageParser = parseAsInteger.withDefault(1);
@ -33,40 +33,40 @@ export async function RunsTable(props: {
offset: (page - 1) * itemPerPage, offset: (page - 1) * itemPerPage,
}); });
return ( return (
<div> <div>
<div className="overflow-auto h-fit w-full"> <div className="overflow-auto h-fit w-full">
<Table className=""> <Table className="">
{allRuns.length == 0 && ( {allRuns.length === 0 && (
<TableCaption>A list of your recent runs.</TableCaption> <TableCaption>A list of your recent runs.</TableCaption>
)} )}
<TableHeader className="bg-background top-0 sticky"> <TableHeader className="bg-background top-0 sticky">
<TableRow> <TableRow>
<TableHead className="truncate">Number</TableHead> <TableHead className="truncate">Number</TableHead>
<TableHead className="truncate">Machine</TableHead> <TableHead className="truncate">Machine</TableHead>
<TableHead className="truncate">Time</TableHead> <TableHead className="truncate">Time</TableHead>
<TableHead className="truncate">Version</TableHead> <TableHead className="truncate">Version</TableHead>
<TableHead className="truncate">Origin</TableHead> <TableHead className="truncate">Origin</TableHead>
<TableHead className="truncate">Duration</TableHead> <TableHead className="truncate">Duration</TableHead>
<TableHead className="truncate">Live Status</TableHead> <TableHead className="truncate">Live Status</TableHead>
<TableHead className="text-right">Status</TableHead> <TableHead className="text-right">Status</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{allRuns.map((run) => ( {allRuns.map((run) => (
<RunDisplay run={run} key={run.id} /> <RunDisplay run={run} key={run.id} />
))} ))}
</TableBody> </TableBody>
</Table> </Table>
</div> </div>
{Math.ceil(total / itemPerPage) > 0 && ( {Math.ceil(total / itemPerPage) > 0 && (
<PaginationControl <PaginationControl
totalPage={Math.ceil(total / itemPerPage)} totalPage={Math.ceil(total / itemPerPage)}
currentPage={page} currentPage={page}
/> />
)} )}
</div> </div>
); );
} }
export async function DeploymentsTable(props: { workflow_id: string }) { export async function DeploymentsTable(props: { workflow_id: string }) {

View File

@ -1,13 +1,13 @@
import { relations, type InferSelectModel } from "drizzle-orm"; import { type InferSelectModel, relations } from "drizzle-orm";
import { import {
text, boolean,
pgSchema, integer,
uuid, jsonb,
integer, pgEnum,
timestamp, pgSchema,
jsonb, text,
pgEnum, timestamp,
boolean, uuid,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
@ -130,29 +130,30 @@ export const machinesStatus = pgEnum("machine_status", [
// We still want to keep the workflow run record. // We still want to keep the workflow run record.
export const workflowRunsTable = dbSchema.table("workflow_runs", { export const workflowRunsTable = dbSchema.table("workflow_runs", {
id: uuid("id").primaryKey().defaultRandom().notNull(), id: uuid("id").primaryKey().defaultRandom().notNull(),
// when workflow version deleted, still want to keep this record // when workflow version deleted, still want to keep this record
workflow_version_id: uuid("workflow_version_id").references( workflow_version_id: uuid("workflow_version_id").references(
() => workflowVersionTable.id, () => workflowVersionTable.id,
{ {
onDelete: "set null", onDelete: "set null",
} },
), ),
workflow_inputs: workflow_inputs:
jsonb("workflow_inputs").$type<Record<string, string | number>>(), jsonb("workflow_inputs").$type<Record<string, string | number>>(),
workflow_id: uuid("workflow_id") workflow_id: uuid("workflow_id")
.notNull() .notNull()
.references(() => workflowTable.id, { .references(() => workflowTable.id, {
onDelete: "cascade", onDelete: "cascade",
}), }),
// when machine deleted, still want to keep this record // when machine deleted, still want to keep this record
machine_id: uuid("machine_id").references(() => machinesTable.id, { machine_id: uuid("machine_id").references(() => machinesTable.id, {
onDelete: "set null", onDelete: "set null",
}), }),
origin: workflowRunOrigin("origin").notNull().default("api"), origin: workflowRunOrigin("origin").notNull().default("api"),
status: workflowRunStatus("status").notNull().default("not-started"), status: workflowRunStatus("status").notNull().default("not-started"),
ended_at: timestamp("ended_at"), ended_at: timestamp("ended_at"),
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
started_at: timestamp("started_at"),
}); });
export const workflowRunRelations = relations( export const workflowRunRelations = relations(

View File

@ -1,11 +1,10 @@
"use server"; "use server";
import { withServerPromise } from "./withServerPromise";
import { db } from "@/db/db"; import { db } from "@/db/db";
import type { import type {
MachineType, MachineType,
WorkflowRunOriginType, WorkflowRunOriginType,
WorkflowVersionType, WorkflowVersionType,
} from "@/db/schema"; } from "@/db/schema";
import { machinesTable, workflowRunsTable } from "@/db/schema"; import { machinesTable, workflowRunsTable } from "@/db/schema";
import type { APIKeyUserType } from "@/server/APIKeyBodyRequest"; import type { APIKeyUserType } from "@/server/APIKeyBodyRequest";
@ -16,219 +15,229 @@ import { and, eq } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import "server-only"; import "server-only";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { withServerPromise } from "./withServerPromise";
export const createRun = withServerPromise( export const createRun = withServerPromise(
async ({ async ({
origin, origin,
workflow_version_id, workflow_version_id,
machine_id, machine_id,
inputs, inputs,
runOrigin, runOrigin,
apiUser, apiUser,
}: { }: {
origin: string; origin: string;
workflow_version_id: string | WorkflowVersionType; workflow_version_id: string | WorkflowVersionType;
machine_id: string | MachineType; machine_id: string | MachineType;
inputs?: Record<string, string | number>; inputs?: Record<string, string | number>;
runOrigin?: WorkflowRunOriginType; runOrigin?: WorkflowRunOriginType;
apiUser?: APIKeyUserType; apiUser?: APIKeyUserType;
}) => { }) => {
const machine = const machine =
typeof machine_id === "string" typeof machine_id === "string"
? await db.query.machinesTable.findFirst({ ? await db.query.machinesTable.findFirst({
where: and( where: and(
eq(machinesTable.id, machine_id), eq(machinesTable.id, machine_id),
eq(machinesTable.disabled, false) eq(machinesTable.disabled, false),
), ),
}) })
: machine_id; : machine_id;
if (!machine) { if (!machine) {
throw new Error("Machine not found"); throw new Error("Machine not found");
} }
const workflow_version_data = const workflow_version_data =
typeof workflow_version_id === "string" typeof workflow_version_id === "string"
? await db.query.workflowVersionTable.findFirst({ ? await db.query.workflowVersionTable.findFirst({
where: eq(workflowRunsTable.id, workflow_version_id), where: eq(workflowRunsTable.id, workflow_version_id),
with: { with: {
workflow: { workflow: {
columns: { columns: {
org_id: true, org_id: true,
user_id: true, user_id: true,
}, },
}, },
}, },
}) })
: workflow_version_id; : workflow_version_id;
if (!workflow_version_data) { if (!workflow_version_data) {
throw new Error("Workflow version not found"); throw new Error("Workflow version not found");
} }
if (apiUser) if (apiUser)
if (apiUser.org_id) { if (apiUser.org_id) {
// is org api call, check org only // is org api call, check org only
if (apiUser.org_id != workflow_version_data.workflow.org_id) { if (apiUser.org_id != workflow_version_data.workflow.org_id) {
throw new Error("Workflow not found"); throw new Error("Workflow not found");
} }
} else { } else {
// is user api call, check user only // is user api call, check user only
if ( if (
apiUser.user_id != workflow_version_data.workflow.user_id && apiUser.user_id != workflow_version_data.workflow.user_id &&
workflow_version_data.workflow.org_id == null workflow_version_data.workflow.org_id == null
) { ) {
throw new Error("Workflow not found"); throw new Error("Workflow not found");
} }
} }
const workflow_api = workflow_version_data.workflow_api; const workflow_api = workflow_version_data.workflow_api;
// Replace the inputs // Replace the inputs
if (inputs && workflow_api) { if (inputs && workflow_api) {
for (const key in inputs) { for (const key in inputs) {
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];
} }
}); });
} }
} }
let prompt_id: string | undefined = undefined; let prompt_id: string | undefined = undefined;
const shareData = { const shareData = {
workflow_api: workflow_api, workflow_api: workflow_api,
status_endpoint: `${origin}/api/update-run`, status_endpoint: `${origin}/api/update-run`,
file_upload_endpoint: `${origin}/api/file-upload`, file_upload_endpoint: `${origin}/api/file-upload`,
}; };
prompt_id = v4(); prompt_id = v4();
// Add to our db // Add to our db
const workflow_run = await db const workflow_run = await db
.insert(workflowRunsTable) .insert(workflowRunsTable)
.values({ .values({
id: prompt_id, id: prompt_id,
workflow_id: workflow_version_data.workflow_id, workflow_id: workflow_version_data.workflow_id,
workflow_version_id: workflow_version_data.id, workflow_version_id: workflow_version_data.id,
workflow_inputs: inputs, workflow_inputs: inputs,
machine_id: machine.id, machine_id: machine.id,
origin: runOrigin, origin: runOrigin,
}) })
.returning(); .returning();
revalidatePath(`/${workflow_version_data.workflow_id}`); revalidatePath(`/${workflow_version_data.workflow_id}`);
try { try {
switch (machine.type) { switch (machine.type) {
case "comfy-deploy-serverless": case "comfy-deploy-serverless":
case "modal-serverless": case "modal-serverless":
const _data = { const _data = {
input: { input: {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}, },
}; };
const ___result = await fetch(`${machine.endpoint}/run`, { const ___result = await fetch(`${machine.endpoint}/run`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(_data), body: JSON.stringify(_data),
cache: "no-store", cache: "no-store",
}); });
console.log(___result); console.log(___result);
if (!___result.ok) if (!___result.ok)
throw new Error( throw new Error(
`Error creating run, ${ `Error creating run, ${
___result.statusText ___result.statusText
} ${await ___result.text()}` } ${await ___result.text()}`,
); );
console.log(_data, ___result); console.log(_data, ___result);
break; break;
case "runpod-serverless": case "runpod-serverless":
const data = { const data = {
input: { input: {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}, },
}; };
if ( if (
!machine.auth_token && !machine.auth_token &&
!machine.endpoint.includes("localhost") && !machine.endpoint.includes("localhost") &&
!machine.endpoint.includes("127.0.0.1") !machine.endpoint.includes("127.0.0.1")
) { ) {
throw new Error("Machine auth token not found"); throw new Error("Machine auth token not found");
} }
const __result = await fetch(`${machine.endpoint}/run`, { const __result = await fetch(`${machine.endpoint}/run`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${machine.auth_token}`, Authorization: `Bearer ${machine.auth_token}`,
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
cache: "no-store", cache: "no-store",
}); });
console.log(__result); console.log(__result);
if (!__result.ok) if (!__result.ok)
throw new Error( throw new Error(
`Error creating run, ${ `Error creating run, ${
__result.statusText __result.statusText
} ${await __result.text()}` } ${await __result.text()}`,
); );
console.log(data, __result); console.log(data, __result);
break; break;
case "classic": case "classic":
const body = { const body = {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}; };
// console.log(body); // console.log(body);
const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`; const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`;
const _result = await fetch(comfyui_endpoint, { const _result = await fetch(comfyui_endpoint, {
method: "POST", method: "POST",
body: JSON.stringify(body), body: JSON.stringify(body),
cache: "no-store", cache: "no-store",
}); });
// console.log(_result); // console.log(_result);
if (!_result.ok) { if (!_result.ok) {
let message = `Error creating run, ${_result.statusText}`; let message = `Error creating run, ${_result.statusText}`;
try { try {
const result = await ComfyAPI_Run.parseAsync( const result = await ComfyAPI_Run.parseAsync(
await _result.json() await _result.json(),
); );
message += ` ${result.node_errors}`; message += ` ${result.node_errors}`;
} catch (error) {} } catch (error) {}
throw new Error(message); throw new Error(message);
} }
// prompt_id = result.prompt_id; // prompt_id = result.prompt_id;
break; break;
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
await db await db
.update(workflowRunsTable) .update(workflowRunsTable)
.set({ .set({
status: "failed", status: "failed",
}) })
.where(eq(workflowRunsTable.id, workflow_run[0].id)); .where(eq(workflowRunsTable.id, workflow_run[0].id));
throw e; throw e;
} }
return { // It successfully started, update the started_at time
workflow_run_id: workflow_run[0].id,
message: "Successful workflow run", await db
}; .update(workflowRunsTable)
} .set({
started_at: new Date(),
})
.where(eq(workflowRunsTable.id, workflow_run[0].id));
return {
workflow_run_id: workflow_run[0].id,
message: "Successful workflow run",
};
},
); );
export async function checkStatus(run_id: string) { export async function checkStatus(run_id: string) {
const { userId } = auth(); const { userId } = auth();
if (!userId) throw new Error("User not found"); if (!userId) throw new Error("User not found");
return await getRunsData(run_id); return await getRunsData(run_id);
} }

View File

@ -16,32 +16,42 @@ export async function findAllRuns({
offset = 0, offset = 0,
}: RunsSearchTypes) { }: RunsSearchTypes) {
return await db.query.workflowRunsTable.findMany({ return await db.query.workflowRunsTable.findMany({
where: eq(workflowRunsTable.workflow_id, workflow_id), where: eq(workflowRunsTable.workflow_id, workflow_id),
orderBy: desc(workflowRunsTable.created_at), orderBy: desc(workflowRunsTable.created_at),
offset: offset, offset: offset,
limit: limit, limit: limit,
extras: { extras: {
number: sql<number>`row_number() over (order by created_at)`.as("number"), number: sql<number>`row_number() over (order by created_at)`.as(
total: sql<number>`count(*) over ()`.as("total"), "number",
duration: ),
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as( total: sql<number>`count(*) over ()`.as("total"),
"duration" duration:
), sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
}, "duration",
with: { ),
machine: { cold_start_duration:
columns: { sql<number>`(extract(epoch from started_at) - extract(epoch from created_at))`.as(
name: true, "cold_start_duration",
endpoint: true, ),
}, run_duration:
}, sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
version: { "run_duration",
columns: { ),
version: true, },
}, with: {
}, machine: {
}, columns: {
}); name: true,
endpoint: true,
},
},
version: {
columns: {
version: true,
},
},
},
});
} }
export async function findAllRunsWithCounts(props: RunsSearchTypes) { export async function findAllRunsWithCounts(props: RunsSearchTypes) {