Compare commits

..
Author SHA1 Message Date
bennykok eb5e2f36cd chore: update default git url 2024-02-02 00:55:38 +08:00
bennykok 0b0015fbd1 fix: async issues with updating python to 3.11.1 2024-02-02 00:31:04 +08:00
bennykok abb4a4798c fix: revert app back with async 2024-02-02 00:11:40 +08:00
bennykok 95238469d6 chore: update custom route 2024-02-01 23:38:35 +08:00
bennykok 86f14b5bce fix: roll back to old app 2024-02-01 22:11:17 +08:00
bennykok 84802f5c4e feat: update custom nodes to latest hash 2024-02-01 19:42:46 +08:00
bennykok 68b4d93639 feat(plugin): add run log in comfydeploy plugin level 2024-02-01 18:32:03 +08:00
bennykok 5d349153d2 fix: if timestamp is undefined skip rendering 2024-02-01 18:30:57 +08:00
bennykok ea675fcd4c fix: client side refresh for component 2024-02-01 17:01:54 +08:00
bennykok 4c715b815a feat: bring back refresh indicator 2024-02-01 16:08:50 +08:00
bennykok bef6ce35de fix: auto refresh table 2024-02-01 15:24:43 +08:00
bennykok d8951df35f fix: log and async task issues with modal script 2024-02-01 13:42:01 +08:00
bennykok 38fea1e79f fix: docs page 2024-02-01 11:57:15 +08:00
bennykok 66ad3ab4c2 fix: remove base model from new machine settings 2024-01-31 23:33:55 +08:00
bennykok 488d2aee8c fix: machine build height 2024-01-31 21:41:04 +08:00
bennykok c4628f6e4c feat: output render support mp4 and webm 2024-01-31 21:23:00 +08:00
bennykok 9ba349d36a refactor(plugin): upload files logic in custom routes 2024-01-31 21:22:44 +08:00
bennykok 08ab93127e chore(plugin): add a 2 secs delay before loading the incoming workflow 2024-01-31 21:22:14 +08:00
bennykok de641f0acf fix: log viewer 2024-01-31 21:21:22 +08:00
bennykok d4d7e98487 fix: comfydeploy npm code display 2024-01-31 18:00:31 +08:00
bennykok 967a77d3a3 fix: gif file not returning file url. 2024-01-31 16:46:08 +08:00
bennykok a4cd5db360 fix: ensure when getting current pricing plan is not deleted 2024-01-31 16:02:01 +08:00
bennykok 576a6744a4 fix: safe catch stripe changed credential 2024-01-31 15:49:21 +08:00
bennykok 3e5ff7702e feat: run log dialog 2024-01-31 15:30:29 +08:00
bennykok 009589630d fix: build 2024-01-31 14:29:42 +08:00
bennykok 8eb2ce3e10 Squashed commit of the following:
commit 33c0ad7d14a85f22c57f943dab58610c13d2ac07
Author: Nicholas Koben Kao <[email protected]>
Date:   Tue Jan 30 21:56:00 2024 -0800

    revert custom form change

commit d2905ad045ad7856156e3647a81d642999352de7
Merge: 654423d e3a1d24
Author: Nicholas Koben Kao <[email protected]>
Date:   Tue Jan 30 20:50:06 2024 -0800

    merge schema

commit 654423d597e019a5ebf1ab6568c9942fcb9181c5
Author: Nicholas Koben Kao <[email protected]>
Date:   Tue Jan 30 20:49:34 2024 -0800

    merge confl.ict

commit 641724c11346319674fbb329e8e29b362117c242
Author: Nicholas Koben Kao <[email protected]>
Date:   Tue Jan 30 20:47:34 2024 -0800

    model reload on create

commit eb4dfe8e3f39a0a98eab0fcf1affe7096c12f33b
Author: Nicholas Koben Kao <[email protected]>
Date:   Tue Jan 30 17:00:03 2024 -0800

    delete models

commit 0bea9583fada102396c4e08fe6da971c94d404df
Author: Nicholas Koben Kao <[email protected]>
Date:   Tue Jan 30 14:35:15 2024 -0800

    deploy volume uploader to have timeouts only be modal related
2024-01-31 14:29:36 +08:00
bennykok e3a1d24304 feat: add auto form value provider 2024-01-31 11:39:13 +08:00
bennykok 2d033570f4 fix: modal logging with complete async io rewrite 2024-01-31 00:41:41 +08:00
bennykok 7ae25aa162 fix: turn log type from text to jsonb 2024-01-31 00:31:00 +08:00
bennykok 4b37de9ec5 fix: turn string check into any for udpate status endpoint 2024-01-30 23:48:15 +08:00
bennykok b0d1bcc303 feat: add timeout and run_log 2024-01-30 21:42:30 +08:00
bennykok 2193dd287d fix: ensure logs are disabled by default 2024-01-30 15:17:25 +08:00
bennykok 81bde40aeb fix: default value 2024-01-30 11:26:50 +08:00
bennykok 317f699c46 tweak 2024-01-30 11:17:41 +08:00
bennykok 0d1bb2aaf4 fix: machine selections 2024-01-30 11:14:46 +08:00
bennykok b0b23783ba fix: type 2024-01-30 10:54:21 +08:00
Nicholas Koben Kao eeb7310955 Merge branch 'doc-update' 2024-01-29 17:47:32 -08:00
Nicholas Koben Kao e40cc5373f new docs section 2024-01-29 17:47:23 -08:00
bennykok debdaf418c feat: revamp custom nodes install 2024-01-29 22:27:05 +08:00
Nick Kao 90107ebe1b change to show npm install instead of copying file (#5) 2024-01-29 22:04:09 +08:00
bennykok a90c6c1db4 chore: machine list machine type badge 2024-01-29 22:04:09 +08:00
bennykok 1bf3c1dcd0 fix: machine selection might revert when being refreshed 2024-01-29 22:04:06 +08:00
bennykok 24e95a1954 fix: add machine model dialog 2024-01-29 13:38:00 +08:00
bennykok 194715920e feat: add models redirect 2024-01-29 02:09:56 +08:00
bennykok 0cb1e92f4f reduce the idle timeout 2024-01-29 01:59:11 +08:00
bennykok d96811a0c3 fix: civitai picker 2024-01-28 22:08:31 +08:00
bennykok 757c587901 feat: add drop down selection for storage 2024-01-28 20:42:15 +08:00
bennykok 3b7db4480b update default comfy deploy version 2024-01-28 19:28:40 +08:00
bennykok 10bbb393a7 fix: invalid input for custom nodes 2024-01-28 17:48:22 +08:00
bennykok c1fc06fd39 ui tweak 2024-01-28 14:41:14 +08:00
bennykok 9de266fbab chore: add pricing plan lock for machine types 2024-01-28 14:36:36 +08:00
Nick Kaoandbennykok 42aaf1acb9 Nickkao/volume improvemnts v3 (#4)
* fix: attempt fixing timeout

* be validation work

* arbitrary model input, BE validation, error record creation with error logs during potential failure points

* remove unused type

---------

Co-authored-by: bennykok <[email protected]>
2024-01-28 11:09:00 +08:00
Nicholas Koben Kao 852d889397 clearer deploy instruct 2024-01-27 09:58:40 -08:00
bennykok cb01c896a0 feat: adding add models lock according to prcing plan 2024-01-28 00:51:44 +08:00
bennykok fbb7b18273 chore: enable promote code 2024-01-28 00:35:19 +08:00
bennykok 5443dcd20b feat: add loading text for machine operation 2024-01-27 22:37:29 +08:00
bennykok e5097a5ed5 chore: add machine delete logic check for only new version 2024-01-27 22:33:25 +08:00
bennykok 9a1ab21e91 chore: update prod fly io settings and name 2024-01-27 22:15:43 +08:00
bennykok d2dbe3410f feat(builder): update builder for updating time log and migrate to class objects 2024-01-27 18:07:37 +08:00
bennykok 0ce07c88f8 fix: adding more time log for the run status 2024-01-27 18:06:48 +08:00
bennykok c7727fc1be feat: revamp cold start time counter 2024-01-27 14:21:59 +08:00
bennykok 72325a4217 feat: add loading dialog for when rebuilding machine request was loading 2024-01-27 14:19:50 +08:00
bennykok 5eba792579 fix: attempt fixing timeout 2024-01-27 13:15:28 +08:00
bennykok fe8f5a49ff test timeout 2024-01-27 12:32:01 +08:00
bennykok f4d93a82e2 attempt to fix timeout issues 2024-01-27 12:25:53 +08:00
bennykok 18c410ba4e fix: machine list 2024-01-27 11:44:56 +08:00
bennykok 6aa9a44f4b fix: when there is auth token, replace it locally 2024-01-27 11:41:29 +08:00
bennykok d3e1ca1b0b Merge branch 'main' of https://github.com/comfy-deploy/comfyui-deploy 2024-01-27 11:38:28 +08:00
bennykok 8f7f06470c feat: ensure open in comfy ui will carry over the auth token 2024-01-27 11:38:10 +08:00
Nicholas Koben Kao 2a55b20887 add a note and change so that by default it uses preview toml file 2024-01-26 19:37:21 -08:00
Nicholas Koben Kao dd0af2a3e9 note about the 2 toml files 2024-01-26 19:35:00 -08:00
Nicholas Koben Kao 9a715e9766 preview toml file use with fly deploy -c 2024-01-26 19:34:07 -08:00
Nick Kao ac5aba2aa9 model volume rename, env for flyio, public and private volumes tested tgt, everything is a public model (#3) 2024-01-27 10:21:15 +08:00
bennykok 95e454e687 fix: maxDuration for workflow page 2024-01-26 21:38:25 +08:00
bennykok 8b790fe7c5 chore: update dev container 2024-01-26 20:57:21 +08:00
bennykok 9e07ccadb4 fix: build finish dialog 2024-01-26 20:57:08 +08:00
bennykok 954de12d08 feat(builder): add ipadapter support - exposing pip install 2024-01-26 20:16:10 +08:00
bennykok 731bdf3982 feat: update with error machine built dialog 2024-01-26 20:13:44 +08:00
bennykok 97aa88eac8 feat: add built finished dialog 2024-01-26 19:39:29 +08:00
bennykok d7ee49aa49 fix: remove duplicated machine badge 2024-01-26 17:15:11 +08:00
bennykok 2bace09f5e feat: add pip modules when selecting custom_nodes 2024-01-26 17:02:55 +08:00
bennykok 78721b3129 chore: update git ignore 2024-01-26 16:10:39 +08:00
bennykok 112c6bb0f6 Squashed commit of the following:
commit 2afcade4f2
Author: bennykok <[email protected]>
Date:   Fri Jan 26 16:07:54 2024 +0800

    fix: add fly io to dev container

commit d70333baa6
Author: BennyKok <[email protected]>
Date:   Fri Jan 26 07:47:41 2024 +0000

    fix: comment typo

commit 43cfebd97a
Author: BennyKok <[email protected]>
Date:   Fri Jan 26 07:33:30 2024 +0000

    fix: only create share slug when public-share deployment
2024-01-26 16:09:50 +08:00
bennykok 0311638ab1 Squashed commit of the following:
commit d70333baa6
Author: BennyKok <[email protected]>
Date:   Fri Jan 26 07:47:41 2024 +0000

    fix: comment typo

commit 43cfebd97a
Author: BennyKok <[email protected]>
Date:   Fri Jan 26 07:33:30 2024 +0000

    fix: only create share slug when public-share deployment
2024-01-26 16:06:27 +08:00
bennykok 15e1b1ccb4 Squashed commit of the following:
commit 43cfebd97a
Author: BennyKok <[email protected]>
Date:   Fri Jan 26 07:33:30 2024 +0000

    fix: only create share slug when public-share deployment
2024-01-26 15:40:42 +08:00
bennykok 4bca9a7376 fix: navbar issues 2024-01-26 12:53:21 +08:00
bennykok e95811474e fix: migration again 2024-01-26 11:58:16 +08:00
bennykok cc03c780e8 fix: migration 2024-01-26 11:52:54 +08:00
bennykok 93b1fa9c79 fix(schema): set default value for mode_type to checkpoint to preview existing db migration issues 2024-01-26 10:13:03 +08:00
bennykok 85477aba9d Squashed commit of the following:
commit c36b0ec0b374dd8ccbee3a6044ee7e3f1fefe368
Author: Nicholas Koben Kao <[email protected]>
Date:   Thu Jan 25 17:54:54 2024 -0800

    nits on wording and removing link to broken storage/:id page

commit 0777fdcf7b0002244bc713199d3d64eea6b6061e
Author: Nicholas Koben Kao <[email protected]>
Date:   Thu Jan 25 17:23:55 2024 -0800

    builder update config and such

commit 958b795bb2b6ac27ce33c5729ef265b068420e1a
Author: Nicholas Koben Kao <[email protected]>
Date:   Thu Jan 25 17:23:43 2024 -0800

    rename all from checkponit to model

commit 7a9c5636e73bd005499b141a4dd382db5672c962
Author: Nicholas Koben Kao <[email protected]>
Date:   Thu Jan 25 16:51:59 2024 -0800

    rename for consistency

commit 48bebbafab9a95388817df97c15f8ea97e0fea75
Author: Nicholas Koben Kao <[email protected]>
Date:   Thu Jan 25 16:18:36 2024 -0800

    bulider

commit 81dacd9af457886f2f027994d225a7748c738abb
Author: Nicholas Koben Kao <[email protected]>
Date:   Thu Jan 25 16:17:56 2024 -0800

    different types of models
2024-01-26 10:08:37 +08:00
bennykok 62a69dba06 chore: update example 2024-01-26 00:28:33 +08:00
bennykok 8305134a8e fix: make sure we were able to detect the pricing plan correct when not in a org 2024-01-26 00:28:05 +08:00
bennykok 6f0499c657 Merge branch 'nickkao/checkpoint-volume'
# Conflicts:
#	web/bun.lockb
2024-01-25 21:07:27 +08:00
bennykok b968fded32 fix: update clerk 2024-01-25 20:35:54 +08:00
bennykok aa419c80f1 fix: add after signout url 2024-01-25 20:25:11 +08:00
bennykok 3c1e49b451 fix: login issues 2024-01-25 19:34:40 +08:00
Nicholas Koben Kao 72312c7a40 generate 2024-01-24 22:37:02 -08:00
Nicholas Koben Kao f22f5eef4e Merge branch 'main' into nickkao/checkpoint-volume 2024-01-24 22:33:00 -08:00
Nicholas Koben Kao 982ef0780d lock 2024-01-24 22:32:48 -08:00
Nicholas Koben Kao a295df973f clean up 2024-01-24 22:28:50 -08:00
Nicholas Koben Kao d8df580339 remove divergent sql 2024-01-24 22:26:42 -08:00
Nicholas Koben Kao 0d88907cfd clean up web 2024-01-24 22:22:59 -08:00
Nicholas Koben Kao 4c047aea62 clean up checkpoint build 2024-01-24 22:20:00 -08:00
Nicholas Koben Kao db04d02d34 working 2024-01-24 22:15:12 -08:00
BennyKok 3d2eacccc9 fix: build 2024-01-25 12:08:09 +08:00
BennyKok 85e4219ea7 fix: remove unused import, fix build 2024-01-25 11:16:41 +08:00
BennyKok df4dfba31d chore: migrate sign in to domain relative path with clerk, add .env.example as well 2024-01-25 10:57:13 +08:00
Nicholas Koben Kao 911cc8d16b merge0 2024-01-24 16:26:16 -08:00
Nicholas Koben Kao 224b006ec2 somethingg 2024-01-24 15:15:05 -08:00
Nicholas Koben Kao dfd139294f print 2024-01-24 14:44:05 -08:00
Nicholas Koben Kao eb04f246a4 status 2024-01-24 12:53:43 -08:00
BennyKok d644edd3c4 Merge branch 'main' into main-private 2024-01-25 00:40:31 +08:00
BennyKok 1143b469ad Merge branch 'main' of https://github.com/comfy-deploy/comfyui-deploy into main-private 2024-01-25 00:37:52 +08:00
BennyKok 0f9e0c9c76 feat: pricing plan + usage page 2024-01-25 00:34:41 +08:00
Nicholas Koben Kao b1e9bcc4e6 blah 2024-01-24 01:58:00 -08:00
BennyKok 14c3ca6bf5 Merge branch 'pricing-plan'
# Conflicts:
#	web/bun.lockb
#	web/drizzle/meta/_journal.json
#	web/src/db/schema.ts
2024-01-23 11:43:53 +08:00
BennyKok 7a7ced3e08 drop migration for merge 2024-01-23 11:41:28 +08:00
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
152 changed files with 22751 additions and 1939 deletions
+5
View File
@@ -12,6 +12,11 @@ RUN curl -L https://fly.io/install.sh | sh
ENV FLYCTL_INSTALL="/root/.fly"
ENV PATH="$FLYCTL_INSTALL/bin:$PATH"
RUN sudo apt-get update
RUN sudo apt-get install python3-pip python3 -y
RUN pip3 install modal
# RUN echo 'export FLYCTL_INSTALL="/home/node/.fly"' >> ~/.bashrc
# RUN echo 'export PATH="$FLYCTL_INSTALL/bin:$PATH"' >> ~/.bashrc
+2 -1
View File
@@ -14,7 +14,8 @@
"stivo.tailwind-fold",
"streetsidesoftware.code-spell-checker",
"GitHub.copilot",
"ms-azuretools.vscode-docker"
"ms-azuretools.vscode-docker",
"ms-python.python"
]
}
}
+2 -1
View File
@@ -1,2 +1,3 @@
__pycache__
.DS_Store
.DS_Store
.vercel
+2 -3
View File
@@ -75,11 +75,10 @@ Major areas
3. `bun i`
4. Start docker
5. `cp .env.example .env.local`
6. Replace `JWT_SECRET` with `openssl rand -hex 32`
6. Repace `JWT_SECRET` with `openssl rand -hex 32`
7. Get a local clerk dev key for `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY`
8. Keep a terminal live for `bun run db-dev`
9. Execute the local migration to create the initial data `bun run migrate-local`
10. Finally start the next server with `bun dev`
9. Finally start the next server with `bun dev`
**Schema Changes**
+4 -1
View File
@@ -1,5 +1,8 @@
MODAL_TOKEN_ID=
MODAL_TOKEN_SECRET=
CIVITAI_API_KEY=
# On production set to False
DEPLOY_TEST_FLAG=True
DEPLOY_TEST_FLAG=True
CIVITAI_API_KEY=
PUBLIC_MODEL_VOLUME_NAME=
+1 -1
View File
@@ -13,4 +13,4 @@ RUN mkdir builds
# CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "80", "--lifespan", "on"]
CMD ["python", "src/main.py"]
# If running behind a proxy like Nginx or Traefik add --proxy-headers
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
+5 -2
View File
@@ -42,6 +42,9 @@ fly secrets set MODAL_TOKEN_SECRET=
## To deploy
We have 2 `toml` files one for `production` and the other for `staging`
it will default to `staging` if you don't include the `-c` flag to choose your config file
```
// model-builder/fly.toml
app = <APP_NAME>
@@ -53,5 +56,5 @@ fly launch
```
if not, run this instead
```
fly deploy
```
fly deploy -c "toml file"
```
+22
View File
@@ -0,0 +1,22 @@
# fly.toml app configuration file generated for modal-builder on 2024-01-03T22:29:34+08:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = "modal-builder-production"
primary_region = "sea"
[build]
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
[[vm]]
cpu_kind = 'shared'
cpus = 1
memory_mb = 1024
+9 -4
View File
@@ -1,10 +1,10 @@
# fly.toml app configuration file generated for modal-builder on 2024-01-03T22:29:34+08:00
# fly.toml app configuration file generated for modal-builder-preview on 2024-01-25T09:26:31Z
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = "modal-builder"
primary_region = "sea"
app = 'modal-builder-preview'
primary_region = 'sea'
[build]
@@ -14,4 +14,9 @@ primary_region = "sea"
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
processes = ['app']
[[vm]]
cpu_kind = 'shared'
cpus = 1
memory_mb = 1024
+139 -1
View File
@@ -8,6 +8,7 @@ from enum import Enum
import json
import subprocess
import time
from uuid import uuid4
from contextlib import asynccontextmanager
import asyncio
import threading
@@ -18,6 +19,7 @@ import requests
from urllib.parse import parse_qs
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp, Scope, Receive, Send
import modal
from concurrent.futures import ThreadPoolExecutor
@@ -45,6 +47,8 @@ machine_id_websocket_dict = {}
machine_id_status = {}
fly_instance_id = os.environ.get('FLY_ALLOC_ID', 'local').split('-')[0]
civitai_api_key = os.environ.get('FLY_ALLOC_ID', 'local')
public_model_volume_name = os.environ.get('PUBLIC_MODEL_VOLUME_NAME', 'local')
class FlyReplayMiddleware(BaseHTTPMiddleware):
@@ -139,6 +143,7 @@ def read_root():
class GitCustomNodes(BaseModel):
hash: str
disabled: bool
pip: Optional[List[str]] = None
class FileCustomNodes(BaseModel):
filename: str
@@ -174,6 +179,9 @@ class Item(BaseModel):
snapshot: Snapshot
models: List[Model]
callback_url: str
model_volume_name: str
run_timeout: Optional[int] = Field(default=60 * 5)
idle_timeout: Optional[int] = Field(default=60)
gpu: GPUType = Field(default=GPUType.T4)
@field_validator('gpu')
@@ -223,6 +231,127 @@ async def websocket_endpoint(websocket: WebSocket, machine_id: str):
# return {"Hello": "World"}
# definition based on web schema
class UploadType(str, Enum):
checkpoint = "checkpoint"
lora = "lora"
embedding = "embedding"
clip = "clip"
clip_vision = "clip_vision"
configs = "configs"
controlnet = "controlnet"
upscale_models = "upscale_models"
vae = "vae"
ipadapter = "ipadapter"
other = "other"
class UploadBody(BaseModel):
download_url: str
volume_name: str
volume_id: str
model_id: str
upload_type: UploadType
callback_url: str
# based on ComfyUI's model dir, and our mappings in ./src/template/data/extra_model_paths.yaml
UPLOAD_TYPE_DIR_MAP = {
UploadType.checkpoint: "checkpoints",
UploadType.lora: "loras",
UploadType.embedding: "embeddings",
UploadType.clip: "clip",
UploadType.clip_vision: "clip_vision",
UploadType.configs: "configs",
UploadType.controlnet: "controlnet",
UploadType.upscale_models: "upscale_models",
UploadType.vae: "vae",
UploadType.ipadapter: "ipadapter",
UploadType.other: "",
}
class DeleteBody(BaseModel):
volume_name: str
path: str
file_name: str
@app.post("/delete-volume-model")
async def delete_model(body: DeleteBody):
global last_activity_time
last_activity_time = time.time()
logger.info(f"Extended inactivity time to {global_timeout}")
full_path = f"{body.path.rstrip('/')}/{body.file_name}"
rm_process = await asyncio.subprocess.create_subprocess_exec("modal", "volume", "rm", body.volume_name, full_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,)
await rm_process.wait()
logger.info(f"Successfully deleted: {full_path} from volume: {body.volume_name}")
stdout, stderr = await rm_process.communicate()
if stdout:
logger.info(f"cp_process stdout: {stdout.decode()}")
if stderr:
logger.info(f"cp_process stderr: {stderr.decode()}")
if rm_process.returncode == 0:
return JSONResponse(status_code=200, content={"status":f"Successfully deleted {full_path} from volume {body.volume_name}"})
else:
return JSONResponse(status_code=500, content={"status": "error", "error": stderr.decode()})
@app.post("/upload-volume")
async def upload_model(body: UploadBody):
global last_activity_time
last_activity_time = time.time()
logger.info(f"Extended inactivity time to {global_timeout}")
asyncio.create_task(upload_logic(body))
# check that this
return JSONResponse(status_code=200, content={"message": "Volume uploading", "build_machine_instance_id": fly_instance_id})
async def upload_logic(body: UploadBody):
folder_path = f"/app/builds/{body.volume_id}-{uuid4()}"
cp_process = await asyncio.subprocess.create_subprocess_exec("cp", "-r", "/app/src/volume_builder", folder_path)
await cp_process.wait()
upload_path = UPLOAD_TYPE_DIR_MAP[body.upload_type]
if upload_path == "":
# TODO: deal with custom paths
pass
config = {
"volume_names": {
body.volume_name: {"download_url": body.download_url, "folder_path": upload_path}
},
"volume_paths": {
body.volume_name: f'/volumes/{uuid4()}'
},
"callback_url": body.callback_url,
"callback_body": {
"model_id": body.model_id,
"volume_id": body.volume_id,
"folder_path": upload_path,
},
"civitai_api_key": os.environ.get('CIVITAI_API_KEY'),
"app_name": f"vol_name_{uuid4()}"
}
with open(f"{folder_path}/config.py", "w") as f:
f.write("config = " + json.dumps(config))
process = await asyncio.subprocess.create_subprocess_shell(
f"python runner.py",
cwd=folder_path,
env={**os.environ, "COLUMNS": "10000"}
)
await process.wait()
# import modal
# modal.deploy_stub(stub)
# stub["download_model"].web_url
@app.post("/create")
async def create_machine(item: Item):
@@ -308,12 +437,21 @@ async def build_logic(item: Item):
cp_process = await asyncio.subprocess.create_subprocess_exec("cp", "-r", "/app/src/template", folder_path)
await cp_process.wait()
pip_modules = set()
for git_custom_node in item.snapshot.git_custom_nodes.values():
if git_custom_node.pip:
pip_modules.update(git_custom_node.pip)
# Write the config file
config = {
"name": item.name,
"deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False"),
"gpu": item.gpu,
"civitai_token": os.environ.get("CIVITAI_TOKEN", "")
"public_model_volume": public_model_volume_name,
"private_model_volume": item.model_volume_name,
"pip": list(pip_modules),
"run_timeout": item.run_timeout,
"idle_timeout": item.idle_timeout,
}
with open(f"{folder_path}/config.py", "w") as f:
f.write("config = " + json.dumps(config))
+268 -112
View File
@@ -1,13 +1,16 @@
from config import config
import modal
from modal import Image, Mount, web_endpoint, Stub, asgi_app
from modal import Image, Mount, web_endpoint, Stub, asgi_app, method, enter, exit
import json
import urllib.request
import urllib.parse
from pydantic import BaseModel
from fastapi import FastAPI, Request
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse
from volume_setup import volumes
from datetime import datetime
import aiohttp
from aiohttp import TCPConnector
# deploy_test = False
import os
@@ -27,18 +30,17 @@ deploy_test = config["deploy_test"] == "True"
web_app = FastAPI()
print(config)
print("deploy_test ", deploy_test)
print('volumes', volumes)
stub = Stub(name=config["name"])
# print(stub.app_id)
if not deploy_test:
# dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data"))
# dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data"))
dockerfile_image = (
modal.Image.debian_slim()
.env({
"CIVITAI_TOKEN": config["civitai_token"],
})
modal.Image.debian_slim(
python_version="3.11.1"
)
.apt_install("git", "wget")
.pip_install(
"git+https://github.com/modal-labs/asgiproxy.git", "httpx", "tqdm"
@@ -51,20 +53,24 @@ if not deploy_test:
# Install comfyui manager
"cd /comfyui/custom_nodes && git clone https://github.com/ltdrdata/ComfyUI-Manager.git",
"cd /comfyui/custom_nodes/ComfyUI-Manager && git reset --hard 9c86f62b912f4625fe2b929c7fc61deb9d16f6d3",
"cd /comfyui/custom_nodes/ComfyUI-Manager && git reset --hard 8dd801435bb75aa1d24b7e382bac070a4c18bc51",
"cd /comfyui/custom_nodes/ComfyUI-Manager && pip install -r requirements.txt",
"cd /comfyui/custom_nodes/ComfyUI-Manager && mkdir startup-scripts",
)
# .run_commands(f"cat /comfyui/server.py")
# .run_commands(f"ls /comfyui/app")
# .run_commands(
# # Install comfy deploy
# "cd /comfyui/custom_nodes && git clone https://github.com/BennyKok/comfyui-deploy.git",
# )
# .copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui")
.copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui")
.copy_local_file(f"{current_directory}/data/start.sh", "/start.sh")
.run_commands("chmod +x /start.sh")
# Restore the custom nodes first
.pip_install(config["pip"])
.copy_local_file(f"{current_directory}/data/restore_snapshot.py", "/")
.copy_local_file(f"{current_directory}/data/snapshot.json", "/comfyui/custom_nodes/ComfyUI-Manager/startup-scripts/restore-snapshot.json")
.run_commands("python restore_snapshot.py")
@@ -80,7 +86,7 @@ if not deploy_test:
# Time to wait between API check attempts in milliseconds
COMFY_API_AVAILABLE_INTERVAL_MS = 50
# Maximum number of API check attempts
COMFY_API_AVAILABLE_MAX_RETRIES = 500
COMFY_API_AVAILABLE_MAX_RETRIES = 1000
# Time to wait between poll attempts in milliseconds
COMFY_POLLING_INTERVAL_MS = 250
# Maximum number of poll attempts
@@ -89,48 +95,34 @@ COMFY_POLLING_MAX_RETRIES = 1000
COMFY_HOST = "127.0.0.1:8188"
def check_server(url, retries=50, delay=500):
import requests
import time
"""
Check if a server is reachable via HTTP GET request
Args:
- url (str): The URL to check
- retries (int, optional): The number of times to attempt connecting to the server. Default is 50
- delay (int, optional): The time in milliseconds to wait between retries. Default is 500
Returns:
bool: True if the server is reachable within the given number of retries, otherwise False
"""
for i in range(retries):
async def check_server(url, retries=50, delay=500):
import aiohttp
# for i in range(retries):
while True:
try:
response = requests.get(url)
# If the response status code is 200, the server is up and running
if response.status_code == 200:
print(f"runpod-worker-comfy - API is reachable")
return True
except requests.RequestException as e:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
# If the response status code is 200, the server is up and running
if response.status == 200:
print(f"comfy-modal - API is reachable")
return True
except Exception as e:
# If an exception occurs, the server may not be ready
pass
# print(f"runpod-worker-comfy - trying")
# Wait for the specified delay before retrying
time.sleep(delay / 1000)
await asyncio.sleep(delay / 1000)
print(
f"runpod-worker-comfy - Failed to connect to server at {url} after {retries} attempts."
f"comfy-modal - Failed to connect to server at {url} after {retries} attempts."
)
return False
def check_status(prompt_id):
req = urllib.request.Request(
f"http://{COMFY_HOST}/comfyui-deploy/check-status?prompt_id={prompt_id}")
return json.loads(urllib.request.urlopen(req).read())
async def check_status(prompt_id):
async with aiohttp.ClientSession() as session:
async with session.get(f"http://{COMFY_HOST}/comfyui-deploy/check-status?prompt_id={prompt_id}") as response:
return await response.json()
class Input(BaseModel):
@@ -140,12 +132,12 @@ class Input(BaseModel):
file_upload_endpoint: str
def queue_workflow_comfy_deploy(data: Input):
async def queue_workflow_comfy_deploy(data: Input):
data_str = data.json()
data_bytes = data_str.encode('utf-8')
req = urllib.request.Request(
f"http://{COMFY_HOST}/comfyui-deploy/run", data=data_bytes)
return json.loads(urllib.request.urlopen(req).read())
async with aiohttp.ClientSession() as session:
async with session.post(f"http://{COMFY_HOST}/comfyui-deploy/run", data=data_bytes) as response:
return await response.json()
class RequestInput(BaseModel):
@@ -156,90 +148,253 @@ image = Image.debian_slim()
target_image = image if deploy_test else dockerfile_image
run_timeout = config["run_timeout"]
idle_timeout = config["idle_timeout"]
@stub.function(image=target_image, gpu=config["gpu"])
def run(input: Input):
import subprocess
import time
# Make sure that the ComfyUI API is available
print(f"comfy-modal - check server")
import asyncio
command = ["python", "main.py",
"--disable-auto-launch", "--disable-metadata"]
server_process = subprocess.Popen(command, cwd="/comfyui")
@stub.cls(
image=target_image,
gpu=config["gpu"] ,
volumes=volumes,
timeout=(config["run_timeout"] + 20),
container_idle_timeout=config["idle_timeout"],
allow_concurrent_inputs=1,
)
class ComfyDeployRunner:
check_server(
f"http://{COMFY_HOST}",
COMFY_API_AVAILABLE_MAX_RETRIES,
COMFY_API_AVAILABLE_INTERVAL_MS,
)
machine_logs = []
job_input = input
async def read_stream(self, stream, isStderr):
import time
while True:
try:
line = await stream.readline()
if line:
l = line.decode('utf-8').strip()
# print(f"comfy-modal - got input {job_input}")
if l == "":
continue
# Queue the workflow
try:
# job_input is the json input
queued_workflow = queue_workflow_comfy_deploy(
job_input) # queue_workflow(workflow)
prompt_id = queued_workflow["prompt_id"]
print(f"comfy-modal - queued workflow with ID {prompt_id}")
except Exception as e:
import traceback
print(traceback.format_exc())
return {"error": f"Error queuing workflow: {str(e)}"}
if not isStderr:
print(l, flush=True)
self.machine_logs.append({
"logs": l,
"timestamp": time.time()
})
# Poll for completion
print(f"comfy-modal - wait until image generation is complete")
retries = 0
status = ""
try:
print("getting request")
while retries < COMFY_POLLING_MAX_RETRIES:
status_result = check_status(prompt_id=prompt_id)
# history = get_history(prompt_id)
else:
# is error
# logger.error(l)
print(l, flush=True)
self.machine_logs.append({
"logs": l,
"timestamp": time.time()
})
else:
break
except asyncio.CancelledError:
# Handle the cancellation here if needed
break # Break out of the loop on cancellation
# Exit the loop if we have found the history
# if prompt_id in history and history[prompt_id].get("outputs"):
# break
@enter()
async def setup(self):
import subprocess
import time
# Exit the loop if we have found the status both success or failed
if 'status' in status_result and (status_result['status'] == 'success' or status_result['status'] == 'failed'):
status = status_result['status']
print(status)
break
else:
# Wait before trying again
time.sleep(COMFY_POLLING_INTERVAL_MS / 1000)
retries += 1
# Make sure that the ComfyUI API is available
print(f"comfy-modal - check server")
self.server_process = await asyncio.subprocess.create_subprocess_shell(
f"python main.py --disable-auto-launch --disable-metadata",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd="/comfyui",
# env={**os.environ, "COLUMNS": "10000"}
)
@exit()
async def cleanup(self, exc_type, exc_value, traceback):
print(f"comfy-modal - cleanup", exc_type, exc_value, traceback)
# Get the current event loop
loop = asyncio.get_event_loop()
# Check if the event loop is closed
if loop.is_closed():
print("The event loop is closed.")
else:
return {"error": "Max retries reached while waiting for image generation"}
except Exception as e:
return {"error": f"Error waiting for image generation: {str(e)}"}
try:
self.server_process.terminate()
await self.server_process.wait()
except Exception as e:
print("Issues when cleaning up", e)
print("The event loop is open.")
print(f"comfy-modal - Finished, turning off")
server_process.terminate()
@method()
async def run(self, input: Input):
import signal
import time
import aiohttp
# Get the generated image and return it as URL in an AWS bucket or as base64
# images_result = process_output_images(history[prompt_id].get("outputs"), job["id"])
# result = {**images_result, "refresh_worker": REFRESH_WORKER}
result = {"status": status}
stdout_task = asyncio.create_task(
self.read_stream(self.server_process.stdout, False))
stderr_task = asyncio.create_task(
self.read_stream(self.server_process.stderr, True))
try:
class TimeoutError(Exception):
pass
return result
print("Running remotely on Modal!")
def timeout_handler(signum, frame):
data = json.dumps({
"run_id": input.prompt_id,
"status": "timeout",
"time": datetime.now().isoformat()
}).encode('utf-8')
req = urllib.request.Request(input.status_endpoint, data=data, method='POST')
urllib.request.urlopen(req)
raise TimeoutError("Operation timed out")
signal.signal(signal.SIGALRM, timeout_handler)
try:
signal.alarm(run_timeout)
ok = await check_server(
f"http://{COMFY_HOST}",
COMFY_API_AVAILABLE_MAX_RETRIES,
COMFY_API_AVAILABLE_INTERVAL_MS,
)
if not ok:
raise Exception("ComfyUI API is not available")
# Set an alarm for some seconds in the future
data = json.dumps({
"run_id": input.prompt_id,
"status": "started",
"time": datetime.now().isoformat()
}).encode('utf-8')
async with aiohttp.ClientSession() as session:
async with session.post(input.status_endpoint, data=data) as response:
pass
job_input = input
try:
queued_workflow = await queue_workflow_comfy_deploy(job_input) # queue_workflow(workflow)
prompt_id = queued_workflow["prompt_id"]
print(f"comfy-modal - queued workflow with ID {prompt_id}")
except Exception as e:
import traceback
print(traceback.format_exc())
return {"error": f"Error queuing workflow: {str(e)}"}
# Poll for completion
print(f"comfy-modal - wait until image generation is complete")
retries = 0
status = ""
try:
print("getting request")
# while retries < COMFY_POLLING_MAX_RETRIES:
while True:
status_result = await check_status(prompt_id=prompt_id)
if 'status' in status_result and (status_result['status'] == 'success' or status_result['status'] == 'failed'):
status = status_result['status']
print(status)
break
else:
# Wait before trying again
await asyncio.sleep(COMFY_POLLING_INTERVAL_MS / 1000)
retries += 1
else:
return {"error": "Max retries reached while waiting for image generation"}
except Exception as e:
return {"error": f"Error waiting for image generation: {str(e)}"}
print(f"comfy-modal - Finished, turning off")
result = {"status": status}
except TimeoutError:
print("Operation timed out")
return {"status": "failed"}
except Exception as e:
print(f"Unexpected error occurred: {str(e)}")
data = json.dumps({
"run_id": input.prompt_id,
"status": "failed",
"time": datetime.now().isoformat()
}).encode('utf-8')
async with aiohttp.ClientSession() as session:
async with session.post(input.status_endpoint, data=data) as response:
print("response", response)
self.machine_logs.append({
"logs": str(e),
"timestamp": time.time()
})
finally:
signal.alarm(0)
print("uploading log_data")
data = json.dumps({
"run_id": input.prompt_id,
"time": datetime.now().isoformat(),
"log_data": self.machine_logs
}).encode('utf-8')
print("my logs", len(self.machine_logs))
# Clear logs
timeout = aiohttp.ClientTimeout(total=60) # 60 seconds total timeout
# Use HTTP/1.1 explicitly and increase the connection pool size
connector = TCPConnector(limit=100, force_close=True, enable_cleanup_closed=True)
async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
try:
async with session.post(input.status_endpoint, data=data) as response:
print("response", response)
# Process your response here
except asyncio.TimeoutError:
print("Request timed out")
except Exception as e:
print(f"An error occurred: {e}")
print("uploaded log_data")
# print(data)
self.machine_logs = []
finally:
stdout_task.cancel()
stderr_task.cancel()
await stdout_task
await stderr_task
return result
@web_app.post("/run")
async def bar(request_input: RequestInput):
# print(request_input)
async def post_run(request_input: RequestInput):
if not deploy_test:
run.spawn(request_input.input)
return {"status": "success"}
# pass
# print(request_input.input.prompt_id, request_input.input.status_endpoint)
data = json.dumps({
"run_id": request_input.input.prompt_id,
"status": "queued",
"time": datetime.now().isoformat()
}).encode('utf-8')
req = urllib.request.Request(request_input.input.status_endpoint, data=data, method='POST')
urllib.request.urlopen(req)
model = ComfyDeployRunner()
call = await model.run.spawn.aio(request_input.input)
@stub.function(image=image)
print("call", call)
# call = run.spawn()
return {"call_id": None}
return {"call_id": None}
@stub.function(image=image
,volumes=volumes
)
@asgi_app()
def comfyui_api():
return web_app
@@ -289,6 +444,7 @@ def spawn_comfyui_in_background():
# to be on a single container.
concurrency_limit=1,
timeout=10 * 60,
volumes=volumes,
)
@asgi_app()
def comfyui_app():
@@ -307,4 +463,4 @@ def comfyui_app():
},
)()
return make_simple_proxy_app(ProxyContext(config))
return make_simple_proxy_app(ProxyContext(config))
+10 -1
View File
@@ -1 +1,10 @@
config = {"name": "my-app", "deploy_test": "True", "gpu": "T4"}
config = {
"name": "my-app",
"deploy_test": "False",
"gpu": "T4",
"public_model_volume": "model-store",
"private_model_volume": "private-model-store",
"pip": [],
"run_timeout": 60 * 5,
"idle_timeout": 60
}
@@ -1,11 +1,26 @@
comfyui:
base_path: /runpod-volume/ComfyUI/
checkpoints: models/checkpoints/
clip: models/clip/
clip_vision: models/clip_vision/
configs: models/configs/
controlnet: models/controlnet/
embeddings: models/embeddings/
loras: models/loras/
upscale_models: models/upscale_models/
vae: models/vae/
public:
base_path: /public_models/
checkpoints: checkpoints
clip: clip
clip_vision: clip_vision
configs: configs
controlnet: controlnet
embeddings: embeddings
loras: loras
upscale_models: upscale_models
vae: vae
ipadapter: ipadapter
private:
base_path: /private_models/
checkpoints: checkpoints
clip: clip
clip_vision: clip_vision
configs: configs
controlnet: controlnet
embeddings: embeddings
loras: loras
upscale_models: upscale_models
vae: vae
ipadapter: ipadapter
@@ -49,15 +49,9 @@ with open('models.json') as f:
models = json.load(f)
for model in models:
import os
if "civitai.com/api" in model['url'] and not "token=" in model['url']:
if "?" in model['url']:
model['url'] += "&token=" + os.environ.get('CIVITAI_TOKEN', '')
else:
model['url'] += "?token=" + os.environ.get('CIVITAI_TOKEN', '')
response = requests.request("POST", f"{root_url}/model/install", json=model, headers=headers)
print(response.text)
# Close the server
server_process.terminate()
print("Finished installing dependencies.")
print("Finished installing dependencies.")
@@ -1,5 +1,10 @@
{
"comfyui": "d0165d819afe76bd4e6bdd710eb5f3e571b6a804",
"git_custom_nodes": {},
"file_custom_nodes": []
}
"comfyui": "d0165d819afe76bd4e6bdd710eb5f3e571b6a804",
"git_custom_nodes": {
"https://github.com/BennyKok/comfyui-deploy.git": {
"hash": "df46e3a0e5ad93fa71f5d216997e376af33b2a6d",
"disabled": false
}
},
"file_custom_nodes": []
}
@@ -0,0 +1,9 @@
import modal
from config import config
public_model_volume = modal.Volume.persisted(config["public_model_volume"])
private_volume = modal.Volume.persisted(config["private_model_volume"])
PUBLIC_BASEMODEL_DIR = "/public_models"
PRIVATE_BASEMODEL_DIR = "/private_models"
volumes = {PUBLIC_BASEMODEL_DIR: public_model_volume, PRIVATE_BASEMODEL_DIR: private_volume}
@@ -0,0 +1,76 @@
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
import modal
from config import config
import os
import subprocess
from pprint import pprint
stub = modal.Stub(config["app_name"])
vol_name_to_links = config["volume_names"]
vol_name_to_path = config["volume_paths"]
callback_url = config["callback_url"]
callback_body = config["callback_body"]
civitai_key = config["civitai_api_key"]
web_app = FastAPI()
# Volume names may only contain alphanumeric characters, dashes, periods, and underscores, and must be less than 64 characters in length.
def is_valid_name(name: str) -> bool:
allowed_characters = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._")
return 0 < len(name) <= 64 and all(char in allowed_characters for char in name)
def create_volumes(volume_names, paths):
path_to_vol = {}
for volume_name in volume_names.keys():
if not is_valid_name(volume_name):
pass
modal_volume = modal.Volume.persisted(volume_name)
path_to_vol[paths[volume_name]] = modal_volume
return path_to_vol
volumes = create_volumes(vol_name_to_links, vol_name_to_path)
image = (
modal.Image.debian_slim().apt_install("wget").pip_install("requests")
)
# download config { "download_url": "", "folder_path": ""}
timeout=5000
@stub.function(volumes=volumes, image=image, timeout=timeout, gpu=None)
def download_model(volume_name, download_config):
import requests
download_url = download_config["download_url"]
folder_path = download_config["folder_path"]
volume_base_path = vol_name_to_path[volume_name]
model_store_path = os.path.join(volume_base_path, folder_path)
modified_download_url = download_url + ("&" if "?" in download_url else "?") + "token=" + civitai_key # civitai requires auth
print('downloading', modified_download_url)
subprocess.run(["wget", modified_download_url , "--content-disposition", "-P", model_store_path, "-nv"])
subprocess.run(["ls", "-la", volume_base_path])
subprocess.run(["ls", "-la", model_store_path])
volumes[volume_base_path].commit()
status = {"status": "success"}
requests.post(callback_url, json={**status, **callback_body})
print(f"finished! sending to {callback_url}")
pprint({**status, **callback_body})
@stub.function(image=image)
# @modal.asgi_app()
def simple_download():
import requests
try:
list(download_model.starmap([(vol_name, download_conf) for vol_name,download_conf in vol_name_to_links.items()]))
except modal.exception.FunctionTimeoutError as e:
status = {"status": "failed", "error_logs": f"{str(e)}", "timeout": timeout}
requests.post(callback_url, json={**status, **callback_body})
print(f"finished! sending to {callback_url}")
pprint({**status, **callback_body})
except Exception as e:
status = {"status": "failed", "error_logs": str(e)}
requests.post(callback_url, json={**status, **callback_body})
print(f"finished! sending to {callback_url}")
pprint({**status, **callback_body})
@@ -0,0 +1,19 @@
config = {
"volume_names": {
"user4": {
"download_url": "https://civitai.com/api/download/models/11745",
"folder_path": "checkpoints"
}
},
"volume_paths": {
"user4": "/volumes/something",
},
"callback_url": "",
"callback_body": {
"model_id": "",
"volume_id": "",
"folder_path": "checkpoints",
},
"civitai_api_key": "",
"app_name": "",
}
@@ -0,0 +1,12 @@
import modal
import requests
from app import stub
from config import config
modal.runner.deploy_stub(stub)
print("deployed stub")
# web_url = stub["simple_download"].web_url
f = modal.Function.lookup(config['app_name'], "simple_download")
f.spawn()
# print(f"web_url: {web_url}")
# requests.post(web_url)
+3 -1
View File
@@ -29,7 +29,9 @@ class ComfyUIDeployExternalText:
CATEGORY = "text"
def run(self, input_id, default_value=None):
return [default_value]
if not input_id or len(input_id.strip()) == 0:
return [default_value]
return [input_id]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalText": ComfyUIDeployExternalText}
+1 -186
View File
@@ -22,8 +22,6 @@ from logging.handlers import RotatingFileHandler
from enum import Enum
from urllib.parse import quote
import threading
import hashlib
import aiohttp
api = None
api_task = None
@@ -101,7 +99,6 @@ async def comfy_deploy_run(request):
prompt_metadata[prompt_id] = {
'status_endpoint': data.get('status_endpoint'),
'file_upload_endpoint': data.get('file_upload_endpoint'),
'workflow_api': workflow_api
}
try:
@@ -118,8 +115,6 @@ async def comfy_deploy_run(request):
"stack_trace": stack_trace
}
})
# When there are critical errors, the prompt is actually not run
await update_run(prompt_id, Status.FAILED)
return web.Response(status=500, reason=f"{error_type}: {e}, {stack_trace_short}")
status = 200
@@ -148,138 +143,6 @@ async def comfy_deploy_run(request):
sockets = dict()
def get_comfyui_path_from_file_path(file_path):
file_path_parts = file_path.split("\\")
if file_path_parts[0] == "input":
print("matching input")
file_path = os.path.join(folder_paths.get_directory_by_type("input"), *file_path_parts[1:])
elif file_path_parts[0] == "models":
print("matching models")
file_path = folder_paths.get_full_path(file_path_parts[1], os.path.join(*file_path_parts[2:]))
print(file_path)
return file_path
# Form ComfyUI Manager
def compute_sha256_checksum(filepath):
filepath = get_comfyui_path_from_file_path(filepath)
"""Compute the SHA256 checksum of a file, in chunks"""
sha256 = hashlib.sha256()
with open(filepath, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b''):
sha256.update(chunk)
return sha256.hexdigest()
# This is start uploading the files to Comfy Deploy
@server.PromptServer.instance.routes.post('/comfyui-deploy/upload-file')
async def upload_file(request):
data = await request.json()
file_path = data.get("file_path")
print("Original file path", file_path)
file_path = get_comfyui_path_from_file_path(file_path)
# return web.json_response({
# "error": f"File not uploaded"
# }, status=500)
token = data.get("token")
get_url = data.get("url")
try:
base = folder_paths.base_path
file_path = os.path.join(base, file_path)
if os.path.exists(file_path):
file_size = os.path.getsize(file_path)
file_extension = os.path.splitext(file_path)[1]
if file_extension in ['.jpg', '.jpeg']:
file_type = 'image/jpeg'
elif file_extension == '.png':
file_type = 'image/png'
elif file_extension == '.webp':
file_type = 'image/webp'
else:
file_type = 'application/octet-stream' # Default to binary file type if unknown
else:
return web.json_response({
"error": f"File not found: {file_path}"
}, status=404)
except Exception as e:
return web.json_response({
"error": str(e)
}, status=500)
if get_url:
try:
async with aiohttp.ClientSession() as session:
headers = {'Authorization': f'Bearer {token}'}
params = {'file_size': file_size, 'type': file_type}
async with session.get(get_url, params=params, headers=headers) as response:
if response.status == 200:
content = await response.json()
upload_url = content["upload_url"]
with open(file_path, 'rb') as f:
headers = {
"Content-Type": file_type,
"x-amz-acl": "public-read",
"Content-Length": str(file_size)
}
async with session.put(upload_url, data=f, headers=headers) as upload_response:
if upload_response.status == 200:
return web.json_response({
"message": "File uploaded successfully",
"download_url": content["download_url"]
})
else:
return web.json_response({
"error": f"Failed to upload file to {upload_url}. Status code: {upload_response.status}"
}, status=upload_response.status)
else:
return web.json_response({
"error": f"Failed to fetch data from {get_url}. Status code: {response.status}"
}, status=response.status)
except Exception as e:
return web.json_response({
"error": f"An error occurred while fetching data from {get_url}: {str(e)}"
}, status=500)
return web.json_response({
"error": f"File not uploaded"
}, status=500)
@server.PromptServer.instance.routes.get('/comfyui-deploy/get-file-hash')
async def get_file_hash(request):
file_path = request.rel_url.query.get('file_path', '')
if file_path is None:
return web.json_response({
"error": "file_path is required"
}, status=400)
try:
base = folder_paths.base_path
file_path = os.path.join(base, file_path)
# print("file_path", file_path)
file_hash = compute_sha256_checksum(
file_path
)
return web.json_response({
"file_hash": file_hash
})
except Exception as e:
return web.json_response({
"error": str(e)
}, status=500)
@server.PromptServer.instance.routes.get('/comfyui-deploy/ws')
async def websocket_handler(request):
ws = web.WebSocketResponse()
@@ -358,34 +221,6 @@ async def send_json_override(self, event, data, sid=None):
if not have_pending_upload(prompt_id):
update_run(prompt_id, Status.SUCCESS)
if event == 'executing' and data.get('node') is not None:
node = data.get('node')
if prompt_id in prompt_metadata:
if 'progress' not in prompt_metadata[prompt_id]:
prompt_metadata[prompt_id]["progress"] = set()
prompt_metadata[prompt_id]["progress"].add(node)
calculated_progress = len(prompt_metadata[prompt_id]["progress"]) / len(prompt_metadata[prompt_id]['workflow_api'])
# print("calculated_progress", calculated_progress)
if 'last_updated_node' in prompt_metadata[prompt_id] and prompt_metadata[prompt_id]['last_updated_node'] == node:
return
prompt_metadata[prompt_id]['last_updated_node'] = node
class_type = prompt_metadata[prompt_id]['workflow_api'][node]['class_type']
print("updating run live status", class_type)
await update_run_live_status(prompt_id, "Executing " + class_type, calculated_progress)
if event == 'execution_cached' and data.get('nodes') is not None:
if prompt_id in prompt_metadata:
if 'progress' not in prompt_metadata[prompt_id]:
prompt_metadata[prompt_id]["progress"] = set()
if 'nodes' in data:
for node in data.get('nodes', []):
prompt_metadata[prompt_id]["progress"].add(node)
# prompt_metadata[prompt_id]["progress"].update(data.get('nodes'))
if event == 'execution_error':
# Careful this might not be fully awaited.
await update_run_with_output(prompt_id, data)
@@ -408,24 +243,6 @@ class Status(Enum):
# Global variable to keep track of the last read line number
last_read_line_number = 0
async def update_run_live_status(prompt_id, live_status, calculated_progress: float):
if prompt_id not in prompt_metadata:
return
print("progress", calculated_progress)
status_endpoint = prompt_metadata[prompt_id]['status_endpoint']
body = {
"run_id": prompt_id,
"live_status": live_status,
"progress": calculated_progress
}
# requests.post(status_endpoint, json=body)
async with aiohttp.ClientSession() as session:
async with session.post(status_endpoint, json=body) as response:
pass
def update_run(prompt_id, status: Status):
global last_read_line_number
@@ -536,9 +353,7 @@ async def upload_file(prompt_id, filename, subfolder=None, content_type="image/p
"Content-Length": str(len(data)),
}
response = requests.put(ok.get("url"), headers=headers, data=data)
async with aiohttp.ClientSession() as session:
async with session.put(ok.get("url"), headers=headers, data=data) as response:
print("upload file response", response.status)
print("upload file response", response.status_code)
def have_pending_upload(prompt_id):
if 'prompt_id' in prompt_metadata and 'uploading_nodes' in prompt_metadata[prompt_id] and len(prompt_metadata[prompt_id]['uploading_nodes']) > 0:
+20 -203
View File
@@ -1,7 +1,6 @@
import { app } from "./app.js";
import { api } from "./api.js";
import { ComfyWidgets, LGraphNode } from "./widgets.js";
import { generateDependencyGraph } from "https://esm.sh/[email protected]";
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
/** @type {ComfyExtension} */
@@ -179,92 +178,6 @@ function showError(title, message) {
);
}
function createDynamicUIHtml(data) {
console.log(data);
let html =
'<div style="min-width: 600px; max-width: 1024px; margin: 14px auto; display: flex; flex-direction: column; gap: 24px;">';
const bgcolor = "var(--comfy-input-bg)";
const evenBg = "var(--border-color)";
const textColor = "var(--input-text)";
// Custom Nodes
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
html +=
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Custom Nodes</h2>';
if (data.missing_nodes?.length > 0) {
html += `
<div style="border-bottom: 1px solid #e2e8f0; padding: 4px 12px; background-color: ${evenBg}">
<h3 style="font-size: 14px; font-weight: semibold; margin-bottom: 8px;">Missing Nodes</h3>
<p style="font-size: 12px;">These nodes are not found with any matching custom_nodes in the ComfyUI Manager Database</p>
${data.missing_nodes
.map((node) => {
return `<p style="font-size: 14px; color: #d69e2e;">${node}</p>`;
})
.join("")}
</div>
`;
}
Object.values(data.custom_nodes).forEach((node) => {
html += `
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
<a href="${
node.url
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${
node.name
}</a>
<p style="font-size: 14px; color: #4b5563;">${node.hash}</p>
${
node.warning
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
: ""
}
</div>
`;
});
html += "</div>";
// Models
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
html +=
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Models</h2>';
Object.entries(data.models).forEach(([section, items]) => {
html += `
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
section.charAt(0).toUpperCase() + section.slice(1)
}</h3>`;
items.forEach((item) => {
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
});
html += "</div>";
});
html += "</div>";
// Models
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
html +=
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Files</h2>';
Object.entries(data.files).forEach(([section, items]) => {
html += `
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
section.charAt(0).toUpperCase() + section.slice(1)
}</h3>`;
items.forEach((item) => {
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
});
html += "</div>";
});
html += "</div>";
html += "</div>";
return html;
}
function addButton() {
const menu = document.querySelector(".comfy-menu");
@@ -276,33 +189,8 @@ function addButton() {
/** @type {LGraph} */
const graph = app.graph;
let { endpoint, apiKey, displayName } = getData();
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
configDialog.show();
return;
}
const ok = await confirmDialog.confirm(
"Confirm deployment -> " +
displayName +
"<br><br><small><div style='font-weight: normal;'>" +
endpoint +
"<div><br>",
`A new version will be deployed, are you confirm? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`,
);
if (!ok) return;
const includeDeps = document.getElementById("include-deps").checked;
if (endpoint.endsWith("/")) {
endpoint = endpoint.slice(0, -1);
}
loadingDialog.showLoading("Generating snapshot", "Please wait...");
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
// console.log(snapshot);
loadingDialog.close();
if (!snapshot) {
showError(
@@ -336,94 +224,32 @@ function addButton() {
const deployMetaNode = deployMeta[0];
// console.log(deployMetaNode);
const workflow_name = deployMetaNode.widgets[0].value;
const workflow_id = deployMetaNode.widgets[1].value;
console.log(workflow_name, workflow_id);
const prompt = await app.graphToPrompt();
let deps = undefined;
console.log(graph);
console.log(prompt);
if (includeDeps) {
loadingDialog.showLoading("Fetching existing version", "Please wait...");
// const endpoint = localStorage.getItem("endpoint") ?? "";
// const apiKey = localStorage.getItem("apiKey");
const existing_workflow = await fetch(
endpoint + "/api/workflow/" + workflow_id,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + apiKey,
},
},
)
.then((x) => x.json())
.catch(() => {
return {};
});
const { endpoint, apiKey, displayName } = getData();
loadingDialog.close();
loadingDialog.showLoading(
"Generating dependency graph",
"Please wait...",
);
deps = await generateDependencyGraph({
workflow_api: prompt.output,
snapshot: snapshot,
computeFileHash: async (file) => {
console.log(file);
loadingDialog.showLoading("Generating hash", file);
const hash = await fetch(
`/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(
file,
)}`,
).then((x) => x.json());
loadingDialog.showLoading("Generating hash", file);
console.log(hash);
return hash.file_hash;
},
handleFileUpload: async (file, hash, prevhash) => {
console.log("Uploading ", file);
loadingDialog.showLoading("Uploading file", file);
try {
const { download_url } = await fetch(
`/comfyui-deploy/upload-file`,
{
method: "POST",
body: JSON.stringify({
file_path: file,
token: apiKey,
url: endpoint + "/api/upload-url",
}),
},
)
.then((x) => x.json())
.catch(() => {
loadingDialog.close();
confirmDialog.confirm("Error", "Unable to upload file " + file);
});
loadingDialog.showLoading("Uploaded file", file);
console.log(download_url);
return download_url;
} catch (error) {
return undefined;
}
},
existingDependencies: existing_workflow.dependencies,
});
loadingDialog.close();
const depsOk = await confirmDialog.confirm(
"Check dependencies",
// JSON.stringify(deps, null, 2),
createDynamicUIHtml(deps),
);
if (!depsOk) return;
console.log(deps);
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
configDialog.show();
return;
}
loadingDialog.showLoading("Deploying...");
const ok = await confirmDialog.confirm(
"Confirm deployment -> " + displayName,
"A new version will be deployed, are you conform?",
);
if (!ok) return;
title.innerText = "Deploying...";
title.style.color = "orange";
@@ -435,8 +261,6 @@ function addButton() {
endpoint = endpoint.slice(0, -1);
}
// console.log(prompt.workflow);
const apiRoute = endpoint + "/api/workflow";
// const userId = apiKey
try {
@@ -446,7 +270,6 @@ function addButton() {
workflow: prompt.workflow,
workflow_api: prompt.output,
snapshot: snapshot,
dependencies: deps,
};
console.log(body);
let data = await fetch(apiRoute, {
@@ -466,8 +289,6 @@ function addButton() {
data = await data.json();
}
loadingDialog.close();
title.textContent = "Done";
title.style.color = "green";
@@ -484,7 +305,6 @@ function addButton() {
title.style.color = "white";
}, 1000);
} catch (e) {
loadingDialog.close();
app.ui.dialog.show(e);
console.error(e);
title.textContent = "Error";
@@ -561,7 +381,7 @@ export class InfoDialog extends ComfyDialog {
showMessage(title, message) {
this.show(`
<div style="width: 100%; max-width: 600px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
<h3 style="margin: 0px;">${title}</h3>
<label>
${message}
@@ -617,10 +437,7 @@ export class LoadingDialog extends ComfyDialog {
showLoading(title, message) {
this.show(`
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${
this.loadingIcon
}</h3>
${message ? `<label>${message}</label>` : ""}
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 4px;">${title} ${this.loadingIcon}</h3>
</div>
`);
}
@@ -739,7 +556,7 @@ export class ConfirmDialog extends InfoDialog {
return new Promise((resolve, reject) => {
this.callback = resolve;
this.show(`
<div style="width: 100%; max-width: 600px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
<h3 style="margin: 0px;">${title}</h3>
<label>
${message}
+11
View File
@@ -20,3 +20,14 @@ PLAUSIBLE_DOMAIN=
NEXT_PUBLIC_POSTHOG_KEY="your-api-key"
NEXT_PUBLIC_POSTHOG_HOST="your-ph-address"
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/auth/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/auth/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
STRIPE_API_KEY="sk_test_"
STRIPE_PR_PRO="price_"
STRIPE_PR_ENTERPRISE="price_"
STRIPE_PR_API="price_"
STRIPE_WEBHOOK_SECRET="whsec_"
BIN
View File
Binary file not shown.
@@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."user_usage" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"org_id" text,
"user_id" text NOT NULL,
"usage_time" real DEFAULT 0 NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"ended_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 $$;
+22
View File
@@ -0,0 +1,22 @@
DO $$ BEGIN
CREATE TYPE "subscription_plan" AS ENUM('basic', 'pro', 'enterprise');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
CREATE TYPE "subscription_plan_status" AS ENUM('active', 'deleted', 'paused');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."subscription_status" (
"stripe_customer_id" text PRIMARY KEY NOT NULL,
"user_id" text,
"org_id" text,
"plan" "subscription_plan" NOT NULL,
"status" "subscription_plan_status" NOT NULL,
"subscription_plan_id" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
+3
View File
@@ -0,0 +1,3 @@
ALTER TABLE "comfyui_deploy"."subscription_status" RENAME COLUMN "subscription_plan_id" TO "subscription_id";--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."subscription_status" ADD COLUMN "subscription_item_plan_id" text;--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."subscription_status" ADD COLUMN "subscription_item_api_id" text;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."subscription_status" ADD COLUMN "cancel_at_period_end" boolean DEFAULT false;
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "user_id" text;--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "org_id" text;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "gpu" "machine_gpu";
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "machine_type" "machine_type";
+64
View File
@@ -0,0 +1,64 @@
DO $$ BEGIN
CREATE TYPE "model_upload_type" AS ENUM('civitai', 'huggingface', 'other');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
CREATE TYPE "resource_upload" AS ENUM('started', 'success', 'failed');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."checkpoints" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text,
"org_id" text,
"description" text,
"checkpoint_volume_id" uuid NOT NULL,
"model_name" text,
"folder_path" text,
"civitai_id" text,
"civitai_version_id" text,
"civitai_url" text,
"civitai_download_url" text,
"civitai_model_response" jsonb,
"hf_url" text,
"s3_url" text,
"client_url" text,
"is_public" boolean DEFAULT false NOT NULL,
"status" "resource_upload" DEFAULT 'started' NOT NULL,
"upload_machine_id" text,
"upload_type" "model_upload_type" NOT NULL,
"error_log" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."checkpoint_volume" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text,
"org_id" text,
"volume_name" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"disabled" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."checkpoints" ADD CONSTRAINT "checkpoints_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."checkpoints" ADD CONSTRAINT "checkpoints_checkpoint_volume_id_checkpoint_volume_id_fk" FOREIGN KEY ("checkpoint_volume_id") REFERENCES "comfyui_deploy"."checkpoint_volume"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."checkpoint_volume" ADD CONSTRAINT "checkpoint_volume_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
+33
View File
@@ -0,0 +1,33 @@
DO $$ BEGIN
CREATE TYPE "model_type" AS ENUM('checkpoint', 'lora', 'embedding', 'vae');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."checkpoints" RENAME TO "models";--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."checkpoint_volume" RENAME TO "user_volume";--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."models" RENAME COLUMN "checkpoint_volume_id" TO "user_volume_id";--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."models" DROP CONSTRAINT "checkpoints_user_id_users_id_fk";
--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."models" DROP CONSTRAINT "checkpoints_checkpoint_volume_id_checkpoint_volume_id_fk";
--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."user_volume" DROP CONSTRAINT "checkpoint_volume_user_id_users_id_fk";
--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."models" ADD COLUMN "model_type" "model_type" DEFAULT 'checkpoint' NOT NULL;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."models" ADD CONSTRAINT "models_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."models" ADD CONSTRAINT "models_user_volume_id_user_volume_id_fk" FOREIGN KEY ("user_volume_id") REFERENCES "comfyui_deploy"."user_volume"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."user_volume" ADD CONSTRAINT "user_volume_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
@@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."models" ALTER COLUMN "is_public" SET DEFAULT true;
+3
View File
@@ -0,0 +1,3 @@
ALTER TYPE "workflow_run_status" ADD VALUE 'started';--> statement-breakpoint
ALTER TYPE "workflow_run_status" ADD VALUE 'queued';--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "queued_at" timestamp;
+1
View File
@@ -0,0 +1 @@
ALTER TYPE "model_upload_type" ADD VALUE 'download_url';
+1
View File
@@ -0,0 +1 @@
ALTER TYPE "model_upload_type" ADD VALUE 'download-url';
+2
View File
@@ -0,0 +1,2 @@
ALTER TYPE "workflow_run_status" ADD VALUE 'timeout';--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "run_log" text;
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "comfyui_deploy"."workflow_runs" DROP COLUMN "run_log";
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "run_log" jsonb;
+894
View File
@@ -0,0 +1,894 @@
{
"id": "2b2a5d0a-2494-45ea-9d4a-58a70df97829",
"prevId": "8d654f92-7f7e-420f-bbd3-73b6b27adf35",
"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"
]
}
}
},
"auth_requests": {
"name": "auth_requests",
"schema": "comfyui_deploy",
"columns": {
"request_id": {
"name": "request_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"api_hash": {
"name": "api_hash",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"expired_date": {
"name": "expired_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"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": {}
}
}
+970
View File
@@ -0,0 +1,970 @@
{
"id": "4db08fc3-2444-42da-b742-f1832ad8caf1",
"prevId": "2b2a5d0a-2494-45ea-9d4a-58a70df97829",
"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"
]
}
}
},
"auth_requests": {
"name": "auth_requests",
"schema": "comfyui_deploy",
"columns": {
"request_id": {
"name": "request_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"api_hash": {
"name": "api_hash",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"expired_date": {
"name": "expired_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"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": {}
},
"subscription_status": {
"name": "subscription_status",
"schema": "comfyui_deploy",
"columns": {
"stripe_customer_id": {
"name": "stripe_customer_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"plan": {
"name": "plan",
"type": "subscription_plan",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "subscription_plan_status",
"primaryKey": false,
"notNull": true
},
"subscription_plan_id": {
"name": "subscription_plan_id",
"type": "text",
"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": {},
"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"
}
},
"subscription_plan": {
"name": "subscription_plan",
"values": {
"basic": "basic",
"pro": "pro",
"enterprise": "enterprise"
}
},
"subscription_plan_status": {
"name": "subscription_plan_status",
"values": {
"active": "active",
"deleted": "deleted",
"paused": "paused"
}
},
"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": {}
}
}
+984
View File
@@ -0,0 +1,984 @@
{
"id": "cde8f758-0055-4326-981d-293ac84db54a",
"prevId": "4db08fc3-2444-42da-b742-f1832ad8caf1",
"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"
]
}
}
},
"auth_requests": {
"name": "auth_requests",
"schema": "comfyui_deploy",
"columns": {
"request_id": {
"name": "request_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"api_hash": {
"name": "api_hash",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"expired_date": {
"name": "expired_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"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": {}
},
"subscription_status": {
"name": "subscription_status",
"schema": "comfyui_deploy",
"columns": {
"stripe_customer_id": {
"name": "stripe_customer_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"plan": {
"name": "plan",
"type": "subscription_plan",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "subscription_plan_status",
"primaryKey": false,
"notNull": true
},
"subscription_id": {
"name": "subscription_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"subscription_item_plan_id": {
"name": "subscription_item_plan_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"subscription_item_api_id": {
"name": "subscription_item_api_id",
"type": "text",
"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": {},
"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"
}
},
"subscription_plan": {
"name": "subscription_plan",
"values": {
"basic": "basic",
"pro": "pro",
"enterprise": "enterprise"
}
},
"subscription_plan_status": {
"name": "subscription_plan_status",
"values": {
"active": "active",
"deleted": "deleted",
"paused": "paused"
}
},
"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\".\"subscription_status\".\"subscription_plan_id\"": "\"comfyui_deploy\".\"subscription_status\".\"subscription_id\""
}
}
}
+989
View File
@@ -0,0 +1,989 @@
{
"id": "bd893271-b9ea-4832-a3e8-a6970b9f20d9",
"prevId": "cde8f758-0055-4326-981d-293ac84db54a",
"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"
]
}
}
},
"auth_requests": {
"name": "auth_requests",
"schema": "comfyui_deploy",
"columns": {
"request_id": {
"name": "request_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"api_hash": {
"name": "api_hash",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"expired_date": {
"name": "expired_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"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": {}
},
"subscription_status": {
"name": "subscription_status",
"schema": "comfyui_deploy",
"columns": {
"stripe_customer_id": {
"name": "stripe_customer_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"plan": {
"name": "plan",
"type": "subscription_plan",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "subscription_plan_status",
"primaryKey": false,
"notNull": true
},
"subscription_id": {
"name": "subscription_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"subscription_item_plan_id": {
"name": "subscription_item_plan_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"subscription_item_api_id": {
"name": "subscription_item_api_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"cancel_at_period_end": {
"name": "cancel_at_period_end",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"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": {},
"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"
}
},
"subscription_plan": {
"name": "subscription_plan",
"values": {
"basic": "basic",
"pro": "pro",
"enterprise": "enterprise"
}
},
"subscription_plan_status": {
"name": "subscription_plan_status",
"values": {
"active": "active",
"deleted": "deleted",
"paused": "paused"
}
},
"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": {}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+106 -1
View File
@@ -246,6 +246,111 @@
"when": 1705902960991,
"tag": "0034_even_lady_ursula",
"breakpoints": true
},
{
"idx": 35,
"version": "5",
"when": 1705981452482,
"tag": "0035_fearless_golden_guardian",
"breakpoints": true
},
{
"idx": 36,
"version": "5",
"when": 1706081335387,
"tag": "0036_flippant_meltdown",
"breakpoints": true
},
{
"idx": 37,
"version": "5",
"when": 1706086271525,
"tag": "0037_eager_cyclops",
"breakpoints": true
},
{
"idx": 38,
"version": "5",
"when": 1706095853896,
"tag": "0038_yummy_darkhawk",
"breakpoints": true
},
{
"idx": 39,
"version": "5",
"when": 1706109812261,
"tag": "0039_nostalgic_lyja",
"breakpoints": true
},
{
"idx": 40,
"version": "5",
"when": 1706110859352,
"tag": "0040_salty_archangel",
"breakpoints": true
},
{
"idx": 41,
"version": "5",
"when": 1706111421524,
"tag": "0041_thick_norrin_radd",
"breakpoints": true
},
{
"idx": 42,
"version": "5",
"when": 1706164614659,
"tag": "0042_windy_madelyne_pryor",
"breakpoints": true
},
{
"idx": 43,
"version": "5",
"when": 1706241449348,
"tag": "0043_wealthy_nicolaos",
"breakpoints": true
},
{
"idx": 44,
"version": "5",
"when": 1706317908300,
"tag": "0044_panoramic_mister_fear",
"breakpoints": true
},
{
"idx": 45,
"version": "5",
"when": 1706336448134,
"tag": "0045_careful_cerise",
"breakpoints": true
},
{
"idx": 46,
"version": "5",
"when": 1706383154642,
"tag": "0046_complex_mentallo",
"breakpoints": true
},
{
"idx": 47,
"version": "5",
"when": 1706384528895,
"tag": "0047_gifted_starbolt",
"breakpoints": true
},
{
"idx": 48,
"version": "5",
"when": 1706600255919,
"tag": "0048_dear_korath",
"breakpoints": true
},
{
"idx": 49,
"version": "5",
"when": 1706631744127,
"tag": "0049_sweet_hex",
"breakpoints": true
}
]
}
}
+6 -1
View File
@@ -19,23 +19,26 @@
"@algolia/autocomplete-core": "^1.13.0",
"@aws-sdk/client-s3": "^3.472.0",
"@aws-sdk/s3-request-presigner": "^3.472.0",
"@clerk/nextjs": "^4.29.3",
"@clerk/nextjs": "4.29.5",
"@headlessui/react": "^1.7.17",
"@headlessui/tailwindcss": "^0.2.0",
"@hono/swagger-ui": "^0.2.1",
"@hono/zod-openapi": "^0.9.5",
"@hono/zod-validator": "^0.1.11",
"@hookform/resolvers": "^3.3.2",
"@lemonsqueezy/lemonsqueezy.js": "^1.2.5",
"@mdx-js/loader": "^3.0.0",
"@mdx-js/react": "^3.0.0",
"@neondatabase/serverless": "^0.6.0",
"@next/mdx": "^14.0.4",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
@@ -46,6 +49,7 @@
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@sindresorhus/slugify": "^2.2.1",
"@stripe/stripe-js": "^2.4.0",
"@tailwindcss/typography": "^0.5.10",
"@tanstack/react-table": "^8.10.7",
"@tanstack/react-virtual": "beta",
@@ -94,6 +98,7 @@
"shikiji": "^0.9.3",
"simple-functional-loader": "^1.2.1",
"sonner": "^1.2.4",
"stripe": "^14.13.0",
"swagger-ui-react": "^5.11.0",
"swr": "^2.2.4",
"tailwind-merge": "^2.1.0",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,175 @@
import { auth, clerkClient } from "@clerk/nextjs";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { stripe } from "../../../../../server/stripe";
import { NextResponse } from "next/server";
import { db } from "@/db/db";
import { subscriptionStatusTable } from "@/db/schema";
import { eq } from "drizzle-orm";
const secret = process.env.STRIPE_WEBHOOK_SECRET || "";
export async function POST(req: Request) {
try {
const body = await req.text();
const signature = headers().get("stripe-signature");
if (!signature)
return new NextResponse("Signature not found.", {
status: 500,
});
const event = stripe.webhooks.constructEvent(body, signature, secret);
console.log(event);
if (event.type === "checkout.session.completed") {
if (!event.data.object.customer_details?.email) {
throw new Error(`missing user email, ${event.id}`);
}
const userId = event.data.object.metadata?.userId;
const plan = event.data.object.metadata?.plan;
if (!userId) {
throw new Error(`missing itinerary_id on metadata, ${event.id}`);
}
if (!plan) {
throw new Error(`missing plan on metadata, ${event.id}`);
}
const orgId = event.data.object.metadata?.orgId;
const customerId =
typeof event.data.object.customer == "string"
? event.data.object.customer
: event.data.object.customer?.id;
const subscriptionId =
typeof event.data.object.subscription == "string"
? event.data.object.subscription
: event.data.object.subscription?.id;
if (!customerId) {
throw new Error(`missing customerId, ${event.id}`);
}
if (!subscriptionId) {
throw new Error(`missing subscriptionId, ${event.id}`);
}
const items = await stripe.subscriptionItems.list({
subscription: subscriptionId,
limit: 5,
});
// getting the subscription item id for the api plan
const subscription_item_api_id = items.data.find(
(x) => x.price.id === process.env.STRIPE_PR_API,
)?.id;
// the plan could be either pro or enterprise
const subscription_item_plan_id =
items.data.find((x) => x.price.id === process.env.STRIPE_PR_PRO)?.id ??
items.data.find((x) => x.price.id === process.env.STRIPE_PR_ENTERPRISE)
?.id;
if (!subscription_item_api_id) {
throw new Error(
`missing plan on subscription_item_api_id, ${event.id}`,
);
}
if (!subscription_item_plan_id) {
throw new Error(
`missing plan on subscription_item_plan_id, ${event.id}`,
);
}
console.log(items);
await db
.insert(subscriptionStatusTable)
.values({
stripe_customer_id: customerId,
org_id: orgId && orgId.length > 0 && orgId != "null" ? orgId : null,
user_id: userId,
subscription_id: subscriptionId,
plan: plan as "pro" | "enterprise" | "basic",
status: "active",
subscription_item_api_id: subscription_item_api_id,
subscription_item_plan_id: subscription_item_plan_id,
})
.onConflictDoNothing();
// updateDatabase(event.data.object.metadata.itinerary_id);
// sendEmail(event.data.object.customer_details.email);
} else if (event.type === "customer.subscription.paused") {
const customerId =
typeof event.data.object.customer == "string"
? event.data.object.customer
: event.data.object.customer?.id;
if (!customerId) {
throw new Error(`missing customerId, ${event.id}`);
}
await db
.update(subscriptionStatusTable)
.set({ status: "paused" })
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
} else if (event.type === "customer.subscription.resumed") {
const customerId =
typeof event.data.object.customer == "string"
? event.data.object.customer
: event.data.object.customer?.id;
if (!customerId) {
throw new Error(`missing customerId, ${event.id}`);
}
await db
.update(subscriptionStatusTable)
.set({ status: "active" })
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
} else if (event.type === "customer.subscription.deleted") {
const customerId =
typeof event.data.object.customer == "string"
? event.data.object.customer
: event.data.object.customer?.id;
if (!customerId) {
throw new Error(`missing customerId, ${event.id}`);
}
await db
.update(subscriptionStatusTable)
.set({ status: "deleted" })
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
} else if (event.type === "customer.subscription.updated") {
const customerId =
typeof event.data.object.customer == "string"
? event.data.object.customer
: event.data.object.customer?.id;
if (!customerId) {
throw new Error(`missing customerId, ${event.id}`);
}
const cancel_at_period_end = event.data.object.cancel_at_period_end;
await db
.update(subscriptionStatusTable)
.set({ cancel_at_period_end: cancel_at_period_end })
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
}
return NextResponse.json({ result: event, ok: true });
} catch (error) {
console.error(error);
return NextResponse.json(
{
message: `Something went wrong: ${error}`,
ok: false,
},
{ status: 500 },
);
}
}
@@ -0,0 +1,46 @@
import { stripe } from "@/server/stripe";
import { auth, clerkClient } from "@clerk/nextjs";
import { redirect } from "next/navigation";
import { getUrlServerSide } from "@/server/getUrlServerSide";
export async function GET(req: Request) {
const { userId, orgId } = auth();
const plan = new URL(req.url).searchParams.get("plan");
if (!userId) return redirect("/");
if (!plan) return redirect("/pricing");
const user = await clerkClient.users.getUser(userId);
const mapping = {
pro: process.env.STRIPE_PR_PRO,
enterprise: process.env.STRIPE_PR_ENTERPRISE,
};
const api = process.env.STRIPE_PR_API;
const session = await stripe.checkout.sessions.create({
success_url: getUrlServerSide(),
line_items: [
{
price: mapping[plan as "pro" | "enterprise"],
quantity: 1,
},
{
price: api,
},
],
metadata: {
userId: userId,
orgId: orgId ?? null,
plan: plan,
},
allow_promotion_codes: true,
client_reference_id: orgId ?? userId,
customer_email: user.emailAddresses[0].emailAddress,
mode: "subscription",
});
if (session.url) redirect(session.url);
}
@@ -0,0 +1,37 @@
import { stripe } from "@/server/stripe";
import { auth } from "@clerk/nextjs";
import { redirect } from "next/navigation";
import { getUrlServerSide } from "@/server/getUrlServerSide";
import { getCurrentPlan } from "@/server/getCurrentPlan";
export async function GET(req: Request) {
const { userId, orgId } = auth();
if (!userId) return redirect("/");
const change = new URL(req.url).searchParams.get("change");
const sub = await getCurrentPlan({
org_id: orgId,
user_id: userId,
});
if (!sub) return redirect("/pricing");
const session = await stripe.billingPortal.sessions.create({
customer: sub.stripe_customer_id,
return_url: getUrlServerSide() + "/pricing",
// flow_data:
// change === "true" && sub.subscription_id
// ? {
// type: "subscription_update",
// subscription_update: {
// subscription: sub.subscription_id,
// },
// }
// : undefined,
});
redirect(session.url);
// if (session.url) redirect(session.url);
}
+132 -38
View File
@@ -1,56 +1,150 @@
import { parseDataSafe } from "../../../../lib/parseDataSafe";
import { db } from "@/db/db";
import { workflowRunOutputs, workflowRunsTable } from "@/db/schema";
import {
WorkflowRunStatusSchema,
workflowRunOutputs,
workflowRunsTable,
} from "@/db/schema";
import { getCurrentPlan } from "@/server/getCurrentPlan";
import { stripe } from "@/server/stripe";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";
import { z } from "zod";
const Request = z.object({
run_id: z.string(),
status: z
.enum(["not-started", "running", "uploading", "success", "failed"])
.optional(),
status: WorkflowRunStatusSchema.optional(),
time: z.coerce.date().optional(),
output_data: z.any().optional(),
log_data: z.any().optional(),
});
export async function POST(request: Request) {
const [data, error] = await parseDataSafe(Request, request);
if (!data || error) return error;
try {
const [data, error] = await parseDataSafe(Request, request);
const { run_id, status, output_data } = data;
if (!data || error) return error;
// console.log(run_id, status, output_data);
const { run_id, status, time, output_data, log_data } = data;
if (output_data) {
const workflow_run_output = await db.insert(workflowRunOutputs).values({
run_id: run_id,
data: output_data,
});
} else if (status) {
// console.log("status", status);
const workflow_run = await db
.update(workflowRunsTable)
.set({
status: status,
ended_at:
status === "success" || status === "failed" ? new Date() : null,
})
.where(eq(workflowRunsTable.id, run_id))
.returning();
}
if (log_data) {
// It successfully started, update the started_at time
await db
.update(workflowRunsTable)
.set({
run_log: log_data,
})
.where(eq(workflowRunsTable.id, run_id));
// const workflow_version = await db.query.workflowVersionTable.findFirst({
// where: eq(workflowRunsTable.id, workflow_run[0].workflow_version_id),
// });
// revalidatePath(`./${workflow_version?.workflow_id}`);
return NextResponse.json(
{
message: "success",
},
{
status: 200,
return NextResponse.json(
{
message: "success",
},
{
status: 200,
},
);
}
);
if (status == "started" && time != undefined) {
// It successfully started, update the started_at time
await db
.update(workflowRunsTable)
.set({
started_at: time,
})
.where(eq(workflowRunsTable.id, run_id));
}
if (status == "queued" && time != undefined) {
// It successfully started, update the started_at time
await db
.update(workflowRunsTable)
.set({
queued_at: time,
})
.where(eq(workflowRunsTable.id, run_id));
}
const ended =
status === "success" || status === "failed" || status === "timeout";
if (output_data) {
const workflow_run_output = await db.insert(workflowRunOutputs).values({
run_id: run_id,
data: output_data,
});
} else if (status) {
const [workflow_run] = await db
.update(workflowRunsTable)
.set({
status: status,
ended_at: ended ? new Date() : null,
})
.where(eq(workflowRunsTable.id, run_id))
.returning();
// Need to filter out only comfy deploy serverless
// Also multiply with the gpu selection
if (workflow_run.machine_type == "comfy-deploy-serverless") {
if (ended && workflow_run.user_id) {
const sub = await getCurrentPlan({
user_id: workflow_run.user_id,
org_id: workflow_run.org_id,
});
if (sub && sub.subscription_item_api_id && workflow_run.ended_at) {
let durationInSec = Math.abs(
(workflow_run.ended_at.getTime() -
workflow_run.created_at.getTime()) /
1000,
);
durationInSec = Math.ceil(durationInSec);
switch (workflow_run.gpu) {
case "A100":
durationInSec *= 7;
break;
case "A10G":
durationInSec *= 4;
break;
}
try {
await stripe.subscriptionItems.createUsageRecord(
sub.subscription_item_api_id,
{
quantity: durationInSec,
},
);
} catch (error) {
console.log(error);
}
}
}
}
}
return NextResponse.json(
{
message: "success",
},
{
status: 200,
},
);
} catch (error: unknown) {
console.log("An error here");
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
console.log(errorMessage);
return NextResponse.json(
{
error: errorMessage,
},
{
status: 500,
},
);
}
}
@@ -0,0 +1,55 @@
import { parseDataSafe } from "../../../../lib/parseDataSafe";
import { db } from "@/db/db";
import { modelTable } from "@/db/schema";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";
import { z } from "zod";
const Request = z.object({
volume_id: z.string(),
model_id: z.string(),
folder_path: z.string().optional(),
status: z.enum(["success", "failed"]),
error_log: z.string().optional(),
timeout: z.number().optional(),
});
export async function POST(request: Request) {
const [data, error] = await parseDataSafe(Request, request);
if (!data || error) return error;
const { model_id, error_log, status, folder_path } = data;
console.log(model_id, error_log, status, folder_path);
if (status === "success") {
await db
.update(modelTable)
.set({
status: "success",
folder_path,
updated_at: new Date(),
// build_log: build_log,
})
.where(eq(modelTable.id, model_id));
} else {
await db
.update(modelTable)
.set({
status: "failed",
error_log,
updated_at: new Date(),
// status: "error",
// build_log: build_log,
})
.where(eq(modelTable.id, model_id));
}
return NextResponse.json(
{
message: "success",
},
{
status: 200,
},
);
}
@@ -0,0 +1,9 @@
import { SignIn } from "@clerk/nextjs";
export default function Page() {
return (
<div className="w-full h-full items-center flex justify-center">
<SignIn />
</div>
);
}
@@ -0,0 +1,9 @@
import { SignUp } from "@clerk/nextjs";
export default function Page() {
return (
<div className="w-full h-full items-center flex justify-center">
<SignUp />
</div>
);
}
-117
View File
@@ -1,117 +0,0 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import Image from "next/image";
import Link from "next/link";
export default function Page() {
return <Examples />;
}
type exampleWorkflow = {
title: string;
description: string;
previewURL: string;
image: {
src: string,
alt: string,
};
};
const exampleWorkflows: exampleWorkflow[] = [
{
title: "Txt2Img SDXL",
description: "The basic workflow, type a prompt and generate images based on that.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-txt2img-sdxl',
image: {
src: '/example-workflows/txt2img.webp',
alt: 'IPAdapter workflow',
}
},
{
title: "Txt2Img LCM SDXL",
description: "Images in a couple of seconds, increase the speed of each generation using LCM Lora.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-lcm-sdxl',
image: {
src: '/example-workflows/txt2img-lcm.webp',
alt: 'txt2img LCM SDXL',
}
},
{
title: "IPAdapter SDXL",
description: "Load images and use them as reference for new generations.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-ip-adapter-sdxl',
image: {
src: '/example-workflows/ipadapter.webp',
alt: 'IPAdapter workflow',
}
},
{
title: "Upscale and Add Detail SDXL",
description: "Upscale and Add Details to your creations.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-upscale-and-add-detail-sdxl',
image: {
src: '/example-workflows/upscale.webp',
alt: 'Upscale and Add Detail SDXL',
}
},
{
title: "Txt2Img SDXL Turbo",
description: "Try SDXL turbo and generate images since 1 step in seconds.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-txt2img-sdxl-turbo',
image: {
src: '/example-workflows/txt2img-sdxl-turbo.webp',
alt: 'Txt2Img SDXL Turbo',
}
},
{
title: "Img2Img SDXL Controlnet",
description: "This workflow uses canny. Generate lines of you original image and create variations.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-img2-img-sdxl-controlnet',
image: {
src: '/example-workflows/txt2img-controlnet.webp',
alt: 'Img2Img SDXL Controlnet',
}
},
{
title: "Automatic Inpainting (SEG)",
description: "Type what do you want to select and change that area with your prompt.",
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-automatic-inpainting-clip-seg',
image: {
src: '/example-workflows/automatic-inpainting-seg.webp',
alt: 'Img2Img SDXL Controlnet',
}
}
];
async function Examples() {
return (
<div className="w-full py-4">
<section className="mx-auto flex max-w-[980px] flex-col items-center gap-2 py-8 md:py-12 md:pb-8 lg:py-24 lg:pb-20">
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl text-center">
Check out some examples
</h1>
<p className="max-w-[560px] text-center text-lg text-muted-foreground">Text to Image, Image to Image, IPAdapter, and more. Here are some examples that you can use to deploy your workflow.</p>
</section>
<section className="flex justify-center flex-wrap gap-5">
{exampleWorkflows.map(workflow => {
return <Card className="w-[350px]">
<CardHeader>
<CardTitle>{workflow.title}</CardTitle>
<CardDescription>{workflow.description}</CardDescription>
</CardHeader>
<CardContent>
<Image src={workflow.image.src} alt={workflow.image.alt} width={350} height={230} />
</CardContent>
<CardFooter className="flex justify-end gap-2">
<Button asChild>
<Link href={workflow.previewURL}>View Workflow</Link>
</Button>
</CardFooter>
</Card>;
})}
</section>
</div>
);
}
+1 -3
View File
@@ -80,9 +80,7 @@
/* @apply rounded-lg p-2 overflow-x-scroll */
@apply p-2 max-w-full overflow-auto w-full
}
.vsc-controller{
position: absolute;
}
@layer base {
* {
@apply border-border;
@@ -35,7 +35,10 @@ export default async function Page({
/>
)}
{machine.status !== "building" && machine.build_log && (
<LogsViewer logs={JSON.parse(machine.build_log)} />
<LogsViewer
logs={JSON.parse(machine.build_log)}
className="h-full max-h-[600px]"
/>
)}
</CardContent>
</Card>
+10 -5
View File
@@ -1,7 +1,9 @@
import { AccessType } from "../../../lib/AccessType";
import { MachineList } from "@/components/MachineList";
import { SubscriptionProvider } from "@/components/useCurrentPlan";
import { db } from "@/db/db";
import { machinesTable } from "@/db/schema";
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
import { auth } from "@clerk/nextjs";
import { clerkClient } from "@clerk/nextjs/server";
import { desc, eq, isNull, and } from "drizzle-orm";
@@ -19,6 +21,8 @@ async function MachineListServer() {
const user = await clerkClient.users.getUser(userId);
const sub = await getCurrentPlanWithAuth();
const machines = await db.query.machinesTable.findMany({
orderBy: desc(machinesTable.updated_at),
where:
@@ -29,11 +33,12 @@ async function MachineListServer() {
return (
<div className="w-full">
{/* <div>Machines</div> */}
<MachineList
data={machines}
userMetadata={AccessType.parse(user.privateMetadata ?? {})}
/>
<SubscriptionProvider sub={sub}>
<MachineList
data={machines}
userMetadata={AccessType.parse(user.privateMetadata ?? {})}
/>
</SubscriptionProvider>
</div>
);
}
+9
View File
@@ -0,0 +1,9 @@
import PricingList from "@/components/PricingPlan";
export default function Home() {
return (
<div>
<PricingList />
</div>
);
}
+8 -8
View File
@@ -1,19 +1,19 @@
// app/providers.tsx
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
"use client";
import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
if (typeof window !== 'undefined') {
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
})
capture_pageview: false, // Disable automatic pageview capture, as we capture manually
});
}
export function PHProvider({
children,
}: {
children: React.ReactNode
children: React.ReactNode;
}) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}
+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()} />;
}
+35
View File
@@ -0,0 +1,35 @@
import { setInitialUserData } from "../../../lib/setInitialUserData";
import { auth } from "@clerk/nextjs";
import { clerkClient } from "@clerk/nextjs/server";
import { ModelList } from "@/components/ModelList";
import { getAllUserModels } from "@/server/getAllUserModel";
export default function Page() {
return <ModelListServer />;
}
async function ModelListServer() {
const { userId } = auth();
if (!userId) {
return <div>No auth</div>;
}
const user = await clerkClient.users.getUser(userId);
if (!user) {
await setInitialUserData(userId);
}
const models = await getAllUserModels();
if (!models) {
return <div>No models found</div>;
}
return (
<div className="w-full">
<ModelList data={models} />
</div>
);
}
+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()} />;
}
+65
View File
@@ -0,0 +1,65 @@
import PricingList from "@/components/PricingPlan";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
import { stripe } from "@/server/stripe";
const freeTierSeconds = 30000;
export default async function Home() {
const sub = await getCurrentPlanWithAuth();
let data: Awaited<
ReturnType<typeof stripe.subscriptionItems.listUsageRecordSummaries>
> | null = null;
try {
data = sub?.subscription_item_api_id
? await stripe.subscriptionItems.listUsageRecordSummaries(
sub?.subscription_item_api_id,
)
: null;
} catch (e) {
console.error(e);
}
return (
<div className="mt-4 flex items-center justify-center">
<Card className="p-4 w-full max-w-[600px]">
<CardHeader>
<CardTitle>Account Usage</CardTitle>
<CardDescription>View you account usage</CardDescription>
<Badge className="w-fit">{sub?.plan}</Badge>
</CardHeader>
{data && (
<CardContent className="text-sm flex flex-col gap-2">
<div className="flex justify-between gap-2">
<span>Current free gpu usage:</span>
{
<div className="flex gap-2">
<Badge>
{data.data[0].total_usage}s /{Math.floor(freeTierSeconds)}s
</Badge>
<Badge>
{Math.floor(data.data[0].total_usage / 60 / 60)}hr /
{Math.floor(freeTierSeconds / 60 / 60)}hr
</Badge>
</div>
}
</div>
<Progress
value={(data.data[0].total_usage / freeTierSeconds) * 100}
></Progress>
</CardContent>
)}
</Card>
</div>
);
}
@@ -1,5 +1,5 @@
import { LoadingWrapper } from "@/components/LoadingWrapper";
import { DeploymentsTable } from "@/components/RunsTable";
import { DeploymentsTable } from "@/components/DeploymentsTable";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default async function Page({
@@ -13,12 +13,12 @@ export default async function Page({
const workflow_id = params.workflow_id;
return (
<Card className="w-full h-fit min-w-0">
<CardHeader className="relative">
<Card className="w-full h-fit min-w-0 relative">
<CardHeader>
<CardTitle>Run</CardTitle>
<div className="absolute right-6 top-6">
{/* <div className="absolute right-6 top-6">
<RouteRefresher interval={5000} autoRefresh={false} />
</div>
</div> */}
</CardHeader>
<CardContent>
@@ -8,8 +8,8 @@ import {
OpenEditButton,
RunWorkflowButton,
VersionSelect,
ViewWorkflowDetailsButton,
} from "@/components/VersionSelect";
import { ViewWorkflowDetailsButton } from "@/components/ViewWorkflowDetailsButton";
import {
Card,
CardContent,
@@ -21,6 +21,8 @@ import { getRelativeTime } from "@/lib/getRelativeTime";
import { getMachines } from "@/server/curdMachine";
import { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
export const maxDuration = 300; // 5 minutes
export default async function Page({
params,
}: {
@@ -1,3 +1,5 @@
export const maxDuration = 300; // 5 minutes
export default async function Layout({
children,
deployment,
+2
View File
@@ -6,6 +6,8 @@ import { usersTable } from "@/db/schema";
import { auth } from "@clerk/nextjs";
import { eq } from "drizzle-orm";
export const maxDuration = 300; // 5 minutes
export default function Home() {
return <WorkflowServer />;
}
+3 -3
View File
@@ -1,12 +1,12 @@
export const metadata = {
title: 'Quickstart',
description:
'This guide will get you all set up and ready to use the Protocol API. Well cover how to get started an API client and how to make your first API request.',
'This guide will get you all set up and ready to use Comfy Deploy. Well cover how to get started an API client and how to make your first API request.',
}
# Getting stated
# Getting started
Install Comfy Deploy's plugin on your local machine to get started with deploying workflow.
Install Comfy Deploy's plugin on your local ComfyUI to get started with deploying workflow.
<CodeGroup>
+58
View File
@@ -0,0 +1,58 @@
export const metadata = {
title: 'Comfy Deploy Video tutorials',
description:
'Get started with API integration to run any deploy ComfyUI workflow',
}
**This is a collection of video tutorials on questions that have come up in our [discord channel](https://discord.gg/Zrar7yP6MJ).**
# Comfy Deploy workflow walkthrough (comprehensive)
Local workflow, to production API
- Set inputs with Comfy Deploy `External Text` nodes
- Deploy the workflow
- Create a machine
- Deploy an API endpoint for the workflow
- Test the workflow in Comfy Deploy
<video controls>
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy_base-sd-setup.mp4" type="video/mp4"/>
Your browser does not support the video tag.
</video>
# Test with Docs
Shows how you can use the <a href='/docs/endpoints'>endpoints docs</a> to test out the API!
<video controls>
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy_interactive_docs.mp4" type="video/mp4"/>
Your browser does not support the video tag.
</video>
## Intermediate IPAdaptor example run
Shows off a semi-complex workflow working on Comfy Deploy
<video controls>
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/ipadapters-test.mp4" type="video/mp4"/>
Your browser does not support the video tag.
</video>
## Animate diff on Comfy Deploy
Shows off animate diff working on Comfy Deploy
<video controls>
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy-animediff.mp4" type="video/mp4"/>
Your browser does not support the video tag.
</video>
## Cloning workflows
What does cloning a workflow look like
<video controls>
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy-clone-template.mp4" type="video/mp4"/>
Your browser does not support the video tag.
</video>
## [Install custom nodes from any github repo (Loom)](https://www.loom.com/share/c2025c5060e348839ade6a3a9d96441d?sid=6a57f779-0209-4341-a5fd-f99769cdb162)
- This is only neccessary if the custom node is not in the ComfyUI Manager node list
-4
View File
@@ -13,10 +13,7 @@ export async function CodeBlock(props: {
return (
<div className="relative w-full text-sm">
{/* max-w-[calc(32rem-1.5rem-1.5rem)] */}
{/* <div className=""> */}
<p
// tabIndex={1}
className="[&>pre]:p-4 rounded-lg max-h-96 overflow-auto w-full"
style={{
overflowWrap: "break-word",
@@ -28,7 +25,6 @@ export async function CodeBlock(props: {
}),
}}
/>
{/* </div> */}
<CopyButton className="absolute right-2 top-2" text={props.code} />
</div>
);
+35
View File
@@ -0,0 +1,35 @@
"use client";
import { CopyButton } from "@/components/CopyButton";
import type { StringLiteralUnion } from "shikiji";
import useSWR from "swr";
import { highlight } from "../server/highlight";
export function CodeBlockClient({
code,
lang,
}: {
code: string;
lang: StringLiteralUnion<string>;
}) {
const { data } = useSWR(code, async () => {
return highlight(code.trim(), lang);
});
return (
<div className="relative w-full text-sm">
{data && (
<p
className="[&>pre]:p-4 rounded-lg max-h-96 overflow-auto w-full"
style={{
overflowWrap: "break-word",
}}
dangerouslySetInnerHTML={{
__html: data,
}}
/>
)}
<CopyButton className="absolute right-2 top-2" text={code} />
</div>
);
}
+3 -1
View File
@@ -7,10 +7,12 @@ import { toast } from "sonner";
export function CopyButton({
className,
children,
...props
}: {
text: string;
className?: string;
children?: React.ReactNode;
}) {
return (
<Button
@@ -21,7 +23,7 @@ export function CopyButton({
}}
className={cn(" p-2 min-h-0 aspect-square", className)}
>
<Copy size={14} />
{children} <Copy size={14} />
</Button>
);
}
@@ -0,0 +1,24 @@
"use client";
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
import * as React from "react";
import { createContext } from "react";
export type CurrentPlanContextType = Awaited<
ReturnType<typeof getCurrentPlanWithAuth>
>;
export const CurrentPlanContext = createContext<
CurrentPlanContextType | undefined
>(undefined);
export function SubscriptionProvider({
sub,
children,
}: {
sub: CurrentPlanContextType;
children: React.ReactNode;
}) {
return (
<CurrentPlanContext.Provider value={sub}>
{children}
</CurrentPlanContext.Provider>
);
}
+8 -11
View File
@@ -69,13 +69,16 @@ const client = new ComfyDeployClient({
`;
const jsClientCreateRunTemplate = `
const { run_id } = await client.run("<ID>", {
const { run_id } = await client.run({
deployment_id: "<ID>",
inputs: {}
});
`;
const jsClientCreateRunNoInputsTemplate = `
const { run_id } = await client.run("<ID>");
const { run_id } = await client.run({
deployment_id: "<ID>"
});
`;
const clientTemplate_checkStatus = `
@@ -118,15 +121,9 @@ export function DeploymentDisplay({
</TabsList>
<TabsContent className="flex flex-col gap-2 !mt-0" value="client">
<div>
Copy and paste the ComfyDeployClient form&nbsp;
<a
href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts"
className="text-blue-500 hover:underline"
target="_blank"
rel="noreferrer"
>
here
</a>
Install the node comfydeploy SDK
<CodeBlock lang="bash" code={`npm i comfydeploy`} />
Initialize your client
</div>
<CodeBlock
lang="js"
+41
View File
@@ -0,0 +1,41 @@
import {
Table,
TableBody,
TableCaption,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { headers } from "next/headers";
import { findAllDeployments } from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
export async function DeploymentsTable(props: { workflow_id: string }) {
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 (
<div className="overflow-auto h-fit w-full">
<Table className="">
<TableCaption>A list of your deployments</TableCaption>
<TableHeader className="bg-background top-0 sticky">
<TableRow>
<TableHead className=" w-[100px]">Environment</TableHead>
<TableHead className=" w-[100px]">Version</TableHead>
<TableHead className="">Machine</TableHead>
<TableHead className=" text-right">Updated At</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{allRuns.map((run) => (
<DeploymentDisplay deployment={run} key={run.id} domain={domain} />
))}
</TableBody>
</Table>
</div>
);
}
+144 -104
View File
@@ -23,14 +23,42 @@ import * as React from "react";
import { useState } from "react";
import type { UnknownKeysParam, ZodObject, ZodRawShape, z } from "zod";
type ContextType = [Partial<any>, React.Dispatch<React.SetStateAction<any>>];
const AutoFormValueContext = React.createContext<ContextType | null>(null);
function AutoFormValueProvider<Z extends ZodObject<any, any>>({
children,
value,
}: {
children: React.ReactNode;
value: ContextType;
}) {
return (
<AutoFormValueContext.Provider value={value}>
{children}
</AutoFormValueContext.Provider>
);
}
export function useAutoFormValueContext<Z extends ZodObject<any, any>>() {
const context = React.useContext(AutoFormValueContext);
// if (!context) {
// throw new Error("useInsertModal must be used within a InsertModalProvider");
// }
return context;
}
export function InsertModal<
K extends ZodRawShape,
Y extends UnknownKeysParam,
Z extends ZodObject<K, Y>
Z extends ZodObject<K, Y>,
>(props: {
tooltip?: string;
disabled?: boolean;
title: string;
title: React.ReactNode;
buttonTitle?: React.ReactNode;
description: string;
dialogClassName?: string;
serverAction: (data: z.infer<Z>) => Promise<unknown>;
@@ -40,72 +68,80 @@ export function InsertModal<
const [open, setOpen] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);
const [values, setValues] = useState<Partial<z.infer<Z>>>({});
return (
<Dialog open={open} onOpenChange={setOpen}>
{/* <DialogTrigger disabled={props.disabled}> */}
{props.tooltip ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
className={props.disabled ? "opacity-50" : ""}
onClick={() => {
if (props.disabled) return;
setOpen(true);
}}
>
{props.title}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{props.tooltip}</p>
</TooltipContent>
</Tooltip>
) : (
<Button
variant="default"
disabled={props.disabled}
onClick={() => {
setOpen(true);
}}
<AutoFormValueProvider value={[values, setValues]}>
<Dialog open={open} onOpenChange={setOpen}>
{/* <DialogTrigger disabled={props.disabled}> */}
{props.tooltip ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
className={props.disabled ? "opacity-50" : ""}
onClick={() => {
if (props.disabled) return;
setOpen(true);
}}
>
{props.buttonTitle ?? props.title}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{props.tooltip}</p>
</TooltipContent>
</Tooltip>
) : (
<Button
variant="default"
disabled={props.disabled}
onClick={() => {
setOpen(true);
}}
>
{props.title}
</Button>
)}
{/* </DialogTrigger> */}
<DialogContent
className={cn("sm:max-w-[425px]", props.dialogClassName)}
>
{props.title}
</Button>
)}
{/* </DialogTrigger> */}
<DialogContent className={cn("sm:max-w-[425px]", props.dialogClassName)}>
<DialogHeader>
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>{props.description}</DialogDescription>
</DialogHeader>
{/* <ScrollArea> */}
<AutoForm
fieldConfig={props.fieldConfig}
formSchema={props.formSchema}
onSubmit={async (data) => {
setIsLoading(true);
await callServerPromise(props.serverAction(data));
setIsLoading(false);
setOpen(false);
}}
>
<div className="flex justify-end">
<AutoFormSubmit>
Save Changes
{isLoading && <LoadingIcon />}
</AutoFormSubmit>
</div>
</AutoForm>
{/* </ScrollArea> */}
</DialogContent>
</Dialog>
<DialogHeader>
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>{props.description}</DialogDescription>
</DialogHeader>
{/* <ScrollArea> */}
<AutoForm
values={values}
onValuesChange={setValues}
fieldConfig={props.fieldConfig}
formSchema={props.formSchema}
onSubmit={async (data) => {
setIsLoading(true);
await callServerPromise(props.serverAction(data));
setIsLoading(false);
setOpen(false);
}}
>
<div className="flex justify-end">
<AutoFormSubmit>
Save Changes
{isLoading && <LoadingIcon />}
</AutoFormSubmit>
</div>
</AutoForm>
{/* </ScrollArea> */}
</DialogContent>
</Dialog>
</AutoFormValueProvider>
);
}
export function UpdateModal<
K extends ZodRawShape,
Y extends UnknownKeysParam,
Z extends ZodObject<K, Y>
Z extends ZodObject<K, Y>,
>(props: {
open?: boolean;
setOpen?: (open: boolean) => void;
@@ -118,7 +154,7 @@ export function UpdateModal<
serverAction: (
data: z.infer<Z> & {
id: string;
}
},
) => Promise<unknown>;
formSchema: Z;
fieldConfig?: FieldConfig<z.infer<Z>>;
@@ -137,49 +173,53 @@ export function UpdateModal<
}, [props.data]);
return (
<Dialog open={open} onOpenChange={setOpen}>
{props.trigger ?? (
<DialogTrigger
className="appearance-none hover:cursor-pointer"
asChild
onClick={() => {
setOpen(true);
}}
<AutoFormValueProvider value={[values, setValues]}>
<Dialog open={open} onOpenChange={setOpen}>
{props.trigger ?? (
<DialogTrigger
className="appearance-none hover:cursor-pointer"
asChild
onClick={() => {
setOpen(true);
}}
>
{props.trigger}
</DialogTrigger>
)}
<DialogContent
className={cn("sm:max-w-[425px]", props.dialogClassName)}
>
{props.trigger}
</DialogTrigger>
)}
<DialogContent className={cn("sm:max-w-[425px]", props.dialogClassName)}>
<DialogHeader>
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>{props.description}</DialogDescription>
</DialogHeader>
<AutoForm
values={values}
onValuesChange={setValues}
fieldConfig={props.fieldConfig}
formSchema={props.formSchema}
onSubmit={async (data) => {
setIsLoading(true);
await callServerPromise(
props.serverAction({
...data,
id: props.data.id,
})
);
setIsLoading(false);
setOpen(false);
}}
>
<div className="flex justify-end flex-wrap gap-2">
{props.extraButtons}
<AutoFormSubmit>
Save Changes
{isLoading && <LoadingIcon />}
</AutoFormSubmit>
</div>
</AutoForm>
</DialogContent>
</Dialog>
<DialogHeader>
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>{props.description}</DialogDescription>
</DialogHeader>
<AutoForm
values={values}
onValuesChange={setValues}
fieldConfig={props.fieldConfig}
formSchema={props.formSchema}
onSubmit={async (data) => {
setIsLoading(true);
await callServerPromise(
props.serverAction({
...data,
id: props.data.id,
}),
);
setIsLoading(false);
setOpen(false);
}}
>
<div className="flex justify-end flex-wrap gap-2">
{props.extraButtons}
<AutoFormSubmit>
Save Changes
{isLoading && <LoadingIcon />}
</AutoFormSubmit>
</div>
</AutoForm>
</DialogContent>
</Dialog>
</AutoFormValueProvider>
);
}
+4 -2
View File
@@ -16,7 +16,7 @@ export function LiveStatus({
(state) =>
state.data
.filter((x) => x.id === run.id)
.sort((a, b) => b.timestamp - a.timestamp)?.[0]
.sort((a, b) => b.timestamp - a.timestamp)?.[0],
);
let status = run.status;
@@ -51,7 +51,9 @@ export function LiveStatus({
<>
<TableCell>
{data && status != "success"
? `${data.json.event} - ${data.json.data.node}`
? `${data.json.event}${
data.json.data.node ? " - " + data.json.data.node : ""
}`
: "-"}
</TableCell>
<TableCell className="truncate text-right">
+41 -5
View File
@@ -1,17 +1,31 @@
"use client";
import React, { useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
export type LogsType = {
machine_id?: string;
logs: string;
timestamp: number;
timestamp?: number;
}[];
export function LogsViewer({ logs }: { logs: LogsType }) {
export function LogsViewer({
logs,
hideTimestamp,
className,
stickToBottom = true,
}: {
logs: LogsType;
hideTimestamp?: boolean;
className?: string;
stickToBottom?: boolean;
}) {
const container = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!stickToBottom) return;
// console.log(logs.length, container.current);
if (container.current) {
const scrollHeight = container.current.scrollHeight;
@@ -21,11 +35,12 @@ export function LogsViewer({ logs }: { logs: LogsType }) {
behavior: "smooth",
});
}
}, [logs.length]);
}, [logs.length, stickToBottom]);
return (
<div
ref={(ref) => {
if (!stickToBottom) return;
if (!container.current && ref) {
const scrollHeight = ref.scrollHeight;
@@ -36,10 +51,31 @@ export function LogsViewer({ logs }: { logs: LogsType }) {
}
container.current = ref;
}}
className="flex flex-col text-xs p-2 overflow-y-scroll max-h-[400px] whitespace-break-spaces"
className={cn(
"h-full w-full flex flex-col text-xs p-2 overflow-y-scroll whitespace-break-spaces",
className,
)}
>
{logs.map((x, i) => (
<div key={i}>{x.logs}</div>
<div
key={i}
className="hover:bg-gray-100 flex flex-row items-center gap-2"
onClick={() => {
toast.success("Copied to clipboard");
navigator.clipboard.writeText(x.logs);
}}
>
{!hideTimestamp && x.timestamp != undefined && (
<>
<span className="w-[150px] flex-shrink-0">
{new Date(x.timestamp * 1000).toLocaleString()}
</span>
<div className="h-full w-[1px] bg-stone-400 flex-shrink-0"></div>
</>
)}
{/* Display timestamp */}
<div>{x.logs}</div>
</div>
))}
</div>
);
+73 -2
View File
@@ -6,6 +6,19 @@ import { getConnectionStatus } from "@/components/getConnectionStatus";
import { useEffect, useState } from "react";
import useWebSocket from "react-use-websocket";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { useRouter } from "next/navigation";
export function MachineBuildLog({
machine_id,
endpoint,
@@ -17,6 +30,7 @@ export function MachineBuildLog({
}) {
const [logs, setLogs] = useState<LogsType>([]);
const [finished, setFinished] = useState(false);
const [status, setStatus] = useState<"failed" | "succuss">();
const wsEndpoint = endpoint.replace(/^http/, "ws");
const query = { fly_instance_id: instance_id };
@@ -27,7 +41,7 @@ export function MachineBuildLog({
reconnectAttempts: 20,
reconnectInterval: 1000,
queryParams: query,
}
},
);
const connectionStatus = getConnectionStatus(readyState);
@@ -43,13 +57,70 @@ export function MachineBuildLog({
setLogs((logs) => [...(logs ?? []), message.data]);
} else if (message?.event === "FINISHED") {
setFinished(true);
setStatus(message.data.status);
}
}, [lastMessage]);
const router = useRouter();
return (
<div>
{connectionStatus}
<LogsViewer logs={logs} />
<LogsViewer logs={logs} className="h-full max-h-[600px]" />
<AlertDialog open={finished}>
<AlertDialogContent>
{status == "succuss" ? (
<>
<AlertDialogHeader>
<AlertDialogTitle>Machine Built</AlertDialogTitle>
<AlertDialogDescription>
Your machine is built, you can now integrate your API, or
directly run to check this machines.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction
onClick={() => {
router.push("/workflows");
}}
>
See Workflows
</AlertDialogAction>
<AlertDialogAction
onClick={() => {
router.push("/machines");
}}
>
See All Machines
</AlertDialogAction>
</AlertDialogFooter>
</>
) : (
<>
<AlertDialogHeader>
<AlertDialogTitle>Machine Failed</AlertDialogTitle>
<AlertDialogDescription>
Something went wrong with the machine build, please check the
log. Possible cause could be conflits with custom nodes, build
got stuck, timeout, or too many custom nodes installed. Please
attempt a rebuild or remove some of the custom nodes.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>See logs</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
router.push("/machines");
}}
>
Back to machines
</AlertDialogAction>
</AlertDialogFooter>
</>
)}
</AlertDialogContent>
</AlertDialog>
</div>
);
}
+97 -46
View File
@@ -40,6 +40,8 @@ import {
updateCustomMachine,
updateMachine,
} from "@/server/curdMachine";
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
import type {
ColumnDef,
ColumnFiltersState,
@@ -54,9 +56,10 @@ import {
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
import { ArrowUpDown, Lock, MoreHorizontal, Plus } from "lucide-react";
import * as React from "react";
import { useState } from "react";
import { toast } from "sonner";
import type { z } from "zod";
export type Machine = MachineType;
@@ -113,16 +116,18 @@ export const columns: ColumnDef<Machine>[] = [
{row.original.status} <LoadingIcon />
</Badge>
)}
{!row.original.disabled && row.original.status && (
<Badge
variant={
row.original.status == "ready" ? "success" : "destructive"
}
className="capitalize"
>
{row.original.status}
</Badge>
)}
{!row.original.disabled &&
row.original.status &&
row.original.status != "building" && (
<Badge
variant={
row.original.status == "ready" ? "success" : "destructive"
}
className="capitalize"
>
{row.original.status}
</Badge>
)}
</div>
// </a>
);
@@ -144,9 +149,16 @@ export const columns: ColumnDef<Machine>[] = [
header: () => <div className="text-left">Type</div>,
cell: ({ row }) => {
return (
<div className="text-left font-medium truncate">
<Badge
className="text-left font-medium truncate"
variant={
row.original.type == "comfy-deploy-serverless"
? "success"
: "outline"
}
>
{row.original.type}
</div>
</Badge>
);
},
},
@@ -178,6 +190,7 @@ export const columns: ColumnDef<Machine>[] = [
cell: ({ row }) => {
const machine = row.original;
const [open, setOpen] = useState(false);
const sub = useCurrentPlan();
return (
<DropdownMenu>
@@ -192,7 +205,9 @@ export const columns: ColumnDef<Machine>[] = [
<DropdownMenuItem
className="text-destructive"
onClick={async () => {
callServerPromise(deleteMachine(machine.id));
callServerPromise(deleteMachine(machine.id), {
loadingText: "Deleting machine",
});
}}
>
Delete Machine
@@ -200,7 +215,9 @@ export const columns: ColumnDef<Machine>[] = [
{machine.disabled ? (
<DropdownMenuItem
onClick={async () => {
callServerPromise(enableMachine(machine.id));
callServerPromise(enableMachine(machine.id), {
loadingText: "Enabling machine",
});
}}
>
Enable Machine
@@ -209,7 +226,9 @@ export const columns: ColumnDef<Machine>[] = [
<DropdownMenuItem
className="text-destructive"
onClick={async () => {
callServerPromise(disableMachine(machine.id));
callServerPromise(disableMachine(machine.id), {
loadingText: "Disabling machine",
});
}}
>
Disable Machine
@@ -217,22 +236,33 @@ export const columns: ColumnDef<Machine>[] = [
)}
{machine.type === "comfy-deploy-serverless" && (
<>
<DropdownMenuItem asChild>
<a
target="_blank"
href={machine.endpoint.replace(
"comfyui-api",
"comfyui-app"
)} rel="noreferrer"
>
Open ComfyUI
</a>
<DropdownMenuItem
onClick={async () => {
const url = await callServerPromise(
editWorkflowOnMachine(machine.id),
{
loadingText: "Getting machine url...",
},
);
if (url && typeof url !== "object") {
window.open(url, "_blank");
} else if (url && typeof url === "object" && url.error) {
console.error(url.error);
}
}}
>
Open ComfyUI
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
buildMachine({
id: machine.id,
});
callServerPromise(
buildMachine({
id: machine.id,
}),
{
loadingText: "Starting machine build process",
},
);
}}
>
Rebuild
@@ -269,7 +299,10 @@ export const columns: ColumnDef<Machine>[] = [
fieldType: "models",
},
gpu: {
inputProps: {},
fieldType: "gpuPicker",
inputProps: {
sub: sub,
},
},
}}
/>
@@ -297,6 +330,8 @@ export const columns: ColumnDef<Machine>[] = [
},
];
import { useCurrentPlan } from "./useCurrentPlan";
export function MachineList({
data,
userMetadata,
@@ -306,12 +341,14 @@ export function MachineList({
}) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const sub = useCurrentPlan();
const table = useReactTable({
data,
columns,
@@ -331,6 +368,21 @@ export function MachineList({
},
});
let machineMaxCount = 2;
// Temp fixes for machine count
if (userMetadata.betaFeaturesAccess) machineMaxCount = 5;
if (sub?.plan == "pro") {
machineMaxCount = 10;
} else if (sub?.plan == "enterprise") {
machineMaxCount = 99;
}
const locked =
data.some((machine) => machine.type === "modal-serverless") &&
data.length >= machineMaxCount;
return (
<div className="w-full">
<div className="flex items-center py-4">
@@ -345,17 +397,18 @@ export function MachineList({
<div className="ml-auto flex gap-2">
<InsertModal
dialogClassName="sm:max-w-[600px]"
disabled={
data.some(
(machine) => machine.type === "comfy-deploy-serverless"
) && !userMetadata.betaFeaturesAccess
}
disabled={locked}
tooltip={
data.some((machine) => machine.type === "comfy-deploy-serverless")
? "Only one hosted machine at preview stage"
: undefined
locked
? `Max ${machineMaxCount} ComfyUI machine for your account, upgrade to unlock more cnfiguration.`
: `Max ${machineMaxCount} ComfyUI machine for your account`
}
title="New Machine"
buttonTitle={
<>
New Machine {locked ? <Lock size={14} /> : <Plus size={14} />}
</>
}
title={"New Machine"}
description="Add custom ComfyUI machines to your account."
serverAction={addCustomMachine}
formSchema={addCustomMachineSchema}
@@ -381,11 +434,9 @@ export function MachineList({
},
},
gpu: {
fieldType: !userMetadata.betaFeaturesAccess
? "fallback"
: "select",
fieldType: "gpuPicker",
inputProps: {
disabled: !userMetadata.betaFeaturesAccess,
sub: sub,
},
},
}}
@@ -410,7 +461,7 @@ export function MachineList({
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
header.getContext(),
)}
</TableHead>
);
@@ -429,7 +480,7 @@ export function MachineList({
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
cell.getContext(),
)}
</TableCell>
))}
+6 -4
View File
@@ -36,7 +36,7 @@ type State = {
json: {
event: string;
data: any;
}
},
) => void;
};
@@ -82,7 +82,7 @@ function MachineWS({
const logs = useStore((x) =>
x.logs
.filter((p) => p.machine_id === machine.id)
.sort((a, b) => a.timestamp - b.timestamp)
.sort((a, b) => a.timestamp - b.timestamp),
);
const [sid, setSid] = useState("");
@@ -96,7 +96,7 @@ function MachineWS({
// queryParams: {
// clientId: sid,
// },
}
},
);
const connectionStatus = getConnectionStatus(readyState);
@@ -135,7 +135,9 @@ function MachineWS({
You can view your run&apos;s outputs here
</DialogDescription>
</DialogHeader>
<LogsViewer logs={logs} />
<div className="h-[400px]">
<LogsViewer logs={logs} hideTimestamp />
</div>
</DialogContent>
</Dialog>
);

Some files were not shown because too many files have changed in this diff Show More