From 850d8473adc4e3d29e4969a72c47a2c1d677b987 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Sun, 24 Dec 2023 18:53:32 +0800 Subject: [PATCH] fix: handle file upload status correctly, add serverless machine type --- custom_routes.py | 80 ++- web/bun.lockb | Bin 341617 -> 342035 bytes web/drizzle/0014_short_sunfire.sql | 7 + web/drizzle/0015_simple_killmonger.sql | 1 + web/drizzle/meta/0014_snapshot.json | 657 +++++++++++++++++ web/drizzle/meta/0015_snapshot.json | 663 ++++++++++++++++++ web/drizzle/meta/_journal.json | 14 + web/package.json | 1 + web/src/components/InsertModal.tsx | 123 ++++ web/src/components/LiveStatus.tsx | 19 +- web/src/components/MachineList.tsx | 189 ++--- web/src/components/MachinesWS.tsx | 11 +- .../components/ui/auto-form/fields/object.tsx | 12 +- web/src/components/ui/auto-form/index.tsx | 22 +- web/src/db/schema.ts | 14 + web/src/server/addMachineSchema.ts | 8 + web/src/server/createRun.ts | 59 +- web/src/server/curdMachine.ts | 43 +- 18 files changed, 1716 insertions(+), 207 deletions(-) create mode 100644 web/drizzle/0014_short_sunfire.sql create mode 100644 web/drizzle/0015_simple_killmonger.sql create mode 100644 web/drizzle/meta/0014_snapshot.json create mode 100644 web/drizzle/meta/0015_snapshot.json create mode 100644 web/src/components/InsertModal.tsx create mode 100644 web/src/server/addMachineSchema.ts diff --git a/custom_routes.py b/custom_routes.py index ab047b2..b120033 100644 --- a/custom_routes.py +++ b/custom_routes.py @@ -51,7 +51,8 @@ def post_prompt(json_data): if "client_id" in json_data: extra_data["client_id"] = json_data["client_id"] if valid[0]: - prompt_id = str(uuid.uuid4()) + # if the prompt id is provided + prompt_id = json_data.get("prompt_id") or str(uuid.uuid4()) outputs_to_execute = valid[2] prompt_server.prompt_queue.put( (number, prompt_id, prompt, extra_data, outputs_to_execute) @@ -80,13 +81,17 @@ async def comfy_deploy_run(request): workflow_api = data.get("workflow_api") + # The prompt id generated from comfy deploy, can be None + prompt_id = data.get("prompt_id") + for key in workflow_api: if 'inputs' in workflow_api[key] and 'seed' in workflow_api[key]['inputs']: workflow_api[key]['inputs']['seed'] = randomSeed() prompt = { "prompt": workflow_api, - "client_id": "comfy_deploy_instance" #api.client_id + "client_id": "comfy_deploy_instance", #api.client_id + "prompt_id": prompt_id } try: @@ -136,6 +141,19 @@ async def websocket_handler(request): sockets.pop(sid, None) return ws +@server.PromptServer.instance.routes.get('/comfyui-deploy/check-status') +async def comfy_deploy_check_status(request): + prompt_server = server.PromptServer.instance + prompt_id = request.rel_url.query.get('prompt_id', None) + if prompt_id in prompt_metadata: + return web.json_response({ + "status": prompt_metadata[prompt_id]['status'].value + }) + else: + return web.json_response({ + "message": "prompt_id not found" + }) + async def send(event, data, sid=None): try: if sid: @@ -168,7 +186,7 @@ async def send_json_override(self, event, data, sid=None): update_run(prompt_id, Status.RUNNING) # the last executing event is none, then the workflow is finished - if event == 'executing' and data.get('node') is None: + if event == 'executing' and data.get('node') is None and not have_pending_upload(prompt_id): update_run(prompt_id, Status.SUCCESS) if event == 'execution_error': @@ -176,7 +194,7 @@ async def send_json_override(self, event, data, sid=None): asyncio.create_task(update_run_with_output(prompt_id, data)) if event == 'executed' and 'node' in data and 'output' in data: - asyncio.create_task(update_run_with_output(prompt_id, data.get('output'))) + asyncio.create_task(update_run_with_output(prompt_id, data.get('output'), node_id=data.get('node'))) # update_run_with_output(prompt_id, data.get('output')) @@ -185,6 +203,7 @@ class Status(Enum): RUNNING = "running" SUCCESS = "success" FAILED = "failed" + UPLOADING = "uploading" def update_run(prompt_id, status: Status): if prompt_id not in prompt_metadata: @@ -260,7 +279,47 @@ async def upload_file(prompt_id, filename, subfolder=None, content_type="image/p response = requests.put(ok.get("url"), headers=headers, data=data) print("upload file response", response.status_code) -async def update_run_with_output(prompt_id, data): +def have_pending_upload(prompt_id): + if 'uploading_nodes' in prompt_metadata[prompt_id] and len(prompt_metadata[prompt_id]['uploading_nodes']) > 0: + return True + return False + +async def update_file_status(prompt_id, data, uploading, have_error=False, node_id=None): + if 'uploading_nodes' not in prompt_metadata[prompt_id]: + prompt_metadata[prompt_id]['uploading_nodes'] = set() + + if node_id is not None: + if uploading: + prompt_metadata[prompt_id]['uploading_nodes'].add(node_id) + else: + prompt_metadata[prompt_id]['uploading_nodes'].discard(node_id) + + # print(prompt_metadata[prompt_id]) + # Update the remote status + + if have_error: + update_run(prompt_id, Status.FAILED) + await send("failed", { + "prompt_id": prompt_id, + }) + return + + # if there are still nodes that are uploading, then we set the status to uploading + if uploading and have_pending_upload(prompt_id): + if prompt_metadata[prompt_id]['status'] != Status.UPLOADING: + update_run(prompt_id, Status.UPLOADING) + await send("uploading", { + "prompt_id": prompt_id, + }) + + # if there are no nodes that are uploading, then we set the status to success + elif not uploading: + update_run(prompt_id, Status.SUCCESS) + await send("success", { + "prompt_id": prompt_id, + }) + +async def update_run_with_output(prompt_id, data, node_id=None): if prompt_id in prompt_metadata: status_endpoint = prompt_metadata[prompt_id]['status_endpoint'] @@ -270,6 +329,13 @@ async def update_run_with_output(prompt_id, data): } try: + have_upload = 'images' in data or 'files' in data + + print("have_upload", have_upload) + + if have_upload: + await update_file_status(prompt_id, data, True, node_id=node_id) + images = data.get('images', []) for image in images: await upload_file(prompt_id, image.get("filename"), subfolder=image.get("subfolder"), type=image.get("type"), content_type=image.get("content_type", "image/png")) @@ -278,6 +344,9 @@ async def update_run_with_output(prompt_id, data): for file in files: await upload_file(prompt_id, file.get("filename"), subfolder=file.get("subfolder"), type=file.get("type"), content_type=image.get("content_type", "image/png")) + if have_upload: + await update_file_status(prompt_id, data, False, node_id=node_id) + except Exception as e: error_type = type(e).__name__ stack_trace = traceback.format_exc().strip() @@ -291,6 +360,7 @@ async def update_run_with_output(prompt_id, data): } } } + await update_file_status(prompt_id, data, False, have_error=True) print(body) print(f"Error occurred while uploading file: {e}") diff --git a/web/bun.lockb b/web/bun.lockb index a80378225b2c6b2f77d903004a30dd0eb6032a7b..704c77856d4c6a6812eeaa561e2094e61e85a67d 100755 GIT binary patch delta 62670 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 statement-breakpoint +ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "type" "machine_type" DEFAULT 'classic' NOT NULL; \ No newline at end of file diff --git a/web/drizzle/0015_simple_killmonger.sql b/web/drizzle/0015_simple_killmonger.sql new file mode 100644 index 0000000..97662e7 --- /dev/null +++ b/web/drizzle/0015_simple_killmonger.sql @@ -0,0 +1 @@ +ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "auth_token" text; \ No newline at end of file diff --git a/web/drizzle/meta/0014_snapshot.json b/web/drizzle/meta/0014_snapshot.json new file mode 100644 index 0000000..0b62c5b --- /dev/null +++ b/web/drizzle/meta/0014_snapshot.json @@ -0,0 +1,657 @@ +{ + "id": "f29ccb7c-8e75-403f-bef6-60758f9db7c7", + "prevId": "61c93b0c-9eae-46b9-bed9-26ef9eed4d19", + "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 + }, + "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 + }, + "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": "no action", + "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 + }, + "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 + }, + "type": { + "name": "type", + "type": "machine_type", + "primaryKey": false, + "notNull": true, + "default": "'classic'" + } + }, + "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()" + } + }, + "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 + }, + "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 + }, + "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" + } + }, + "machine_type": { + "name": "machine_type", + "values": { + "classic": "classic", + "runpod-serverless": "runpod-serverless" + } + }, + "workflow_run_origin": { + "name": "workflow_run_origin", + "values": { + "manual": "manual", + "api": "api" + } + }, + "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/0015_snapshot.json b/web/drizzle/meta/0015_snapshot.json new file mode 100644 index 0000000..dba1c20 --- /dev/null +++ b/web/drizzle/meta/0015_snapshot.json @@ -0,0 +1,663 @@ +{ + "id": "92bef822-0089-48aa-8f43-5bba40cdce2e", + "prevId": "f29ccb7c-8e75-403f-bef6-60758f9db7c7", + "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 + }, + "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 + }, + "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": "no action", + "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 + }, + "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'" + } + }, + "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()" + } + }, + "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 + }, + "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 + }, + "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" + } + }, + "machine_type": { + "name": "machine_type", + "values": { + "classic": "classic", + "runpod-serverless": "runpod-serverless" + } + }, + "workflow_run_origin": { + "name": "workflow_run_origin", + "values": { + "manual": "manual", + "api": "api" + } + }, + "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 ea56dc6..85e9471 100644 --- a/web/drizzle/meta/_journal.json +++ b/web/drizzle/meta/_journal.json @@ -99,6 +99,20 @@ "when": 1703338425429, "tag": "0013_stormy_starbolt", "breakpoints": true + }, + { + "idx": 14, + "version": "5", + "when": 1703403388113, + "tag": "0014_short_sunfire", + "breakpoints": true + }, + { + "idx": 15, + "version": "5", + "when": 1703409502387, + "tag": "0015_simple_killmonger", + "breakpoints": true } ] } \ No newline at end of file diff --git a/web/package.json b/web/package.json index 4785091..22d78c6 100644 --- a/web/package.json +++ b/web/package.json @@ -42,6 +42,7 @@ "date-fns": "^3.0.5", "dayjs": "^1.11.10", "drizzle-orm": "^0.29.1", + "drizzle-zod": "^0.5.1", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.294.0", "nanoid": "^5.0.4", diff --git a/web/src/components/InsertModal.tsx b/web/src/components/InsertModal.tsx new file mode 100644 index 0000000..cb6251e --- /dev/null +++ b/web/src/components/InsertModal.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { LoadingIcon } from "./LoadingIcon"; +import { callServerPromise } from "@/components/callServerPromise"; +import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form"; +import type { FieldConfig } from "@/components/ui/auto-form/types"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import * as React from "react"; +import type { UnknownKeysParam, ZodObject, ZodRawShape, z } from "zod"; + +export function InsertModal< + K extends ZodRawShape, + Y extends UnknownKeysParam, + Z extends ZodObject +>(props: { + title: string; + description: string; + serverAction: (data: z.infer) => Promise; + formSchema: Z; + fieldConfig?: FieldConfig>; +}) { + const [open, setOpen] = React.useState(false); + const [isLoading, setIsLoading] = React.useState(false); + + return ( + + + + + + + {props.title} + {props.description} + + { + setIsLoading(true); + await callServerPromise(props.serverAction(data)); + setIsLoading(false); + setOpen(false); + }} + > +
+ + Save Changes + {isLoading && } + +
+
+
+
+ ); +} + +export function UpdateModal< + K extends ZodRawShape, + Y extends UnknownKeysParam, + Z extends ZodObject +>(props: { + open: boolean; + setOpen: (open: boolean) => void; + title: string; + description: string; + data: z.infer; + serverAction: ( + data: z.infer & { + id: string; + } + ) => Promise; + formSchema: Z; + fieldConfig?: FieldConfig>; +}) { + // const [open, setOpen] = React.useState(false); + const [isLoading, setIsLoading] = React.useState(false); + + return ( + + {/* + {props.title} + */} + + + {props.title} + {props.description} + + { + setIsLoading(true); + await callServerPromise( + props.serverAction({ + ...data, + id: props.data.id, + }) + ); + setIsLoading(false); + props.setOpen(false); + }} + > +
+ + Save Changes + {isLoading && } + +
+
+
+
+ ); +} diff --git a/web/src/components/LiveStatus.tsx b/web/src/components/LiveStatus.tsx index e3f74ed..8df690c 100644 --- a/web/src/components/LiveStatus.tsx +++ b/web/src/components/LiveStatus.tsx @@ -5,7 +5,7 @@ import { StatusBadge } from "@/components/StatusBadge"; import { TableCell } from "@/components/ui/table"; import { type findAllRuns } from "@/server/findAllRuns"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; export function LiveStatus({ run, @@ -16,23 +16,30 @@ export function LiveStatus({ (state) => state.data .filter((x) => x.id === run.id) - .sort((a, b) => b.timestamp - a.timestamp)?.[0], + .sort((a, b) => b.timestamp - a.timestamp)?.[0] ); let status = run.status; // const [view, setView] = useState(); - if (data?.json.event == "executing" && data.json.data.node == undefined) { - status = "success"; - } else if (data?.json.event == "executing") { + // if (data?.json.event == "executing" && data.json.data.node == undefined) { + // status = "success"; + // } else + if (data?.json.event == "executing") { status = "running"; + } else if (data?.json.event == "uploading") { + status = "uploading"; + } else if (data?.json.event == "success") { + status = "success"; + } else if (data?.json.event == "failed") { + status = "failed"; } const router = useRouter(); useEffect(() => { if (data?.json.event === "outputs_uploaded") { - router.refresh() + router.refresh(); } }, [data?.json.event]); diff --git a/web/src/components/MachineList.tsx b/web/src/components/MachineList.tsx index 8cc370c..fdca613 100644 --- a/web/src/components/MachineList.tsx +++ b/web/src/components/MachineList.tsx @@ -1,28 +1,11 @@ "use client"; import { getRelativeTime } from "../lib/getRelativeTime"; -import { LoadingIcon } from "./LoadingIcon"; +import { InsertModal, UpdateModal } from "./InsertModal"; import { callServerPromise } from "./callServerPromise"; -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Form, -} from "./ui/form"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, @@ -39,14 +22,15 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import type { MachineType } from "@/db/schema"; +import { type MachineType } from "@/db/schema"; +import { addMachineSchema } from "@/server/addMachineSchema"; import { addMachine, deleteMachine, disableMachine, enableMachine, + updateMachine, } from "@/server/curdMachine"; -import { zodResolver } from "@hookform/resolvers/zod"; import type { ColumnDef, ColumnFiltersState, @@ -63,8 +47,7 @@ import { } from "@tanstack/react-table"; import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import * as React from "react"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; +import { useState } from "react"; export type Machine = MachineType; @@ -127,6 +110,13 @@ export const columns: ColumnDef[] = [ ); }, }, + { + accessorKey: "type", + header: () =>
Type
, + cell: ({ row }) => { + return
{row.original.type}
; + }, + }, { accessorKey: "date", sortingFn: "datetime", @@ -154,6 +144,7 @@ export const columns: ColumnDef[] = [ enableHiding: false, cell: ({ row }) => { const machine = row.original; + const [open, setOpen] = useState(false); return ( @@ -191,10 +182,33 @@ export const columns: ColumnDef[] = [ Disable Machine )} - {/* - View customer - View payment details */} + setOpen(true)}> + Edit + + ); }, @@ -241,33 +255,12 @@ export function MachineList({ data }: { data: Machine[] }) { className="max-w-sm" />
- - {/* - - - - - {table - .getAllColumns() - .filter((column) => column.getCanHide()) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ); - })} - - */} +
@@ -347,95 +340,3 @@ export function MachineList({ data }: { data: Machine[] }) {
); } - -const formSchema = z.object({ - name: z.string().min(1), - endpoint: z.string().min(1), -}); - -function AddMachinesDialog() { - const [open, setOpen] = React.useState(false); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - name: "My Local Machine", - endpoint: "http://127.0.0.1:8188", - }, - }); - - return ( - - - - - -
- { - await addMachine(data.name, data.endpoint); - // await new Promise(resolve => setTimeout(resolve, 3000)); - setOpen(false); - })} - > - - Add Machines - - Add Comfyui machines to your account. - - -
- {/*
*/} - ( - - Name - - - - {/* - This is your public display name. - */} - - - )} - /> - - ( - - Endpoint - - - - {/* - This is your public display name. - */} - - - )} - /> -
- - - - - - -
- ); -} - -function AddWorkflowButton({ pending }: { pending: boolean }) { - // const { pending } = useFormStatus(); - return ( - - ); -} diff --git a/web/src/components/MachinesWS.tsx b/web/src/components/MachinesWS.tsx index 3aaff34..c923040 100644 --- a/web/src/components/MachinesWS.tsx +++ b/web/src/components/MachinesWS.tsx @@ -62,9 +62,11 @@ export function MachinesWSMain(props: {
Machine Status
- {props.machines.map((x) => ( - - ))} + {props.machines + .filter((x) => x.type === "classic") + .map((x) => ( + + ))}
); @@ -112,13 +114,14 @@ function MachineWS({ if (!lastMessage?.data) return; const message = JSON.parse(lastMessage.data); - console.log(message.event, message); + // console.log(message.event, message); if (message.data.sid) { setSid(message.data.sid); } if (message.data?.prompt_id) { + console.log(message.event, message); addData(message.data.prompt_id, message); } diff --git a/web/src/components/ui/auto-form/fields/object.tsx b/web/src/components/ui/auto-form/fields/object.tsx index 9e00789..d40d90e 100644 --- a/web/src/components/ui/auto-form/fields/object.tsx +++ b/web/src/components/ui/auto-form/fields/object.tsx @@ -1,28 +1,28 @@ -import * as z from "zod"; -import { useForm } from "react-hook-form"; -import { FieldConfig, FieldConfigItem } from "../types"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "../../accordion"; +import { FormField } from "../../form"; +import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from "../config"; +import type { FieldConfig, FieldConfigItem } from "../types"; import { beautifyObjectName, getBaseSchema, getBaseType, zodToHtmlInputProps, } from "../utils"; -import { FormField } from "../../form"; -import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from "../config"; import AutoFormArray from "./array"; +import type { useForm } from "react-hook-form"; +import type * as z from "zod"; function DefaultParent({ children }: { children: React.ReactNode }) { return <>{children}; } export default function AutoFormObject< - SchemaType extends z.ZodObject, + SchemaType extends z.ZodObject >({ schema, form, diff --git a/web/src/components/ui/auto-form/index.tsx b/web/src/components/ui/auto-form/index.tsx index 668cd16..fa5d7e3 100644 --- a/web/src/components/ui/auto-form/index.tsx +++ b/web/src/components/ui/auto-form/index.tsx @@ -1,20 +1,16 @@ "use client"; -import React from "react"; -import { z } from "zod"; -import { Form } from "../form"; -import { DefaultValues, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; import { Button } from "../button"; +import { Form } from "../form"; +import type { FieldConfig } from "./types"; +import type { ZodObjectOrWrapped } from "./utils"; +import { getDefaultValues, getObjectFormSchema } from "./utils"; +import AutoFormObject from "@/components/ui/auto-form/fields/object"; import { cn } from "@/lib/utils"; - -import { FieldConfig } from "./types"; -import { - ZodObjectOrWrapped, - getDefaultValues, - getObjectFormSchema, -} from "./utils"; -import AutoFormObject from "./fields/object"; +import { zodResolver } from "@hookform/resolvers/zod"; +import type { DefaultValues } from "react-hook-form"; +import { useForm } from "react-hook-form"; +import type { z } from "zod"; export function AutoFormSubmit({ children }: { children?: React.ReactNode }) { return ; diff --git a/web/src/db/schema.ts b/web/src/db/schema.ts index a895442..d237818 100644 --- a/web/src/db/schema.ts +++ b/web/src/db/schema.ts @@ -9,6 +9,7 @@ import { pgEnum, boolean, } from "drizzle-orm/pg-core"; +import { createInsertSchema } from "drizzle-zod"; import { z } from "zod"; export const dbSchema = pgSchema("comfyui_deploy"); @@ -100,6 +101,11 @@ export const workflowRunOrigin = pgEnum("workflow_run_origin", [ "api", ]); +export const machinesType = pgEnum("machine_type", [ + "classic", + "runpod-serverless", +]); + // We still want to keep the workflow run record. export const workflowRunsTable = dbSchema.table("workflow_runs", { id: uuid("id").primaryKey().defaultRandom().notNull(), @@ -178,6 +184,14 @@ export const machinesTable = dbSchema.table("machines", { created_at: timestamp("created_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(), disabled: boolean("disabled").default(false).notNull(), + auth_token: text("auth_token"), + type: machinesType("type").notNull().default("classic"), +}); + +export const insertMachineSchema = createInsertSchema(machinesTable, { + name: (schema) => schema.name.default("My Machine"), + endpoint: (schema) => schema.endpoint.default("http://127.0.0.1:8188"), + type: (schema) => schema.type.default("classic"), }); export const deploymentsTable = dbSchema.table("deployments", { diff --git a/web/src/server/addMachineSchema.ts b/web/src/server/addMachineSchema.ts new file mode 100644 index 0000000..1119930 --- /dev/null +++ b/web/src/server/addMachineSchema.ts @@ -0,0 +1,8 @@ +import { insertMachineSchema } from "@/db/schema"; + +export const addMachineSchema = insertMachineSchema.pick({ + name: true, + endpoint: true, + type: true, + auth_token: true, +}); diff --git a/web/src/server/createRun.ts b/web/src/server/createRun.ts index d0c56ec..7ba530a 100644 --- a/web/src/server/createRun.ts +++ b/web/src/server/createRun.ts @@ -7,6 +7,7 @@ import { ComfyAPI_Run } from "@/types/ComfyAPI_Run"; import { and, eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import "server-only"; +import { v4 } from "uuid"; export const createRun = withServerPromise( async ( @@ -37,8 +38,6 @@ export const createRun = withServerPromise( throw new Error("Workflow version not found"); } - const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`; - const workflow_api = workflow_version_data.workflow_api; // Replace the inputs @@ -52,33 +51,57 @@ export const createRun = withServerPromise( } } - const body = { + let prompt_id: string | undefined = undefined; + const shareData = { workflow_api: workflow_api, status_endpoint: `${origin}/api/update-run`, file_upload_endpoint: `${origin}/api/file-upload`, }; - // console.log(body); - const bodyJson = JSON.stringify(body); - // console.log(bodyJson); - // Sending to comfyui - const _result = await fetch(comfyui_endpoint, { - method: "POST", - body: bodyJson, - cache: "no-store", - }); - - if (!_result.ok) { - throw new Error(`Error creating run, ${_result.statusText}`); + switch (machine.type) { + case "runpod-serverless": + prompt_id = v4(); + const data = { + input: { + ...shareData, + prompt_id: prompt_id, + }, + }; + console.log(data); + const __result = await fetch(`${machine.endpoint}/run`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${machine.auth_token}`, + }, + body: JSON.stringify(data), + cache: "no-store", + }); + console.log(__result); + if (!__result.ok) + throw new Error(`Error creating run, ${__result.statusText}`); + console.log(data, __result); + break; + case "classic": + const body = shareData; + const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`; + const _result = await fetch(comfyui_endpoint, { + method: "POST", + body: JSON.stringify(body), + cache: "no-store", + }); + if (!_result.ok) + throw new Error(`Error creating run, ${_result.statusText}`); + const result = await ComfyAPI_Run.parseAsync(await _result.json()); + prompt_id = result.prompt_id; + break; } - const result = await ComfyAPI_Run.parseAsync(await _result.json()); - // Add to our db const workflow_run = await db .insert(workflowRunsTable) .values({ - id: result.prompt_id, + id: prompt_id, workflow_id: workflow_version_data.workflow_id, workflow_version_id: workflow_version_data.id, workflow_inputs: inputs, diff --git a/web/src/server/curdMachine.ts b/web/src/server/curdMachine.ts index 45feab4..11a89ea 100644 --- a/web/src/server/curdMachine.ts +++ b/web/src/server/curdMachine.ts @@ -1,5 +1,6 @@ "use server"; +import type { addMachineSchema } from "./addMachineSchema"; import { withServerPromise } from "./withServerPromise"; import { db } from "@/db/db"; import { machinesTable } from "@/db/schema"; @@ -7,6 +8,7 @@ import { auth } from "@clerk/nextjs"; import { and, eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import "server-only"; +import type { z } from "zod"; export async function getMachines() { const { userId } = auth(); @@ -20,17 +22,36 @@ export async function getMachines() { return machines; } -export async function addMachine(name: string, endpoint: string) { - const { userId } = auth(); - if (!userId) throw new Error("No user id"); - console.log(name, endpoint); - await db.insert(machinesTable).values({ - user_id: userId, - name, - endpoint, - }); - revalidatePath("/machines"); -} +export const addMachine = withServerPromise( + async ({ name, endpoint, type }: z.infer) => { + const { userId } = auth(); + if (!userId) return { error: "No user id" }; + console.log(name, endpoint); + await db.insert(machinesTable).values({ + user_id: userId, + name, + endpoint, + type, + }); + revalidatePath("/machines"); + return { message: "Machine Added" }; + } +); + +export const updateMachine = withServerPromise( + async ({ + id, + ...data + }: z.infer & { + id: string; + }) => { + const { userId } = auth(); + if (!userId) return { error: "No user id" }; + await db.update(machinesTable).set(data).where(eq(machinesTable.id, id)); + revalidatePath("/machines"); + return { message: "Machine Updated" }; + } +); export const deleteMachine = withServerPromise( async (machine_id: string): Promise<{ message: string }> => {