From 6de7bf3f20c74d42fe2c39cc6eeef3e0d7a05af5 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Sun, 21 Jan 2024 11:40:48 +0800 Subject: [PATCH] feat: share page slug --- web/drizzle/0032_shallow_vermin.sql | 2 + web/drizzle/meta/0032_snapshot.json | 776 ++++++++++++++++++ web/drizzle/meta/_journal.json | 7 + web/src/app/(app)/share/[share_id]/page.tsx | 160 ++-- .../[workflow_id]/@workflow/page.tsx | 14 +- web/src/components/ButtonActionLoader.tsx | 132 +-- web/src/components/CreateShareButton.tsx | 66 ++ web/src/components/DeploymentDisplay.tsx | 277 +++---- web/src/components/DeploymentRow.tsx | 90 +- web/src/components/SharePageSettings.tsx | 3 +- web/src/components/VersionSelect.tsx | 76 +- web/src/db/schema.ts | 49 +- web/src/server/curdDeploments.ts | 416 +++++----- 13 files changed, 1453 insertions(+), 615 deletions(-) create mode 100644 web/drizzle/0032_shallow_vermin.sql create mode 100644 web/drizzle/meta/0032_snapshot.json create mode 100644 web/src/components/CreateShareButton.tsx diff --git a/web/drizzle/0032_shallow_vermin.sql b/web/drizzle/0032_shallow_vermin.sql new file mode 100644 index 0000000..88e48f3 --- /dev/null +++ b/web/drizzle/0032_shallow_vermin.sql @@ -0,0 +1,2 @@ +ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "share_slug" text;--> statement-breakpoint +ALTER TABLE "comfyui_deploy"."deployments" ADD CONSTRAINT "deployments_share_slug_unique" UNIQUE("share_slug"); \ No newline at end of file diff --git a/web/drizzle/meta/0032_snapshot.json b/web/drizzle/meta/0032_snapshot.json new file mode 100644 index 0000000..a470ac9 --- /dev/null +++ b/web/drizzle/meta/0032_snapshot.json @@ -0,0 +1,776 @@ +{ + "id": "1425ee00-66fb-4541-8da7-19b217944545", + "prevId": "1ca4fdb7-c0c4-4c39-8b47-f40282293da0", + "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 + }, + "share_slug": { + "name": "share_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "deployments_share_slug_unique": { + "name": "deployments_share_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "share_slug" + ] + } + } + }, + "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": {} + } +} \ No newline at end of file diff --git a/web/drizzle/meta/_journal.json b/web/drizzle/meta/_journal.json index 360bc49..a262f43 100644 --- a/web/drizzle/meta/_journal.json +++ b/web/drizzle/meta/_journal.json @@ -225,6 +225,13 @@ "when": 1705763980972, "tag": "0031_fast_lyja", "breakpoints": true + }, + { + "idx": 32, + "version": "5", + "when": 1705806921697, + "tag": "0032_shallow_vermin", + "breakpoints": true } ] } \ No newline at end of file diff --git a/web/src/app/(app)/share/[share_id]/page.tsx b/web/src/app/(app)/share/[share_id]/page.tsx index e49922d..c7cfb5b 100644 --- a/web/src/app/(app)/share/[share_id]/page.tsx +++ b/web/src/app/(app)/share/[share_id]/page.tsx @@ -2,11 +2,11 @@ import { ButtonActionMenu } from "@/components/ButtonActionLoader"; import { RunWorkflowInline } from "@/components/RunWorkflowInline"; import { PublicRunOutputs } from "@/components/VersionSelect"; import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { db } from "@/db/db"; import { usersTable } from "@/db/schema"; @@ -14,9 +14,9 @@ import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow"; import { getRelativeTime } from "@/lib/getRelativeTime"; import { setInitialUserData } from "@/lib/setInitialUserData"; import { - cloneMachine, - cloneWorkflow, - findSharedDeployment, + cloneMachine, + cloneWorkflow, + findSharedDeployment, } from "@/server/curdDeploments"; import { auth, clerkClient } from "@clerk/nextjs/server"; import { eq } from "drizzle-orm"; @@ -25,89 +25,87 @@ import { redirect } from "next/navigation"; export const maxDuration = 300; // 5 minutes export default async function Page({ - params, + params, }: { - params: { share_id: string }; + params: { share_id: string }; }) { - const { userId } = await auth(); + const { userId } = await auth(); - // If there is user, check if the user data is present - if (userId) { - const user = await db.query.usersTable.findFirst({ - where: eq(usersTable.id, userId), - }); + // If there is user, check if the user data is present + if (userId) { + const user = await db.query.usersTable.findFirst({ + where: eq(usersTable.id, userId), + }); - if (!user) { - await setInitialUserData(userId); - } - } + if (!user) { + await setInitialUserData(userId); + } + } - const sharedDeployment = await findSharedDeployment(params.share_id); + const sharedDeployment = await findSharedDeployment(params.share_id); - if (!sharedDeployment) return redirect("/"); + if (!sharedDeployment) return redirect("/"); - const userName = sharedDeployment.workflow.org_id - ? await clerkClient.organizations - .getOrganization({ - organizationId: sharedDeployment.workflow.org_id, - }) - .then((x) => x.name) - : sharedDeployment.user.name; + const userName = sharedDeployment.workflow.org_id + ? await clerkClient.organizations + .getOrganization({ + organizationId: sharedDeployment.workflow.org_id, + }) + .then((x) => x.name) + : sharedDeployment.user.name; - const inputs = getInputsFromWorkflow(sharedDeployment.version); + const inputs = getInputsFromWorkflow(sharedDeployment.version); - return ( -
- - - -
- {userName} - {" / "} - {sharedDeployment.workflow.name} -
+ return ( +
+ + + +
+ {userName} + {" / "} + {sharedDeployment.workflow.name} +
- -
- - {getRelativeTime(sharedDeployment?.updated_at)} - -
+ + + + {getRelativeTime(sharedDeployment?.updated_at)} + + - -
- {sharedDeployment?.description && ( - <>{sharedDeployment?.description} - )} -
- -
-
- - - Run outputs - + +
+ {sharedDeployment?.description && sharedDeployment?.description} +
+ +
+
+ + + Run outputs + - - - - -
- ); + + + +
+
+ ); } diff --git a/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx b/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx index c3c7904..620b2b4 100644 --- a/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx +++ b/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx @@ -1,13 +1,13 @@ +import { CreateShareButton } from "@/components/CreateShareButton"; import { MachinesWSMain } from "@/components/MachinesWS"; import { VersionDetails } from "@/components/VersionDetails"; import { - CopyWorkflowVersion, - CreateDeploymentButton, - CreateShareButton, - MachineSelect, - RunWorkflowButton, - VersionSelect, - ViewWorkflowDetailsButton, + CopyWorkflowVersion, + CreateDeploymentButton, + MachineSelect, + RunWorkflowButton, + VersionSelect, + ViewWorkflowDetailsButton, } from "@/components/VersionSelect"; import { Card, diff --git a/web/src/components/ButtonActionLoader.tsx b/web/src/components/ButtonActionLoader.tsx index 9f517d4..568e87d 100644 --- a/web/src/components/ButtonActionLoader.tsx +++ b/web/src/components/ButtonActionLoader.tsx @@ -4,10 +4,10 @@ import { LoadingIcon } from "@/components/LoadingIcon"; import { callServerPromise } from "@/components/callServerPromise"; import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useAuth, useClerk } from "@clerk/nextjs"; import { MoreVertical } from "lucide-react"; @@ -15,74 +15,80 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; export function ButtonAction({ - action, - children, - ...rest + action, + children, + routerAction = "back", + ...rest }: { - action: () => Promise; - children: React.ReactNode; + action: () => Promise; + routerAction?: "refresh" | "back"; + children: React.ReactNode; }) { - const [pending, setPending] = useState(false); - const router = useRouter(); + const [pending, setPending] = useState(false); + const router = useRouter(); - return ( - - ); + if (routerAction === "back") { + router.back(); + router.refresh(); + } + else if (routerAction === "refresh") router.refresh(); + }} + {...rest} + > + {children} {pending && } + + ); } export function ButtonActionMenu(props: { - title?: string; - actions: { - title: string; - action: () => Promise; - }[]; + title?: string; + actions: { + title: string; + action: () => Promise; + }[]; }) { - const user = useAuth(); - const [isLoading, setIsLoading] = useState(false); - const clerk = useClerk(); + const user = useAuth(); + const [isLoading, setIsLoading] = useState(false); + const clerk = useClerk(); - return ( - - - - - - {props.actions.map((action) => ( - { - if (!user.isSignedIn) { - clerk.openSignIn({ - redirectUrl: window.location.href, - }); - return; - } + return ( + + + + + + {props.actions.map((action) => ( + { + if (!user.isSignedIn) { + clerk.openSignIn({ + redirectUrl: window.location.href, + }); + return; + } - setIsLoading(true); - await callServerPromise(action.action()); - setIsLoading(false); - }} - > - {action.title} - - ))} - - - ); + setIsLoading(true); + await callServerPromise(action.action()); + setIsLoading(false); + }} + > + {action.title} + + ))} + + + ); } diff --git a/web/src/components/CreateShareButton.tsx b/web/src/components/CreateShareButton.tsx new file mode 100644 index 0000000..8447887 --- /dev/null +++ b/web/src/components/CreateShareButton.tsx @@ -0,0 +1,66 @@ +"use client"; +import { LoadingIcon } from "@/components/LoadingIcon"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { createDeployments } from "@/server/curdDeploments"; +import type { getMachines } from "@/server/curdMachine"; +import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion"; +import { Share } from "lucide-react"; +import { parseAsInteger, useQueryState } from "next-usequerystate"; +import { useState } from "react"; +import { useSelectedMachine } from "./VersionSelect"; +import { callServerPromise } from "./callServerPromise"; + +export function CreateShareButton({ + workflow, + machines, +}: { + workflow: Awaited>; + machines: Awaited>; +}) { + const [version] = useQueryState("version", { + defaultValue: workflow?.versions[0].version ?? 1, + ...parseAsInteger, + }); + const [machine] = useSelectedMachine(machines); + + const [isLoading, setIsLoading] = useState(false); + const workflow_version_id = workflow?.versions.find( + (x) => x.version === version, + )?.id; + + return ( + + + + + + { + if (!workflow_version_id) return; + + setIsLoading(true); + await callServerPromise( + createDeployments( + workflow.id, + workflow_version_id, + machine, + "public-share", + ), + ); + setIsLoading(false); + }} + > + Public + + + + ); +} diff --git a/web/src/components/DeploymentDisplay.tsx b/web/src/components/DeploymentDisplay.tsx index 631fbce..87be9a2 100644 --- a/web/src/components/DeploymentDisplay.tsx +++ b/web/src/components/DeploymentDisplay.tsx @@ -1,18 +1,18 @@ -import { DeploymentRow, SharePageDeploymentRow } from "./DeploymentRow"; import { CodeBlock } from "@/components/CodeBlock"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; import { TableRow } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow"; import type { findAllDeployments } from "@/server/findAllRuns"; +import { DeploymentRow, SharePageDeploymentRow } from "./DeploymentRow"; const curlTemplate = ` curl --request POST \ @@ -83,144 +83,145 @@ const run = await client.getRun(run_id); `; export function DeploymentDisplay({ - deployment, - domain, + deployment, + domain, }: { - deployment: Awaited>[0]; - domain: string; + deployment: Awaited>[0]; + domain: string; }) { - const workflowInput = getInputsFromWorkflow(deployment.version); + const workflowInput = getInputsFromWorkflow(deployment.version); - if (deployment.environment == "public-share") { - return ; - } + if (deployment.environment === "public-share") { + return ; + } - return ( - - - - - - - - - - {deployment.environment} Deployment - - Code for your deployment client - - - - - Server Client - NodeJS Fetch - CURL - - -
- Copy and paste the ComfyDeployClient form  - - here - -
- - Create a run via deployment id - 0 - ? jsClientCreateRunTemplate - : jsClientCreateRunNoInputsTemplate, - deployment, - domain, - workflowInput - )} - /> - Check the status of the run, and retrieve the outputs - -
- - Trigger the workflow - - Check the status of the run, and retrieve the outputs - - - - - - -
-
-
-
- ); + return ( + + + + + + + + + + {deployment.environment} Deployment + + Code for your deployment client + + + + + Server Client + NodeJS Fetch + CURL + + +
+ Copy and paste the ComfyDeployClient form  + + here + +
+ + Create a run via deployment id + 0 + ? jsClientCreateRunTemplate + : jsClientCreateRunNoInputsTemplate, + deployment, + domain, + workflowInput, + )} + /> + Check the status of the run, and retrieve the outputs + +
+ + Trigger the workflow + + Check the status of the run, and retrieve the outputs + + + + + + +
+
+
+
+ ); } function formatCode( - codeTemplate: string, - deployment: Awaited>[0], - domain: string, - inputs?: ReturnType, - inputsTabs?: number + codeTemplate: string, + deployment: Awaited>[0], + domain: string, + inputs?: ReturnType, + inputsTabs?: number, ) { - if (inputs && inputs.length > 0) { - codeTemplate = codeTemplate.replace( - "inputs: {}", - `inputs: ${JSON.stringify( - Object.fromEntries( - inputs.map((x) => { - return [x?.input_id, ""]; - }) - ), - null, - 2 - ) - .split("\n") - .map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line - .join("\n")}` - ); - } else { - codeTemplate = codeTemplate.replace( - ` + if (inputs && inputs.length > 0) { + codeTemplate = codeTemplate.replace( + "inputs: {}", + `inputs: ${JSON.stringify( + Object.fromEntries( + inputs.map((x) => { + return [x?.input_id, ""]; + }), + ), + null, + 2, + ) + .split("\n") + .map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line + .join("\n")}`, + ); + } else { + codeTemplate = codeTemplate.replace( + ` inputs: {}`, - "" - ); - } - return codeTemplate - .replace("", `${domain ?? "http://localhost:3000"}/api/run`) - .replace("", deployment.id) - .replace("", domain ?? "http://localhost:3000"); + "", + ); + } + return codeTemplate + .replace("", `${domain ?? "http://localhost:3000"}/api/run`) + .replace("", deployment.id) + .replace("", domain ?? "http://localhost:3000"); } diff --git a/web/src/components/DeploymentRow.tsx b/web/src/components/DeploymentRow.tsx index 13a06ed..2c35fa2 100644 --- a/web/src/components/DeploymentRow.tsx +++ b/web/src/components/DeploymentRow.tsx @@ -6,55 +6,57 @@ import type { findAllDeployments } from "@/server/findAllRuns"; import { useRouter } from "next/navigation"; export function SharePageDeploymentRow({ - deployment, + deployment, }: { - deployment: Awaited>[0]; + deployment: Awaited>[0]; }) { - const router = useRouter(); - return ( - { - if (deployment.environment == "public-share") { - router.push(`/share/${deployment.id}/settings`); - } - }} - > - - {deployment.environment} - - - {deployment.version?.version} - - - {deployment.machine?.name} - - - {getRelativeTime(deployment.updated_at)} - - - ); + const router = useRouter(); + return ( + { + if (deployment.environment === "public-share") { + router.push( + `/share/${deployment.share_slug ?? deployment.id}/settings`, + ); + } + }} + > + + {deployment.environment} + + + {deployment.version?.version} + + + {deployment.machine?.name} + + + {getRelativeTime(deployment.updated_at)} + + + ); } export function DeploymentRow({ - deployment, + deployment, }: { - deployment: Awaited>[0]; + deployment: Awaited>[0]; }) { - return ( - <> - - {deployment.environment} - - - {deployment.version?.version} - - - {deployment.machine?.name} - - - {getRelativeTime(deployment.updated_at)} - - - ); + return ( + <> + + {deployment.environment} + + + {deployment.version?.version} + + + {deployment.machine?.name} + + + {getRelativeTime(deployment.updated_at)} + + + ); } diff --git a/web/src/components/SharePageSettings.tsx b/web/src/components/SharePageSettings.tsx index d43d9d9..f1f246a 100644 --- a/web/src/components/SharePageSettings.tsx +++ b/web/src/components/SharePageSettings.tsx @@ -58,13 +58,14 @@ export function SharePageSettings({ type="button" > Remove diff --git a/web/src/components/VersionSelect.tsx b/web/src/components/VersionSelect.tsx index 0f391b6..96edf18 100644 --- a/web/src/components/VersionSelect.tsx +++ b/web/src/components/VersionSelect.tsx @@ -1,8 +1,5 @@ "use client"; -import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod"; -import { callServerPromise } from "./callServerPromise"; -import fetcher from "./fetcher"; import { LoadingIcon } from "@/components/LoadingIcon"; import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form"; import { Badge } from "@/components/ui/badge"; @@ -44,20 +41,16 @@ import { checkStatus, createRun } from "@/server/createRun"; import { createDeployments } from "@/server/curdDeploments"; import type { getMachines } from "@/server/curdMachine"; import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion"; -import { - Copy, - ExternalLink, - Info, - MoreVertical, - Play, - Share, -} from "lucide-react"; +import { Copy, ExternalLink, Info, MoreVertical, Play } from "lucide-react"; import { parseAsInteger, useQueryState } from "next-usequerystate"; import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import useSWR from "swr"; import type { z } from "zod"; import { create } from "zustand"; +import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod"; +import { callServerPromise } from "./callServerPromise"; +import fetcher from "./fetcher"; export function VersionSelect({ workflow, @@ -122,12 +115,14 @@ export function MachineSelect({ ); } -function useSelectedMachine(machines: Awaited>) { - const a = useQueryState("machine", { - defaultValue: machines?.[0]?.id ?? "", - }); +export function useSelectedMachine( + machines: Awaited>, +) { + const a = useQueryState("machine", { + defaultValue: machines?.[0]?.id ?? "", + }); - return a; + return a; } type PublicRunStore = { @@ -373,55 +368,6 @@ export function CreateDeploymentButton({ ); } -export function CreateShareButton({ - workflow, - machines, -}: { - workflow: Awaited>; - machines: Awaited>; -}) { - const [version] = useQueryState("version", { - defaultValue: workflow?.versions[0].version ?? 1, - ...parseAsInteger, - }); - const [machine] = useSelectedMachine(machines); - - const [isLoading, setIsLoading] = useState(false); - const workflow_version_id = workflow?.versions.find( - (x) => x.version === version - )?.id; - - return ( - - - - - - { - if (!workflow_version_id) return; - - setIsLoading(true); - await callServerPromise( - createDeployments( - workflow.id, - workflow_version_id, - machine, - "public-share" - ) - ); - setIsLoading(false); - }} - > - Public - - - - ); -} - export function CopyWorkflowVersion({ workflow, }: { diff --git a/web/src/db/schema.ts b/web/src/db/schema.ts index 783717c..20372ff 100644 --- a/web/src/db/schema.ts +++ b/web/src/db/schema.ts @@ -257,30 +257,31 @@ export const showcaseMediaNullable = z .nullable(); export const deploymentsTable = dbSchema.table("deployments", { - id: uuid("id").primaryKey().defaultRandom().notNull(), - user_id: text("user_id") - .references(() => usersTable.id, { - onDelete: "cascade", - }) - .notNull(), - org_id: text("org_id"), - workflow_version_id: uuid("workflow_version_id") - .notNull() - .references(() => workflowVersionTable.id), - workflow_id: uuid("workflow_id") - .notNull() - .references(() => workflowTable.id, { - onDelete: "cascade", - }), - machine_id: uuid("machine_id") - .notNull() - .references(() => machinesTable.id), - description: text("description"), - showcase_media: - jsonb("showcase_media").$type>(), - environment: deploymentEnvironment("environment").notNull(), - created_at: timestamp("created_at").defaultNow().notNull(), - updated_at: timestamp("updated_at").defaultNow().notNull(), + id: uuid("id").primaryKey().defaultRandom().notNull(), + user_id: text("user_id") + .references(() => usersTable.id, { + onDelete: "cascade", + }) + .notNull(), + org_id: text("org_id"), + workflow_version_id: uuid("workflow_version_id") + .notNull() + .references(() => workflowVersionTable.id), + workflow_id: uuid("workflow_id") + .notNull() + .references(() => workflowTable.id, { + onDelete: "cascade", + }), + machine_id: uuid("machine_id") + .notNull() + .references(() => machinesTable.id), + share_slug: text("share_slug").unique(), + description: text("description"), + showcase_media: + jsonb("showcase_media").$type>(), + environment: deploymentEnvironment("environment").notNull(), + created_at: timestamp("created_at").defaultNow().notNull(), + updated_at: timestamp("updated_at").defaultNow().notNull(), }); export const publicShareDeployment = z.object({ diff --git a/web/src/server/curdDeploments.ts b/web/src/server/curdDeploments.ts index 85270cf..9ea5190 100644 --- a/web/src/server/curdDeploments.ts +++ b/web/src/server/curdDeploments.ts @@ -7,247 +7,279 @@ import { createNewWorkflow } from "@/server/createNewWorkflow"; import { addCustomMachine } from "@/server/curdMachine"; import { withServerPromise } from "@/server/withServerPromise"; import { auth } from "@clerk/nextjs"; -import { and, eq, isNull } from "drizzle-orm"; +import { clerkClient } from "@clerk/nextjs/server"; +import slugify from "@sindresorhus/slugify"; +import { and, eq, isNull, or } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import "server-only"; +import { validate as isValidUUID } from "uuid"; import type { z } from "zod"; - export async function createDeployments( - workflow_id: string, - version_id: string, - machine_id: string, - environment: DeploymentType["environment"] + workflow_id: string, + version_id: string, + machine_id: string, + environment: DeploymentType["environment"], ) { - const { userId, orgId } = auth(); - if (!userId) throw new Error("No user id"); + const { userId, orgId } = auth(); + if (!userId) throw new Error("No user id"); - if (!machine_id) { - throw new Error("No machine id provided"); - } + if (!machine_id) { + throw new Error("No machine id provided"); + } - // Same environment and same workflow - const existingDeployment = await db.query.deploymentsTable.findFirst({ - where: and( - eq(deploymentsTable.workflow_id, workflow_id), - eq(deploymentsTable.environment, environment) - ), - }); + // Same environment and same workflow + const existingDeployment = await db.query.deploymentsTable.findFirst({ + where: and( + eq(deploymentsTable.workflow_id, workflow_id), + eq(deploymentsTable.environment, environment), + ), + }); - if (existingDeployment) { - await db - .update(deploymentsTable) - .set({ - workflow_id, - workflow_version_id: version_id, - machine_id, - org_id: orgId, - }) - .where(eq(deploymentsTable.id, existingDeployment.id)); - } else { - await db.insert(deploymentsTable).values({ - user_id: userId, - workflow_id, - workflow_version_id: version_id, - machine_id, - environment, - org_id: orgId, - }); - } - revalidatePath(`/${workflow_id}`); - return { - message: `Successfully created deployment for ${environment}`, - }; + if (existingDeployment) { + await db + .update(deploymentsTable) + .set({ + workflow_id, + workflow_version_id: version_id, + machine_id, + org_id: orgId, + }) + .where(eq(deploymentsTable.id, existingDeployment.id)); + } else { + const workflow = await db.query.workflowTable.findFirst({ + where: eq(workflowTable.id, workflow_id), + with: { + user: { + columns: { + name: true, + }, + }, + }, + }); + + if (!workflow) throw new Error("No workflow found"); + + const userName = workflow.org_id + ? await clerkClient.organizations + .getOrganization({ + organizationId: workflow.org_id, + }) + .then((x) => x.name) + : workflow.user.name; + + await db.insert(deploymentsTable).values({ + user_id: userId, + workflow_id, + workflow_version_id: version_id, + machine_id, + environment, + org_id: orgId, + share_slug: slugify(`${userName} ${workflow.name}`), + }); + } + revalidatePath(`/${workflow_id}`); + return { + message: `Successfully created deployment for ${environment}`, + }; } export async function findAllDeployments() { - const { userId, orgId } = auth(); - if (!userId) throw new Error("No user id"); + const { userId, orgId } = auth(); + if (!userId) throw new Error("No user id"); - const deployments = await db.query.workflowTable.findMany({ - where: and( - orgId - ? eq(workflowTable.org_id, orgId) - : and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)) - ), - columns: { - name: true, - }, - with: { - deployments: { - columns: { - environment: true, - }, - with: { - version: { - columns: { - id: true, - snapshot: true, - }, - }, - }, - }, - }, - }); + const deployments = await db.query.workflowTable.findMany({ + where: and( + orgId + ? eq(workflowTable.org_id, orgId) + : and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)), + ), + columns: { + name: true, + }, + with: { + deployments: { + columns: { + environment: true, + }, + with: { + version: { + columns: { + id: true, + snapshot: true, + }, + }, + }, + }, + }, + }); - return deployments; + return deployments; } export async function findSharedDeployment(workflow_id: string) { - const deploymentData = await db.query.deploymentsTable.findFirst({ - where: and( - eq(deploymentsTable.environment, "public-share"), - eq(deploymentsTable.id, workflow_id) - ), - with: { - user: true, - machine: true, - workflow: { - columns: { - name: true, - org_id: true, - user_id: true, - }, - }, - version: true, - }, - }); + const deploymentData = await db.query.deploymentsTable.findFirst({ + where: and( + eq(deploymentsTable.environment, "public-share"), + isValidUUID(workflow_id) + ? eq(deploymentsTable.id, workflow_id) + : eq(deploymentsTable.share_slug, workflow_id), + ), + with: { + user: true, + machine: true, + workflow: { + columns: { + name: true, + org_id: true, + user_id: true, + }, + }, + version: true, + }, + }); - return deploymentData; + return deploymentData; } export const removePublicShareDeployment = withServerPromise( - async (deployment_id: string) => { - await db - .delete(deploymentsTable) - .where( - and( - eq(deploymentsTable.environment, "public-share"), - eq(deploymentsTable.id, deployment_id) - ) - ); - } + async (deployment_id: string) => { + const [removed] = await db + .delete(deploymentsTable) + .where( + and( + eq(deploymentsTable.environment, "public-share"), + eq(deploymentsTable.id, deployment_id), + ), + ).returning(); + + // revalidatePath( + // `/workflows/${removed.workflow_id}` + // ) + }, ); export const cloneWorkflow = withServerPromise( - async (deployment_id: string) => { - const deployment = await db.query.deploymentsTable.findFirst({ - where: and( - eq(deploymentsTable.environment, "public-share"), - eq(deploymentsTable.id, deployment_id) - ), - with: { - version: true, - workflow: true, - }, - }); + async (deployment_id: string) => { + const deployment = await db.query.deploymentsTable.findFirst({ + where: and( + eq(deploymentsTable.environment, "public-share"), + eq(deploymentsTable.id, deployment_id), + ), + with: { + version: true, + workflow: true, + }, + }); - if (!deployment) throw new Error("No deployment found"); + if (!deployment) throw new Error("No deployment found"); - const { userId, orgId } = auth(); + const { userId, orgId } = auth(); - if (!userId) throw new Error("No user id"); + if (!userId) throw new Error("No user id"); - await createNewWorkflow({ - user_id: userId, - org_id: orgId, - workflow_name: `${deployment.workflow.name} (Cloned)`, - workflowData: { - workflow: deployment.version.workflow, - workflow_api: deployment?.version.workflow_api, - snapshot: deployment?.version.snapshot, - }, - }); + await createNewWorkflow({ + user_id: userId, + org_id: orgId, + workflow_name: `${deployment.workflow.name} (Cloned)`, + workflowData: { + workflow: deployment.version.workflow, + workflow_api: deployment?.version.workflow_api, + snapshot: deployment?.version.snapshot, + }, + }); - redirect(`/workflows/${deployment.workflow.id}`); + redirect(`/workflows/${deployment.workflow.id}`); - return { - message: "Successfully cloned workflow", - }; - } + return { + message: "Successfully cloned workflow", + }; + }, ); export const cloneMachine = withServerPromise(async (deployment_id: string) => { - const deployment = await db.query.deploymentsTable.findFirst({ - where: and( - eq(deploymentsTable.environment, "public-share"), - eq(deploymentsTable.id, deployment_id) - ), - with: { - machine: true, - }, - }); + const deployment = await db.query.deploymentsTable.findFirst({ + where: and( + eq(deploymentsTable.environment, "public-share"), + eq(deploymentsTable.id, deployment_id), + ), + with: { + machine: true, + }, + }); - if (!deployment) throw new Error("No deployment found"); - if (deployment.machine.type !== "comfy-deploy-serverless") - throw new Error("Can only clone comfy-deploy-serverlesss"); + if (!deployment) throw new Error("No deployment found"); + if (deployment.machine.type !== "comfy-deploy-serverless") + throw new Error("Can only clone comfy-deploy-serverlesss"); - const { userId, orgId } = auth(); + const { userId, orgId } = auth(); - if (!userId) throw new Error("No user id"); + if (!userId) throw new Error("No user id"); - await addCustomMachine({ - gpu: deployment.machine.gpu, - models: deployment.machine.models, - snapshot: deployment.machine.snapshot, - name: `${deployment.machine.name} (Cloned)`, - type: "comfy-deploy-serverless", - }); + await addCustomMachine({ + gpu: deployment.machine.gpu, + models: deployment.machine.models, + snapshot: deployment.machine.snapshot, + name: `${deployment.machine.name} (Cloned)`, + type: "comfy-deploy-serverless", + }); - return { - message: "Successfully cloned workflow", - }; + return { + message: "Successfully cloned workflow", + }; }); export async function findUserShareDeployment(share_id: string) { - const { userId, orgId } = auth(); + const { userId, orgId } = auth(); - if (!userId) throw new Error("No user id"); + if (!userId) throw new Error("No user id"); - const [deployment] = await db - .select() - .from(deploymentsTable) - .where( - and( - eq(deploymentsTable.id, share_id), - eq(deploymentsTable.environment, "public-share"), - orgId - ? eq(deploymentsTable.org_id, orgId) - : and( - eq(deploymentsTable.user_id, userId), - isNull(deploymentsTable.org_id) - ) - ) - ); + const [deployment] = await db + .select() + .from(deploymentsTable) + .where( + and( + isValidUUID(share_id) + ? eq(deploymentsTable.id, share_id) + : eq(deploymentsTable.share_slug, share_id), + eq(deploymentsTable.environment, "public-share"), + orgId + ? eq(deploymentsTable.org_id, orgId) + : and( + eq(deploymentsTable.user_id, userId), + isNull(deploymentsTable.org_id), + ), + ), + ); - if (!deployment) throw new Error("No deployment found"); + if (!deployment) throw new Error("No deployment found"); - return deployment; + return deployment; } export const updateSharePageInfo = withServerPromise( - async ({ - id, - ...data - }: z.infer & { - id: string; - }) => { - const { userId } = auth(); - if (!userId) return { error: "No user id" }; + async ({ + id, + ...data + }: z.infer & { + id: string; + }) => { + const { userId } = auth(); + if (!userId) return { error: "No user id" }; - console.log(data); + console.log(data); - const [deployment] = await db - .update(deploymentsTable) - .set(data) - .where( - and( - eq(deploymentsTable.environment, "public-share"), - eq(deploymentsTable.id, id) - ) - ) - .returning(); + const [deployment] = await db + .update(deploymentsTable) + .set(data) + .where( + and( + eq(deploymentsTable.environment, "public-share"), + eq(deploymentsTable.id, id), + ), + ) + .returning(); - return { message: "Info Updated" }; - } + return { message: "Info Updated" }; + }, );