Compare commits

...
Author SHA1 Message Date
Karrix a9f46b0846 chore: old testing code in pricing page 2024-01-23 02:00:19 +08:00
Karrix f948fca78c add: add unit (per second) to lemonsqueezy after run, and sync to db table 2024-01-23 01:58:05 +08:00
Karrix 0ba1a6d1f0 add: feature flag 2024-01-21 19:49:26 +08:00
Karrix ac8f6e2808 update: pricing plan with usage base 2024-01-21 15:48:56 +08:00
Karrix 8a134ed39e fix: gpu table width too large 2024-01-21 15:47:47 +08:00
Karrix 9cbf0760a0 feat: implement lemonsqueezy to pricing table 2024-01-21 15:47:47 +08:00
Karrix 931b4e144a feat: pricing plan & gpu ui 2024-01-21 15:47:47 +08:00
BennyKok 6de7bf3f20 feat: share page slug 2024-01-21 11:40:48 +08:00
BennyKok ca1b05fff5 feat: add cold start duration display 2024-01-21 11:40:06 +08:00
BennyKok 1d2497116d chore: update custom node inputs list 2024-01-20 22:58:08 +08:00
BennyKok fb020f9f3c feat(plugin): add external checkpoitns node 2024-01-20 22:49:35 +08:00
BennyKok e344c3e6a4 chore: add dev containers 2024-01-20 22:38:31 +08:00
BennyKok e400966117 chore: migrate to biomejs 2024-01-20 20:09:28 +08:00
BennyKok c98a16a2dd feat: add duration display 2024-01-20 17:46:15 +08:00
BennyKok 1a0d73ff8b fix: unable to open deployment code template 2024-01-20 14:27:01 +08:00
BennyKok 163e6f0426 feat: add share page settings dialog + add org id to deployment as well 2024-01-20 12:06:26 +08:00
Nicholas Koben Kao a2d0e93172 posthog install. from: https://posthog.com/docs/libraries/next-js 2024-01-19 19:38:35 -08:00
BennyKok b2690ab2c5 fix: build 2024-01-19 18:14:50 +08:00
BennyKok c7772ca91e feat: update workflow page ux 2024-01-19 18:12:07 +08:00
BennyKok ffb117d85d fix: uploading new workflow will refresh the update date 2024-01-19 17:34:50 +08:00
BennyKok 45c7be3d50 feat: update machine list ui 2024-01-19 17:30:02 +08:00
BennyKok 544de57628 fix: error page in workflow run 2024-01-19 17:24:04 +08:00
BennyKok 85618c8470 fix: button size 2024-01-19 17:08:05 +08:00
BennyKok e7e91f4c50 fix: nav bar ui tweak 2024-01-19 17:08:05 +08:00
BennyKok e220921440 feat: add run origin options, fix client code templates, buttons and badge styles 2024-01-19 17:08:05 +08:00
BennyKok 81cb44c5cf Update nextjs 14.1 2024-01-19 17:08:05 +08:00
BennyKok 8302a934cc Update README.md 2024-01-19 17:08:05 +08:00
93 changed files with 8557 additions and 1445 deletions
+7
View File
@@ -0,0 +1,7 @@
ARG VARIANT=18-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:${VARIANT}
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
RUN npm install -g bun
+17
View File
@@ -0,0 +1,17 @@
{
"name": "Comfy Deploy Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"postCreateCommand": "cd web && bun install && bun run migrate-local",
"customizations": {
"vscode": {
"extensions": [
"biomejs.biome",
"formulahendry.auto-rename-tag",
"bradlc.vscode-tailwindcss",
"stivo.tailwind-fold"
]
}
}
}
+48
View File
@@ -0,0 +1,48 @@
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
environment:
VSCODE_DEV_CONTAINER: true
volumes:
# Forwards the local Docker socket to the container.
- /var/run/docker.sock:/var/run/docker-host.sock
# Update this to wherever you want VS Code to mount the folder of your project
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
# entrypoint: /usr/local/share/docker-init.sh
command: sleep infinity
postgres:
image: "postgres:15.2-alpine"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: verceldb
ports:
- "5480:5432"
pg_proxy:
image: ghcr.io/neondatabase/wsproxy:latest
environment:
APPEND_PORT: "postgres:5432"
ALLOW_ADDR_REGEX: ".*"
LOG_TRAFFIC: "true"
ports:
- "5481:80"
depends_on:
- postgres
localstack:
image: localstack/localstack:latest
environment:
SERVICES: s3
ports:
- 4566:4566
volumes:
- ../web/aws:/etc/localstack/init/ready.d
- ../web/aws:/app/web/aws
+2 -3
View File
@@ -1,14 +1,13 @@
{ {
"recommendations": [ "recommendations": [
"DavidAnson.vscode-markdownlint", // markdown linting "DavidAnson.vscode-markdownlint", // markdown linting
"yzhang.markdown-all-in-one", // nicer markdown support
"esbenp.prettier-vscode", // prettier plugin "esbenp.prettier-vscode", // prettier plugin
"dbaeumer.vscode-eslint", // eslint plugin "dbaeumer.vscode-eslint", // eslint plugin
"bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind "bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind
"ban.spellright", // Spell check for docs "ban.spellright", // Spell check for docs
"stripe.vscode-stripe", // stripe VSCode extension "stripe.vscode-stripe", // stripe VSCode extension
"Prisma.prisma", // syntax|format|completion for prisma
"rebornix.project-snippets", // Share useful snippets between collaborators "rebornix.project-snippets", // Share useful snippets between collaborators
"inlang.vs-code-extension" // improved i18n DX "inlang.vs-code-extension",
"biomejs.biome" // improved i18n DX
] ]
} }
+3 -2
View File
@@ -1,8 +1,9 @@
{ {
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": false, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
}, },
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"spellright.language": ["en"], "spellright.language": ["en"],
+2
View File
@@ -4,6 +4,8 @@ Open source comfyui deployment platform, a `vercel` for generative workflow infr
Join [Discord](https://discord.gg/EEYcQmdYZw) to chat more or visit [Comfy Deploy](https://comfydeploy.com/) to get started! Join [Discord](https://discord.gg/EEYcQmdYZw) to chat more or visit [Comfy Deploy](https://comfydeploy.com/) to get started!
Check out our latest [nextjs starter kit](https://github.com/BennyKok/comfyui-deploy-next-example) with Comfy Deploy
![63shots_so 1](https://github.com/BennyKok/comfyui-deploy/assets/18395202/c0b88377-0135-4d9a-8a20-8b38b975bb48)\# How it works ![63shots_so 1](https://github.com/BennyKok/comfyui-deploy/assets/18395202/c0b88377-0135-4d9a-8a20-8b38b975bb48)\# How it works
1. Comfy Deploy Dashboard (https://comfydeploy.com) or self-hosted version 1. Comfy Deploy Dashboard (https://comfydeploy.com) or self-hosted version
+56
View File
@@ -0,0 +1,56 @@
import folder_paths
from PIL import Image, ImageOps
import numpy as np
import torch
import folder_paths
class ComfyUIDeployExternalCheckpoints:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_checkpoints"},
),
},
"optional": {
"default_checkpoints_name": (folder_paths.get_filename_list("checkpoints"), ),
}
}
RETURN_TYPES = (folder_paths.get_filename_list("checkpoints"),)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "deploy"
def run(self, input_id, default_checkpoints_name=None):
import requests
import os
import uuid
if input_id and input_id.startswith('http'):
unique_filename = str(uuid.uuid4()) + ".safetensors"
print(unique_filename)
print(folder_paths.folder_names_and_paths["checkpoints"][0][0])
destination_path = os.path.join(
folder_paths.folder_names_and_paths["checkpoints"][0][0], unique_filename)
print(destination_path)
print("Downloading external checkpoints - " +
input_id + " to " + destination_path)
response = requests.get(
input_id, headers={'User-Agent': 'Mozilla/5.0'}, allow_redirects=True)
with open(destination_path, 'wb') as out_file:
out_file.write(response.content)
return (unique_filename,)
else:
return (default_checkpoints_name,)
NODE_CLASS_MAPPINGS = {
"ComfyUIDeployExternalCheckpoints": ComfyUIDeployExternalCheckpoints}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalCheckpoints": "External Checkpoints (ComfyUI Deploy)"}
+3
View File
@@ -17,3 +17,6 @@ MODAL_BUILDER_URL=
JWT_SECRET="openssl rand -hex 32" JWT_SECRET="openssl rand -hex 32"
PLAUSIBLE_DOMAIN= PLAUSIBLE_DOMAIN=
NEXT_PUBLIC_POSTHOG_KEY="your-api-key"
NEXT_PUBLIC_POSTHOG_HOST="your-ph-address"
-6
View File
@@ -1,6 +0,0 @@
node_modules
**/node_modules
**/.next
**/public
packages/prisma/zod
apps/web/public/embed
-95
View File
@@ -1,95 +0,0 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: [
// "plugin:playwright/playwright-test",
"next",
// "next/core-web-vitals",
"plugin:prettier/recommended",
// "turbo",
// "plugin:you-dont-need-lodash-underscore/compatible-warn",
],
plugins: ["unused-imports"],
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
// project: ["./apps/*/tsconfig.json", "./packages/*/tsconfig.json"],
},
settings: {
next: {
// rootDir: ["apps/*/", "packages/*/"],
rootDir: ["src"],
},
},
rules: {
"@next/next/no-img-element": "off",
"@next/next/no-html-link-for-pages": "off",
"jsx-a11y/role-supports-aria-props": "off", // @see https://github.com/vercel/next.js/issues/27989#issuecomment-897638654
// "playwright/no-page-pause": "error",
"react/jsx-curly-brace-presence": [
"error",
{ props: "never", children: "never" },
],
"react/self-closing-comp": ["error", { component: true, html: true }],
"@typescript-eslint/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
},
],
"unused-imports/no-unused-imports": "error",
"no-restricted-imports": [
"error",
{
patterns: ["lodash"],
},
],
"prefer-template": "error",
},
overrides: [
{
files: ["*.ts", "*.tsx"],
extends: [
"plugin:@typescript-eslint/recommended",
// "plugin:@calcom/eslint/recommended",
],
plugins: [
"@typescript-eslint",
// "@calcom/eslint"
],
parser: "@typescript-eslint/parser",
rules: {
"@typescript-eslint/consistent-type-imports": [
"error",
{
prefer: "type-imports",
// TODO: enable this once prettier supports it
// fixStyle: "inline-type-imports",
fixStyle: "separate-type-imports",
disallowTypeAnnotations: false,
},
],
},
// overrides: [
// {
// files: ["**/playwright/**/*.{tsx,ts}"],
// rules: {
// "@typescript-eslint/no-unused-vars": "off",
// "no-undef": "off",
// },
// },
// ],
},
// {
// files: ["**/playwright/**/*.{js,jsx}"],
// rules: {
// "@typescript-eslint/no-unused-vars": "off",
// "no-undef": "off",
// },
// },
],
};
+23
View File
@@ -0,0 +1,23 @@
{
"$schema": "https://biomejs.dev/schemas/1.5.2/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"json": {
"parser": {
"allowComments": true
}
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
}
}
BIN
View File
Binary file not shown.
@@ -0,0 +1 @@
ALTER TYPE "workflow_run_origin" ADD VALUE 'public-share';
@@ -0,0 +1,2 @@
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "description" text;--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "showcase_media" jsonb;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "org_id" text;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "started_at" timestamp;
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "share_slug" text;--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."deployments" ADD CONSTRAINT "deployments_share_slug_unique" UNIQUE("share_slug");
+13
View File
@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."user_usage" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"usage_time" real DEFAULT 0 NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."user_usage" ADD CONSTRAINT "user_usage_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "comfyui_deploy"."user_usage" RENAME COLUMN "updated_at" TO "ended_at";--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."user_usage" ADD COLUMN "org_id" text;
+738
View File
@@ -0,0 +1,738 @@
{
"id": "7bdeb193-ee27-40cc-8252-59ddaf505ab8",
"prevId": "dddfedc3-5b2f-4d54-a996-821945b9ff80",
"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
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
+750
View File
@@ -0,0 +1,750 @@
{
"id": "a7d6a8dd-0e15-4165-98a2-de2a334455dc",
"prevId": "7bdeb193-ee27-40cc-8252-59ddaf505ab8",
"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
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
+756
View File
@@ -0,0 +1,756 @@
{
"id": "db06ea66-92c2-4ebe-93c1-6cb8a90ccd8b",
"prevId": "a7d6a8dd-0e15-4165-98a2-de2a334455dc",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
+762
View File
@@ -0,0 +1,762 @@
{
"id": "1ca4fdb7-c0c4-4c39-8b47-f40282293da0",
"prevId": "db06ea66-92c2-4ebe-93c1-6cb8a90ccd8b",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
+776
View File
@@ -0,0 +1,776 @@
{
"id": "1425ee00-66fb-4541-8da7-19b217944545",
"prevId": "1ca4fdb7-c0c4-4c39-8b47-f40282293da0",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"share_slug": {
"name": "share_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"deployments_share_slug_unique": {
"name": "deployments_share_slug_unique",
"nullsNotDistinct": false,
"columns": [
"share_slug"
]
}
}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
+834
View File
@@ -0,0 +1,834 @@
{
"id": "91bb0461-452a-4e59-abf4-8757fcd75a89",
"prevId": "1425ee00-66fb-4541-8da7-19b217944545",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"share_slug": {
"name": "share_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"deployments_share_slug_unique": {
"name": "deployments_share_slug_unique",
"nullsNotDistinct": false,
"columns": [
"share_slug"
]
}
}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user_usage": {
"name": "user_usage",
"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
},
"usage_time": {
"name": "usage_time",
"type": "real",
"primaryKey": false,
"notNull": true,
"default": 0
},
"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": {
"user_usage_user_id_users_id_fk": {
"name": "user_usage_user_id_users_id_fk",
"tableFrom": "user_usage",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
+842
View File
@@ -0,0 +1,842 @@
{
"id": "fad17dc9-86c5-4081-8e73-47c113f48936",
"prevId": "91bb0461-452a-4e59-abf4-8757fcd75a89",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"share_slug": {
"name": "share_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"deployments_share_slug_unique": {
"name": "deployments_share_slug_unique",
"nullsNotDistinct": false,
"columns": [
"share_slug"
]
}
}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user_usage": {
"name": "user_usage",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"usage_time": {
"name": "usage_time",
"type": "real",
"primaryKey": false,
"notNull": true,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"user_usage_user_id_users_id_fk": {
"name": "user_usage_user_id_users_id_fk",
"tableFrom": "user_usage",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {
"\"comfyui_deploy\".\"user_usage\".\"updated_at\"": "\"comfyui_deploy\".\"user_usage\".\"ended_at\""
}
}
}
+49
View File
@@ -197,6 +197,55 @@
"when": 1705228261543, "when": 1705228261543,
"tag": "0027_eminent_lilith", "tag": "0027_eminent_lilith",
"breakpoints": true "breakpoints": true
},
{
"idx": 28,
"version": "5",
"when": 1705642345817,
"tag": "0028_futuristic_lady_deathstrike",
"breakpoints": true
},
{
"idx": 29,
"version": "5",
"when": 1705662714161,
"tag": "0029_large_frightful_four",
"breakpoints": true
},
{
"idx": 30,
"version": "5",
"when": 1705716303820,
"tag": "0030_kind_doorman",
"breakpoints": true
},
{
"idx": 31,
"version": "5",
"when": 1705763980972,
"tag": "0031_fast_lyja",
"breakpoints": true
},
{
"idx": 32,
"version": "5",
"when": 1705806921697,
"tag": "0032_shallow_vermin",
"breakpoints": true
},
{
"idx": 33,
"version": "5",
"when": 1705824362978,
"tag": "0033_fantastic_marvel_boy",
"breakpoints": true
},
{
"idx": 34,
"version": "5",
"when": 1705840184127,
"tag": "0034_previous_viper",
"breakpoints": true
} }
] ]
} }
+4 -3
View File
@@ -12,10 +12,11 @@ let sslMode: string | boolean = process.env.SSL || "require";
if (sslMode === "false") sslMode = false; if (sslMode === "false") sslMode = false;
console.log(migrationsFolderName, sslMode); let connectionString = process.env.POSTGRES_URL!;
const isDevContainer = process.env.VSCODE_DEV_CONTAINER !== undefined;
if (isDevContainer) connectionString = connectionString.replace("localhost","host.docker.internal")
const connectionString = process.env.POSTGRES_URL!;
console.log(connectionString);
const sql = postgres(connectionString, { max: 1, ssl: sslMode as any }); const sql = postgres(connectionString, { max: 1, ssl: sslMode as any });
const db = drizzle(sql, { const db = drizzle(sql, {
logger: true, logger: true,
+5 -4
View File
@@ -1,4 +1,3 @@
import million from 'million/compiler';
import { recmaPlugins } from "./src/mdx/recma.mjs"; import { recmaPlugins } from "./src/mdx/recma.mjs";
import { rehypePlugins } from "./src/mdx/rehype.mjs"; import { rehypePlugins } from "./src/mdx/rehype.mjs";
import { remarkPlugins } from "./src/mdx/remark.mjs"; import { remarkPlugins } from "./src/mdx/remark.mjs";
@@ -21,6 +20,8 @@ const nextConfig = {
}, },
}; };
export default million.next( export default withSearch(withMDX(nextConfig));
withSearch(withMDX(nextConfig)), { auto: { rsc: true } }
); // export default million.next(
// withSearch(withMDX(nextConfig)), { auto: { rsc: true } }
// );
+8 -12
View File
@@ -12,7 +12,8 @@
"migrate-production": "bun run migrate.mts", "migrate-production": "bun run migrate.mts",
"migrate-local": "SSL=false LOCAL=true bun run migrate.mts", "migrate-local": "SSL=false LOCAL=true bun run migrate.mts",
"db-up": "docker-compose up", "db-up": "docker-compose up",
"db-dev": "bun run db-up && bun run migrate-local" "db-dev": "bun run db-up && bun run migrate-local",
"lint:fix": "bunx @biomejs/biome lint --apply ./src"
}, },
"dependencies": { "dependencies": {
"@algolia/autocomplete-core": "^1.13.0", "@algolia/autocomplete-core": "^1.13.0",
@@ -25,6 +26,7 @@
"@hono/zod-openapi": "^0.9.5", "@hono/zod-openapi": "^0.9.5",
"@hono/zod-validator": "^0.1.11", "@hono/zod-validator": "^0.1.11",
"@hookform/resolvers": "^3.3.2", "@hookform/resolvers": "^3.3.2",
"@lemonsqueezy/lemonsqueezy.js": "^1.2.5",
"@mdx-js/loader": "^3.0.0", "@mdx-js/loader": "^3.0.0",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
"@neondatabase/serverless": "^0.6.0", "@neondatabase/serverless": "^0.6.0",
@@ -60,6 +62,7 @@
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"drizzle-orm": "^0.29.1", "drizzle-orm": "^0.29.1",
"drizzle-zod": "^0.5.1", "drizzle-zod": "^0.5.1",
"embla-carousel-react": "^8.0.0-rc19",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"flexsearch": "^0.7.31", "flexsearch": "^0.7.31",
"framer-motion": "^10.16.16", "framer-motion": "^10.16.16",
@@ -71,11 +74,13 @@
"million": "latest", "million": "latest",
"mitata": "^0.1.6", "mitata": "^0.1.6",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"next": "14.0.4", "next": "14.1",
"next-plausible": "^3.12.0", "next-plausible": "^3.12.0",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"next-usequerystate": "^1.13.2", "next-usequerystate": "^1.13.2",
"pg": "^8.11.3", "pg": "^8.11.3",
"posthog-js": "^1.100.0",
"posthog-node": "^3.5.0",
"react": "^18", "react": "^18",
"react-day-picker": "^8.9.1", "react-day-picker": "^8.9.1",
"react-dom": "^18", "react-dom": "^18",
@@ -102,26 +107,17 @@
"zustand": "^4.4.7" "zustand": "^4.4.7"
}, },
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "4.1.1", "@biomejs/biome": "1.5.2",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"autoprefixer": "^10.0.1", "autoprefixer": "^10.0.1",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"drizzle-kit": "^0.20.6", "drizzle-kit": "^0.20.6",
"eslint": "8.34.0", "eslint": "8.34.0",
"eslint-config-next": "^14.0.4",
"eslint-config-prettier": "^8.6.0",
"eslint-config-turbo": "latest",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-unused-imports": "^3.0.0",
"postcss": "^8", "postcss": "^8",
"postgres": "^3.4.3", "postgres": "^3.4.3",
"prettier": "2.8.6",
"prettier-plugin-tailwindcss": "0.2.5",
"sharp": "^0.33.1", "sharp": "^0.33.1",
"tailwindcss": "^3.3.0", "tailwindcss": "^3.3.0",
"typescript": "^5" "typescript": "^5"
@@ -0,0 +1,9 @@
import { SharePageSettings } from "@/components/SharePageSettings";
export default async function Page({
params,
}: {
params: { share_id: string };
}) {
return <SharePageSettings deployment_id={params.share_id} />;
}
+7
View File
@@ -0,0 +1,7 @@
import type { FC } from "react";
const Default: FC = () => {
return null;
};
export default Default;
+29
View File
@@ -0,0 +1,29 @@
// app/PostHogPageView.tsx
'use client'
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { usePostHog } from 'posthog-js/react';
export default function PostHogPageView() {
const pathname = usePathname();
const searchParams = useSearchParams();
const posthog = usePostHog();
// Track pageviews
useEffect(() => {
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}
posthog.capture(
'$pageview',
{
'$current_url': url,
}
)
}
}, [pathname, searchParams, posthog])
return null
}
+61 -4
View File
@@ -1,6 +1,13 @@
import { parseDataSafe } from "../../../../lib/parseDataSafe"; import { parseDataSafe } from "../../../../lib/parseDataSafe";
import { db } from "@/db/db"; import { db } from "@/db/db";
import { workflowRunOutputs, workflowRunsTable } from "@/db/schema"; import {
userUsageTable,
workflowRunOutputs,
workflowRunsTable,
workflowTable,
} from "@/db/schema";
import { getDuration } from "@/lib/getRelativeTime";
import { getSubscription, setUsage } from "@/server/linkToPricing";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { z } from "zod"; import { z } from "zod";
@@ -27,7 +34,6 @@ export async function POST(request: Request) {
data: output_data, data: output_data,
}); });
} else if (status) { } else if (status) {
// console.log("status", status);
const workflow_run = await db const workflow_run = await db
.update(workflowRunsTable) .update(workflowRunsTable)
.set({ .set({
@@ -35,8 +41,15 @@ export async function POST(request: Request) {
ended_at: ended_at:
status === "success" || status === "failed" ? new Date() : null, status === "success" || status === "failed" ? new Date() : null,
}) })
.where(eq(workflowRunsTable.id, run_id)) .where(eq(workflowRunsTable.id, run_id));
.returning();
// get data from workflowRunsTable
const userUsageTime = await importUserUsageData(run_id);
if (userUsageTime) {
// get the usage_time from userUsage
await addSubscriptionUnit(userUsageTime);
}
} }
// const workflow_version = await db.query.workflowVersionTable.findFirst({ // const workflow_version = await db.query.workflowVersionTable.findFirst({
@@ -54,3 +67,47 @@ export async function POST(request: Request) {
} }
); );
} }
async function addSubscriptionUnit(userUsageTime: number) {
const subscription = await getSubscription();
// round up userUsageTime to the nearest integer
const roundedUsageTime = Math.ceil(userUsageTime);
if (subscription) {
const usage = await setUsage(
subscription.data[0].attributes.first_subscription_item.id,
roundedUsageTime
);
}
}
async function importUserUsageData(run_id: string) {
const workflowRuns = await db.query.workflowRunsTable.findFirst({
where: eq(workflowRunsTable.id, run_id),
});
if (!workflowRuns?.workflow_id) return;
// find if workflowTable id column contains workflowRunsTable workflow_id
const workflow = await db.query.workflowTable.findFirst({
where: eq(workflowTable.id, workflowRuns.workflow_id),
});
if (workflowRuns?.ended_at === null || workflow == null) return;
const usageTime = parseFloat(
getDuration((workflowRuns?.ended_at - workflowRuns?.started_at) / 1000)
);
// add data to userUsageTable
const user_usage = await db.insert(userUsageTable).values({
user_id: workflow.user_id,
created_at: workflowRuns.ended_at,
org_id: workflow.org_id,
ended_at: workflowRuns.ended_at,
usage_time: usageTime,
});
return usageTime;
}
+10 -1
View File
@@ -4,11 +4,12 @@ import { db } from "@/db/db";
import { import {
snapshotType, snapshotType,
workflowAPIType, workflowAPIType,
workflowTable,
workflowType, workflowType,
workflowVersionTable, workflowVersionTable,
} from "@/db/schema"; } from "@/db/schema";
import { parseDataSafe } from "@/lib/parseDataSafe"; import { parseDataSafe } from "@/lib/parseDataSafe";
import { sql } from "drizzle-orm"; import { eq, sql } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { z } from "zod"; import { z } from "zod";
@@ -132,6 +133,14 @@ export async function POST(request: Request) {
}) })
.returning(); .returning();
version = data[0].version; version = data[0].version;
await db
.update(workflowTable)
.set({
updated_at: new Date(),
})
.where(eq(workflowTable.id, workflow_id))
.returning();
} else { } else {
return NextResponse.json( return NextResponse.json(
{ {
+5
View File
@@ -0,0 +1,5 @@
"use client";
import { ErrorFullPage } from "@/components/docs/ErrorPage";
export default ErrorFullPage;
+26 -14
View File
@@ -1,13 +1,19 @@
import { Navbar } from "../../components/Navbar"; import { Navbar } from "../../components/Navbar";
import "./globals.css"; import "./globals.css";
import { PHProvider } from "./providers";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { ClerkProvider } from "@clerk/nextjs"; import { ClerkProvider } from "@clerk/nextjs";
import type { Metadata } from "next"; import type { Metadata } from "next";
import meta from "next-gen/config"; import meta from "next-gen/config";
import PlausibleProvider from "next-plausible"; import PlausibleProvider from "next-plausible";
import dynamic from "next/dynamic";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
const PostHogPageView = dynamic(() => import("./PostHogPageView"), {
ssr: false,
});
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -27,8 +33,10 @@ export const metadata: Metadata = {
export default function RootLayout({ export default function RootLayout({
children, children,
modal,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
modal: React.ReactNode;
}) { }) {
return ( return (
<html lang="en"> <html lang="en">
@@ -39,20 +47,24 @@ export default function RootLayout({
<PlausibleProvider domain={process.env.PLAUSIBLE_DOMAIN} /> <PlausibleProvider domain={process.env.PLAUSIBLE_DOMAIN} />
</head> </head>
)} )}
<body className={inter.className}> <PHProvider>
<main className="w-full flex min-h-[100dvh] flex-col items-center justify-start"> <body className={inter.className}>
<div className="z-[-1] fixed h-full w-full bg-white"> <PostHogPageView />
<div className="absolute h-full w-full bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" /> <main className="w-full flex min-h-[100dvh] flex-col items-center justify-start">
</div> <div className="z-[-1] fixed h-full w-full bg-white">
<div className="sticky w-full h-18 flex items-center justify-between gap-4 p-4 border-b border-gray-200"> <div className="absolute h-full w-full bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
<Navbar /> </div>
</div> <div className="sticky w-full h-18 flex items-center justify-between gap-4 p-4 border-b border-gray-200">
<div className="md:px-10 px-6 w-full h-[calc(100dvh-73px)]"> <Navbar />
{children} </div>
</div> <div className="md:px-10 px-6 w-full h-[calc(100dvh-73px)]">
<Toaster richColors /> {children}
</main> </div>
</body> <Toaster richColors />
{modal}
</main>
</body>
</PHProvider>
</TooltipProvider> </TooltipProvider>
</ClerkProvider> </ClerkProvider>
</html> </html>
@@ -0,0 +1,81 @@
const people = [
{
name: "Nvidia T4 GPU",
gpu: "1x",
ram: "16GB",
price: "$0.000225/sec",
},
{
name: "Nvidia A40 GPU",
gpu: "1x",
ram: "48GB",
price: "$0.000575/sec",
},
];
export function GpuPricingPlan() {
return (
<div className="flex justify-center w-full py-8">
<div className="w-full max-w-4xl">
<table className="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th
scope="col"
className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6"
>
GPU
</th>
<th
scope="col"
className="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell"
>
No.
</th>
<th
scope="col"
className="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell"
>
RAM
</th>
<th
scope="col"
className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
>
Price
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{people.map((person) => (
<tr key={person.ram} className="even:bg-gray-50">
<td className="w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6">
{person.name}
<dl className="font-normal lg:hidden">
<dt className="sr-only">No.</dt>
<dd className="mt-1 truncate text-gray-700">
{person.gpu}
</dd>
<dt className="sr-only sm:hidden">RAM</dt>
<dd className="mt-1 truncate text-gray-500 sm:hidden">
{person.ram}
</dd>
</dl>
</td>
<td className="hidden px-3 py-4 text-sm text-gray-500 lg:table-cell">
{person.gpu}
</td>
<td className="hidden px-3 py-4 text-sm text-gray-500 sm:table-cell">
{person.ram}
</td>
<td className="px-3 py-4 text-sm text-gray-500">
{person.price}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
@@ -0,0 +1,157 @@
import { checkMarkIcon, crossMarkIcon } from "../const/Icon";
import { cn } from "@/lib/utils";
import { getPricing } from "@/server/linkToPricing";
import { useEffect, useState } from "react";
type Tier = {
name: string;
id: string;
href: string;
priceMonthly: string;
description: string;
features: string[];
featured: boolean;
priority?: TierPriority;
};
enum TierPriority {
Free = "free",
Pro = "pro",
Enterprise = "enterprise",
}
export default function PricingList() {
const [productTiers, setProductTiers] = useState<Tier[]>();
useEffect(() => {
(async () => {
const product = await getPricing();
if (!product) return;
const newProductTiers: Tier[] = product.data.map((item) => {
// Create a new DOMParser instance
const parser = new DOMParser();
// Parse the description HTML string to a new document
const doc = parser.parseFromString(
item.attributes.description,
"text/html"
);
// Extract the description and features
const description = doc.querySelector("p")?.textContent || "";
const features = Array.from(doc.querySelectorAll("ul > li")).map(
(li) => li.textContent || ""
);
return {
name: item.attributes.name,
id: item.id,
href: item.attributes.buy_now_url,
priceMonthly:
item.attributes.price_formatted.split("/")[0] == "Usage-based"
? "$20.00"
: item.attributes.price_formatted.split("/")[0],
description: description,
features: features,
// if name contains pro, it's featured
featured: item.attributes.name.toLowerCase().includes("pro"),
// give priority if name contain in enum
priority: Object.values(TierPriority).find((priority) =>
item.attributes.name.toLowerCase().includes(priority)
),
};
});
// sort newProductTiers by priority
newProductTiers.sort((a, b) => {
if (!a.priority) return 1;
if (!b.priority) return -1;
return (
Object.values(TierPriority).indexOf(a.priority) -
Object.values(TierPriority).indexOf(b.priority)
);
});
setProductTiers(newProductTiers);
})();
}, []);
return (
<div className="relative isolate px-6 py-24 lg:px-8">
<div className="mx-auto max-w-2xl text-center lg:max-w-4xl">
<h2 className="text-base font-semibold leading-7 text-indigo-600">
Pricing
</h2>
<p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
The right price for you, whoever you are
</p>
</div>
<p className="mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600">
Qui iusto aut est earum eos quae. Eligendi est at nam aliquid ad quo
reprehenderit in aliquid fugiat dolorum voluptatibus.
</p>
<div className="mx-auto mt-16 grid max-w-lg grid-cols-1 items-center gap-y-6 sm:mt-20 sm:gap-y-0 lg:max-w-4xl lg:grid-cols-2 xl:max-w-6xl xl:grid-cols-3">
{productTiers &&
productTiers.map((tier, tierIdx) => (
<div
key={tier.id}
className={cn(
tier.featured
? "relative bg-white shadow-2xl"
: "bg-white/60 sm:mx-8 lg:mx-0",
tier.featured
? ""
: tierIdx === 0
? "rounded-t-3xl sm:rounded-b-none lg:rounded-tr-none lg:rounded-bl-3xl"
: "sm:rounded-t-none lg:rounded-tr-3xl lg:rounded-bl-none",
"rounded-3xl p-8 ring-1 ring-gray-900/10 sm:p-10"
)}
>
<h3
id={tier.id}
className="text-base font-semibold leading-7 text-indigo-600"
>
{tier.name}
</h3>
<p className="mt-4 flex items-baseline gap-x-2">
<span className="text-5xl font-bold tracking-tight text-gray-900">
{tier.priceMonthly}
</span>
<span className="text-base text-gray-500">/month</span>
</p>
<p className="mt-6 text-base leading-7 text-gray-600">
{tier.description}
</p>
<ul
role="list"
className="mt-8 space-y-3 text-sm leading-6 text-gray-600 sm:mt-10"
>
{tier.features.map((feature) => (
<li key={feature} className="flex gap-x-3">
<div className="flex justify-center items-center">
{feature.includes("[x]") ? crossMarkIcon : checkMarkIcon}
</div>
{feature.replace("[x]", "")}
</li>
))}
</ul>
<a
href={tier.href}
aria-describedby={tier.id}
className={cn(
tier.featured
? "bg-indigo-600 text-white shadow hover:bg-indigo-500"
: "text-indigo-600 ring-1 ring-inset ring-indigo-200 hover:ring-indigo-300",
"mt-8 block rounded-md py-2.5 px-3.5 text-center text-sm font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:mt-10"
)}
>
Get started today
</a>
</div>
))}
</div>
</div>
);
}
+37
View File
@@ -0,0 +1,37 @@
export const checkMarkIcon = (
<svg
className="h-5 w-5 flex-shrink-0 text-green-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clipRule="evenodd"
/>
</svg>
);
export const crossMarkIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="20"
height="20"
viewBox="0 0 48 48"
>
<path
fill="#F44336"
d="M21.5 4.5H26.501V43.5H21.5z"
transform="rotate(45.001 24 24)"
/>
<path
fill="#F44336"
d="M21.5 4.5H26.5V43.501H21.5z"
transform="rotate(135.008 24 24)"
/>
</svg>
);
+9
View 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()} />;
}
+13
View File
@@ -0,0 +1,13 @@
"use client";
import { GpuPricingPlan } from "@/app/(app)/pricing/components/gpuPricingTable";
import PricingList from "@/app/(app)/pricing/components/pricePlanList";
export default function Home() {
return (
<div>
<PricingList />
<GpuPricingPlan />
</div>
);
}
+19
View File
@@ -0,0 +1,19 @@
// app/providers.tsx
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
if (typeof window !== 'undefined') {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
capture_pageview: false // Disable automatic pageview capture, as we capture manually
})
}
export function PHProvider({
children,
}: {
children: React.ReactNode
}) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}
+81 -80
View File
@@ -1,14 +1,12 @@
import { ButtonActionMenu } from "@/components/ButtonActionLoader"; import { ButtonActionMenu } from "@/components/ButtonActionLoader";
import { RunWorkflowInline } from "@/components/RunWorkflowInline";
import { PublicRunOutputs } from "@/components/VersionSelect";
import { import {
PublicRunOutputs, Card,
RunWorkflowInline, CardContent,
} from "@/components/VersionSelect"; CardDescription,
import { CardHeader,
Card, CardTitle,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { db } from "@/db/db"; import { db } from "@/db/db";
import { usersTable } from "@/db/schema"; import { usersTable } from "@/db/schema";
@@ -16,9 +14,9 @@ import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
import { getRelativeTime } from "@/lib/getRelativeTime"; import { getRelativeTime } from "@/lib/getRelativeTime";
import { setInitialUserData } from "@/lib/setInitialUserData"; import { setInitialUserData } from "@/lib/setInitialUserData";
import { import {
cloneMachine, cloneMachine,
cloneWorkflow, cloneWorkflow,
findSharedDeployment, findSharedDeployment,
} from "@/server/curdDeploments"; } from "@/server/curdDeploments";
import { auth, clerkClient } from "@clerk/nextjs/server"; import { auth, clerkClient } from "@clerk/nextjs/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
@@ -27,84 +25,87 @@ import { redirect } from "next/navigation";
export const maxDuration = 300; // 5 minutes export const maxDuration = 300; // 5 minutes
export default async function Page({ export default async function Page({
params, params,
}: { }: {
params: { share_id: string }; params: { share_id: string };
}) { }) {
const { userId } = await auth(); const { userId } = await auth();
// If there is user, check if the user data is present // If there is user, check if the user data is present
if (userId) { if (userId) {
const user = await db.query.usersTable.findFirst({ const user = await db.query.usersTable.findFirst({
where: eq(usersTable.id, userId), where: eq(usersTable.id, userId),
}); });
if (!user) { if (!user) {
await setInitialUserData(userId); await setInitialUserData(userId);
} }
} }
const sharedDeployment = await findSharedDeployment(params.share_id); const sharedDeployment = await findSharedDeployment(params.share_id);
if (!sharedDeployment) return redirect("/"); if (!sharedDeployment) return redirect("/");
const userName = sharedDeployment.workflow.org_id const userName = sharedDeployment.workflow.org_id
? await clerkClient.organizations ? await clerkClient.organizations
.getOrganization({ .getOrganization({
organizationId: sharedDeployment.workflow.org_id, organizationId: sharedDeployment.workflow.org_id,
}) })
.then((x) => x.name) .then((x) => x.name)
: sharedDeployment.user.name; : sharedDeployment.user.name;
const inputs = getInputsFromWorkflow(sharedDeployment.version); const inputs = getInputsFromWorkflow(sharedDeployment.version);
return ( return (
<div className="mt-4 w-full grid grid-rows-[1fr,1fr] lg:grid-cols-[minmax(auto,500px),1fr] gap-4 max-h-[calc(100dvh-100px)]"> <div className="mt-4 w-full grid grid-rows-[1fr,1fr] lg:grid-cols-[minmax(auto,500px),1fr] gap-4 max-h-[calc(100dvh-100px)]">
<Card className="w-full h-fit mt-4"> <Card className="w-full h-fit mt-4">
<CardHeader> <CardHeader>
<CardTitle className="flex justify-between items-center"> <CardTitle className="flex justify-between items-center">
<div> <div>
{userName} {userName}
{" / "} {" / "}
{sharedDeployment.workflow.name} {sharedDeployment.workflow.name}
</div> </div>
<ButtonActionMenu <ButtonActionMenu
title="Clone" title="Clone"
actions={[ actions={[
{ {
title: "Workflow", title: "Workflow",
action: cloneWorkflow.bind(null, sharedDeployment.id), action: cloneWorkflow.bind(null, sharedDeployment.id),
}, },
{ {
title: "Machine", title: "Machine",
action: cloneMachine.bind(null, sharedDeployment.id), action: cloneMachine.bind(null, sharedDeployment.id),
}, },
]} ]}
/> />
</CardTitle> </CardTitle>
<CardDescription suppressHydrationWarning={true}> <CardDescription suppressHydrationWarning={true}>
{getRelativeTime(sharedDeployment?.updated_at)} {getRelativeTime(sharedDeployment?.updated_at)}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<RunWorkflowInline <div>
inputs={inputs} {sharedDeployment?.description && sharedDeployment?.description}
machine_id={sharedDeployment.machine_id} </div>
workflow_version_id={sharedDeployment.workflow_version_id} <RunWorkflowInline
/> inputs={inputs}
</CardContent> machine_id={sharedDeployment.machine_id}
</Card> workflow_version_id={sharedDeployment.workflow_version_id}
<Card className="w-full h-fit mt-4"> />
<CardHeader> </CardContent>
<CardDescription>Run outputs</CardDescription> </Card>
</CardHeader> <Card className="w-full h-fit mt-4">
<CardHeader>
<CardDescription>Run outputs</CardDescription>
</CardHeader>
<CardContent> <CardContent>
<PublicRunOutputs /> <PublicRunOutputs preview={sharedDeployment.showcase_media} />
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
); );
} }
@@ -0,0 +1,9 @@
import { SharePageSettings } from "@/components/SharePageSettings";
export default async function Page({
params,
}: {
params: { share_id: string };
}) {
return <SharePageSettings deployment_id={params.share_id} />;
}
@@ -0,0 +1,5 @@
"use client";
import ErrorPage from "@/components/docs/ErrorPage";
export default ErrorPage;
@@ -1,51 +1,5 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import ErrorPage from "@/components/docs/ErrorPage";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
// Error components must be Client Components
import { useEffect } from "react";
export default function Error({ export default ErrorPage;
error,
reset,
}: {
error?: Error & { digest?: string };
reset?: () => void;
}) {
useEffect(() => {
// Log the error to an error reporting service
console.log(error?.message);
}, [error]);
return (
<div className="border rounded-2xl flex flex-col w-full gap-20 justify-center items-center">
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>
<div className="text-2xl">Something went wrong!</div>
</CardTitle>
<CardDescription
suppressHydrationWarning={true}
className="flex flex-col gap-4"
>
<div className="text-lg">{error?.message}</div>
<Button
className="w-full"
onClick={
// Attempt to recover by trying to re-render the segment
() => reset?.()
}
>
Try again
</Button>
</CardDescription>
</CardHeader>
</Card>
</div>
);
}
@@ -17,7 +17,7 @@ export default async function Page({
<CardHeader className="relative"> <CardHeader className="relative">
<CardTitle>Run</CardTitle> <CardTitle>Run</CardTitle>
<div className="absolute right-6 top-6"> <div className="absolute right-6 top-6">
<RouteRefresher interval={5000} /> <RouteRefresher interval={5000} autoRefresh={false} />
</div> </div>
</CardHeader> </CardHeader>
@@ -0,0 +1,5 @@
"use client";
import ErrorPage from "@/components/docs/ErrorPage";
export default ErrorPage;
@@ -1,13 +1,13 @@
import { CreateShareButton } from "@/components/CreateShareButton";
import { MachinesWSMain } from "@/components/MachinesWS"; import { MachinesWSMain } from "@/components/MachinesWS";
import { VersionDetails } from "@/components/VersionDetails"; import { VersionDetails } from "@/components/VersionDetails";
import { import {
CopyWorkflowVersion, CopyWorkflowVersion,
CreateDeploymentButton, CreateDeploymentButton,
CreateShareButton, MachineSelect,
MachineSelect, RunWorkflowButton,
RunWorkflowButton, VersionSelect,
VersionSelect, ViewWorkflowDetailsButton,
ViewWorkflowDetailsButton,
} from "@/components/VersionSelect"; } from "@/components/VersionSelect";
import { import {
Card, Card,
@@ -1,6 +1,3 @@
import Error from "@/app/(app)/workflows/[workflow_id]/@runs/error";
import { ErrorBoundary } from "@/components/ErrorBoundary";
export default async function Layout({ export default async function Layout({
children, children,
deployment, deployment,
@@ -13,15 +10,13 @@ export default async function Layout({
workflow: React.ReactNode; workflow: React.ReactNode;
}) { }) {
return ( return (
<ErrorBoundary fallback={<Error />}> <div className="mt-4 w-full grid grid-rows-[1fr,1fr] lg:grid-cols-[minmax(auto,500px),1fr] gap-4 max-h-[calc(100dvh-100px)]">
<div className="mt-4 w-full grid grid-rows-[1fr,1fr] lg:grid-cols-[minmax(auto,500px),1fr] gap-4 max-h-[calc(100dvh-100px)]"> <div className="w-full flex gap-4 flex-col min-w-0">
<div className="w-full flex gap-4 flex-col min-w-0"> {workflow}
{workflow} {deployment}
{deployment}
</div>
{runs}
{children}
</div> </div>
</ErrorBoundary> {runs}
{children}
</div>
); );
} }
+10 -33
View File
@@ -1,16 +1,17 @@
import { setInitialUserData } from "../../../lib/setInitialUserData"; import { setInitialUserData } from "../../../lib/setInitialUserData";
import { getAllUserWorkflow } from "../../../server/getAllUserWorkflow";
import { WorkflowList } from "@/components/WorkflowList"; import { WorkflowList } from "@/components/WorkflowList";
import { db } from "@/db/db"; import { db } from "@/db/db";
import { usersTable, workflowTable, workflowVersionTable } from "@/db/schema"; import { usersTable } from "@/db/schema";
import { auth } from "@clerk/nextjs"; import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm"; import { eq } from "drizzle-orm";
export default function Home() { export default function Home() {
return <WorkflowServer />; return <WorkflowServer />;
} }
async function WorkflowServer() { async function WorkflowServer() {
const { userId, orgId } = await auth(); const { userId } = await auth();
if (!userId) { if (!userId) {
return <div>No auth</div>; return <div>No auth</div>;
@@ -24,35 +25,11 @@ async function WorkflowServer() {
await setInitialUserData(userId); await setInitialUserData(userId);
} }
const workflow = await db.query.workflowTable.findMany({ const workflow = await getAllUserWorkflow();
// extras: {
// count: sql<number>`(select count(*) from ${workflowVersionTable})`.as(
// "count",
// ),
// },
with: {
versions: {
limit: 1,
orderBy: desc(workflowVersionTable.version),
},
},
orderBy: desc(workflowTable.updated_at),
where:
orgId != undefined
? eq(workflowTable.org_id, orgId)
: and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)),
});
return ( if (!workflow) {
<WorkflowList return <div>No workflow found</div>;
data={workflow.map((x) => { }
return {
id: x.id, return <WorkflowList data={workflow} />;
email: x.name,
amount: x.versions[0]?.version ?? 0,
date: x.updated_at,
};
})}
/>
);
} }
+71 -55
View File
@@ -4,75 +4,91 @@ import { LoadingIcon } from "@/components/LoadingIcon";
import { callServerPromise } from "@/components/callServerPromise"; import { callServerPromise } from "@/components/callServerPromise";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useAuth, useClerk } from "@clerk/nextjs";
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
export function ButtonAction({ export function ButtonAction({
action, action,
children, children,
...rest routerAction = "back",
...rest
}: { }: {
action: () => Promise<any>; action: () => Promise<any>;
children: React.ReactNode; routerAction?: "refresh" | "back";
children: React.ReactNode;
}) { }) {
const [pending, setPending] = useState(false); const [pending, setPending] = useState(false);
const router = useRouter(); const router = useRouter();
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (pending) return; if (pending) return;
setPending(true); setPending(true);
await callServerPromise(action()); await callServerPromise(action());
setPending(false); setPending(false);
router.refresh(); if (routerAction === "back") {
}} router.back();
{...rest} router.refresh();
> }
{children} {pending && <LoadingIcon />} else if (routerAction === "refresh") router.refresh();
</button> }}
); {...rest}
>
{children} {pending && <LoadingIcon />}
</button>
);
} }
export function ButtonActionMenu(props: { export function ButtonActionMenu(props: {
title?: string; title?: string;
actions: { actions: {
title: string; title: string;
action: () => Promise<any>; action: () => Promise<any>;
}[]; }[];
}) { }) {
const [isLoading, setIsLoading] = useState(false); const user = useAuth();
const [isLoading, setIsLoading] = useState(false);
const clerk = useClerk();
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button className="gap-2" variant="outline" disabled={isLoading}> <Button className="gap-2" variant="outline" disabled={isLoading}>
{props.title} {props.title}
{isLoading ? <LoadingIcon /> : <MoreVertical size={14} />} {isLoading ? <LoadingIcon /> : <MoreVertical size={14} />}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-56"> <DropdownMenuContent className="w-56">
{props.actions.map((action) => ( {props.actions.map((action) => (
<DropdownMenuItem <DropdownMenuItem
key={action.title} key={action.title}
onClick={async () => { onClick={async () => {
setIsLoading(true); if (!user.isSignedIn) {
await callServerPromise(action.action()); clerk.openSignIn({
setIsLoading(false); redirectUrl: window.location.href,
}} });
> return;
{action.title} }
</DropdownMenuItem>
))} setIsLoading(true);
</DropdownMenuContent> await callServerPromise(action.action());
</DropdownMenu> setIsLoading(false);
); }}
>
{action.title}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
} }
+66
View File
@@ -0,0 +1,66 @@
"use client";
import { LoadingIcon } from "@/components/LoadingIcon";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine";
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
import { Share } from "lucide-react";
import { parseAsInteger, useQueryState } from "next-usequerystate";
import { useState } from "react";
import { useSelectedMachine } from "./VersionSelect";
import { callServerPromise } from "./callServerPromise";
export function CreateShareButton({
workflow,
machines,
}: {
workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
machines: Awaited<ReturnType<typeof getMachines>>;
}) {
const [version] = useQueryState("version", {
defaultValue: workflow?.versions[0].version ?? 1,
...parseAsInteger,
});
const [machine] = useSelectedMachine(machines);
const [isLoading, setIsLoading] = useState(false);
const workflow_version_id = workflow?.versions.find(
(x) => x.version === version,
)?.id;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-2" disabled={isLoading} variant="outline">
Share {isLoading ? <LoadingIcon /> : <Share size={14} />}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem
onClick={async () => {
if (!workflow_version_id) return;
setIsLoading(true);
await callServerPromise(
createDeployments(
workflow.id,
workflow_version_id,
machine,
"public-share",
),
);
setIsLoading(false);
}}
>
Public
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
+157 -189
View File
@@ -1,24 +1,18 @@
import { ButtonAction } from "@/components/ButtonActionLoader";
import { CodeBlock } from "@/components/CodeBlock"; import { CodeBlock } from "@/components/CodeBlock";
import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { TableCell, TableRow } from "@/components/ui/table"; import { TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow"; import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
import { getRelativeTime } from "@/lib/getRelativeTime";
import { removePublicShareDeployment } from "@/server/curdDeploments";
import type { findAllDeployments } from "@/server/findAllRuns"; import type { findAllDeployments } from "@/server/findAllRuns";
import { ExternalLink } from "lucide-react"; import { DeploymentRow, SharePageDeploymentRow } from "./DeploymentRow";
import { headers } from "next/headers";
import Link from "next/link";
const curlTemplate = ` const curlTemplate = `
curl --request POST \ curl --request POST \
@@ -61,199 +55,173 @@ const output = fetch("<URL>?run_id=" + run_id, {
}).then(response => response.json()) }).then(response => response.json())
`; `;
const clientTemplate = ` const jsClientSetupTemplate = `
const client = new ComfyDeployClient({ const client = new ComfyDeployClient({
apiBase: "<URLONLY>", apiBase: "<URLONLY>",
apiToken: process.env.COMFY_DEPLOY_API_KEY!, apiToken: process.env.COMFY_DEPLOY_API_KEY!,
}); });
`;
export async function generateTextures(scrImageId: string) { const jsClientSetupTemplateHostedVersion = `
const result = await client.run("<ID>", { const client = new ComfyDeployClient({
input_image: "", apiToken: process.env.COMFY_DEPLOY_API_KEY!,
}); });
if (!result || !result.run_id) return { error: "run id not found" }; `;
return { id: result.run_id };
} const jsClientCreateRunTemplate = `
const { run_id } = await client.run("<ID>", {
inputs: {}
});
`;
const jsClientCreateRunNoInputsTemplate = `
const { run_id } = await client.run("<ID>");
`; `;
const clientTemplate_checkStatus = ` const clientTemplate_checkStatus = `
const run_id = "<RUN_ID>"; const run = await client.getRun(run_id);
while (true) {
const run = await client.getRun(run_id);
await new Promise((resolve) => setTimeout(resolve, 3000));
if (!run) continue;
run.outputs.map((val) => {
if (!val.data.image) return;
});
}
`; `;
export function DeploymentDisplay({ export function DeploymentDisplay({
deployment, deployment,
domain,
}: { }: {
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0]; deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
domain: string;
}) { }) {
const headersList = headers(); const workflowInput = getInputsFromWorkflow(deployment.version);
const host = headersList.get("host") || "";
const protocol = headersList.get("x-forwarded-proto") || "";
const domain = `${protocol}://${host}`;
const workflowInput = getInputsFromWorkflow(deployment.version); if (deployment.environment === "public-share") {
return <SharePageDeploymentRow deployment={deployment} />;
}
return ( return (
<Dialog> <Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer"> <DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow> <TableRow>
<TableCell className="capitalize truncate"> <DeploymentRow deployment={deployment} />
{deployment.environment} </TableRow>
</TableCell> </DialogTrigger>
<TableCell className="font-medium truncate"> <DialogContent className="max-w-3xl">
{deployment.version?.version} <DialogHeader>
</TableCell> <DialogTitle className="capitalize">
<TableCell className="font-medium truncate"> {deployment.environment} Deployment
{deployment.machine?.name} </DialogTitle>
</TableCell> <DialogDescription>Code for your deployment client</DialogDescription>
<TableCell className="text-right truncate"> </DialogHeader>
{getRelativeTime(deployment.updated_at)} <ScrollArea className="max-h-[600px] pr-4">
</TableCell> <Tabs defaultValue="client" className="w-full gap-2 text-sm">
</TableRow> <TabsList className="grid w-fit grid-cols-3 mb-2">
</DialogTrigger> <TabsTrigger value="client">Server Client</TabsTrigger>
<DialogContent className="max-w-3xl"> <TabsTrigger value="js">NodeJS Fetch</TabsTrigger>
<DialogHeader> <TabsTrigger value="curl">CURL</TabsTrigger>
<DialogTitle className="capitalize"> </TabsList>
{deployment.environment} Deployment <TabsContent className="flex flex-col gap-2 !mt-0" value="client">
</DialogTitle> <div>
<DialogDescription>Code for your deployment client</DialogDescription> Copy and paste the ComfyDeployClient form&nbsp;
</DialogHeader> <a
<ScrollArea className="max-h-[600px] pr-4"> href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts"
{deployment.environment !== "public-share" ? ( className="text-blue-500 hover:underline"
<Tabs defaultValue="client" className="w-full gap-2"> target="_blank"
<TabsList className="grid w-fit grid-cols-3"> rel="noreferrer"
<TabsTrigger value="client">client</TabsTrigger> >
<TabsTrigger value="js">js</TabsTrigger> here
<TabsTrigger value="curl">curl</TabsTrigger> </a>
</TabsList> </div>
<TabsContent className="flex flex-col gap-2 !mt-0" value="client"> <CodeBlock
<div> lang="js"
Trigger the workflow with&nbsp; code={formatCode(
<a domain == "https://www.comfydeploy.com"
href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts" ? jsClientSetupTemplateHostedVersion
className="text-blue-500 hover:underline" : jsClientSetupTemplate,
target="_blank" deployment,
> domain,
comfy deploy wrapper workflowInput,
</a> )}
</div> />
<div> Create a run via deployment id
Copy the wrapper to your project, and import the function <CodeBlock
</div> lang="js"
<CodeBlock code={formatCode(
lang="js" workflowInput && workflowInput.length > 0
code={formatCode( ? jsClientCreateRunTemplate
clientTemplate, : jsClientCreateRunNoInputsTemplate,
deployment, deployment,
domain, domain,
workflowInput workflowInput,
)} )}
/> />
Check the status of the run, and retrieve the outputs Check the status of the run, and retrieve the outputs
<CodeBlock <CodeBlock
lang="js" lang="js"
code={formatCode( code={formatCode(
clientTemplate_checkStatus, clientTemplate_checkStatus,
deployment, deployment,
domain domain,
)} )}
/> />
</TabsContent> </TabsContent>
<TabsContent className="flex flex-col gap-2 !mt-0" value="js"> <TabsContent className="flex flex-col gap-2 !mt-0" value="js">
Trigger the workflow Trigger the workflow
<CodeBlock <CodeBlock
lang="js" lang="js"
code={formatCode( code={formatCode(jsTemplate, deployment, domain, workflowInput)}
jsTemplate, />
deployment, Check the status of the run, and retrieve the outputs
domain, <CodeBlock
workflowInput lang="js"
)} code={formatCode(jsTemplate_checkStatus, deployment, domain)}
/> />
Check the status of the run, and retrieve the outputs </TabsContent>
<CodeBlock <TabsContent className="flex flex-col gap-2 !mt-2" value="curl">
lang="js" <CodeBlock
code={formatCode(jsTemplate_checkStatus, deployment, domain)} lang="bash"
/> code={formatCode(curlTemplate, deployment, domain)}
</TabsContent> />
<TabsContent className="flex flex-col gap-2 !mt-2" value="curl"> <CodeBlock
<CodeBlock lang="bash"
lang="bash" code={formatCode(curlTemplate_checkStatus, deployment, domain)}
code={formatCode(curlTemplate, deployment, domain)} />
/> </TabsContent>
<CodeBlock </Tabs>
lang="bash" </ScrollArea>
code={formatCode( </DialogContent>
curlTemplate_checkStatus, </Dialog>
deployment, );
domain
)}
/>
</TabsContent>
</Tabs>
) : (
<div className="w-full justify-end flex gap-2 py-1">
<Button asChild className="gap-2" variant="outline" type="submit">
<ButtonAction
action={removePublicShareDeployment.bind(null, deployment.id)}
>
Remove
</ButtonAction>
</Button>
<Button asChild className="gap-2">
<Link href={`/share/${deployment.id}`} target="_blank">
View Share Page <ExternalLink size={14} />
</Link>
</Button>
</div>
)}
</ScrollArea>
</DialogContent>
</Dialog>
);
} }
function formatCode( function formatCode(
codeTemplate: string, codeTemplate: string,
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0], deployment: Awaited<ReturnType<typeof findAllDeployments>>[0],
domain: string, domain: string,
inputs?: ReturnType<typeof getInputsFromWorkflow> inputs?: ReturnType<typeof getInputsFromWorkflow>,
inputsTabs?: number,
) { ) {
if (inputs && inputs.length > 0) { if (inputs && inputs.length > 0) {
codeTemplate = codeTemplate.replace( codeTemplate = codeTemplate.replace(
"inputs: {}", "inputs: {}",
`inputs: ${JSON.stringify( `inputs: ${JSON.stringify(
Object.fromEntries( Object.fromEntries(
inputs.map((x) => { inputs.map((x) => {
return [x?.input_id, ""]; return [x?.input_id, ""];
}) }),
), ),
null, null,
2 2,
) )
.split("\n") .split("\n")
.map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line .map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line
.join("\n")}` .join("\n")}`,
); );
} else { } else {
codeTemplate = codeTemplate.replace( codeTemplate = codeTemplate.replace(
` `
inputs: {}`, inputs: {}`,
"" "",
); );
} }
return codeTemplate return codeTemplate
.replace("<URL>", `${domain ?? "http://localhost:3000"}/api/run`) .replace("<URL>", `${domain ?? "http://localhost:3000"}/api/run`)
.replace("<ID>", deployment.id) .replace("<ID>", deployment.id)
.replace("<URLONLY>", domain ?? "http://localhost:3000"); .replace("<URLONLY>", domain ?? "http://localhost:3000");
} }
+62
View File
@@ -0,0 +1,62 @@
"use client";
import { TableCell, TableRow } from "@/components/ui/table";
import { getRelativeTime } from "@/lib/getRelativeTime";
import type { findAllDeployments } from "@/server/findAllRuns";
import { useRouter } from "next/navigation";
export function SharePageDeploymentRow({
deployment,
}: {
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
}) {
const router = useRouter();
return (
<TableRow
className="appearance-none hover:cursor-pointer"
onClick={() => {
if (deployment.environment === "public-share") {
router.push(
`/share/${deployment.share_slug ?? deployment.id}/settings`,
);
}
}}
>
<TableCell className="capitalize truncate">
{deployment.environment}
</TableCell>
<TableCell className="font-medium truncate">
{deployment.version?.version}
</TableCell>
<TableCell className="font-medium truncate">
{deployment.machine?.name}
</TableCell>
<TableCell className="text-right truncate">
{getRelativeTime(deployment.updated_at)}
</TableCell>
</TableRow>
);
}
export function DeploymentRow({
deployment,
}: {
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
}) {
return (
<>
<TableCell className="capitalize truncate">
{deployment.environment}
</TableCell>
<TableCell className="font-medium truncate">
{deployment.version?.version}
</TableCell>
<TableCell className="font-medium truncate">
{deployment.machine?.name}
</TableCell>
<TableCell className="text-right truncate">
{getRelativeTime(deployment.updated_at)}
</TableCell>
</>
);
}
+29 -12
View File
@@ -11,6 +11,7 @@ import {
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { import {
Tooltip, Tooltip,
@@ -91,7 +92,7 @@ export function InsertModal<
<div className="flex justify-end"> <div className="flex justify-end">
<AutoFormSubmit> <AutoFormSubmit>
Save Changes Save Changes
<span className="ml-2">{isLoading && <LoadingIcon />}</span> {isLoading && <LoadingIcon />}
</AutoFormSubmit> </AutoFormSubmit>
</div> </div>
</AutoForm> </AutoForm>
@@ -106,12 +107,14 @@ export function UpdateModal<
Y extends UnknownKeysParam, Y extends UnknownKeysParam,
Z extends ZodObject<K, Y> Z extends ZodObject<K, Y>
>(props: { >(props: {
open: boolean; open?: boolean;
setOpen: (open: boolean) => void; setOpen?: (open: boolean) => void;
title: string; title: string;
description: string; description: string;
dialogClassName?: string; dialogClassName?: string;
data: z.infer<Z>; data: z.infer<Z> & {
id: string;
};
serverAction: ( serverAction: (
data: z.infer<Z> & { data: z.infer<Z> & {
id: string; id: string;
@@ -119,8 +122,13 @@ export function UpdateModal<
) => Promise<unknown>; ) => Promise<unknown>;
formSchema: Z; formSchema: Z;
fieldConfig?: FieldConfig<z.infer<Z>>; fieldConfig?: FieldConfig<z.infer<Z>>;
trigger?: React.ReactNode;
extraButtons?: React.ReactNode;
}) { }) {
// const [open, setOpen] = React.useState(false); const [_open, _setOpen] = React.useState(false);
const open = props.open ?? _open;
const setOpen = props.setOpen ?? _setOpen;
const [values, setValues] = useState<Partial<z.infer<Z>>>({}); const [values, setValues] = useState<Partial<z.infer<Z>>>({});
const [isLoading, setIsLoading] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false);
@@ -129,10 +137,18 @@ export function UpdateModal<
}, [props.data]); }, [props.data]);
return ( return (
<Dialog open={props.open} onOpenChange={props.setOpen}> <Dialog open={open} onOpenChange={setOpen}>
{/* <DialogTrigger asChild> {props.trigger ?? (
<DropdownMenuItem>{props.title}</DropdownMenuItem> <DialogTrigger
</DialogTrigger> */} className="appearance-none hover:cursor-pointer"
asChild
onClick={() => {
setOpen(true);
}}
>
{props.trigger}
</DialogTrigger>
)}
<DialogContent className={cn("sm:max-w-[425px]", props.dialogClassName)}> <DialogContent className={cn("sm:max-w-[425px]", props.dialogClassName)}>
<DialogHeader> <DialogHeader>
<DialogTitle>{props.title}</DialogTitle> <DialogTitle>{props.title}</DialogTitle>
@@ -152,13 +168,14 @@ export function UpdateModal<
}) })
); );
setIsLoading(false); setIsLoading(false);
props.setOpen(false); setOpen(false);
}} }}
> >
<div className="flex justify-end"> <div className="flex justify-end flex-wrap gap-2">
{props.extraButtons}
<AutoFormSubmit> <AutoFormSubmit>
Save Changes Save Changes
<span className="ml-2">{isLoading && <LoadingIcon />}</span> {isLoading && <LoadingIcon />}
</AutoFormSubmit> </AutoFormSubmit>
</div> </div>
</AutoForm> </AutoForm>
+15 -2
View File
@@ -3,6 +3,7 @@
import { getRelativeTime } from "../lib/getRelativeTime"; import { getRelativeTime } from "../lib/getRelativeTime";
import { InsertModal, UpdateModal } from "./InsertModal"; import { InsertModal, UpdateModal } from "./InsertModal";
import { callServerPromise } from "./callServerPromise"; import { callServerPromise } from "./callServerPromise";
import { LoadingIcon } from "@/components/LoadingIcon";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
@@ -107,8 +108,20 @@ export const columns: ColumnDef<Machine>[] = [
{row.original.disabled && ( {row.original.disabled && (
<Badge variant="destructive">Disabled</Badge> <Badge variant="destructive">Disabled</Badge>
)} )}
{row.original.status == "building" && (
<Badge variant="amber" className="capitalize">
{row.original.status} <LoadingIcon />
</Badge>
)}
{!row.original.disabled && row.original.status && ( {!row.original.disabled && row.original.status && (
<Badge variant="outline">{row.original.status}</Badge> <Badge
variant={
row.original.status == "ready" ? "success" : "destructive"
}
className="capitalize"
>
{row.original.status}
</Badge>
)} )}
</div> </div>
// </a> // </a>
@@ -210,7 +223,7 @@ export const columns: ColumnDef<Machine>[] = [
href={machine.endpoint.replace( href={machine.endpoint.replace(
"comfyui-api", "comfyui-api",
"comfyui-app" "comfyui-app"
)} )} rel="noreferrer"
> >
Open ComfyUI Open ComfyUI
</a> </a>
+1 -1
View File
@@ -69,7 +69,7 @@ export default async function Main() {
</Section.Announcement> </Section.Announcement>
<Section.Title className="text-left"> <Section.Title className="text-left">
<span className="text-6xl md:text-7xl pb-2 inline-flex animate-background-shine bg-[linear-gradient(110deg,#1e293b,45%,#939393,55%,#1e293b)] bg-[length:250%_100%] bg-clip-text text-transparent"> <span className="text-5xl sm:text-6xl md:text-7xl pb-2 inline-flex animate-background-shine bg-[linear-gradient(110deg,#1e293b,45%,#939393,55%,#1e293b)] bg-[length:250%_100%] bg-clip-text text-transparent">
{meta.tagline} {meta.tagline}
</span> </span>
</Section.Title> </Section.Title>
+21 -3
View File
@@ -22,6 +22,7 @@ import {
} from "@clerk/nextjs"; } from "@clerk/nextjs";
import { Github, Menu } from "lucide-react"; import { Github, Menu } from "lucide-react";
import meta from "next-gen/config"; import meta from "next-gen/config";
import { useFeatureFlagEnabled } from "posthog-js/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useMediaQuery } from "usehooks-ts"; import { useMediaQuery } from "usehooks-ts";
@@ -29,18 +30,22 @@ export function Navbar() {
const { organization } = useOrganization(); const { organization } = useOrganization();
const _isDesktop = useMediaQuery("(min-width: 1024px)"); const _isDesktop = useMediaQuery("(min-width: 1024px)");
const [isDesktop, setIsDesktop] = useState(true); const [isDesktop, setIsDesktop] = useState(true);
const pricingPlanFlagEnable = useFeatureFlagEnabled("pricing-plan");
useEffect(() => { useEffect(() => {
setIsDesktop(_isDesktop); setIsDesktop(_isDesktop);
}, [_isDesktop]); }, [_isDesktop]);
return ( return (
<> <>
<div className="flex flex-row items-center gap-4"> <div className="flex flex-row items-center gap-4">
{!isDesktop && ( {!isDesktop && (
<Sheet> <Sheet>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant="ghost" className="w-8 h-8 p-2"> <button className="flex items-center justify-center w-8 h-8 p-2">
<Menu /> <Menu />
</Button> </button>
</SheetTrigger> </SheetTrigger>
<SheetContent side="left" className="flex flex-col gap-4"> <SheetContent side="left" className="flex flex-col gap-4">
<SheetHeader> <SheetHeader>
@@ -85,6 +90,15 @@ export function Navbar() {
</div> </div>
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
{isDesktop && <NavbarMenu />} {isDesktop && <NavbarMenu />}
{pricingPlanFlagEnable && (
<Button
asChild
variant="link"
className="rounded-full aspect-square p-2 mr-4"
>
<a href="/pricing">Pricing</a>
</Button>
)}
<Button <Button
asChild asChild
variant="link" variant="link"
@@ -98,7 +112,11 @@ export function Navbar() {
variant="outline" variant="outline"
className="rounded-full aspect-square p-2" className="rounded-full aspect-square p-2"
> >
<a target="_blank" href="https://github.com/BennyKok/comfyui-deploy"> <a
target="_blank"
href="https://github.com/BennyKok/comfyui-deploy"
rel="noreferrer"
>
<Github /> <Github />
</a> </a>
</Button> </Button>
+52
View File
@@ -0,0 +1,52 @@
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
type CarouselApi,
} from "@/components/ui/carousel";
import * as React from "react";
export function OutputPreview() {
const [api, setApi] = React.useState<CarouselApi>();
const [current, setCurrent] = React.useState(0);
const [count, setCount] = React.useState(0);
React.useEffect(() => {
if (!api) {
return;
}
setCount(api.scrollSnapList().length);
setCurrent(api.selectedScrollSnap() + 1);
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1);
});
}, [api]);
return (
<div>
<Carousel setApi={setApi} className="w-full max-w-xs">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div className="py-2 text-center text-sm text-muted-foreground">
Slide {current} of {count}
</div>
</div>
);
}
+27 -3
View File
@@ -1,14 +1,21 @@
"use client"; "use client";
import { LoadingIcon } from "@/components/LoadingIcon"; import { LoadingIcon } from "@/components/LoadingIcon";
import { Button } from "@/components/ui/button";
import { RefreshCcw } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect, useTransition } from "react"; import { useEffect, useTransition } from "react";
export function RouteRefresher(props: { interval: number }) { export function RouteRefresher(props: {
interval: number;
autoRefresh: boolean;
}) {
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
if (!props.autoRefresh) return;
let timeout: NodeJS.Timeout; let timeout: NodeJS.Timeout;
const refresh = () => { const refresh = () => {
@@ -35,7 +42,24 @@ export function RouteRefresher(props: { interval: number }) {
clearTimeout(timeout); clearTimeout(timeout);
window.removeEventListener("visibilitychange", handleVisibilityChange); window.removeEventListener("visibilitychange", handleVisibilityChange);
}; };
}, [props.interval, router]); }, [props.interval, router, props.autoRefresh]);
return <div>{isPending && <LoadingIcon />}</div>; return (
<div>
{isPending && <LoadingIcon />}
{!isPending && !props.autoRefresh && (
<Button
className="p-0 h-min"
variant="ghost"
onClick={() => {
startTransition(() => {
router.refresh();
});
}}
>
<RefreshCcw size={14} />
</Button>
)}
</div>
);
} }
+62 -42
View File
@@ -1,54 +1,74 @@
import { LiveStatus } from "./LiveStatus";
import { RunInputs } from "@/components/RunInputs"; import { RunInputs } from "@/components/RunInputs";
import { RunOutputs } from "@/components/RunOutputs"; import { RunOutputs } from "@/components/RunOutputs";
import { Badge } from "@/components/ui/badge";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { TableCell, TableRow } from "@/components/ui/table"; import { TableCell, TableRow } from "@/components/ui/table";
import { getRelativeTime } from "@/lib/getRelativeTime"; import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
import { type findAllRuns } from "@/server/findAllRuns"; import { type findAllRuns } from "@/server/findAllRuns";
import { Suspense } from "react"; import { Suspense } from "react";
import { LiveStatus } from "./LiveStatus";
export async function RunDisplay({ export async function RunDisplay({
run, run,
}: { }: {
run: Awaited<ReturnType<typeof findAllRuns>>[0]; run: Awaited<ReturnType<typeof findAllRuns>>[0];
}) { }) {
return ( return (
<Dialog> <Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer"> <DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow> <TableRow>
<TableCell>{run.number}</TableCell> <TableCell>{run.number}</TableCell>
<TableCell className="font-medium truncate"> <TableCell className="font-medium truncate">
{run.machine?.name} {run.machine?.name}
</TableCell> </TableCell>
<TableCell className="truncate"> <TableCell className="truncate">
{getRelativeTime(run.created_at)} {getRelativeTime(run.created_at)}
</TableCell> </TableCell>
<TableCell>{run.version?.version}</TableCell> <TableCell>{run.version?.version}</TableCell>
<LiveStatus run={run} /> <TableCell>
</TableRow> <Badge variant="outline" className="truncate">
</DialogTrigger> {run.origin}
<DialogContent className="max-w-3xl"> </Badge>
<DialogHeader> </TableCell>
<DialogTitle>Run outputs</DialogTitle> <TableCell className="truncate">
<DialogDescription> <Tooltip>
You can view your run&apos;s outputs here <TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
</DialogDescription> <TooltipContent>
</DialogHeader> <div>Cold start: {getDuration(run.cold_start_duration)}</div>
<div className="max-h-96 overflow-y-scroll"> <div>Run duration: {getDuration(run.run_duration)}</div>
<RunInputs run={run} /> </TooltipContent>
<Suspense> </Tooltip>
<RunOutputs run_id={run.id} /> </TableCell>
</Suspense> <LiveStatus run={run} />
</div> </TableRow>
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */} </DialogTrigger>
</DialogContent> <DialogContent className="max-w-3xl">
</Dialog> <DialogHeader>
); <DialogTitle>Run outputs</DialogTitle>
<DialogDescription>
You can view your run&apos;s outputs here
</DialogDescription>
</DialogHeader>
<div className="max-h-96 overflow-y-scroll">
<RunInputs run={run} />
<Suspense>
<RunOutputs run_id={run.id} />
</Suspense>
</div>
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
</DialogContent>
</Dialog>
);
} }
+108
View File
@@ -0,0 +1,108 @@
"use client";
import { plainInputsToZod } from "../lib/workflowVersionInputsToZod";
import { publicRunStore } from "./VersionSelect";
import { callServerPromise } from "./callServerPromise";
import { LoadingIcon } from "@/components/LoadingIcon";
import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form";
import { Button } from "@/components/ui/button";
import type { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
import { createRun } from "@/server/createRun";
import { useAuth, useClerk } from "@clerk/nextjs";
import { Play } from "lucide-react";
import { useMemo, useState } from "react";
// For share page
export function RunWorkflowInline({
inputs,
workflow_version_id,
machine_id,
}: {
inputs: ReturnType<typeof getInputsFromWorkflow>;
workflow_version_id: string;
machine_id: string;
}) {
const [values, setValues] = useState<Record<string, string>>({});
const [isLoading, setIsLoading] = useState(false);
const user = useAuth();
const clerk = useClerk();
const schema = useMemo(() => {
return plainInputsToZod(inputs);
}, [inputs]);
const {
setRunId,
loading,
setLoading: setLoading2,
setStatus,
} = publicRunStore();
const runWorkflow = async () => {
if (!user.isSignedIn) {
clerk.openSignIn({
redirectUrl: window.location.href,
});
return;
}
console.log(values);
const val = Object.keys(values).length > 0 ? values : undefined;
setLoading2(true);
setIsLoading(true);
setStatus("preparing");
try {
const origin = window.location.origin;
const a = await callServerPromise(
createRun({
origin,
workflow_version_id: workflow_version_id,
machine_id: machine_id,
inputs: val,
runOrigin: "public-share",
})
);
if (a && !("error" in a)) {
setRunId(a.workflow_run_id);
} else {
setLoading2(false);
}
console.log(a);
setIsLoading(false);
} catch (error) {
setIsLoading(false);
setLoading2(false);
}
};
return (
<>
{schema && (
<AutoForm
formSchema={schema}
values={values}
onValuesChange={setValues}
onSubmit={runWorkflow}
className="px-1"
>
<div className="flex justify-end">
<AutoFormSubmit disabled={isLoading || loading}>
Run
{isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
</AutoFormSubmit>
</div>
</AutoForm>
)}
{!schema && (
<Button
className="gap-2"
disabled={isLoading || loading}
onClick={runWorkflow}
>
Confirm {isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
</Button>
)}
</>
);
}
+49 -40
View File
@@ -1,10 +1,3 @@
import {
findAllDeployments,
findAllRunsWithCounts,
} from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
import { PaginationControl } from "./PaginationControl";
import { RunDisplay } from "./RunDisplay";
import { import {
Table, Table,
TableBody, TableBody,
@@ -14,6 +7,14 @@ import {
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { parseAsInteger } from "next-usequerystate"; import { parseAsInteger } from "next-usequerystate";
import { headers } from "next/headers";
import {
findAllDeployments,
findAllRunsWithCounts,
} from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
import { PaginationControl } from "./PaginationControl";
import { RunDisplay } from "./RunDisplay";
const itemPerPage = 6; const itemPerPage = 6;
const pageParser = parseAsInteger.withDefault(1); const pageParser = parseAsInteger.withDefault(1);
@@ -32,44 +33,52 @@ export async function RunsTable(props: {
offset: (page - 1) * itemPerPage, offset: (page - 1) * itemPerPage,
}); });
return ( return (
<div> <div>
<div className="overflow-auto h-fit w-full"> <div className="overflow-auto h-fit w-full">
<Table className=""> <Table className="">
{allRuns.length == 0 && ( {allRuns.length === 0 && (
<TableCaption>A list of your recent runs.</TableCaption> <TableCaption>A list of your recent runs.</TableCaption>
)} )}
<TableHeader className="bg-background top-0 sticky"> <TableHeader className="bg-background top-0 sticky">
<TableRow> <TableRow>
<TableHead className="w-[100px]">Number</TableHead> <TableHead className="truncate">Number</TableHead>
<TableHead className="">Machine</TableHead> <TableHead className="truncate">Machine</TableHead>
<TableHead className="">Time</TableHead> <TableHead className="truncate">Time</TableHead>
<TableHead className="w-[100px]">Version</TableHead> <TableHead className="truncate">Version</TableHead>
<TableHead className="truncate">Live Status</TableHead> <TableHead className="truncate">Origin</TableHead>
<TableHead className=" text-right">Status</TableHead> <TableHead className="truncate">Duration</TableHead>
</TableRow> <TableHead className="truncate">Live Status</TableHead>
</TableHeader> <TableHead className="text-right">Status</TableHead>
<TableBody> </TableRow>
{allRuns.map((run) => ( </TableHeader>
<RunDisplay run={run} key={run.id} /> <TableBody>
))} {allRuns.map((run) => (
</TableBody> <RunDisplay run={run} key={run.id} />
</Table> ))}
</div> </TableBody>
</Table>
</div>
{Math.ceil(total / itemPerPage) > 0 && ( {Math.ceil(total / itemPerPage) > 0 && (
<PaginationControl <PaginationControl
totalPage={Math.ceil(total / itemPerPage)} totalPage={Math.ceil(total / itemPerPage)}
currentPage={page} currentPage={page}
/> />
)} )}
</div> </div>
); );
} }
export async function DeploymentsTable(props: { workflow_id: string }) { export async function DeploymentsTable(props: { workflow_id: string }) {
const allRuns = await findAllDeployments(props.workflow_id); const allRuns = await findAllDeployments(props.workflow_id);
const headersList = headers();
const host = headersList.get("host") || "";
const protocol = headersList.get("x-forwarded-proto") || "";
const domain = `${protocol}://${host}`;
return ( return (
<div className="overflow-auto h-fit w-full"> <div className="overflow-auto h-fit w-full">
<Table className=""> <Table className="">
<TableCaption>A list of your deployments</TableCaption> <TableCaption>A list of your deployments</TableCaption>
<TableHeader className="bg-background top-0 sticky"> <TableHeader className="bg-background top-0 sticky">
@@ -82,7 +91,7 @@ export async function DeploymentsTable(props: { workflow_id: string }) {
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{allRuns.map((run) => ( {allRuns.map((run) => (
<DeploymentDisplay deployment={run} key={run.id} /> <DeploymentDisplay deployment={run} key={run.id} domain={domain} />
))} ))}
</TableBody> </TableBody>
</Table> </Table>
+79 -75
View File
@@ -1,33 +1,37 @@
import { Button, buttonVariants } from '@/components/ui/button';
type ButtonProps = React.ComponentProps<typeof Button>;
type LinkProps = React.ComponentProps<typeof Link>;
import { Card as BaseCard } from '@/components/ui/card';
type CardProps = React.ComponentProps<typeof BaseCard>;
import { Tabs, TabsTrigger as Tab, TabsList } from '@/components/ui/tabs';
type TabsProps = React.ComponentProps<typeof Tabs>;
import { import {
Accordion, Accordion,
AccordionItem, AccordionItem,
AccordionContent, AccordionContent,
AccordionTrigger, AccordionTrigger,
} from '@/components/ui/accordion'; } from "@/components/ui/accordion";
type AccordionProps = React.ComponentProps<typeof Accordion>; import { Badge as Chip } from "@/components/ui/badge";
import { Badge as Chip } from '@/components/ui/badge'; import { Button, buttonVariants } from "@/components/ui/button";
type ChipProps = React.ComponentProps<typeof Chip>; import { Card as BaseCard } from "@/components/ui/card";
import { Tabs, TabsTrigger as Tab, TabsList } from "@/components/ui/tabs";
import Link from 'next/link'; // import { PiCheckCircleDuotone } from 'react-icons/pi';
import { cn } from "@/lib/utils";
import { ChevronRight as MdChevronRight } from "lucide-react";
import { CheckCircle as PiCheckCircleDuotone } from "lucide-react";
import Link from "next/link";
import type { import type {
HTMLAttributeAnchorTarget, HTMLAttributeAnchorTarget,
HTMLAttributes, HTMLAttributes,
ReactNode, ReactNode,
} from 'react'; } from "react";
import { twMerge } from 'tailwind-merge';
import { ChevronRight as MdChevronRight} from 'lucide-react'
// import { MdChevronRight } from 'react-icons/md'; // import { MdChevronRight } from 'react-icons/md';
import React from 'react'; import React from "react";
import { CheckCircle as PiCheckCircleDuotone } from 'lucide-react' import { twMerge } from "tailwind-merge";
// import { PiCheckCircleDuotone } from 'react-icons/pi';
import { cn } from '@/lib/utils'; type ButtonProps = React.ComponentProps<typeof Button>;
type LinkProps = React.ComponentProps<typeof Link>;
type CardProps = React.ComponentProps<typeof BaseCard>;
type TabsProps = React.ComponentProps<typeof Tabs>;
type AccordionProps = React.ComponentProps<typeof Accordion>;
type ChipProps = React.ComponentProps<typeof Chip>;
function Section({ function Section({
className, className,
@@ -41,8 +45,8 @@ function Section({
return ( return (
<section <section
className={twMerge( className={twMerge(
'flex min-h-[400px] w-full max-w-6xl flex-col justify-center gap-2 rounded-lg px-10 py-10 md:px-20', "flex min-h-[400px] w-full max-w-6xl flex-col justify-center gap-2 rounded-lg px-2 sm:px-10 py-10 md:px-20",
className, className
)} )}
{...props} {...props}
> >
@@ -64,12 +68,12 @@ function Title({
<h1 <h1
{...props} {...props}
className={twMerge( className={twMerge(
'text-center text-4xl font-bold md:text-6xl', "text-center text-4xl font-bold md:text-6xl",
className, className
)} )}
style={{ style={{
// @ts-ignore // @ts-ignore
textWrap: 'balance', textWrap: "balance",
}} }}
> >
{children} {children}
@@ -86,12 +90,12 @@ function Subtitle({
<h2 <h2
{...props} {...props}
className={twMerge( className={twMerge(
'text text-center overflow-hidden text-ellipsis text-xl', "text text-center overflow-hidden text-ellipsis text-xl",
className, className
)} )}
style={{ style={{
// @ts-ignore // @ts-ignore
textWrap: 'balance', textWrap: "balance",
}} }}
> >
{children} {children}
@@ -103,7 +107,7 @@ function Announcement({
className, className,
children, children,
href, href,
target = '_blank', target = "_blank",
...props ...props
}: ChipProps & { }: ChipProps & {
href?: string; //string | UrlObject; href?: string; //string | UrlObject;
@@ -112,8 +116,8 @@ function Announcement({
return ( return (
<Chip <Chip
className={twMerge( className={twMerge(
'w-fit group bg-foreground-50 text-center transition-colors hover:bg-gray-200', "w-fit group bg-foreground-50 text-center transition-colors hover:bg-gray-200",
className, className
)} )}
variant="outline" variant="outline"
// href={href} // href={href}
@@ -127,13 +131,13 @@ function Announcement({
// } // }
style={{ style={{
// @ts-ignore // @ts-ignore
textWrap: 'balance', textWrap: "balance",
}} }}
{...props} {...props}
> >
<a href={href} target={target}> <a href={href} target={target}>
{children} {children}
</a>{' '} </a>{" "}
<MdChevronRight <MdChevronRight
size={20} size={20}
className="pr-1 transition-transform group-hover:translate-x-[2px]" className="pr-1 transition-transform group-hover:translate-x-[2px]"
@@ -143,14 +147,14 @@ function Announcement({
} }
type ActionProps = ButtonProps & { type ActionProps = ButtonProps & {
be: 'button'; be: "button";
hideArrow?: boolean; hideArrow?: boolean;
}; };
type ActionLinkProps = LinkProps & { type ActionLinkProps = LinkProps & {
be?: 'a'; be?: "a";
hideArrow?: boolean; hideArrow?: boolean;
variant?: ButtonProps['variant']; variant?: ButtonProps["variant"];
}; };
function PrimaryAction({ function PrimaryAction({
@@ -160,15 +164,15 @@ function PrimaryAction({
hideArrow, hideArrow,
...props ...props
}: ActionLinkProps | ActionProps) { }: ActionLinkProps | ActionProps) {
if (props.be === 'button') { if (props.be === "button") {
return ( return (
<Button <Button
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: variant, variant: variant,
}), }),
'group', "group",
className, className
)} )}
{...props} {...props}
> >
@@ -186,8 +190,8 @@ function PrimaryAction({
buttonVariants({ buttonVariants({
variant: variant, variant: variant,
}), }),
'group', "group",
className, className
)} )}
{...props} {...props}
> >
@@ -206,17 +210,17 @@ function SecondaryAction({
hideArrow, hideArrow,
...props ...props
}: ActionLinkProps | ActionProps) { }: ActionLinkProps | ActionProps) {
if (props.be === 'button') { if (props.be === "button") {
return ( return (
<Button <Button
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: variant, variant: variant,
}), }),
'group', "group",
className, className
)} )}
variant={'ghost'} variant="ghost"
{...props} {...props}
> >
{children} {children}
@@ -231,10 +235,10 @@ function SecondaryAction({
<Link <Link
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: 'ghost', variant: "ghost",
}), }),
'group', "group",
className, className
)} )}
{...props} {...props}
> >
@@ -249,31 +253,31 @@ function PricingCard({
className, className,
children, children,
...props ...props
}: Omit<CardProps, 'children'> & { }: Omit<CardProps, "children"> & {
children: children:
| ReactNode | ReactNode
| ReactNode[] | ReactNode[]
| ((pricingType: PricingType) => ReactNode | ReactNode[]); | ((pricingType: PricingType) => ReactNode | ReactNode[]);
}) { }) {
// const { pricingType } = usePricingContext(); // const { pricingType } = usePricingContext();
if (typeof children === 'function') if (typeof children === "function")
children = (children('month') as React.ReactElement).props.children as children = (children("month") as React.ReactElement).props.children as
| ReactNode | ReactNode
| ReactNode[]; | ReactNode[];
// extract the title and subtitle from the children // extract the title and subtitle from the children
// const cardTitleStyles = // const cardTitleStyles =
const title = getChildComponent(children, Title, { const title = getChildComponent(children, Title, {
className: 'text-2xl md:text-2xl text-start font-bold', className: "text-2xl md:text-2xl text-start font-bold",
}); });
const subTitle = getChildComponent(children, Subtitle, { const subTitle = getChildComponent(children, Subtitle, {
className: 'text-md text-start text-foreground-500 mt-4', className: "text-md text-start text-foreground-500 mt-4",
}); });
const priceTags = getChildComponents(children, PriceTag, { const priceTags = getChildComponents(children, PriceTag, {
className: 'text-4xl font-bold', className: "text-4xl font-bold",
}); });
const primaryAction = getChildComponent(children, PrimaryAction, { const primaryAction = getChildComponent(children, PrimaryAction, {
className: 'w-full', className: "w-full",
}); });
return ( return (
@@ -281,8 +285,8 @@ function PricingCard({
// shadow="sm" // shadow="sm"
{...props} {...props}
className={twMerge( className={twMerge(
'flex flex-col min-h-[400px] w-full max-w-full items-start justify-between gap-2 p-8 text-sm', "flex flex-col min-h-[400px] w-full max-w-full items-start justify-between gap-2 p-8 text-sm",
className, className
)} )}
> >
<div> <div>
@@ -320,7 +324,7 @@ function PricingCard({
// setPricingType: (pricingType: PricingType) => {}, // setPricingType: (pricingType: PricingType) => {},
// }); // });
const PricingTypeValue = ['month', 'year'] as const; const PricingTypeValue = ["month", "year"] as const;
export type PricingType = (typeof PricingTypeValue)[number]; export type PricingType = (typeof PricingTypeValue)[number];
// // an helper function to useContext // // an helper function to useContext
@@ -350,7 +354,7 @@ function PricingOption({ className, ...props }: TabsProps) {
return ( return (
<Tabs <Tabs
className={twMerge('w-fit', className)} className={twMerge("w-fit", className)}
defaultValue="month" defaultValue="month"
aria-label="Pricing Options" aria-label="Pricing Options"
{...props} {...props}
@@ -384,10 +388,10 @@ function PriceTag({
pricingType, pricingType,
...props ...props
}: HTMLAttributes<HTMLHeadingElement> & { }: HTMLAttributes<HTMLHeadingElement> & {
pricingType?: 'month' | 'year' | string; pricingType?: "month" | "year" | string;
}) { }) {
// const { pricingType: currentPricingType } = usePricingContext(); // const { pricingType: currentPricingType } = usePricingContext();
let currentPricingType = 'month'; const currentPricingType = "month";
if (pricingType != undefined && currentPricingType !== pricingType) if (pricingType != undefined && currentPricingType !== pricingType)
return <></>; return <></>;
@@ -399,10 +403,10 @@ function Card({ className, children, ...props }: CardProps) {
// extract the title and subtitle from the children // extract the title and subtitle from the children
// const cardTitleStyles = // const cardTitleStyles =
const title = getChildComponent(children, Title, { const title = getChildComponent(children, Title, {
className: 'text-2xl md:text-2xl font-normal text-center', className: "text-2xl md:text-2xl font-normal text-center",
}); });
const subTitle = getChildComponent(children, Subtitle, { const subTitle = getChildComponent(children, Subtitle, {
className: 'text-md text-center', className: "text-md text-center",
}); });
const image = getChildComponent(children, ImageArea); const image = getChildComponent(children, ImageArea);
@@ -411,8 +415,8 @@ function Card({ className, children, ...props }: CardProps) {
// shadow="sm" // shadow="sm"
{...props} {...props}
className={twMerge( className={twMerge(
'flex min-h-[280px] w-full max-w-full items-center justify-center gap-2 p-4 text-sm flex-col', "flex min-h-[280px] w-full max-w-full items-center justify-center gap-2 p-4 text-sm flex-col",
className, className
)} )}
> >
{image} {image}
@@ -431,7 +435,7 @@ function ImageArea({
return ( return (
<div <div
{...props} {...props}
className={twMerge('aspect-square w-14 bg-foreground-300', className)} className={twMerge("aspect-square w-14 bg-foreground-300", className)}
> >
{children} {children}
</div> </div>
@@ -442,11 +446,11 @@ function ImageArea({
function getChildComponent<T extends (...args: any[]) => React.JSX.Element>( function getChildComponent<T extends (...args: any[]) => React.JSX.Element>(
children: React.ReactNode | React.ReactNode[], children: React.ReactNode | React.ReactNode[],
type: T, type: T,
propsOverride?: Partial<Parameters<T>[0]>, propsOverride?: Partial<Parameters<T>[0]>
) { ) {
const childrenArr = React.Children.toArray(children); const childrenArr = React.Children.toArray(children);
let child = childrenArr.find( let child = childrenArr.find(
(child) => React.isValidElement(child) && child.type === type, (child) => React.isValidElement(child) && child.type === type
) as React.ReactElement< ) as React.ReactElement<
Parameters<T>[0], Parameters<T>[0],
string | React.JSXElementConstructor<any> string | React.JSXElementConstructor<any>
@@ -466,12 +470,12 @@ function getChildComponent<T extends (...args: any[]) => React.JSX.Element>(
function getChildComponents<T extends (...args: any[]) => React.JSX.Element>( function getChildComponents<T extends (...args: any[]) => React.JSX.Element>(
children: React.ReactNode | React.ReactNode[], children: React.ReactNode | React.ReactNode[],
type: T, type: T,
propsOverride?: Partial<Parameters<T>[0]>, propsOverride?: Partial<Parameters<T>[0]>
) { ) {
const childrenArr = React.Children.toArray(children); const childrenArr = React.Children.toArray(children);
let child = ( const child = (
childrenArr.filter( childrenArr.filter(
(child) => React.isValidElement(child) && child.type === type, (child) => React.isValidElement(child) && child.type === type
) as React.ReactElement< ) as React.ReactElement<
Parameters<T>[0], Parameters<T>[0],
string | React.JSXElementConstructor<any> string | React.JSXElementConstructor<any>
@@ -492,10 +496,10 @@ function getChildComponents<T extends (...args: any[]) => React.JSX.Element>(
function removeFromChildren( function removeFromChildren(
children: React.ReactNode | React.ReactNode[], children: React.ReactNode | React.ReactNode[],
types: any[], types: any[]
): React.ReactNode[] { ): React.ReactNode[] {
return React.Children.toArray(children).filter( return React.Children.toArray(children).filter(
(child) => React.isValidElement(child) && !types.includes(child.type), (child) => React.isValidElement(child) && !types.includes(child.type)
); );
} }
@@ -508,11 +512,11 @@ function FAQItem({
...props ...props
}: { }: {
children: React.ReactNode | React.ReactNode[]; children: React.ReactNode | React.ReactNode[];
'aria-label': string; "aria-label": string;
title: string; title: string;
}): JSX.Element { }): JSX.Element {
return ( return (
<AccordionItem value={props['aria-label']}> <AccordionItem value={props["aria-label"]}>
<AccordionTrigger>{props.title}</AccordionTrigger> <AccordionTrigger>{props.title}</AccordionTrigger>
<AccordionContent>{children}</AccordionContent> <AccordionContent>{children}</AccordionContent>
</AccordionItem> </AccordionItem>
+91
View File
@@ -0,0 +1,91 @@
"use client";
import { ButtonAction } from "@/components/ButtonActionLoader";
import { UpdateModal } from "@/components/InsertModal";
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
import { Button } from "@/components/ui/button";
import { publicShareDeployment } from "@/db/schema";
import {
findUserShareDeployment,
removePublicShareDeployment,
updateSharePageInfo,
} from "@/server/curdDeploments";
import { ExternalLink } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useServerActionData } from "./useServerActionData";
export function SharePageSettings({
deployment_id,
}: {
deployment_id: string;
}) {
const {
data: deployment,
pending,
started,
} = useServerActionData(findUserShareDeployment, deployment_id);
const [_open, _setOpen] = useState(false);
const router = useRouter();
if (pending) return <LoadingPageWrapper className="h-full" tag="settings" />;
if (!deployment && started && !pending)
return (
<div className="h-full w-full flex items-center justify-center">
<p>Settings page not found.</p>
</div>
);
if (!deployment) return null;
return (
<>
<UpdateModal
dialogClassName="sm:max-w-[600px]"
open={true}
setOpen={() => {
router.back();
}}
extraButtons={
<>
<Button
asChild
className="gap-2 truncate"
variant="outline"
type="button"
>
<ButtonAction
routerAction="back"
action={removePublicShareDeployment.bind(null, deployment.id)}
>
Remove
</ButtonAction>
</Button>
<Button asChild className="gap-2 truncate" type="button">
<Link href={`/share/${deployment.share_slug ?? deployment.id}`} target="_blank">
View Share Page <ExternalLink size={14} />
</Link>
</Button>
</>
}
data={{
id: deployment.id,
description: deployment.description,
showcase_media: deployment.showcase_media ?? [],
}}
title="Share Page"
description="Edit share page details."
serverAction={updateSharePageInfo}
formSchema={publicShareDeployment}
fieldConfig={{
description: {
fieldType: "textarea",
},
}}
/>
</>
);
}
+28 -177
View File
@@ -1,11 +1,5 @@
"use client"; "use client";
import {
plainInputsToZod,
workflowVersionInputsToZod,
} from "../lib/workflowVersionInputsToZod";
import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher";
import { LoadingIcon } from "@/components/LoadingIcon"; import { LoadingIcon } from "@/components/LoadingIcon";
import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form"; import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@@ -42,27 +36,21 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import type { workflowAPINodeType } from "@/db/schema"; import type { showcaseMediaNullable, workflowAPINodeType } from "@/db/schema";
import type { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
import { checkStatus, createRun } from "@/server/createRun"; import { checkStatus, createRun } from "@/server/createRun";
import { createDeployments } from "@/server/curdDeploments"; import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine"; import type { getMachines } from "@/server/curdMachine";
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion"; import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
import { useAuth, useClerk } from "@clerk/nextjs"; import { Copy, ExternalLink, Info, MoreVertical, Play } from "lucide-react";
import {
Copy,
ExternalLink,
Info,
MoreVertical,
Play,
Share,
} from "lucide-react";
import { parseAsInteger, useQueryState } from "next-usequerystate"; import { parseAsInteger, useQueryState } from "next-usequerystate";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import useSWR from "swr"; import useSWR from "swr";
import type { z } from "zod"; import type { z } from "zod";
import { create } from "zustand"; import { create } from "zustand";
import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod";
import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher";
export function VersionSelect({ export function VersionSelect({
workflow, workflow,
@@ -127,12 +115,14 @@ export function MachineSelect({
); );
} }
function useSelectedMachine(machines: Awaited<ReturnType<typeof getMachines>>) { export function useSelectedMachine(
const a = useQueryState("machine", { machines: Awaited<ReturnType<typeof getMachines>>,
defaultValue: machines?.[0]?.id ?? "", ) {
}); const a = useQueryState("machine", {
defaultValue: machines?.[0]?.id ?? "",
});
return a; return a;
} }
type PublicRunStore = { type PublicRunStore = {
@@ -147,7 +137,7 @@ type PublicRunStore = {
setStatus: (status: string) => void; setStatus: (status: string) => void;
}; };
const publicRunStore = create<PublicRunStore>((set) => ({ export const publicRunStore = create<PublicRunStore>((set) => ({
image: "", image: "",
loading: false, loading: false,
runId: "", runId: "",
@@ -159,7 +149,9 @@ const publicRunStore = create<PublicRunStore>((set) => ({
setStatus: (status) => set({ status }), setStatus: (status) => set({ status }),
})); }));
export function PublicRunOutputs() { export function PublicRunOutputs(props: {
preview: z.infer<typeof showcaseMediaNullable>;
}) {
const { image, loading, runId, status, setStatus, setImage, setLoading } = const { image, loading, runId, status, setStatus, setImage, setLoading } =
publicRunStore(); publicRunStore();
@@ -181,6 +173,15 @@ export function PublicRunOutputs() {
return ( return (
<div className="border border-gray-200 w-full square h-[400px] rounded-lg relative"> <div className="border border-gray-200 w-full square h-[400px] rounded-lg relative">
{!loading && !image && props.preview && props.preview.length > 0 && (
<>
<img
className="w-full h-full object-contain"
src={props.preview[0]?.url}
alt="Generated image"
/>
</>
)}
{!loading && image && ( {!loading && image && (
<img <img
className="w-full h-full object-contain" className="w-full h-full object-contain"
@@ -198,105 +199,6 @@ export function PublicRunOutputs() {
); );
} }
export function RunWorkflowInline({
inputs,
workflow_version_id,
machine_id,
}: {
inputs: ReturnType<typeof getInputsFromWorkflow>;
workflow_version_id: string;
machine_id: string;
}) {
const [values, setValues] = useState<Record<string, string>>({});
const [isLoading, setIsLoading] = useState(false);
const user = useAuth();
const clerk = useClerk();
const schema = useMemo(() => {
return plainInputsToZod(inputs);
}, [inputs]);
const {
setRunId,
loading,
setLoading: setLoading2,
setStatus,
} = publicRunStore();
const runWorkflow = async () => {
console.log();
if (!user.isSignedIn) {
clerk.openSignIn({
redirectUrl: window.location.href,
});
console.log("hi");
return;
}
console.log(values);
const val = Object.keys(values).length > 0 ? values : undefined;
setLoading2(true);
setIsLoading(true);
setStatus("preparing");
try {
const origin = window.location.origin;
const a = await callServerPromise(
createRun({
origin,
workflow_version_id: workflow_version_id,
machine_id: machine_id,
inputs: val,
isManualRun: true,
})
);
if (a && !("error" in a)) {
setRunId(a.workflow_run_id);
} else {
setLoading2(false);
}
console.log(a);
setIsLoading(false);
} catch (error) {
setIsLoading(false);
setLoading2(false);
}
};
return (
<>
{schema && (
<AutoForm
formSchema={schema}
values={values}
onValuesChange={setValues}
onSubmit={runWorkflow}
className="px-1"
>
<div className="flex justify-end">
<AutoFormSubmit disabled={isLoading || loading}>
Run
<span className="ml-2">
{isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
</span>
</AutoFormSubmit>
</div>
</AutoForm>
)}
{!schema && (
<Button
className="gap-2"
disabled={isLoading || loading}
onClick={runWorkflow}
>
Confirm {isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
</Button>
)}
</>
);
}
export function RunWorkflowButton({ export function RunWorkflowButton({
workflow, workflow,
machines, machines,
@@ -345,7 +247,7 @@ export function RunWorkflowButton({
workflow_version_id, workflow_version_id,
machine_id: machine, machine_id: machine,
inputs: val, inputs: val,
isManualRun: true, runOrigin: "manual",
}) })
); );
// console.log(res.json()); // console.log(res.json());
@@ -385,9 +287,7 @@ export function RunWorkflowButton({
<div className="flex justify-end"> <div className="flex justify-end">
<AutoFormSubmit> <AutoFormSubmit>
Run Run
<span className="ml-2"> {isLoading ? <LoadingIcon /> : <Play size={14} />}
{isLoading ? <LoadingIcon /> : <Play size={14} />}
</span>
</AutoFormSubmit> </AutoFormSubmit>
</div> </div>
</AutoForm> </AutoForm>
@@ -468,55 +368,6 @@ export function CreateDeploymentButton({
); );
} }
export function CreateShareButton({
workflow,
machines,
}: {
workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
machines: Awaited<ReturnType<typeof getMachines>>;
}) {
const [version] = useQueryState("version", {
defaultValue: workflow?.versions[0].version ?? 1,
...parseAsInteger,
});
const [machine] = useSelectedMachine(machines);
const [isLoading, setIsLoading] = useState(false);
const workflow_version_id = workflow?.versions.find(
(x) => x.version === version
)?.id;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-2" disabled={isLoading} variant="outline">
Share {isLoading ? <LoadingIcon /> : <Share size={14} />}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem
onClick={async () => {
if (!workflow_version_id) return;
setIsLoading(true);
await callServerPromise(
createDeployments(
workflow.id,
workflow_version_id,
machine,
"public-share"
)
);
setIsLoading(false);
}}
>
Public
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
export function CopyWorkflowVersion({ export function CopyWorkflowVersion({
workflow, workflow,
}: { }: {
@@ -693,7 +544,7 @@ export function ViewWorkflowDetailsButton({
<a <a
href={group.url} href={group.url}
target="_blank" target="_blank"
className="hover:underline" className="hover:underline" rel="noreferrer"
> >
{key} {key}
<ExternalLink <ExternalLink
+35 -58
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { getRelativeTime } from "../lib/getRelativeTime"; import { getRelativeTime } from "../lib/getRelativeTime";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { import {
@@ -21,6 +22,7 @@ import {
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { deleteWorkflow } from "@/server/deleteWorkflow"; import { deleteWorkflow } from "@/server/deleteWorkflow";
import type { getAllUserWorkflow } from "@/server/getAllUserWorkflow";
import type { import type {
ColumnDef, ColumnDef,
ColumnFiltersState, ColumnFiltersState,
@@ -38,14 +40,11 @@ import {
import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import { ArrowUpDown, MoreHorizontal } from "lucide-react";
import * as React from "react"; import * as React from "react";
export type Payment = { export type WorkflowItemList = NonNullable<
id: string; Awaited<ReturnType<typeof getAllUserWorkflow>>
amount: number; >[0];
date: Date;
email: string;
};
export const columns: ColumnDef<Payment>[] = [ export const columns: ColumnDef<WorkflowItemList>[] = [
{ {
accessorKey: "id", accessorKey: "id",
id: "select", id: "select",
@@ -70,7 +69,7 @@ export const columns: ColumnDef<Payment>[] = [
enableHiding: false, enableHiding: false,
}, },
{ {
accessorKey: "email", accessorKey: "name",
header: ({ column }) => { header: ({ column }) => {
return ( return (
<button <button
@@ -83,27 +82,35 @@ export const columns: ColumnDef<Payment>[] = [
); );
}, },
cell: ({ row }) => { cell: ({ row }) => {
const workflow = row.original;
return ( return (
<a className="hover:underline" href={`/workflows/${row.original.id}`}> <a
{row.getValue("email")} className="hover:underline flex gap-2"
href={`/workflows/${workflow.id}`}
>
<span className="truncate max-w-[200px]">{row.original.name}</span>
<Badge variant="default">v{row.original.versions[0]?.version}</Badge>
{workflow.deployments?.[0] && <Badge variant="success">Public</Badge>}
</a> </a>
); );
}, },
}, },
{ {
accessorKey: "amount", accessorKey: "creator",
header: () => <div className="text-left">Version</div>, header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Creator
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => { cell: ({ row }) => {
const amount = parseFloat(row.getValue("amount")); return <Badge variant="cyan">{row.original.user.name}</Badge>;
// Format the amount as a dollar amount
// const formatted = new Intl.NumberFormat("en-US", {
// style: "currency",
// currency: "USD",
// }).format(amount);
return <div className="text-left font-medium">{amount}</div>;
}, },
}, },
{ {
@@ -113,7 +120,7 @@ export const columns: ColumnDef<Payment>[] = [
header: ({ column }) => { header: ({ column }) => {
return ( return (
<button <button
className="w-full flex items-center justify-end hover:underline" className="w-full flex items-center justify-end hover:underline truncate"
// variant="ghost" // variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
> >
@@ -123,8 +130,8 @@ export const columns: ColumnDef<Payment>[] = [
); );
}, },
cell: ({ row }) => ( cell: ({ row }) => (
<div className="w-full capitalize text-right"> <div className="w-full capitalize text-right truncate">
{getRelativeTime(row.original.date)} {getRelativeTime(row.original.updated_at)}
</div> </div>
), ),
}, },
@@ -148,14 +155,10 @@ export const columns: ColumnDef<Payment>[] = [
className="text-destructive" className="text-destructive"
onClick={() => { onClick={() => {
deleteWorkflow(workflow.id); deleteWorkflow(workflow.id);
// navigator.clipboard.writeText(payment.id)
}} }}
> >
Delete Workflow Delete Workflow
</DropdownMenuItem> </DropdownMenuItem>
{/* <DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem> */}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );
@@ -163,7 +166,7 @@ export const columns: ColumnDef<Payment>[] = [
}, },
]; ];
export function WorkflowList({ data }: { data: Payment[] }) { export function WorkflowList({ data }: { data: WorkflowItemList[] }) {
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>( const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[] []
@@ -196,38 +199,12 @@ export function WorkflowList({ data }: { data: Payment[] }) {
<div className="flex flex-row w-full items-center py-4"> <div className="flex flex-row w-full items-center py-4">
<Input <Input
placeholder="Filter workflows..." placeholder="Filter workflows..."
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""} value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
onChange={(event) => onChange={(event) =>
table.getColumn("email")?.setFilterValue(event.target.value) table.getColumn("name")?.setFilterValue(event.target.value)
} }
className="max-w-sm" className="max-w-sm"
/> />
{/* <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns <ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu> */}
</div> </div>
<ScrollArea className="h-full w-full rounded-md border"> <ScrollArea className="h-full w-full rounded-md border">
<Table> <Table>
+8 -6
View File
@@ -1,8 +1,10 @@
export const customInputNodes: Record<string, string> = { export const customInputNodes: Record<string, string> = {
ComfyUIDeployExternalText: "string", ComfyUIDeployExternalText: "string",
ComfyUIDeployExternalImage: "string - (public image url)", ComfyUIDeployExternalImage: "string - (public image url)",
ComfyUIDeployExternalImageAlpha: "string - (public image url)", ComfyUIDeployExternalImageAlpha: "string - (public image url)",
ComfyUIDeployExternalNumber: "float", ComfyUIDeployExternalNumber: "float",
ComfyUIDeployExternalNumberInt: "integer", ComfyUIDeployExternalNumberInt: "integer",
ComfyUIDeployExternalLora: "string - (public lora download url)", ComfyUIDeployExternalLora: "string - (public lora download url)",
ComfyUIDeployExternalCheckpoints:
"string - (public checkpoints download url)",
}; };
+62
View File
@@ -0,0 +1,62 @@
"use client";
import { Button } from "@/components/ui/button";
import { CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { cn } from "@/lib/utils";
// Error components must be Client Components
import { useEffect } from "react";
export default function ErrorPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.log(error.message);
}, [error]);
return (
<div className="border rounded-2xl flex flex-col w-full h-full gap-20 justify-center items-center text-xs">
<div className="max-w-xl w-full">
<CardHeader>
<CardTitle>
<div className="text-xl">Unexpected error.</div>
</CardTitle>
<CardDescription className="flex flex-col gap-4">
<div className="text-sm">Error: {error.message}</div>
<div className="flex w-full justify-end">
<Button
className="w-fit"
onClick={() => {
window.location.reload();
}}
>
Refresh Page
</Button>
</div>
</CardDescription>
</CardHeader>
</div>
</div>
);
}
export function ErrorFullPage({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div
className={cn(
"w-full py-4 flex justify-center items-center gap-2 text-sm h-full"
)}
>
<ErrorPage error={error} reset={reset} />
</div>
);
}
+1 -1
View File
@@ -69,7 +69,7 @@ const FeedbackThanks = forwardRef<React.ElementRef<'div'>>(
) )
export function Feedback() { export function Feedback() {
let [submitted, setSubmitted] = useState(false) const [submitted, setSubmitted] = useState(false)
function onSubmit(event: React.FormEvent<HTMLFormElement>) { function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault() event.preventDefault()
+1 -1
View File
@@ -8,7 +8,7 @@ export function Prose<T extends React.ElementType = 'div'>({
as?: T as?: T
className?: string className?: string
}) { }) {
let Component = as ?? 'div' const Component = as ?? 'div'
return ( return (
<Component <Component
+3 -3
View File
@@ -22,9 +22,9 @@ function MoonIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
} }
export function ThemeToggle() { export function ThemeToggle() {
let { resolvedTheme, setTheme } = useTheme() const { resolvedTheme, setTheme } = useTheme()
let otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark' const otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark'
let [mounted, setMounted] = useState(false) const [mounted, setMounted] = useState(false)
useEffect(() => { useEffect(() => {
setMounted(true) setMounted(true)
+1 -1
View File
@@ -21,7 +21,7 @@ export function AutoFormSubmit({
disabled?: boolean; disabled?: boolean;
}) { }) {
return ( return (
<Button type="submit" disabled={disabled}> <Button type="submit" disabled={disabled} className="flex gap-2">
{children ?? "Submit"} {children ?? "Submit"}
</Button> </Button>
); );
+13 -12
View File
@@ -1,5 +1,5 @@
import { DefaultValues } from "react-hook-form"; import type { DefaultValues } from "react-hook-form";
import { z } from "zod"; import type { z } from "zod";
// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions. // TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.
export type ZodObjectOrWrapped = export type ZodObjectOrWrapped =
@@ -21,7 +21,7 @@ export function beautifyObjectName(string: string) {
* This will unpack optionals, refinements, etc. * This will unpack optionals, refinements, etc.
*/ */
export function getBaseSchema< export function getBaseSchema<
ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny, ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny
>(schema: ChildType | z.ZodEffects<ChildType>): ChildType { >(schema: ChildType | z.ZodEffects<ChildType>): ChildType {
if ("innerType" in schema._def) { if ("innerType" in schema._def) {
return getBaseSchema(schema._def.innerType as ChildType); return getBaseSchema(schema._def.innerType as ChildType);
@@ -54,12 +54,12 @@ export function getDefaultValueInZodStack(schema: z.ZodAny): any {
if ("innerType" in typedSchema._def) { if ("innerType" in typedSchema._def) {
return getDefaultValueInZodStack( return getDefaultValueInZodStack(
typedSchema._def.innerType as unknown as z.ZodAny, typedSchema._def.innerType as unknown as z.ZodAny
); );
} }
if ("schema" in typedSchema._def) { if ("schema" in typedSchema._def) {
return getDefaultValueInZodStack( return getDefaultValueInZodStack(
(typedSchema._def as any).schema as z.ZodAny, (typedSchema._def as any).schema as z.ZodAny
); );
} }
return undefined; return undefined;
@@ -69,7 +69,7 @@ export function getDefaultValueInZodStack(schema: z.ZodAny): any {
* Get all default values from a Zod schema. * Get all default values from a Zod schema.
*/ */
export function getDefaultValues<Schema extends z.ZodObject<any, any>>( export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
schema: Schema, schema: Schema
) { ) {
const { shape } = schema; const { shape } = schema;
type DefaultValuesType = DefaultValues<Partial<z.infer<Schema>>>; type DefaultValuesType = DefaultValues<Partial<z.infer<Schema>>>;
@@ -80,7 +80,7 @@ export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
if (getBaseType(item) === "ZodObject") { if (getBaseType(item) === "ZodObject") {
const defaultItems = getDefaultValues( const defaultItems = getDefaultValues(
getBaseSchema(item) as unknown as z.ZodObject<any, any>, getBaseSchema(item) as unknown as z.ZodObject<any, any>
); );
for (const defaultItemKey of Object.keys(defaultItems)) { for (const defaultItemKey of Object.keys(defaultItems)) {
const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType; const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType;
@@ -98,7 +98,7 @@ export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
} }
export function getObjectFormSchema( export function getObjectFormSchema(
schema: ZodObjectOrWrapped, schema: ZodObjectOrWrapped
): z.ZodObject<any, any> { ): z.ZodObject<any, any> {
if (schema._def.typeName === "ZodEffects") { if (schema._def.typeName === "ZodEffects") {
const typedSchema = schema as z.ZodEffects<z.ZodObject<any, any>>; const typedSchema = schema as z.ZodEffects<z.ZodObject<any, any>>;
@@ -116,7 +116,7 @@ export function zodToHtmlInputProps(
| z.ZodNumber | z.ZodNumber
| z.ZodString | z.ZodString
| z.ZodOptional<z.ZodNumber | z.ZodString> | z.ZodOptional<z.ZodNumber | z.ZodString>
| any, | any
): React.InputHTMLAttributes<HTMLInputElement> { ): React.InputHTMLAttributes<HTMLInputElement> {
if (["ZodOptional", "ZodNullable"].includes(schema._def.typeName)) { if (["ZodOptional", "ZodNullable"].includes(schema._def.typeName)) {
const typedSchema = schema as z.ZodOptional<z.ZodNumber | z.ZodString>; const typedSchema = schema as z.ZodOptional<z.ZodNumber | z.ZodString>;
@@ -128,9 +128,10 @@ export function zodToHtmlInputProps(
const typedSchema = schema as z.ZodNumber | z.ZodString; const typedSchema = schema as z.ZodNumber | z.ZodString;
if (!("checks" in typedSchema._def)) return { if (!("checks" in typedSchema._def))
required: true return {
}; required: true,
};
const { checks } = typedSchema._def; const { checks } = typedSchema._def;
const inputProps: React.InputHTMLAttributes<HTMLInputElement> = { const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {
+37 -10
View File
@@ -2,20 +2,47 @@ import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react"; import * as React from "react";
const colors = {
red: "bg-red-500/15 text-red-700 group-data-[hover]:bg-red-500/25 dark:bg-red-500/10 dark:text-red-400 dark:group-data-[hover]:bg-red-500/20",
orange:
"bg-orange-500/15 text-orange-700 group-data-[hover]:bg-orange-500/25 dark:bg-orange-500/10 dark:text-orange-400 dark:group-data-[hover]:bg-orange-500/20",
amber:
"bg-amber-400/20 text-amber-700 group-data-[hover]:bg-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400 dark:group-data-[hover]:bg-amber-400/15",
yellow:
"bg-yellow-400/20 text-yellow-700 group-data-[hover]:bg-yellow-400/30 dark:bg-yellow-400/10 dark:text-yellow-300 dark:group-data-[hover]:bg-yellow-400/15",
lime: "bg-lime-400/20 text-lime-700 group-data-[hover]:bg-lime-400/30 dark:bg-lime-400/10 dark:text-lime-300 dark:group-data-[hover]:bg-lime-400/15",
green:
"bg-green-500/15 text-green-700 group-data-[hover]:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:group-data-[hover]:bg-green-500/20",
emerald:
"bg-emerald-500/15 text-emerald-700 group-data-[hover]:bg-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400 dark:group-data-[hover]:bg-emerald-500/20",
teal: "bg-teal-500/15 text-teal-700 group-data-[hover]:bg-teal-500/25 dark:bg-teal-500/10 dark:text-teal-300 dark:group-data-[hover]:bg-teal-500/20",
cyan: "bg-cyan-400/20 text-cyan-700 group-data-[hover]:bg-cyan-400/30 dark:bg-cyan-400/10 dark:text-cyan-300 dark:group-data-[hover]:bg-cyan-400/15",
sky: "bg-sky-500/15 text-sky-700 group-data-[hover]:bg-sky-500/25 dark:bg-sky-500/10 dark:text-sky-300 dark:group-data-[hover]:bg-sky-500/20",
blue: "bg-blue-500/15 text-blue-700 group-data-[hover]:bg-blue-500/25 dark:text-blue-400 dark:group-data-[hover]:bg-blue-500/25",
indigo:
"bg-indigo-500/15 text-indigo-700 group-data-[hover]:bg-indigo-500/25 dark:text-indigo-400 dark:group-data-[hover]:bg-indigo-500/20",
violet:
"bg-violet-500/15 text-violet-700 group-data-[hover]:bg-violet-500/25 dark:text-violet-400 dark:group-data-[hover]:bg-violet-500/20",
purple:
"bg-purple-500/15 text-purple-700 group-data-[hover]:bg-purple-500/25 dark:text-purple-400 dark:group-data-[hover]:bg-purple-500/20",
fuchsia:
"bg-fuchsia-400/15 text-fuchsia-700 group-data-[hover]:bg-fuchsia-400/25 dark:bg-fuchsia-400/10 dark:text-fuchsia-400 dark:group-data-[hover]:bg-fuchsia-400/20",
pink: "bg-pink-400/15 text-pink-700 group-data-[hover]:bg-pink-400/25 dark:bg-pink-400/10 dark:text-pink-400 dark:group-data-[hover]:bg-pink-400/20",
rose: "bg-rose-400/15 text-rose-700 group-data-[hover]:bg-rose-400/25 dark:bg-rose-400/10 dark:text-rose-400 dark:group-data-[hover]:bg-rose-400/20",
zinc: "bg-zinc-600/10 text-zinc-700 group-data-[hover]:bg-zinc-600/20 dark:bg-white/5 dark:text-zinc-400 dark:group-data-[hover]:bg-white/10",
};
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "inline-flex items-center gap-x-1.5 rounded-md px-2 py-0.5 text-sm/5 font-medium sm:text-xs/5 forced-colors:outline",
{ {
variants: { variants: {
variant: { variant: {
default: default: colors["zinc"],
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80", secondary: colors["zinc"],
secondary: success: colors["green"],
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: colors["red"],
success: outline: "text-foreground border",
"border-transparent bg-green-200 text-secondary-foreground hover:bg-green-200/80", ...colors,
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
+193 -26
View File
@@ -3,34 +3,201 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react"; import * as React from "react";
const buttonVariants = cva( const styles = {
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", base: [
{ // Base
variants: { "relative isolate inline-flex items-center justify-center gap-x-2 rounded-lg border text-base/6 font-semibold",
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", // Sizing
destructive: "px-[calc(theme(spacing.3)-1px)] py-[calc(theme(spacing[1.5])-1px)] text-sm/6",
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: // Focus
"border border-input bg-background hover:bg-accent hover:text-accent-foreground", "focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80", // Disabled
ghost: "hover:bg-accent hover:text-accent-foreground", "data-[disabled]:opacity-50",
link: "text-primary underline-offset-4 hover:underline",
}, // Icon
size: { "[&>[data-slot=icon]]:-mx-0.5 [&>[data-slot=icon]]:my-0.5 [&>[data-slot=icon]]:size-5 [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-[--btn-icon] [&>[data-slot=icon]]:sm:my-1 [&>[data-slot=icon]]:sm:size-4 forced-colors:[--btn-icon:ButtonText] forced-colors:data-[hover]:[--btn-icon:ButtonText]",
default: "h-10 px-4 py-2", ],
sm: "h-9 rounded-md px-3", solid: [
lg: "h-11 rounded-md px-8", // Optical border, implemented as the button background to avoid corner artifacts
icon: "h-10 w-10", "border-transparent bg-[--btn-border]",
},
// Dark mode: border is rendered on `after` so background is set to button background
"dark:bg-[--btn-bg]",
// Button background, implemented as foreground layer to stack on top of pseudo-border layer
"before:absolute before:inset-0 before:-z-10 before:rounded-[calc(theme(borderRadius.lg)-1px)] before:bg-[--btn-bg]",
// Drop shadow, applied to the inset `before` layer so it blends with the border
"before:shadow",
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
"dark:before:hidden",
// Dark mode: Subtle white outline is applied using a border
"dark:border-white/5",
// Shim/overlay, inset to match button foreground and used for hover state + highlight shadow
"after:absolute after:inset-0 after:-z-10 after:rounded-[calc(theme(borderRadius.lg)-1px)]",
// Inner highlight shadow
"after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)]",
// White overlay on hover
"after:data-[active]:bg-[--btn-hover-overlay] after:data-[hover]:bg-[--btn-hover-overlay]",
// Dark mode: `after` layer expands to cover entire button
"dark:after:-inset-px dark:after:rounded-lg",
// Disabled
"before:data-[disabled]:shadow-none after:data-[disabled]:shadow-none",
],
outline: [
// Base
"border-zinc-950/10 text-zinc-950 data-[active]:bg-zinc-950/[2.5%] data-[hover]:bg-zinc-950/[2.5%]",
// Dark mode
"dark:border-white/15 dark:text-white dark:[--btn-bg:transparent] dark:data-[active]:bg-white/5 dark:data-[hover]:bg-white/5",
// Icon
"[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
],
plain: [
// Base
"border-transparent text-zinc-950 data-[active]:bg-zinc-950/5 data-[hover]:bg-zinc-950/5",
// Dark mode
"dark:text-white dark:data-[active]:bg-white/10 dark:data-[hover]:bg-white/10",
// Icon
"[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
],
colors: {
"dark/zinc": [
"text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]",
"dark:text-white dark:[--btn-bg:theme(colors.zinc.600)] dark:[--btn-hover-overlay:theme(colors.white/5%)]",
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]",
],
light: [
"text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]",
"dark:text-white dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]",
"[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
],
"dark/white": [
"text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]",
"dark:text-zinc-950 dark:[--btn-bg:white] dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]",
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
],
dark: [
"text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]",
"dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]",
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]",
],
white: [
"text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]",
"dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]",
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.500)] data-[hover]:[--btn-icon:theme(colors.zinc.500)]",
],
zinc: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.zinc.600)] [--btn-border:theme(colors.zinc.700/90%)]",
"dark:[--btn-hover-overlay:theme(colors.white/5%)]",
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]",
],
indigo: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.indigo.500)] [--btn-border:theme(colors.indigo.600/90%)]",
"[--btn-icon:theme(colors.indigo.300)] data-[active]:[--btn-icon:theme(colors.indigo.200)] data-[hover]:[--btn-icon:theme(colors.indigo.200)]",
],
cyan: [
"text-cyan-950 [--btn-bg:theme(colors.cyan.300)] [--btn-border:theme(colors.cyan.400/80%)] [--btn-hover-overlay:theme(colors.white/25%)]",
"[--btn-icon:theme(colors.cyan.500)]",
],
red: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.red.600)] [--btn-border:theme(colors.red.700/90%)]",
"[--btn-icon:theme(colors.red.300)] data-[active]:[--btn-icon:theme(colors.red.200)] data-[hover]:[--btn-icon:theme(colors.red.200)]",
],
orange: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.orange.500)] [--btn-border:theme(colors.orange.600/90%)]",
"[--btn-icon:theme(colors.orange.300)] data-[active]:[--btn-icon:theme(colors.orange.200)] data-[hover]:[--btn-icon:theme(colors.orange.200)]",
],
amber: [
"text-amber-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.amber.400)] [--btn-border:theme(colors.amber.500/80%)]",
"[--btn-icon:theme(colors.amber.600)]",
],
yellow: [
"text-yellow-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.yellow.300)] [--btn-border:theme(colors.yellow.400/80%)]",
"[--btn-icon:theme(colors.yellow.600)] data-[active]:[--btn-icon:theme(colors.yellow.700)] data-[hover]:[--btn-icon:theme(colors.yellow.700)]",
],
lime: [
"text-lime-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.lime.300)] [--btn-border:theme(colors.lime.400/80%)]",
"[--btn-icon:theme(colors.lime.600)] data-[active]:[--btn-icon:theme(colors.lime.700)] data-[hover]:[--btn-icon:theme(colors.lime.700)]",
],
green: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.green.600)] [--btn-border:theme(colors.green.700/90%)]",
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
],
emerald: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.emerald.600)] [--btn-border:theme(colors.emerald.700/90%)]",
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
],
teal: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.teal.600)] [--btn-border:theme(colors.teal.700/90%)]",
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
],
sky: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.sky.500)] [--btn-border:theme(colors.sky.600/80%)]",
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
],
blue: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.blue.600)] [--btn-border:theme(colors.blue.700/90%)]",
"[--btn-icon:theme(colors.blue.400)] data-[active]:[--btn-icon:theme(colors.blue.300)] data-[hover]:[--btn-icon:theme(colors.blue.300)]",
],
violet: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.violet.500)] [--btn-border:theme(colors.violet.600/90%)]",
"[--btn-icon:theme(colors.violet.300)] data-[active]:[--btn-icon:theme(colors.violet.200)] data-[hover]:[--btn-icon:theme(colors.violet.200)]",
],
purple: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.purple.500)] [--btn-border:theme(colors.purple.600/90%)]",
"[--btn-icon:theme(colors.purple.300)] data-[active]:[--btn-icon:theme(colors.purple.200)] data-[hover]:[--btn-icon:theme(colors.purple.200)]",
],
fuchsia: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.fuchsia.500)] [--btn-border:theme(colors.fuchsia.600/90%)]",
"[--btn-icon:theme(colors.fuchsia.300)] data-[active]:[--btn-icon:theme(colors.fuchsia.200)] data-[hover]:[--btn-icon:theme(colors.fuchsia.200)]",
],
pink: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.pink.500)] [--btn-border:theme(colors.pink.600/90%)]",
"[--btn-icon:theme(colors.pink.300)] data-[active]:[--btn-icon:theme(colors.pink.200)] data-[hover]:[--btn-icon:theme(colors.pink.200)]",
],
rose: [
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.rose.500)] [--btn-border:theme(colors.rose.600/90%)]",
"[--btn-icon:theme(colors.rose.300)] data-[active]:[--btn-icon:theme(colors.rose.200)] data-[hover]:[--btn-icon:theme(colors.rose.200)]",
],
},
};
const buttonVariants = cva(styles.base, {
variants: {
variant: {
default: [...styles.solid, ...styles.colors.zinc],
destructive: [...styles.solid, ...styles.colors.red],
outline: styles.outline,
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: styles.plain,
link: "border-none text-primary underline-offset-4 hover:underline",
}, },
defaultVariants: { size: {
variant: "default", default: "h-10 px-4 py-2",
size: "default", sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
}, },
} },
); defaultVariants: {
variant: "default",
size: "default",
},
});
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+262
View File
@@ -0,0 +1,262 @@
"use client"
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
},
ref
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return
}
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) {
return
}
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) {
return
}
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel()
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
})
CarouselContent.displayName = "CarouselContent"
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel()
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
})
CarouselItem.displayName = "CarouselItem"
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
)
})
CarouselPrevious.displayName = "CarouselPrevious"
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
)
})
CarouselNext.displayName = "CarouselNext"
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}
@@ -0,0 +1,26 @@
"use client";
import { callServerPromise } from "@/components/callServerPromise";
import { useEffect, useState, useTransition } from "react";
export function useServerActionData<I, O>(
action: (data: I) => Promise<O>,
input: I
) {
const [data, setData] = useState<O | null>(null);
const [pending, startTransition] = useTransition();
const [started, setStarted] = useState(false);
useEffect(() => {
startTransition(() => {
setStarted(true);
callServerPromise(action(input)).then(setData);
});
}, [action, input]);
return {
started,
data,
pending,
};
}
+9 -1
View File
@@ -2,10 +2,18 @@ import * as schema from "./schema";
import { neonConfig, Pool } from "@neondatabase/serverless"; import { neonConfig, Pool } from "@neondatabase/serverless";
import { drizzle as neonDrizzle } from "drizzle-orm/neon-serverless"; import { drizzle as neonDrizzle } from "drizzle-orm/neon-serverless";
const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
// if we're running locally // if we're running locally
if (process.env.VERCEL_ENV !== "production") { if (process.env.VERCEL_ENV !== "production") {
// Set the WebSocket proxy to work with the local instance // Set the WebSocket proxy to work with the local instance
neonConfig.wsProxy = (host) => `${host}:5481/v1`; if (isDevContainer) {
// Running inside a VS Code devcontainer
neonConfig.wsProxy = (host) => `host.docker.internal:5481/v1`;
} else {
// Not running inside a VS Code devcontainer
neonConfig.wsProxy = (host) => `${host}:5481/v1`;
}
// Disable all authentication and encryption // Disable all authentication and encryption
neonConfig.useSecureWebSocket = false; neonConfig.useSecureWebSocket = false;
neonConfig.pipelineTLS = false; neonConfig.pipelineTLS = false;
+65 -7
View File
@@ -1,13 +1,14 @@
import { relations, type InferSelectModel } from "drizzle-orm"; import { type InferSelectModel, relations } from "drizzle-orm";
import { import {
text, boolean,
pgSchema,
uuid,
integer, integer,
timestamp,
jsonb, jsonb,
pgEnum, pgEnum,
boolean, pgSchema,
text,
timestamp,
uuid,
real,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
@@ -35,7 +36,11 @@ export const workflowTable = dbSchema.table("workflows", {
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
}); });
export const workflowRelations = relations(workflowTable, ({ many }) => ({ export const workflowRelations = relations(workflowTable, ({ many, one }) => ({
user: one(usersTable, {
fields: [workflowTable.user_id],
references: [usersTable.id],
}),
versions: many(workflowVersionTable), versions: many(workflowVersionTable),
deployments: many(deploymentsTable), deployments: many(deploymentsTable),
})); }));
@@ -103,8 +108,12 @@ export const deploymentEnvironment = pgEnum("deployment_environment", [
export const workflowRunOrigin = pgEnum("workflow_run_origin", [ export const workflowRunOrigin = pgEnum("workflow_run_origin", [
"manual", "manual",
"api", "api",
"public-share",
]); ]);
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
export const machineGPUOptions = pgEnum("machine_gpu", ["T4", "A10G", "A100"]); export const machineGPUOptions = pgEnum("machine_gpu", ["T4", "A10G", "A100"]);
export const machinesType = pgEnum("machine_type", [ export const machinesType = pgEnum("machine_type", [
@@ -145,6 +154,7 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
status: workflowRunStatus("status").notNull().default("not-started"), status: workflowRunStatus("status").notNull().default("not-started"),
ended_at: timestamp("ended_at"), ended_at: timestamp("ended_at"),
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
started_at: timestamp("started_at"),
}); });
export const workflowRunRelations = relations( export const workflowRunRelations = relations(
@@ -231,6 +241,22 @@ export const insertMachineSchema = createInsertSchema(machinesTable, {
type: (schema) => schema.type.default("classic"), type: (schema) => schema.type.default("classic"),
}); });
export const showcaseMedia = z.array(
z.object({
url: z.string(),
isCover: z.boolean().default(false),
})
);
export const showcaseMediaNullable = z
.array(
z.object({
url: z.string(),
isCover: z.boolean().default(false),
})
)
.nullable();
export const deploymentsTable = dbSchema.table("deployments", { export const deploymentsTable = dbSchema.table("deployments", {
id: uuid("id").primaryKey().defaultRandom().notNull(), id: uuid("id").primaryKey().defaultRandom().notNull(),
user_id: text("user_id") user_id: text("user_id")
@@ -238,6 +264,7 @@ export const deploymentsTable = dbSchema.table("deployments", {
onDelete: "cascade", onDelete: "cascade",
}) })
.notNull(), .notNull(),
org_id: text("org_id"),
workflow_version_id: uuid("workflow_version_id") workflow_version_id: uuid("workflow_version_id")
.notNull() .notNull()
.references(() => workflowVersionTable.id), .references(() => workflowVersionTable.id),
@@ -249,11 +276,28 @@ export const deploymentsTable = dbSchema.table("deployments", {
machine_id: uuid("machine_id") machine_id: uuid("machine_id")
.notNull() .notNull()
.references(() => machinesTable.id), .references(() => machinesTable.id),
share_slug: text("share_slug").unique(),
description: text("description"),
showcase_media:
jsonb("showcase_media").$type<z.infer<typeof showcaseMedia>>(),
environment: deploymentEnvironment("environment").notNull(), environment: deploymentEnvironment("environment").notNull(),
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
}); });
export const publicShareDeployment = z.object({
description: z.string().nullable(),
showcase_media: showcaseMedia,
});
// createInsertSchema(deploymentsTable, {
// description: (schema) => schema.description.default(""),
// showcase_media: () => showcaseMedia.default([]),
// }).pick({
// description: true,
// showcase_media: true,
// });
export const deploymentsRelations = relations(deploymentsTable, ({ one }) => ({ export const deploymentsRelations = relations(deploymentsTable, ({ one }) => ({
machine: one(machinesTable, { machine: one(machinesTable, {
fields: [deploymentsTable.machine_id], fields: [deploymentsTable.machine_id],
@@ -288,8 +332,22 @@ export const apiKeyTable = dbSchema.table("api_keys", {
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
}); });
export const userUsageTable = dbSchema.table("user_usage", {
id: uuid("id").primaryKey().defaultRandom().notNull(),
org_id: text("org_id"),
user_id: text("user_id")
.references(() => usersTable.id, {
onDelete: "cascade",
})
.notNull(),
usage_time: real("usage_time").default(0).notNull(),
created_at: timestamp("created_at").defaultNow().notNull(),
ended_at: timestamp("ended_at").defaultNow().notNull(),
});
export type UserType = InferSelectModel<typeof usersTable>; export type UserType = InferSelectModel<typeof usersTable>;
export type WorkflowType = InferSelectModel<typeof workflowTable>; export type WorkflowType = InferSelectModel<typeof workflowTable>;
export type MachineType = InferSelectModel<typeof machinesTable>; export type MachineType = InferSelectModel<typeof machinesTable>;
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>; export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
export type DeploymentType = InferSelectModel<typeof deploymentsTable>; export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
export type UserUsageType = InferSelectModel<typeof userUsageTable>;
+16 -2
View File
@@ -1,12 +1,26 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
import React from "react";
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
dayjs.extend(duration);
export function getRelativeTime(time: string | Date | null | undefined) { export function getRelativeTime(time: string | Date | null | undefined) {
if (typeof time === "string" || time instanceof Date) { if (typeof time === "string" || time instanceof Date) {
return dayjs().to(time); return dayjs().to(time);
} }
return null; return null;
} }
function formatDuration(seconds: number) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (minutes > 0) {
return `${minutes}.${remainingSeconds} mins`;
} else {
return `${remainingSeconds.toFixed(1)} secs`;
}
}
export function getDuration(durationInSecs: number) {
return `${formatDuration(durationInSecs)}`;
}
+6 -4
View File
@@ -1,8 +1,10 @@
export function remToPx(remValue: number) { export function remToPx(remValue: number) {
let rootFontSize = const rootFontSize =
typeof window === 'undefined' typeof window === "undefined"
? 16 ? 16
: parseFloat(window.getComputedStyle(document.documentElement).fontSize) : parseFloat(
window.getComputedStyle(document.documentElement).fontSize,
);
return remValue * rootFontSize return remValue * rootFontSize
} }
+10 -10
View File
@@ -27,13 +27,13 @@ function rehypeShiki() {
visit(tree, "element", (node) => { visit(tree, "element", (node) => {
if (node.tagName === "pre" && node.children[0]?.tagName === "code") { if (node.tagName === "pre" && node.children[0]?.tagName === "code") {
let codeNode = node.children[0]; const codeNode = node.children[0];
let textNode = codeNode.children[0]; const textNode = codeNode.children[0];
node.properties.code = textNode.value; node.properties.code = textNode.value;
if (node.properties.language) { if (node.properties.language) {
let tokens = highlighter.codeToThemedTokens( const tokens = highlighter.codeToThemedTokens(
textNode.value, textNode.value,
node.properties.language node.properties.language
); );
@@ -53,7 +53,7 @@ function rehypeShiki() {
function rehypeSlugify() { function rehypeSlugify() {
return (tree) => { return (tree) => {
let slugify = slugifyWithCounter(); const slugify = slugifyWithCounter();
visit(tree, "element", (node) => { visit(tree, "element", (node) => {
if (node.tagName === "h2" && !node.properties.id) { if (node.tagName === "h2" && !node.properties.id) {
node.properties.id = slugify(toString(node)); node.properties.id = slugify(toString(node));
@@ -64,10 +64,10 @@ function rehypeSlugify() {
function rehypeAddMDXExports(getExports) { function rehypeAddMDXExports(getExports) {
return (tree) => { return (tree) => {
let exports = Object.entries(getExports(tree)); const exports = Object.entries(getExports(tree));
for (let [name, value] of exports) { for (const [name, value] of exports) {
for (let node of tree.children) { for (const node of tree.children) {
if ( if (
node.type === "mdxjsEsm" && node.type === "mdxjsEsm" &&
new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value) new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value)
@@ -76,7 +76,7 @@ function rehypeAddMDXExports(getExports) {
} }
} }
let exportStr = `export const ${name} = ${value}`; const exportStr = `export const ${name} = ${value}`;
tree.children.push({ tree.children.push({
type: "mdxjsEsm", type: "mdxjsEsm",
@@ -93,9 +93,9 @@ function rehypeAddMDXExports(getExports) {
} }
function getSections(node) { function getSections(node) {
let sections = []; const sections = [];
for (let child of node.children ?? []) { for (const child of node.children ?? []) {
if (child.type === "element" && child.tagName === "h2") { if (child.type === "element" && child.tagName === "h2") {
sections.push(`{ sections.push(`{
title: ${JSON.stringify(toString(child))}, title: ${JSON.stringify(toString(child))},
+8 -8
View File
@@ -31,9 +31,9 @@ function extractSections() {
visit(tree, (node) => { visit(tree, (node) => {
if (node.type === "heading" || node.type === "paragraph") { if (node.type === "heading" || node.type === "paragraph") {
let content = toString(excludeObjectExpressions(node)); const content = toString(excludeObjectExpressions(node));
if (node.type === "heading" && node.depth <= 2) { if (node.type === "heading" && node.depth <= 2) {
let hash = node.depth === 1 ? null : slugify(content); const hash = node.depth === 1 ? null : slugify(content);
sections.push([content, hash, []]); sections.push([content, hash, []]);
} else { } else {
sections.at(-1)?.[2].push(content); sections.at(-1)?.[2].push(content);
@@ -45,7 +45,7 @@ function extractSections() {
} }
export default function (nextConfig = {}) { export default function (nextConfig = {}) {
let cache = new Map(); const cache = new Map();
return Object.assign({}, nextConfig, { return Object.assign({}, nextConfig, {
webpack(config, options) { webpack(config, options) {
@@ -53,20 +53,20 @@ export default function (nextConfig = {}) {
test: __filename, test: __filename,
use: [ use: [
createLoader(function () { createLoader(function () {
let appDir = path.resolve("./src/app/(docs)/docs"); const appDir = path.resolve("./src/app/(docs)/docs");
this.addContextDependency(appDir); this.addContextDependency(appDir);
let files = glob.sync("**/*.mdx", { cwd: appDir }); const files = glob.sync("**/*.mdx", { cwd: appDir });
let data = files.map((file) => { const data = files.map((file) => {
let url = `/${file.replace(/(^|\/)page\.mdx$/, "")}`; let url = `/${file.replace(/(^|\/)page\.mdx$/, "")}`;
let mdx = fs.readFileSync(path.join(appDir, file), "utf8"); const mdx = fs.readFileSync(path.join(appDir, file), "utf8");
let sections = []; let sections = [];
if (cache.get(file)?.[0] === mdx) { if (cache.get(file)?.[0] === mdx) {
sections = cache.get(file)[1]; sections = cache.get(file)[1];
} else { } else {
let vfile = { value: mdx, sections }; const vfile = { value: mdx, sections };
processor.runSync(processor.parse(vfile), vfile); processor.runSync(processor.parse(vfile), vfile);
cache.set(file, [mdx, sections]); cache.set(file, [mdx, sections]);
} }
+2
View File
@@ -1,4 +1,6 @@
import type { ResponseConfig } from "@asteasolutions/zod-to-openapi"; import type { ResponseConfig } from "@asteasolutions/zod-to-openapi";
import { z } from "@hono/zod-openapi"; import { z } from "@hono/zod-openapi";
export const authError = { export const authError = {
+1 -1
View File
@@ -81,7 +81,7 @@ export const registerCreateRunRoute = (app: App) => {
workflow_version_id: deploymentData.version, workflow_version_id: deploymentData.version,
machine_id: deploymentData.machine, machine_id: deploymentData.machine,
inputs, inputs,
isManualRun: false, runOrigin: "api",
apiUser: apiKeyTokenData, apiUser: apiKeyTokenData,
}); });
+208 -195
View File
@@ -1,8 +1,11 @@
"use server"; "use server";
import { withServerPromise } from "./withServerPromise";
import { db } from "@/db/db"; import { db } from "@/db/db";
import type { MachineType, WorkflowVersionType } from "@/db/schema"; import type {
MachineType,
WorkflowRunOriginType,
WorkflowVersionType,
} from "@/db/schema";
import { machinesTable, workflowRunsTable } from "@/db/schema"; import { machinesTable, workflowRunsTable } from "@/db/schema";
import type { APIKeyUserType } from "@/server/APIKeyBodyRequest"; import type { APIKeyUserType } from "@/server/APIKeyBodyRequest";
import { getRunsData } from "@/server/getRunsData"; import { getRunsData } from "@/server/getRunsData";
@@ -12,219 +15,229 @@ import { and, eq } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import "server-only"; import "server-only";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { withServerPromise } from "./withServerPromise";
export const createRun = withServerPromise( export const createRun = withServerPromise(
async ({ async ({
origin, origin,
workflow_version_id, workflow_version_id,
machine_id, machine_id,
inputs, inputs,
isManualRun, runOrigin,
apiUser, apiUser,
}: { }: {
origin: string; origin: string;
workflow_version_id: string | WorkflowVersionType; workflow_version_id: string | WorkflowVersionType;
machine_id: string | MachineType; machine_id: string | MachineType;
inputs?: Record<string, string | number>; inputs?: Record<string, string | number>;
isManualRun?: boolean; runOrigin?: WorkflowRunOriginType;
apiUser?: APIKeyUserType; apiUser?: APIKeyUserType;
}) => { }) => {
const machine = const machine =
typeof machine_id === "string" typeof machine_id === "string"
? await db.query.machinesTable.findFirst({ ? await db.query.machinesTable.findFirst({
where: and( where: and(
eq(machinesTable.id, machine_id), eq(machinesTable.id, machine_id),
eq(machinesTable.disabled, false) eq(machinesTable.disabled, false),
), ),
}) })
: machine_id; : machine_id;
if (!machine) { if (!machine) {
throw new Error("Machine not found"); throw new Error("Machine not found");
} }
const workflow_version_data = const workflow_version_data =
typeof workflow_version_id === "string" typeof workflow_version_id === "string"
? await db.query.workflowVersionTable.findFirst({ ? await db.query.workflowVersionTable.findFirst({
where: eq(workflowRunsTable.id, workflow_version_id), where: eq(workflowRunsTable.id, workflow_version_id),
with: { with: {
workflow: { workflow: {
columns: { columns: {
org_id: true, org_id: true,
user_id: true, user_id: true,
}, },
}, },
}, },
}) })
: workflow_version_id; : workflow_version_id;
if (!workflow_version_data) { if (!workflow_version_data) {
throw new Error("Workflow version not found"); throw new Error("Workflow version not found");
} }
if (apiUser) if (apiUser)
if (apiUser.org_id) { if (apiUser.org_id) {
// is org api call, check org only // is org api call, check org only
if (apiUser.org_id != workflow_version_data.workflow.org_id) { if (apiUser.org_id != workflow_version_data.workflow.org_id) {
throw new Error("Workflow not found"); throw new Error("Workflow not found");
} }
} else { } else {
// is user api call, check user only // is user api call, check user only
if ( if (
apiUser.user_id != workflow_version_data.workflow.user_id && apiUser.user_id != workflow_version_data.workflow.user_id &&
workflow_version_data.workflow.org_id == null workflow_version_data.workflow.org_id == null
) { ) {
throw new Error("Workflow not found"); throw new Error("Workflow not found");
} }
} }
const workflow_api = workflow_version_data.workflow_api; const workflow_api = workflow_version_data.workflow_api;
// Replace the inputs // Replace the inputs
if (inputs && workflow_api) { if (inputs && workflow_api) {
for (const key in inputs) { for (const key in inputs) {
Object.entries(workflow_api).forEach(([_, node]) => { Object.entries(workflow_api).forEach(([_, node]) => {
if (node.inputs["input_id"] === key) { if (node.inputs["input_id"] === key) {
node.inputs["input_id"] = inputs[key]; node.inputs["input_id"] = inputs[key];
} }
}); });
} }
} }
let prompt_id: string | undefined = undefined; let prompt_id: string | undefined = undefined;
const shareData = { const shareData = {
workflow_api: workflow_api, workflow_api: workflow_api,
status_endpoint: `${origin}/api/update-run`, status_endpoint: `${origin}/api/update-run`,
file_upload_endpoint: `${origin}/api/file-upload`, file_upload_endpoint: `${origin}/api/file-upload`,
}; };
prompt_id = v4(); prompt_id = v4();
// Add to our db // Add to our db
const workflow_run = await db const workflow_run = await db
.insert(workflowRunsTable) .insert(workflowRunsTable)
.values({ .values({
id: prompt_id, id: prompt_id,
workflow_id: workflow_version_data.workflow_id, workflow_id: workflow_version_data.workflow_id,
workflow_version_id: workflow_version_data.id, workflow_version_id: workflow_version_data.id,
workflow_inputs: inputs, workflow_inputs: inputs,
machine_id: machine.id, machine_id: machine.id,
origin: isManualRun ? "manual" : "api", origin: runOrigin,
}) })
.returning(); .returning();
revalidatePath(`/${workflow_version_data.workflow_id}`); revalidatePath(`/${workflow_version_data.workflow_id}`);
try { try {
switch (machine.type) { switch (machine.type) {
case "comfy-deploy-serverless": case "comfy-deploy-serverless":
case "modal-serverless": case "modal-serverless":
const _data = { const _data = {
input: { input: {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}, },
}; };
const ___result = await fetch(`${machine.endpoint}/run`, { const ___result = await fetch(`${machine.endpoint}/run`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(_data), body: JSON.stringify(_data),
cache: "no-store", cache: "no-store",
}); });
console.log(___result); console.log(___result);
if (!___result.ok) if (!___result.ok)
throw new Error( throw new Error(
`Error creating run, ${ `Error creating run, ${
___result.statusText ___result.statusText
} ${await ___result.text()}` } ${await ___result.text()}`,
); );
console.log(_data, ___result); console.log(_data, ___result);
break; break;
case "runpod-serverless": case "runpod-serverless":
const data = { const data = {
input: { input: {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}, },
}; };
if ( if (
!machine.auth_token && !machine.auth_token &&
!machine.endpoint.includes("localhost") && !machine.endpoint.includes("localhost") &&
!machine.endpoint.includes("127.0.0.1") !machine.endpoint.includes("127.0.0.1")
) { ) {
throw new Error("Machine auth token not found"); throw new Error("Machine auth token not found");
} }
const __result = await fetch(`${machine.endpoint}/run`, { const __result = await fetch(`${machine.endpoint}/run`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${machine.auth_token}`, Authorization: `Bearer ${machine.auth_token}`,
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
cache: "no-store", cache: "no-store",
}); });
console.log(__result); console.log(__result);
if (!__result.ok) if (!__result.ok)
throw new Error( throw new Error(
`Error creating run, ${ `Error creating run, ${
__result.statusText __result.statusText
} ${await __result.text()}` } ${await __result.text()}`,
); );
console.log(data, __result); console.log(data, __result);
break; break;
case "classic": case "classic":
const body = { const body = {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}; };
// console.log(body); // console.log(body);
const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`; const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`;
const _result = await fetch(comfyui_endpoint, { const _result = await fetch(comfyui_endpoint, {
method: "POST", method: "POST",
body: JSON.stringify(body), body: JSON.stringify(body),
cache: "no-store", cache: "no-store",
}); });
// console.log(_result); // console.log(_result);
if (!_result.ok) { if (!_result.ok) {
let message = `Error creating run, ${_result.statusText}`; let message = `Error creating run, ${_result.statusText}`;
try { try {
const result = await ComfyAPI_Run.parseAsync( const result = await ComfyAPI_Run.parseAsync(
await _result.json() await _result.json(),
); );
message += ` ${result.node_errors}`; message += ` ${result.node_errors}`;
} catch (error) {} } catch (error) {}
throw new Error(message); throw new Error(message);
} }
// prompt_id = result.prompt_id; // prompt_id = result.prompt_id;
break; break;
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
await db await db
.update(workflowRunsTable) .update(workflowRunsTable)
.set({ .set({
status: "failed", status: "failed",
}) })
.where(eq(workflowRunsTable.id, workflow_run[0].id)); .where(eq(workflowRunsTable.id, workflow_run[0].id));
throw e; throw e;
} }
return { // It successfully started, update the started_at time
workflow_run_id: workflow_run[0].id,
message: "Successful workflow run", await db
}; .update(workflowRunsTable)
} .set({
started_at: new Date(),
})
.where(eq(workflowRunsTable.id, workflow_run[0].id));
return {
workflow_run_id: workflow_run[0].id,
message: "Successful workflow run",
};
},
); );
export async function checkStatus(run_id: string) { export async function checkStatus(run_id: string) {
const { userId } = auth(); const { userId } = auth();
if (!userId) throw new Error("User not found"); if (!userId) throw new Error("User not found");
return await getRunsData(run_id); return await getRunsData(run_id);
} }
+239 -151
View File
@@ -1,197 +1,285 @@
"use server"; "use server";
import { db } from "@/db/db"; import { db } from "@/db/db";
import type { DeploymentType } from "@/db/schema"; import type { DeploymentType, publicShareDeployment } from "@/db/schema";
import { deploymentsTable, workflowTable } from "@/db/schema"; import { deploymentsTable, workflowTable } from "@/db/schema";
import { createNewWorkflow } from "@/server/createNewWorkflow"; import { createNewWorkflow } from "@/server/createNewWorkflow";
import { addCustomMachine } from "@/server/curdMachine"; import { addCustomMachine } from "@/server/curdMachine";
import { withServerPromise } from "@/server/withServerPromise"; import { withServerPromise } from "@/server/withServerPromise";
import { auth } from "@clerk/nextjs"; import { auth } from "@clerk/nextjs";
import { and, eq, isNull } from "drizzle-orm"; import { clerkClient } from "@clerk/nextjs/server";
import slugify from "@sindresorhus/slugify";
import { and, eq, isNull, or } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import "server-only"; import "server-only";
import { validate as isValidUUID } from "uuid";
import type { z } from "zod";
export async function createDeployments( export async function createDeployments(
workflow_id: string, workflow_id: string,
version_id: string, version_id: string,
machine_id: string, machine_id: string,
environment: DeploymentType["environment"] environment: DeploymentType["environment"],
) { ) {
const { userId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
if (!machine_id) { if (!machine_id) {
throw new Error("No machine id provided"); throw new Error("No machine id provided");
} }
// Same environment and same workflow // Same environment and same workflow
const existingDeployment = await db.query.deploymentsTable.findFirst({ const existingDeployment = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.workflow_id, workflow_id), eq(deploymentsTable.workflow_id, workflow_id),
eq(deploymentsTable.environment, environment) eq(deploymentsTable.environment, environment),
), ),
}); });
if (existingDeployment) { if (existingDeployment) {
await db await db
.update(deploymentsTable) .update(deploymentsTable)
.set({ .set({
workflow_id, workflow_id,
workflow_version_id: version_id, workflow_version_id: version_id,
machine_id, machine_id,
}) org_id: orgId,
.where(eq(deploymentsTable.id, existingDeployment.id)); })
} else { .where(eq(deploymentsTable.id, existingDeployment.id));
await db.insert(deploymentsTable).values({ } else {
user_id: userId, const workflow = await db.query.workflowTable.findFirst({
workflow_id, where: eq(workflowTable.id, workflow_id),
workflow_version_id: version_id, with: {
machine_id, user: {
environment, columns: {
}); name: true,
} },
revalidatePath(`/${workflow_id}`); },
return { },
message: `Successfully created deployment for ${environment}`, });
};
if (!workflow) throw new Error("No workflow found");
const userName = workflow.org_id
? await clerkClient.organizations
.getOrganization({
organizationId: workflow.org_id,
})
.then((x) => x.name)
: workflow.user.name;
await db.insert(deploymentsTable).values({
user_id: userId,
workflow_id,
workflow_version_id: version_id,
machine_id,
environment,
org_id: orgId,
share_slug: slugify(`${userName} ${workflow.name}`),
});
}
revalidatePath(`/${workflow_id}`);
return {
message: `Successfully created deployment for ${environment}`,
};
} }
export async function findAllDeployments() { export async function findAllDeployments() {
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
const deployments = await db.query.workflowTable.findMany({ const deployments = await db.query.workflowTable.findMany({
where: and( where: and(
orgId orgId
? eq(workflowTable.org_id, orgId) ? eq(workflowTable.org_id, orgId)
: and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)) : and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)),
), ),
columns: { columns: {
name: true, name: true,
}, },
with: { with: {
deployments: { deployments: {
columns: { columns: {
environment: true, environment: true,
}, },
with: { with: {
version: { version: {
columns: { columns: {
id: true, id: true,
snapshot: true, snapshot: true,
}, },
}, },
}, },
}, },
}, },
}); });
return deployments; return deployments;
} }
export async function findSharedDeployment(workflow_id: string) { export async function findSharedDeployment(workflow_id: string) {
const deploymentData = await db.query.deploymentsTable.findFirst({ const deploymentData = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, workflow_id) isValidUUID(workflow_id)
), ? eq(deploymentsTable.id, workflow_id)
with: { : eq(deploymentsTable.share_slug, workflow_id),
user: true, ),
machine: true, with: {
workflow: { user: true,
columns: { machine: true,
name: true, workflow: {
org_id: true, columns: {
user_id: true, name: true,
}, org_id: true,
}, user_id: true,
version: true, },
}, },
}); version: true,
},
});
return deploymentData; return deploymentData;
} }
export const removePublicShareDeployment = withServerPromise( export const removePublicShareDeployment = withServerPromise(
async (deployment_id: string) => { async (deployment_id: string) => {
await db const [removed] = await db
.delete(deploymentsTable) .delete(deploymentsTable)
.where( .where(
and( and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, deployment_id) eq(deploymentsTable.id, deployment_id),
) ),
); ).returning();
}
// revalidatePath(
// `/workflows/${removed.workflow_id}`
// )
},
); );
export const cloneWorkflow = withServerPromise( export const cloneWorkflow = withServerPromise(
async (deployment_id: string) => { async (deployment_id: string) => {
const deployment = await db.query.deploymentsTable.findFirst({ const deployment = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, deployment_id) eq(deploymentsTable.id, deployment_id),
), ),
with: { with: {
version: true, version: true,
workflow: true, workflow: true,
}, },
}); });
if (!deployment) throw new Error("No deployment found"); if (!deployment) throw new Error("No deployment found");
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
await createNewWorkflow({ await createNewWorkflow({
user_id: userId, user_id: userId,
org_id: orgId, org_id: orgId,
workflow_name: `${deployment.workflow.name} (Cloned)`, workflow_name: `${deployment.workflow.name} (Cloned)`,
workflowData: { workflowData: {
workflow: deployment.version.workflow, workflow: deployment.version.workflow,
workflow_api: deployment?.version.workflow_api, workflow_api: deployment?.version.workflow_api,
snapshot: deployment?.version.snapshot, snapshot: deployment?.version.snapshot,
}, },
}); });
redirect(`/workflows/${deployment.workflow.id}`); redirect(`/workflows/${deployment.workflow.id}`);
return { return {
message: "Successfully cloned workflow", message: "Successfully cloned workflow",
}; };
} },
); );
export const cloneMachine = withServerPromise(async (deployment_id: string) => { export const cloneMachine = withServerPromise(async (deployment_id: string) => {
const deployment = await db.query.deploymentsTable.findFirst({ const deployment = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, deployment_id) eq(deploymentsTable.id, deployment_id),
), ),
with: { with: {
machine: true, machine: true,
}, },
}); });
if (!deployment) throw new Error("No deployment found"); if (!deployment) throw new Error("No deployment found");
if (deployment.machine.type !== "comfy-deploy-serverless") if (deployment.machine.type !== "comfy-deploy-serverless")
throw new Error("Can only clone comfy-deploy-serverlesss"); throw new Error("Can only clone comfy-deploy-serverlesss");
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
await addCustomMachine({ await addCustomMachine({
gpu: deployment.machine.gpu, gpu: deployment.machine.gpu,
models: deployment.machine.models, models: deployment.machine.models,
snapshot: deployment.machine.snapshot, snapshot: deployment.machine.snapshot,
name: `${deployment.machine.name} (Cloned)`, name: `${deployment.machine.name} (Cloned)`,
type: "comfy-deploy-serverless", type: "comfy-deploy-serverless",
}); });
return { return {
message: "Successfully cloned workflow", message: "Successfully cloned workflow",
}; };
}); });
export async function findUserShareDeployment(share_id: string) {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const [deployment] = await db
.select()
.from(deploymentsTable)
.where(
and(
isValidUUID(share_id)
? eq(deploymentsTable.id, share_id)
: eq(deploymentsTable.share_slug, share_id),
eq(deploymentsTable.environment, "public-share"),
orgId
? eq(deploymentsTable.org_id, orgId)
: and(
eq(deploymentsTable.user_id, userId),
isNull(deploymentsTable.org_id),
),
),
);
if (!deployment) throw new Error("No deployment found");
return deployment;
}
export const updateSharePageInfo = withServerPromise(
async ({
id,
...data
}: z.infer<typeof publicShareDeployment> & {
id: string;
}) => {
const { userId } = auth();
if (!userId) return { error: "No user id" };
console.log(data);
const [deployment] = await db
.update(deploymentsTable)
.set(data)
.where(
and(
eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, id),
),
)
.returning();
return { message: "Info Updated" };
},
);
+36 -22
View File
@@ -16,28 +16,42 @@ export async function findAllRuns({
offset = 0, offset = 0,
}: RunsSearchTypes) { }: RunsSearchTypes) {
return await db.query.workflowRunsTable.findMany({ return await db.query.workflowRunsTable.findMany({
where: eq(workflowRunsTable.workflow_id, workflow_id), where: eq(workflowRunsTable.workflow_id, workflow_id),
orderBy: desc(workflowRunsTable.created_at), orderBy: desc(workflowRunsTable.created_at),
offset: offset, offset: offset,
limit: limit, limit: limit,
extras: { extras: {
number: sql<number>`row_number() over (order by created_at)`.as("number"), number: sql<number>`row_number() over (order by created_at)`.as(
total: sql<number>`count(*) over ()`.as("total"), "number",
}, ),
with: { total: sql<number>`count(*) over ()`.as("total"),
machine: { duration:
columns: { sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
name: true, "duration",
endpoint: true, ),
}, cold_start_duration:
}, sql<number>`(extract(epoch from started_at) - extract(epoch from created_at))`.as(
version: { "cold_start_duration",
columns: { ),
version: true, run_duration:
}, sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
}, "run_duration",
}, ),
}); },
with: {
machine: {
columns: {
name: true,
endpoint: true,
},
},
version: {
columns: {
version: true,
},
},
},
});
} }
export async function findAllRunsWithCounts(props: RunsSearchTypes) { export async function findAllRunsWithCounts(props: RunsSearchTypes) {
+53
View File
@@ -0,0 +1,53 @@
import { db } from "@/db/db";
import {
deploymentsTable,
workflowTable,
workflowVersionTable,
} from "@/db/schema";
import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm";
export async function getAllUserWorkflow() {
const { userId, orgId } = await auth();
if (!userId) {
return null;
}
const workflow = await db.query.workflowTable.findMany({
with: {
user: {
columns: {
name: true,
},
},
versions: {
limit: 1,
orderBy: desc(workflowVersionTable.version),
columns: {
id: true,
version: true,
},
},
deployments: {
limit: 1,
where: eq(deploymentsTable.environment, "public-share"),
columns: {
id: true,
},
},
},
columns: {
id: true,
updated_at: true,
name: true,
},
orderBy: desc(workflowTable.updated_at),
where:
orgId != undefined
? eq(workflowTable.org_id, orgId)
: and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)),
});
return workflow;
}
+45
View File
@@ -0,0 +1,45 @@
"use server";
import { LemonSqueezy } from "@lemonsqueezy/lemonsqueezy.js";
import "server-only";
const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY || "");
export async function getPricing() {
const products = await ls.getProducts();
return products;
}
export async function getUsage() {
const usageRecord = await ls.getUsageRecords();
return usageRecord;
}
export async function setUsage(id: number, quantity: number) {
const setUsage = await ls.createUsageRecord({
subscriptionItemId: id,
quantity: quantity,
});
return setUsage;
}
export async function getSubscription() {
const subscription = await ls.getSubscriptions();
return subscription;
}
export async function getSubscriptionItem() {
const subscriptionItem = await ls.getSubscriptionItems();
return subscriptionItem;
}
export async function getUserData() {
const user = await ls.getUser();
return user;
}