feat: add preview image builder
This commit is contained in:
		
							parent
							
								
									314eb9fd16
								
							
						
					
					
						commit
						9937252777
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1 +1,2 @@
 | 
			
		||||
__pycache__
 | 
			
		||||
.DS_Store
 | 
			
		||||
@ -13,5 +13,7 @@ SPACES_SECRET="aaa"
 | 
			
		||||
SPACES_CDN_DONT_INCLUDE_BUCKET="false"
 | 
			
		||||
SPACES_CDN_FORCE_PATH_STYLE="true"
 | 
			
		||||
 | 
			
		||||
MODAL_BUILDER_URL=
 | 
			
		||||
 | 
			
		||||
JWT_SECRET="openssl rand -hex 32"
 | 
			
		||||
PLAUSIBLE_DOMAIN=
 | 
			
		||||
							
								
								
									
										9
									
								
								web/drizzle/0020_complete_black_tom.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/drizzle/0020_complete_black_tom.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
DO $$ BEGIN
 | 
			
		||||
 CREATE TYPE "machine_status" AS ENUM('ready', 'building', 'error');
 | 
			
		||||
EXCEPTION
 | 
			
		||||
 WHEN duplicate_object THEN null;
 | 
			
		||||
END $$;
 | 
			
		||||
--> statement-breakpoint
 | 
			
		||||
ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "status" "machine_status" DEFAULT 'ready' NOT NULL;--> statement-breakpoint
 | 
			
		||||
ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "snapshot" jsonb;--> statement-breakpoint
 | 
			
		||||
ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "build_log" text;
 | 
			
		||||
							
								
								
									
										703
									
								
								web/drizzle/meta/0020_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										703
									
								
								web/drizzle/meta/0020_snapshot.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,703 @@
 | 
			
		||||
{
 | 
			
		||||
  "id": "60fd6aaf-0bd4-4b77-b707-fea7c44e829d",
 | 
			
		||||
  "prevId": "4c2423cd-420e-49fc-a6fe-2e6c56ce8c61",
 | 
			
		||||
  "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": "cascade",
 | 
			
		||||
          "onUpdate": "no action"
 | 
			
		||||
        },
 | 
			
		||||
        "deployments_machine_id_machines_id_fk": {
 | 
			
		||||
          "name": "deployments_machine_id_machines_id_fk",
 | 
			
		||||
          "tableFrom": "deployments",
 | 
			
		||||
          "tableTo": "machines",
 | 
			
		||||
          "columnsFrom": [
 | 
			
		||||
            "machine_id"
 | 
			
		||||
          ],
 | 
			
		||||
          "columnsTo": [
 | 
			
		||||
            "id"
 | 
			
		||||
          ],
 | 
			
		||||
          "onDelete": "no action",
 | 
			
		||||
          "onUpdate": "no action"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "compositePrimaryKeys": {},
 | 
			
		||||
      "uniqueConstraints": {}
 | 
			
		||||
    },
 | 
			
		||||
    "machines": {
 | 
			
		||||
      "name": "machines",
 | 
			
		||||
      "schema": "comfyui_deploy",
 | 
			
		||||
      "columns": {
 | 
			
		||||
        "id": {
 | 
			
		||||
          "name": "id",
 | 
			
		||||
          "type": "uuid",
 | 
			
		||||
          "primaryKey": true,
 | 
			
		||||
          "notNull": true,
 | 
			
		||||
          "default": "gen_random_uuid()"
 | 
			
		||||
        },
 | 
			
		||||
        "user_id": {
 | 
			
		||||
          "name": "user_id",
 | 
			
		||||
          "type": "text",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true
 | 
			
		||||
        },
 | 
			
		||||
        "name": {
 | 
			
		||||
          "name": "name",
 | 
			
		||||
          "type": "text",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true
 | 
			
		||||
        },
 | 
			
		||||
        "org_id": {
 | 
			
		||||
          "name": "org_id",
 | 
			
		||||
          "type": "text",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": false
 | 
			
		||||
        },
 | 
			
		||||
        "endpoint": {
 | 
			
		||||
          "name": "endpoint",
 | 
			
		||||
          "type": "text",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true
 | 
			
		||||
        },
 | 
			
		||||
        "created_at": {
 | 
			
		||||
          "name": "created_at",
 | 
			
		||||
          "type": "timestamp",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true,
 | 
			
		||||
          "default": "now()"
 | 
			
		||||
        },
 | 
			
		||||
        "updated_at": {
 | 
			
		||||
          "name": "updated_at",
 | 
			
		||||
          "type": "timestamp",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true,
 | 
			
		||||
          "default": "now()"
 | 
			
		||||
        },
 | 
			
		||||
        "disabled": {
 | 
			
		||||
          "name": "disabled",
 | 
			
		||||
          "type": "boolean",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true,
 | 
			
		||||
          "default": false
 | 
			
		||||
        },
 | 
			
		||||
        "auth_token": {
 | 
			
		||||
          "name": "auth_token",
 | 
			
		||||
          "type": "text",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": false
 | 
			
		||||
        },
 | 
			
		||||
        "type": {
 | 
			
		||||
          "name": "type",
 | 
			
		||||
          "type": "machine_type",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true,
 | 
			
		||||
          "default": "'classic'"
 | 
			
		||||
        },
 | 
			
		||||
        "status": {
 | 
			
		||||
          "name": "status",
 | 
			
		||||
          "type": "machine_status",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": true,
 | 
			
		||||
          "default": "'ready'"
 | 
			
		||||
        },
 | 
			
		||||
        "snapshot": {
 | 
			
		||||
          "name": "snapshot",
 | 
			
		||||
          "type": "jsonb",
 | 
			
		||||
          "primaryKey": false,
 | 
			
		||||
          "notNull": false
 | 
			
		||||
        },
 | 
			
		||||
        "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()"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "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
 | 
			
		||||
        },
 | 
			
		||||
        "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_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"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "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": {}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -141,6 +141,13 @@
 | 
			
		||||
      "when": 1704174903117,
 | 
			
		||||
      "tag": "0019_damp_stick",
 | 
			
		||||
      "breakpoints": true
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idx": 20,
 | 
			
		||||
      "version": "5",
 | 
			
		||||
      "when": 1704350033885,
 | 
			
		||||
      "tag": "0020_complete_black_tom",
 | 
			
		||||
      "breakpoints": true
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								web/src/app/(app)/api/machine-built/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								web/src/app/(app)/api/machine-built/route.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
import { parseDataSafe } from "../../../../lib/parseDataSafe";
 | 
			
		||||
import { db } from "@/db/db";
 | 
			
		||||
import { machinesTable } from "@/db/schema";
 | 
			
		||||
import { eq } from "drizzle-orm";
 | 
			
		||||
import { NextResponse } from "next/server";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
const Request = z.object({
 | 
			
		||||
  machine_id: z.string(),
 | 
			
		||||
  endpoint: z.string().optional(),
 | 
			
		||||
  build_log: z.string().optional(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export async function POST(request: Request) {
 | 
			
		||||
  const [data, error] = await parseDataSafe(Request, request);
 | 
			
		||||
  if (!data || error) return error;
 | 
			
		||||
 | 
			
		||||
  console.log(data);
 | 
			
		||||
 | 
			
		||||
  const { machine_id, endpoint, build_log } = data;
 | 
			
		||||
 | 
			
		||||
  if (endpoint) {
 | 
			
		||||
    await db
 | 
			
		||||
      .update(machinesTable)
 | 
			
		||||
      .set({
 | 
			
		||||
        status: "ready",
 | 
			
		||||
        endpoint: endpoint,
 | 
			
		||||
        build_log: build_log,
 | 
			
		||||
      })
 | 
			
		||||
      .where(eq(machinesTable.id, machine_id));
 | 
			
		||||
  } else {
 | 
			
		||||
    // console.log(data);
 | 
			
		||||
    await db
 | 
			
		||||
      .update(machinesTable)
 | 
			
		||||
      .set({
 | 
			
		||||
        status: "error",
 | 
			
		||||
        build_log: build_log,
 | 
			
		||||
      })
 | 
			
		||||
      .where(eq(machinesTable.id, machine_id));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return NextResponse.json(
 | 
			
		||||
    {
 | 
			
		||||
      message: "success",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      status: 200,
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								web/src/app/(app)/machines/[machine_id]/loading.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/src/app/(app)/machines/[machine_id]/loading.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
			
		||||
import { usePathname } from "next/navigation";
 | 
			
		||||
 | 
			
		||||
export default function Loading() {
 | 
			
		||||
  const pathName = usePathname();
 | 
			
		||||
  return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								web/src/app/(app)/machines/[machine_id]/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/src/app/(app)/machines/[machine_id]/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
import { MachineBuildLog } from "../../../../components/MachineBuildLog";
 | 
			
		||||
import { LogsViewer } from "@/components/LogsViewer";
 | 
			
		||||
import {
 | 
			
		||||
  Card,
 | 
			
		||||
  CardContent,
 | 
			
		||||
  CardDescription,
 | 
			
		||||
  CardHeader,
 | 
			
		||||
  CardTitle,
 | 
			
		||||
} from "@/components/ui/card";
 | 
			
		||||
import { getRelativeTime } from "@/lib/getRelativeTime";
 | 
			
		||||
import { getMachineById } from "@/server/curdMachine";
 | 
			
		||||
 | 
			
		||||
export default async function Page({
 | 
			
		||||
  params,
 | 
			
		||||
}: {
 | 
			
		||||
  params: { machine_id: string };
 | 
			
		||||
}) {
 | 
			
		||||
  const machine = await getMachineById(params.machine_id);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <Card className="w-full h-fit mt-4">
 | 
			
		||||
        <CardHeader>
 | 
			
		||||
          <CardTitle>{machine.name}</CardTitle>
 | 
			
		||||
          <CardDescription suppressHydrationWarning={true}>
 | 
			
		||||
            {getRelativeTime(machine?.updated_at)}
 | 
			
		||||
          </CardDescription>
 | 
			
		||||
        </CardHeader>
 | 
			
		||||
        <CardContent>
 | 
			
		||||
          {machine.status == "building" && (
 | 
			
		||||
            <MachineBuildLog
 | 
			
		||||
              machine_id={params.machine_id}
 | 
			
		||||
              endpoint={process.env.MODAL_BUILDER_URL!}
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
          {machine.build_log && (
 | 
			
		||||
            <LogsViewer logs={JSON.parse(machine.build_log)} />
 | 
			
		||||
          )}
 | 
			
		||||
        </CardContent>
 | 
			
		||||
      </Card>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
			
		||||
 | 
			
		||||
export default function Loading() {
 | 
			
		||||
  // You can add any UI inside Loading, including a Skeleton.
 | 
			
		||||
  return <LoadingPageWrapper tag="workflow" />;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								web/src/components/LogsViewer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								web/src/components/LogsViewer.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useRef } from "react";
 | 
			
		||||
 | 
			
		||||
export type LogsType = {
 | 
			
		||||
  machine_id?: string;
 | 
			
		||||
  logs: string;
 | 
			
		||||
  timestamp: number;
 | 
			
		||||
}[];
 | 
			
		||||
 | 
			
		||||
export function LogsViewer({ logs }: { logs: LogsType }) {
 | 
			
		||||
  const container = useRef<HTMLDivElement | null>(null);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // console.log(logs.length, container.current);
 | 
			
		||||
    if (container.current) {
 | 
			
		||||
      const scrollHeight = container.current.scrollHeight;
 | 
			
		||||
 | 
			
		||||
      container.current.scrollTo({
 | 
			
		||||
        top: scrollHeight,
 | 
			
		||||
        behavior: "smooth",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [logs.length]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={(ref) => {
 | 
			
		||||
        if (!container.current && ref) {
 | 
			
		||||
          const scrollHeight = ref.scrollHeight;
 | 
			
		||||
 | 
			
		||||
          ref.scrollTo({
 | 
			
		||||
            top: scrollHeight,
 | 
			
		||||
            behavior: "instant",
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        container.current = ref;
 | 
			
		||||
      }}
 | 
			
		||||
      className="flex flex-col text-xs p-2 overflow-y-scroll max-h-[400px] whitespace-break-spaces"
 | 
			
		||||
    >
 | 
			
		||||
      {logs.map((x, i) => (
 | 
			
		||||
        <div key={i}>{x.logs}</div>
 | 
			
		||||
      ))}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								web/src/components/MachineBuildLog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								web/src/components/MachineBuildLog.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import type { LogsType } from "@/components/LogsViewer";
 | 
			
		||||
import { LogsViewer } from "@/components/LogsViewer";
 | 
			
		||||
import { getConnectionStatus } from "@/components/getConnectionStatus";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import useWebSocket from "react-use-websocket";
 | 
			
		||||
 | 
			
		||||
export function MachineBuildLog({
 | 
			
		||||
  machine_id,
 | 
			
		||||
  endpoint,
 | 
			
		||||
}: {
 | 
			
		||||
  machine_id: string;
 | 
			
		||||
  endpoint: string;
 | 
			
		||||
}) {
 | 
			
		||||
  const [logs, setLogs] = useState<LogsType>([]);
 | 
			
		||||
 | 
			
		||||
  const wsEndpoint = endpoint.replace(/^http/, "ws");
 | 
			
		||||
  const { lastMessage, readyState } = useWebSocket(
 | 
			
		||||
    `${wsEndpoint}/ws/${machine_id}`,
 | 
			
		||||
    {
 | 
			
		||||
      shouldReconnect: () => true,
 | 
			
		||||
      reconnectAttempts: 20,
 | 
			
		||||
      reconnectInterval: 1000,
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const connectionStatus = getConnectionStatus(readyState);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!lastMessage?.data) return;
 | 
			
		||||
 | 
			
		||||
    const message = JSON.parse(lastMessage.data);
 | 
			
		||||
 | 
			
		||||
    console.log(message);
 | 
			
		||||
 | 
			
		||||
    if (message?.event === "LOGS") {
 | 
			
		||||
      setLogs((logs) => [...(logs ?? []), message.data]);
 | 
			
		||||
    }
 | 
			
		||||
  }, [lastMessage]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      {connectionStatus}
 | 
			
		||||
      <LogsViewer logs={logs} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -23,8 +23,12 @@ import {
 | 
			
		||||
  TableRow,
 | 
			
		||||
} from "@/components/ui/table";
 | 
			
		||||
import { type MachineType } from "@/db/schema";
 | 
			
		||||
import { addMachineSchema } from "@/server/addMachineSchema";
 | 
			
		||||
import {
 | 
			
		||||
  addCustomMachineSchema,
 | 
			
		||||
  addMachineSchema,
 | 
			
		||||
} from "@/server/addMachineSchema";
 | 
			
		||||
import {
 | 
			
		||||
  addCustomMachine,
 | 
			
		||||
  addMachine,
 | 
			
		||||
  deleteMachine,
 | 
			
		||||
  disableMachine,
 | 
			
		||||
@ -92,10 +96,15 @@ export const columns: ColumnDef<Machine>[] = [
 | 
			
		||||
      return (
 | 
			
		||||
        // <a className="hover:underline" href={`/${row.original.id}`}>
 | 
			
		||||
        <div className="flex flex-row gap-2 items-center">
 | 
			
		||||
          <div>{row.getValue("name")}</div>
 | 
			
		||||
          <a href={`/machines/${row.original.id}`} className="hover:underline">
 | 
			
		||||
            {row.getValue("name")}
 | 
			
		||||
          </a>
 | 
			
		||||
          {row.original.disabled && (
 | 
			
		||||
            <Badge variant="destructive">Disabled</Badge>
 | 
			
		||||
          )}
 | 
			
		||||
          {!row.original.disabled && row.original.status && (
 | 
			
		||||
            <Badge variant="outline">{row.original.status}</Badge>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
        // </a>
 | 
			
		||||
      );
 | 
			
		||||
@ -254,6 +263,20 @@ export function MachineList({ data }: { data: Machine[] }) {
 | 
			
		||||
            serverAction={addMachine}
 | 
			
		||||
            formSchema={addMachineSchema}
 | 
			
		||||
          />
 | 
			
		||||
          <InsertModal
 | 
			
		||||
            title="Custom Machine"
 | 
			
		||||
            description="Add custom Comfyui machines to your account."
 | 
			
		||||
            serverAction={addCustomMachine}
 | 
			
		||||
            formSchema={addCustomMachineSchema}
 | 
			
		||||
            fieldConfig={{
 | 
			
		||||
              type: {
 | 
			
		||||
                fieldType: "fallback",
 | 
			
		||||
                inputProps: {
 | 
			
		||||
                  disabled: true,
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="rounded-md border overflow-x-auto w-full">
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { LogsViewer } from "./LogsViewer";
 | 
			
		||||
import { getConnectionStatus } from "./getConnectionStatus";
 | 
			
		||||
import { Badge } from "@/components/ui/badge";
 | 
			
		||||
import {
 | 
			
		||||
  Dialog,
 | 
			
		||||
@ -10,9 +12,8 @@ import {
 | 
			
		||||
  DialogTrigger,
 | 
			
		||||
} from "@/components/ui/dialog";
 | 
			
		||||
import type { getMachines } from "@/server/curdMachine";
 | 
			
		||||
import { Check, CircleOff, SatelliteDish } from "lucide-react";
 | 
			
		||||
import React, { useEffect, useRef, useState } from "react";
 | 
			
		||||
import useWebSocket, { ReadyState } from "react-use-websocket";
 | 
			
		||||
import React, { useEffect, useState } from "react";
 | 
			
		||||
import useWebSocket from "react-use-websocket";
 | 
			
		||||
import { create } from "zustand";
 | 
			
		||||
 | 
			
		||||
type State = {
 | 
			
		||||
@ -98,17 +99,7 @@ function MachineWS({
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const connectionStatus = {
 | 
			
		||||
    [ReadyState.CONNECTING]: (
 | 
			
		||||
      <SatelliteDish size={14} className="text-orange-500" />
 | 
			
		||||
    ),
 | 
			
		||||
    [ReadyState.OPEN]: <Check size={14} className="text-green-500" />,
 | 
			
		||||
    [ReadyState.CLOSING]: <CircleOff size={14} className="text-orange-500" />,
 | 
			
		||||
    [ReadyState.CLOSED]: <CircleOff size={14} className="text-red-500" />,
 | 
			
		||||
    [ReadyState.UNINSTANTIATED]: "Uninstantiated",
 | 
			
		||||
  }[readyState];
 | 
			
		||||
 | 
			
		||||
  const container = useRef<HTMLDivElement | null>(null);
 | 
			
		||||
  const connectionStatus = getConnectionStatus(readyState);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!lastMessage?.data) return;
 | 
			
		||||
@ -130,18 +121,6 @@ function MachineWS({
 | 
			
		||||
    }
 | 
			
		||||
  }, [lastMessage]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // console.log(logs.length, container.current);
 | 
			
		||||
    if (container.current) {
 | 
			
		||||
      const scrollHeight = container.current.scrollHeight;
 | 
			
		||||
 | 
			
		||||
      container.current.scrollTo({
 | 
			
		||||
        top: scrollHeight,
 | 
			
		||||
        behavior: "smooth",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [logs.length]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog>
 | 
			
		||||
      <DialogTrigger asChild className="">
 | 
			
		||||
@ -156,24 +135,7 @@ function MachineWS({
 | 
			
		||||
            You can view your run's outputs here
 | 
			
		||||
          </DialogDescription>
 | 
			
		||||
        </DialogHeader>
 | 
			
		||||
        <div
 | 
			
		||||
          ref={(ref) => {
 | 
			
		||||
            if (!container.current && ref) {
 | 
			
		||||
              const scrollHeight = ref.scrollHeight;
 | 
			
		||||
 | 
			
		||||
              ref.scrollTo({
 | 
			
		||||
                top: scrollHeight,
 | 
			
		||||
                behavior: "instant",
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
            container.current = ref;
 | 
			
		||||
          }}
 | 
			
		||||
          className="flex flex-col text-xs p-2 overflow-y-scroll max-h-[400px] whitespace-break-spaces"
 | 
			
		||||
        >
 | 
			
		||||
          {logs.map((x, i) => (
 | 
			
		||||
            <div key={i}>{x.logs}</div>
 | 
			
		||||
          ))}
 | 
			
		||||
        </div>
 | 
			
		||||
        <LogsViewer logs={logs} />
 | 
			
		||||
      </DialogContent>
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								web/src/components/getConnectionStatus.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								web/src/components/getConnectionStatus.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
import { Check, CircleOff, SatelliteDish } from "lucide-react";
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { ReadyState } from "react-use-websocket";
 | 
			
		||||
 | 
			
		||||
export function getConnectionStatus(readyState: ReadyState) {
 | 
			
		||||
  const connectionStatus = {
 | 
			
		||||
    [ReadyState.CONNECTING]: (
 | 
			
		||||
      <SatelliteDish size={14} className="text-orange-500" />
 | 
			
		||||
    ),
 | 
			
		||||
    [ReadyState.OPEN]: <Check size={14} className="text-green-500" />,
 | 
			
		||||
    [ReadyState.CLOSING]: <CircleOff size={14} className="text-orange-500" />,
 | 
			
		||||
    [ReadyState.CLOSED]: <CircleOff size={14} className="text-red-500" />,
 | 
			
		||||
    [ReadyState.UNINSTANTIATED]: "Uninstantiated",
 | 
			
		||||
  }[readyState];
 | 
			
		||||
 | 
			
		||||
  return connectionStatus;
 | 
			
		||||
}
 | 
			
		||||
@ -108,6 +108,12 @@ export const machinesType = pgEnum("machine_type", [
 | 
			
		||||
  "modal-serverless",
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
export const machinesStatus = pgEnum("machine_status", [
 | 
			
		||||
  "ready",
 | 
			
		||||
  "building",
 | 
			
		||||
  "error",
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
// We still want to keep the workflow run record.
 | 
			
		||||
export const workflowRunsTable = dbSchema.table("workflow_runs", {
 | 
			
		||||
  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
			
		||||
@ -193,6 +199,9 @@ export const machinesTable = dbSchema.table("machines", {
 | 
			
		||||
  disabled: boolean("disabled").default(false).notNull(),
 | 
			
		||||
  auth_token: text("auth_token"),
 | 
			
		||||
  type: machinesType("type").notNull().default("classic"),
 | 
			
		||||
  status: machinesStatus("status").notNull().default("ready"),
 | 
			
		||||
  snapshot: jsonb("snapshot").$type<any>(),
 | 
			
		||||
  build_log: text("build_log"),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const insertMachineSchema = createInsertSchema(machinesTable, {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import { insertMachineSchema } from "@/db/schema";
 | 
			
		||||
import { insertMachineSchema, machinesTable } from "@/db/schema";
 | 
			
		||||
import { createInsertSchema } from "drizzle-zod";
 | 
			
		||||
 | 
			
		||||
export const addMachineSchema = insertMachineSchema.pick({
 | 
			
		||||
  name: true,
 | 
			
		||||
@ -6,3 +7,14 @@ export const addMachineSchema = insertMachineSchema.pick({
 | 
			
		||||
  type: true,
 | 
			
		||||
  auth_token: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const insertCustomMachineSchema = createInsertSchema(machinesTable, {
 | 
			
		||||
  name: (schema) => schema.name.default("My Machine"),
 | 
			
		||||
  type: (schema) => schema.type.default("modal-serverless"),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const addCustomMachineSchema = insertCustomMachineSchema.pick({
 | 
			
		||||
  name: true,
 | 
			
		||||
  type: true,
 | 
			
		||||
  snapshot: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,17 @@
 | 
			
		||||
"use server";
 | 
			
		||||
 | 
			
		||||
import type { addMachineSchema } from "./addMachineSchema";
 | 
			
		||||
import type {
 | 
			
		||||
  addCustomMachineSchema,
 | 
			
		||||
  addMachineSchema,
 | 
			
		||||
} from "./addMachineSchema";
 | 
			
		||||
import { withServerPromise } from "./withServerPromise";
 | 
			
		||||
import { db } from "@/db/db";
 | 
			
		||||
import { machinesTable } from "@/db/schema";
 | 
			
		||||
import { auth } from "@clerk/nextjs";
 | 
			
		||||
import { and, eq } from "drizzle-orm";
 | 
			
		||||
import { and, eq, isNull } from "drizzle-orm";
 | 
			
		||||
import { revalidatePath } from "next/cache";
 | 
			
		||||
import { headers } from "next/headers";
 | 
			
		||||
import { redirect } from "next/navigation";
 | 
			
		||||
import "server-only";
 | 
			
		||||
import type { z } from "zod";
 | 
			
		||||
 | 
			
		||||
@ -27,6 +32,26 @@ export async function getMachines() {
 | 
			
		||||
  return machines;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getMachineById(id: string) {
 | 
			
		||||
  const { userId, orgId } = auth();
 | 
			
		||||
  if (!userId) throw new Error("No user id");
 | 
			
		||||
  const machines = await db
 | 
			
		||||
    .select()
 | 
			
		||||
    .from(machinesTable)
 | 
			
		||||
    .where(
 | 
			
		||||
      and(
 | 
			
		||||
        orgId
 | 
			
		||||
          ? eq(machinesTable.org_id, orgId)
 | 
			
		||||
          : and(
 | 
			
		||||
              eq(machinesTable.user_id, userId),
 | 
			
		||||
              isNull(machinesTable.org_id)
 | 
			
		||||
            ),
 | 
			
		||||
        eq(machinesTable.id, id)
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  return machines[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const addMachine = withServerPromise(
 | 
			
		||||
  async (data: z.infer<typeof addMachineSchema>) => {
 | 
			
		||||
    const { userId, orgId } = auth();
 | 
			
		||||
@ -42,6 +67,72 @@ export const addMachine = withServerPromise(
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const addCustomMachine = withServerPromise(
 | 
			
		||||
  async (data: z.infer<typeof addCustomMachineSchema>) => {
 | 
			
		||||
    const { userId, orgId } = auth();
 | 
			
		||||
    const headersList = headers();
 | 
			
		||||
 | 
			
		||||
    if (!userId) return { error: "No user id" };
 | 
			
		||||
 | 
			
		||||
    // Insert to our db
 | 
			
		||||
    const a = await db
 | 
			
		||||
      .insert(machinesTable)
 | 
			
		||||
      .values({
 | 
			
		||||
        ...data,
 | 
			
		||||
        org_id: orgId,
 | 
			
		||||
        user_id: userId,
 | 
			
		||||
        status: "building",
 | 
			
		||||
        endpoint: "not-ready",
 | 
			
		||||
      })
 | 
			
		||||
      .returning();
 | 
			
		||||
 | 
			
		||||
    const b = a[0];
 | 
			
		||||
 | 
			
		||||
    // const origin = new URL(request.url).origin;
 | 
			
		||||
    const domain = headersList.get("x-forwarded-host") || "";
 | 
			
		||||
    const protocol = headersList.get("x-forwarded-proto") || "";
 | 
			
		||||
    // console.log("domain", domain);
 | 
			
		||||
    // console.log("domain", `${protocol}://${domain}/api/machine-built`);
 | 
			
		||||
    // return { message: "Machine Building" };
 | 
			
		||||
 | 
			
		||||
    if (domain === "") {
 | 
			
		||||
      throw new Error("No domain");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Call remote builder
 | 
			
		||||
    const result = await fetch(`${process.env.MODAL_BUILDER_URL!}/create`, {
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      headers: {
 | 
			
		||||
        "Content-Type": "application/json",
 | 
			
		||||
      },
 | 
			
		||||
      body: JSON.stringify({
 | 
			
		||||
        machine_id: b.id,
 | 
			
		||||
        name: b.id,
 | 
			
		||||
        snapshot: JSON.parse(data.snapshot as string),
 | 
			
		||||
        callback_url: `${protocol}://${domain}/api/machine-built`,
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (!result.ok) {
 | 
			
		||||
      const error_log = await result.text();
 | 
			
		||||
      await db
 | 
			
		||||
        .update(machinesTable)
 | 
			
		||||
        .set({
 | 
			
		||||
          ...data,
 | 
			
		||||
          status: "error",
 | 
			
		||||
          build_log: error_log,
 | 
			
		||||
        })
 | 
			
		||||
        .where(eq(machinesTable.id, b.id));
 | 
			
		||||
      throw new Error(`Error: ${result.statusText} ${error_log}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    redirect(`/machines/${b.id}`);
 | 
			
		||||
 | 
			
		||||
    // revalidatePath("/machines");
 | 
			
		||||
    return { message: "Machine Building" };
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const updateMachine = withServerPromise(
 | 
			
		||||
  async ({
 | 
			
		||||
    id,
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,8 @@
 | 
			
		||||
import { isRedirectError } from "next/dist/client/components/redirect";
 | 
			
		||||
 | 
			
		||||
export async function wrapServerPromise<T>(result: Promise<T>) {
 | 
			
		||||
  return result.catch((error) => {
 | 
			
		||||
    if (isRedirectError(error)) throw error;
 | 
			
		||||
    return {
 | 
			
		||||
      error: error.message,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user