Compare commits

...
Author SHA1 Message Date
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
90 changed files with 15396 additions and 423 deletions
+16
View File
@@ -4,4 +4,20 @@ FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:${VARIANT}
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
WORKDIR "/"
# Install fly
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
RUN npm install -g bun
+3
View File
@@ -4,6 +4,7 @@
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"postCreateCommand": "cd web && bun install && bun run migrate-local",
"remoteUser": "root",
"customizations": {
"vscode": {
"extensions": [
@@ -13,6 +14,8 @@
"stivo.tailwind-fold",
"streetsidesoftware.code-spell-checker",
"GitHub.copilot",
"ms-azuretools.vscode-docker",
"ms-python.python"
]
}
}
+1
View File
@@ -1,2 +1,3 @@
__pycache__
.DS_Store
.vercel
+3
View File
@@ -1,5 +1,8 @@
MODAL_TOKEN_ID=
MODAL_TOKEN_SECRET=
CIVITAI_API_KEY=
# On production set to False
DEPLOY_TEST_FLAG=True
CIVITAI_API_KEY=
PUBLIC_MODEL_VOLUME_NAME=
+3
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>
+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
+80 -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
@@ -19,6 +20,7 @@ from urllib.parse import parse_qs
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp, Scope, Receive, Send
from concurrent.futures import ThreadPoolExecutor
# executor = ThreadPoolExecutor(max_workers=5)
@@ -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,7 @@ class Item(BaseModel):
snapshot: Snapshot
models: List[Model]
callback_url: str
model_volume_name: str
gpu: GPUType = Field(default=GPUType.T4)
@field_validator('gpu')
@@ -223,6 +229,71 @@ 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"
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",
}
@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}"
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]
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')
}
with open(f"{folder_path}/config.py", "w") as f:
f.write("config = " + json.dumps(config))
await asyncio.subprocess.create_subprocess_shell(
f"modal run app.py",
cwd=folder_path,
env={**os.environ, "COLUMNS": "10000"}
)
@app.post("/create")
async def create_machine(item: Item):
@@ -308,11 +379,19 @@ 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
"gpu": item.gpu,
"public_model_volume": public_model_volume_name,
"private_model_volume": item.model_volume_name,
"pip": list(pip_modules)
}
with open(f"{folder_path}/config.py", "w") as f:
f.write("config = " + json.dumps(config))
+96 -69
View File
@@ -1,13 +1,14 @@
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
# deploy_test = False
import os
@@ -27,8 +28,8 @@ 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"))
@@ -52,16 +53,20 @@ if not deploy_test:
"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")
@@ -153,89 +158,110 @@ image = Image.debian_slim()
target_image = image if deploy_test else dockerfile_image
@stub.cls(image=target_image, gpu=config["gpu"] ,volumes=volumes, timeout=60 * 10, container_idle_timeout=60 * 5)
class ComfyDeployRunner:
@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")
@enter()
def setup(self):
import subprocess
import time
# Make sure that the ComfyUI API is available
print(f"comfy-modal - check server")
command = ["python", "main.py",
"--disable-auto-launch", "--disable-metadata"]
server_process = subprocess.Popen(command, cwd="/comfyui")
command = ["python", "main.py",
"--disable-auto-launch", "--disable-metadata"]
check_server(
f"http://{COMFY_HOST}",
COMFY_API_AVAILABLE_MAX_RETRIES,
COMFY_API_AVAILABLE_INTERVAL_MS,
)
self.server_process = subprocess.Popen(command, cwd="/comfyui")
job_input = input
check_server(
f"http://{COMFY_HOST}",
COMFY_API_AVAILABLE_MAX_RETRIES,
COMFY_API_AVAILABLE_INTERVAL_MS,
)
# print(f"comfy-modal - got input {job_input}")
@exit()
def cleanup(self, exc_type, exc_value, traceback):
self.server_process.terminate()
# 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)}"}
@method()
def run(self, input: Input):
data = json.dumps({
"run_id": input.prompt_id,
"status": "started",
"time": datetime.now().isoformat()
}).encode('utf-8')
req = urllib.request.Request(input.status_endpoint, data=data, method='POST')
urllib.request.urlopen(req)
# 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)
job_input = input
# Exit the loop if we have found the history
# if prompt_id in history and history[prompt_id].get("outputs"):
# break
try:
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)}"}
# 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
# 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)
# Exit the loop if we have found the history
# if prompt_id in history and history[prompt_id].get("outputs"):
# break
# 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
else:
# Wait before trying again
time.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)}"}
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")
server_process.terminate()
print(f"comfy-modal - Finished, turning off")
# 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}
result = {"status": status}
return result
print("Running remotely on Modal!")
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:
return run.remote(request_input.input)
# 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 = model.run.spawn(request_input.input)
@stub.function(image=image)
# call = run.spawn()
return {"call_id": call.object_id}
return {"call_id": None}
@stub.function(image=image
,volumes=volumes
)
@asgi_app()
def comfyui_api():
return web_app
@@ -285,6 +311,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():
+8 -1
View File
@@ -1 +1,8 @@
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": []
}
@@ -1,11 +1,23 @@
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
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
@@ -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": "a838cb7ad425e5652c3931fbafdc886b53c48a22",
"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,74 @@
import modal
from config import config
import os
import subprocess
from pprint import pprint
stub = modal.Stub()
# 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
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"]
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
print('downloading', modified_download_url)
subprocess.run(["wget", modified_download_url , "--content-disposition", "-P", model_store_path])
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.local_entrypoint()
def simple_download():
import requests
try:
list(download_model.starmap([(vol_name, link) for vol_name,link 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,18 @@
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": "",
}
+25 -27
View File
@@ -15,27 +15,28 @@ const ext = {
const auth_token = queryParams.get("auth_token");
const org_display = queryParams.get("org_display");
const origin = queryParams.get("origin");
const data = getData();
let endpoint = data.endpoint;
let apiKey = data.apiKey;
// If there is auth token override it
if (auth_token) {
apiKey = auth_token;
endpoint = origin;
saveData({
displayName: org_display,
endpoint: origin,
apiKey: auth_token,
displayName: org_display,
environment: "cloud",
});
localStorage.setItem("comfy_deploy_env", "cloud");
}
if (!workflow_version_id) {
console.error("No workflow_version_id provided in query parameters.");
} else {
const data = getData();
let endpoint = data.endpoint;
let apiKey = data.apiKey;
// If there is auth token override it
if (auth_token) {
apiKey = auth_token;
endpoint = origin;
saveData({
displayName: org_display,
endpoint: origin,
apiKey: auth_token,
displayName: org_display,
environment: "cloud",
});
localStorage.setItem("comfy_deploy_env", "cloud");
}
loadingDialog.showLoading(
"Loading workflow from " + org_display,
"Please wait...",
@@ -681,19 +682,16 @@ export class ConfigDialog extends ComfyDialog {
</label>
<label style="color: white; width: 100%;">
Endpoint:
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${
data.endpoint
}">
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${data.endpoint
}">
</label>
<label style="color: white;">
API Key: ${data.displayName ?? ""}
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${
data.apiKey
}">
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${data.apiKey
}">
<button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;">
${
data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
}
${data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
}
</button>
</label>
</div>
+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;
+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
+77
View File
@@ -246,6 +246,83 @@
"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
}
]
}
+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",
@@ -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,45 @@
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,
},
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);
}
+112 -39
View File
@@ -1,56 +1,129 @@
import { parseDataSafe } from "../../../../lib/parseDataSafe";
import { db } from "@/db/db";
import { workflowRunOutputs, workflowRunsTable } from "@/db/schema";
import {
WorkflowRunStatusSchema,
userUsageTable,
workflowRunOutputs,
workflowRunsTable,
workflowTable,
} 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(),
});
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 } = 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();
}
// 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,
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));
}
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:
status === "success" || status === "failed" ? 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 (
(status === "success" || status === "failed") &&
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;
}
await stripe.subscriptionItems.createUsageRecord(
sub.subscription_item_api_id,
{
quantity: durationInSec,
},
);
}
}
}
}
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>
);
}
+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()} />;
}
+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()} />;
}
+57
View File
@@ -0,0 +1,57 @@
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();
const data = sub?.subscription_item_api_id
? await stripe.subscriptionItems.listUsageRecordSummaries(
sub?.subscription_item_api_id,
)
: null;
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>
);
}
@@ -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 />;
}
+60
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 };
@@ -43,13 +57,59 @@ 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} />
<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>
);
}
+51 -30
View File
@@ -40,6 +40,7 @@ import {
updateCustomMachine,
updateMachine,
} from "@/server/curdMachine";
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
import type {
ColumnDef,
ColumnFiltersState,
@@ -57,6 +58,7 @@ import {
import { ArrowUpDown, MoreHorizontal } 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 +115,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>
);
@@ -192,7 +196,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 +206,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 +217,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 +227,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
@@ -306,7 +327,7 @@ export function MachineList({
}) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
@@ -347,7 +368,7 @@ export function MachineList({
dialogClassName="sm:max-w-[600px]"
disabled={
data.some(
(machine) => machine.type === "comfy-deploy-serverless"
(machine) => machine.type === "comfy-deploy-serverless",
) && !userMetadata.betaFeaturesAccess
}
tooltip={
@@ -410,7 +431,7 @@ export function MachineList({
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
header.getContext(),
)}
</TableHead>
);
@@ -429,7 +450,7 @@ export function MachineList({
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
cell.getContext(),
)}
</TableCell>
))}
+407
View File
@@ -0,0 +1,407 @@
"use client";
import { getRelativeTime } from "../lib/getRelativeTime";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { InsertModal } from "./InsertModal";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import type { getAllUserModels as getAllUserModels } from "@/server/getAllUserModel";
import type {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
} from "@tanstack/react-table";
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { ArrowUpDown } from "lucide-react";
import * as React from "react";
import { addCivitaiModel } from "@/server/curdModel";
import { addCivitaiModelSchema } from "@/server/addCivitaiModelSchema";
import { modelEnumType } from "@/db/schema";
export type ModelItemList = NonNullable<
Awaited<ReturnType<typeof getAllUserModels>>
>[0];
export const columns: ColumnDef<ModelItemList>[] = [
{
accessorKey: "id",
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "model_name",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Model Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
const model = row.original;
return (
<>
{
/*<a
className="hover:underline flex gap-2"
href={`/storage/${model.id}`} // TODO
>*/
}
<span className="truncate max-w-[200px]">
{row.original.model_name}
</span>
{model.is_public
? <></>
: <Badge variant="orange">Private</Badge>}
</>
);
},
},
{
accessorKey: "status",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Status
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
return (
<Badge
variant={row.original.status === "failed"
? "red"
: (row.original.status === "started" ? "yellow" : "green")}
>
{row.original.status}
</Badge>
);
// NOTE: retry downloads on failures
// const oneHourAgo = new Date(new Date().getTime() - (60 * 60 * 1000));
// const lastUpdated = new Date(row.original.updated_at);
// const canRefresh = row.original.status === "failed" && lastUpdated < oneHourAgo;
// const canRefresh = row.original.status === "failed" && lastUpdated < oneHourAgo;
// cell: ({ row }) => {
// // const oneHourAgo = new Date(new Date().getTime() - (60 * 60 * 1000));
// // const lastUpdated = new Date(row.original.updated_at);
// // const canRefresh = row.original.status === "failed" && lastUpdated < oneHourAgo;
// const canReDownload = true;
//
// return (
// <div className="flex items-center space-x-2">
// <Badge
// variant={row.original.status === "failed"
// ? "red"
// : row.original.status === "started"
// ? "yellow"
// : "green"}
// >
// {row.original.status}
// </Badge>
// {canReDownload && (
// <RefreshCcw
// onClick={() => {
// redownloadCheckpoint(row.original);
// }}
// className="h-4 w-4 cursor-pointer" // Adjust the size with h-x and w-x classes
// />
// )}
// </div>
// );
// },
},
},
{
accessorKey: "upload_type",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Source
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
return <Badge variant="cyan">{row.original.upload_type}</Badge>;
},
},
{
accessorKey: "model_type",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Model Type
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
const model_type_map: Record<modelEnumType, any> = {
"checkpoint": "amber",
"lora": "green",
"embedding": "violet",
"vae": "teal",
};
function getBadgeColor(modelType: modelEnumType) {
return model_type_map[modelType] || "default";
}
const color = getBadgeColor(row.original.model_type);
return <Badge variant={color}>{row.original.model_type}</Badge>;
},
},
{
accessorKey: "date",
sortingFn: "datetime",
enableSorting: true,
header: ({ column }) => {
return (
<button
className="w-full flex items-center justify-end hover:underline truncate"
// variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Update Date
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => (
<div className="w-full capitalize text-right truncate">
{getRelativeTime(row.original.updated_at)}
</div>
),
},
// TODO: deletion and editing for future sprint
// {
// id: "actions",
// enableHiding: false,
// cell: ({ row }) => {
// const checkpoint = row.original;
//
// return (
// <DropdownMenu>
// <DropdownMenuTrigger asChild>
// <Button variant="ghost" className="h-8 w-8 p-0">
// <span className="sr-only">Open menu</span>
// <MoreHorizontal className="h-4 w-4" />
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent align="end">
// <DropdownMenuLabel>Actions</DropdownMenuLabel>
// <DropdownMenuItem
// className="text-destructive"
// onClick={() => {
// deleteWorkflow(checkpoint.id);
// }}
// >
// Delete Workflow
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// );
// },
// },
];
export function ModelList({ data }: { data: ModelItemList[] }) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] = React.useState<
VisibilityState
>({});
const [rowSelection, setRowSelection] = React.useState({});
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
});
return (
<div className="grid grid-rows-[auto,1fr,auto] h-full">
<div className="flex flex-row w-full items-center py-4">
<Input
placeholder="Filter workflows..."
value={(table.getColumn("model_name")?.getFilterValue() as string) ??
""}
onChange={(event) =>
table.getColumn("model_name")?.setFilterValue(event.target.value)}
className="max-w-sm"
/>
<div className="ml-auto flex gap-2">
<InsertModal
dialogClassName="sm:max-w-[600px]"
disabled={
false
// TODO: limitations based on plan
}
tooltip={"Add models using their civitai url!"}
title="Add a Civitai Model"
description="Pick a model from civitai"
serverAction={addCivitaiModel}
formSchema={addCivitaiModelSchema}
fieldConfig={{
civitai_url: {
fieldType: "fallback",
inputProps: { required: true },
description: (
<>
Pick a model from{" "}
<a
href="https://www.civitai.com/models"
target="_blank"
className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"
>
civitai.com
</a>{" "}
and place it's url here
</>
),
},
}}
/>
</div>
</div>
<ScrollArea className="h-full w-full rounded-md border">
<Table>
<TableHeader className="bg-background top-0 sticky">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length
? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
)
: (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</ScrollArea>
<div className="flex flex-row items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
);
}
+14 -11
View File
@@ -22,6 +22,7 @@ import {
} from "@clerk/nextjs";
import { Github, Menu } from "lucide-react";
import meta from "next-gen/config";
import { useFeatureFlagEnabled } from "posthog-js/react";
import { useEffect, useState } from "react";
import { useMediaQuery } from "usehooks-ts";
@@ -29,9 +30,12 @@ export function Navbar() {
const { organization } = useOrganization();
const _isDesktop = useMediaQuery("(min-width: 1024px)");
const [isDesktop, setIsDesktop] = useState(true);
useEffect(() => {
setIsDesktop(_isDesktop);
}, [_isDesktop]);
return (
<>
<div className="flex flex-row items-center gap-4">
@@ -48,13 +52,6 @@ export function Navbar() {
</SheetHeader>
<div className="grid h-full grid-rows-[1fr_auto]">
<NavbarMenu className=" h-full" />
{/* <OrganizationSwitcher
appearance={{
elements: {
rootBox: "flex items-center justify-center z-[50]",
},
}}
/> */}
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">
@@ -85,20 +82,26 @@ export function Navbar() {
</div>
<div className="flex flex-row items-center gap-2">
{isDesktop && <NavbarMenu />}
<Button
{/* <Button
asChild
variant="link"
className="rounded-full aspect-square p-2 mr-4"
>
<a href="/docs">Docs</a>
</Button>
<UserButton />
</Button> */}
<UserButton
afterSignOutUrl="/"
/>
<Button
asChild
variant="outline"
className="rounded-full aspect-square p-2"
>
<a target="_blank" href="https://github.com/BennyKok/comfyui-deploy" rel="noreferrer">
<a
target="_blank"
href="https://github.com/BennyKok/comfyui-deploy"
rel="noreferrer"
>
<Github />
</a>
</Button>
+25 -2
View File
@@ -6,6 +6,7 @@ import { cn } from "@/lib/utils";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useRouter } from "next/navigation";
import { useFeatureFlagEnabled } from "posthog-js/react";
import { useEffect, useState } from "react";
import { useMediaQuery } from "usehooks-ts";
@@ -21,6 +22,8 @@ export function NavbarMenu({ className }: { className?: string }) {
const router = useRouter();
const pricingPlanFlagEnable = useFeatureFlagEnabled("pricing-plan");
const pages = [
{
name: "Workflows",
@@ -34,17 +37,37 @@ export function NavbarMenu({ className }: { className?: string }) {
name: "API Keys",
path: "/api-keys",
},
{
name: "Storage",
path: "/storage",
},
];
if (pricingPlanFlagEnable) {
pages.push({
name: "Pricing",
path: "/pricing",
})
pages.push({
name: "Usage",
path: "/usage",
})
}
pages.push({
name: "Docs",
path: "/docs",
})
return (
<div className={cn("mr-2", className)}>
{/* <div className="w-full h-full absolute inset-x-0 top-0 flex items-center justify-center pointer-events-none"> */}
{isDesktop && (
<Tabs
defaultValue={pathname}
className="w-[300px] flex pointer-events-auto"
className="w-fit flex pointer-events-auto"
>
<TabsList className="grid w-full grid-cols-3">
<TabsList className=" w-full ">
{pages.map((page) => (
<TabsTrigger
key={page.name}
+411
View File
@@ -0,0 +1,411 @@
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { Check, Info, Minus } from "lucide-react";
import { Fragment } from "react";
import { auth } from "@clerk/nextjs";
import { getCurrentPlan } from "../server/getCurrentPlan";
const tiers = [
{
name: "Basic",
id: "basic",
// href: "/api/checkout?plan=basic",
href: "/workflows",
priceMonthly: "$0",
description: "Instant Comfy UI API",
mostPopular: false,
},
{
name: "Pro",
id: "pro",
href: "/api/stripe/checkout?plan=pro",
priceMonthly: "$20",
description: "Accelerate Comfy UI",
mostPopular: false,
},
{
name: "Enterprise",
id: "enterprise",
href: "/api/stripe/checkout?plan=enterprise",
priceMonthly: "$100",
description: "Scale your Products",
mostPopular: true,
},
];
type TierFeature = {
Basic?: string | JSX.Element | boolean;
Pro?: string | JSX.Element | boolean;
Enterprise?: string | JSX.Element | boolean;
};
type Feature = {
name: string;
tiers: TierFeature;
};
type Section = {
name: string;
features: Feature[];
};
const sections: Section[] = [
{
name: "Features",
features: [
{
name: "GPU",
tiers: { Basic: "T4", Pro: "T4, A10G", Enterprise: "T4, A10G, A100" },
},
{
name: "Compute Credit",
tiers: {
Basic: (
<Tooltip>
<TooltipTrigger className="flex items-center justify-center gap-2">
30k secs free + usage <Info size={14} />
</TooltipTrigger>
<TooltipContent>
<ul className="flex flex-col items-start justify-start">
GPU Price /s = $0.00015
<li>- T4 Multiplier = x1</li>
</ul>
</TooltipContent>
</Tooltip>
),
Pro: (
<Tooltip>
<TooltipTrigger className="flex items-center justify-center gap-2">
30k secs free + usage <Info size={14} />
</TooltipTrigger>
<TooltipContent>
<ul className="flex flex-col items-start justify-start">
GPU Price /s = $0.00015
<li>- T4 Multiplier = x1</li>
<li>- A10G Multiplier = x4</li>
</ul>
</TooltipContent>
</Tooltip>
),
Enterprise: (
<Tooltip>
<TooltipTrigger className="flex items-center justify-center gap-2">
30k secs free + usage <Info size={14} />
</TooltipTrigger>
<TooltipContent>
<ul className="flex flex-col items-start justify-start">
GPU Price /s = $0.00015
<li>- T4 Multiplier = x1</li>
<li>- A10G Multiplier = x4</li>
<li>- A100 Multiplier = x7</li>
</ul>
</TooltipContent>
</Tooltip>
),
},
},
{
name: "Workflows",
tiers: { Basic: "2", Pro: "25", Enterprise: "Unlimited" },
},
{
name: "Serverless Machines",
tiers: { Basic: "2", Pro: "10", Enterprise: "Unlimited" },
},
{
name: "Outputs Storage",
tiers: { Basic: "2 GB", Pro: "10 GB", Enterprise: "Unlimited" },
},
{
name: "Dedicated Support",
tiers: { Enterprise: true },
},
{
name: "Private Model Hosting",
tiers: { Enterprise: "Coming Soon" },
},
],
},
];
export default async function PricingList() {
const { userId, orgId } = auth();
if (!userId) {
return <>No user id</>;
}
const sub = await getCurrentPlan({ user_id: userId, org_id: orgId });
const getHrefFromTier = (tier: (typeof tiers)[0]) => {
if (sub?.status == "active") {
if (tier.id == sub?.plan) return "/api/stripe/dashboard";
// This is actually cancelled
if (sub.cancel_at_period_end) return tier.href;
return "/api/stripe/dashboard?change=true";
} else {
return tier.href;
}
};
const getNameFromTier = (tier: (typeof tiers)[0]) => {
if (tier.id == sub?.plan && sub.status == "active") {
return sub.cancel_at_period_end ? (
<>
Current <span className="text-2xs"> - Ending this period</span>
</>
) : (
"Current"
);
}
if (sub?.status == "active") {
return "Get Started";
} else {
return "Get Started";
}
};
return (
<div className="bg-white py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-4xl text-center">
<h2 className="text-base font-semibold leading-7 text-indigo-600">
Pricing
</h2>
<p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
Turn any workflow into API
</p>
</div>
<p className="mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600">
ComfyDeploy is now under technical preview.
</p>
{/* xs to lg */}
<div className="mx-auto mt-12 max-w-md space-y-8 sm:mt-16 lg:hidden">
{tiers.map((tier) => (
<section
key={tier.id}
className={cn(
tier.mostPopular
? "rounded-xl bg-gray-400/5 ring-1 ring-inset ring-gray-200"
: "",
"p-8",
)}
>
<h3
id={tier.id}
className="text-sm font-semibold leading-6 text-gray-900"
>
{tier.name}
</h3>
<p className="mt-2 flex items-baseline gap-x-1 text-gray-900">
<span className="text-4xl font-bold">{tier.priceMonthly}</span>
<span className="text-sm font-semibold">/month</span>
</p>
<br></br>
<div className="text-xl font-semibold">{tier.description}</div>
<a
href={getHrefFromTier(tier)}
aria-describedby={tier.id}
className={cn(
tier.mostPopular
? "bg-indigo-600 text-white hover:bg-indigo-500"
: "text-indigo-600 ring-1 ring-inset ring-indigo-200 hover:ring-indigo-300",
"mt-8 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600",
)}
>
{getNameFromTier(tier)}
</a>
<ul
role="list"
className="mt-10 space-y-4 text-sm leading-6 text-gray-900"
>
{sections.map((section) => (
<li key={section.name}>
<ul role="list" className="space-y-4">
{section.features.map((feature) =>
feature.tiers[tier.name as keyof TierFeature] ? (
<li key={feature.name} className="flex gap-x-3">
<Check
className="h-6 w-5 flex-none text-indigo-600"
aria-hidden="true"
/>
<span>
{feature.name}{" "}
{typeof feature.tiers[
tier.name as keyof TierFeature
] === "string" ? (
<span className="text-sm leading-6 text-gray-500">
(
{
feature.tiers[
tier.name as keyof TierFeature
]
}
)
</span>
) : null}
</span>
</li>
) : null,
)}
</ul>
</li>
))}
</ul>
</section>
))}
</div>
{/* lg+ */}
<div className="isolate mt-20 hidden lg:block border-gray-100 border p-6 shadow-md rounded-lg">
<div className="relative -mx-8">
{tiers.some((tier) => tier.mostPopular) ? (
<div className="absolute inset-x-4 inset-y-0 -z-10 flex">
<div
className="flex w-1/4 px-4"
aria-hidden="true"
style={{
marginLeft: `${
(tiers.findIndex((tier) => tier.mostPopular) + 1) * 25
}%`,
}}
>
<div className="w-full rounded-t-xl border-x border-t border-gray-900/10 bg-gray-400/5" />
</div>
</div>
) : null}
<table className="w-full table-fixed border-separate border-spacing-x-8 text-left">
<caption className="sr-only">Pricing plan comparison</caption>
<colgroup>
<col className="w-1/4" />
<col className="w-1/4" />
<col className="w-1/4" />
<col className="w-1/4" />
</colgroup>
<thead>
<tr>
<td />
{tiers.map((tier) => (
<th
key={tier.id}
scope="col"
className="px-6 pt-6 xl:px-8 xl:pt-8"
>
<div className="text-sm font-semibold leading-7 text-gray-900">
{tier.name}
</div>
</th>
))}
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
<span className="sr-only">Price</span>
</th>
{tiers.map((tier) => (
<td key={tier.id} className="px-6 pt-2 xl:px-8">
<div className="flex items-baseline gap-x-1 text-gray-900">
<span className="text-4xl font-bold">
{tier.priceMonthly}
</span>
<span className="text-sm font-semibold leading-6">
/month
</span>
</div>
<br></br>
<div className="text-md font-semibold">
{tier.description}
</div>
<a
href={getHrefFromTier(tier)}
className={cn(
tier.mostPopular
? "bg-indigo-600 text-white hover:bg-indigo-500"
: "text-indigo-600 ring-1 ring-inset ring-indigo-200 hover:ring-indigo-300",
"mt-8 block rounded-md py-2 px-3 text-center text-sm font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600",
)}
>
{getNameFromTier(tier)}
</a>
</td>
))}
</tr>
{sections.map((section, sectionIdx) => (
<Fragment key={section.name}>
<tr>
<th
scope="colgroup"
colSpan={4}
className={cn(
sectionIdx === 0 ? "pt-8" : "pt-16",
"pb-4 text-sm font-semibold leading-6 text-gray-900",
)}
>
{section.name}
<div className="absolute inset-x-8 mt-4 h-px bg-gray-900/10" />
</th>
</tr>
{section.features.map((feature) => (
<tr key={feature.name}>
<th
scope="row"
className="py-4 text-sm font-normal leading-6 text-gray-900"
>
{feature.name}
<div className="absolute inset-x-8 mt-4 h-px bg-gray-900/5" />
</th>
{tiers.map((tier) => (
<td key={tier.id} className="px-6 py-4 xl:px-8">
{typeof feature.tiers[
tier.name as keyof TierFeature
] === "string" ||
typeof feature.tiers[
tier.name as keyof TierFeature
] === "object" ? (
<div className="flex items-center justify-center text-center text-sm leading-6 text-gray-500">
{feature.tiers[tier.name as keyof TierFeature]}
</div>
) : (
<>
{feature.tiers[
tier.name as keyof TierFeature
] === true ? (
<Check
className="mx-auto h-5 w-5 text-indigo-600"
aria-hidden="true"
/>
) : (
<Minus
className="mx-auto h-5 w-5 text-gray-400"
aria-hidden="true"
/>
)}
<span className="sr-only">
{feature.tiers[
tier.name as keyof TierFeature
] === true
? "Included"
: "Not included"}{" "}
in {tier.name}
</span>
</>
)}
</td>
))}
</tr>
))}
</Fragment>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
+67 -57
View File
@@ -2,18 +2,18 @@ import { RunInputs } from "@/components/RunInputs";
import { RunOutputs } from "@/components/RunOutputs";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { TableCell, TableRow } from "@/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
import { type findAllRuns } from "@/server/findAllRuns";
@@ -21,54 +21,64 @@ import { Suspense } from "react";
import { LiveStatus } from "./LiveStatus";
export async function RunDisplay({
run,
run,
}: {
run: Awaited<ReturnType<typeof findAllRuns>>[0];
run: Awaited<ReturnType<typeof findAllRuns>>[0];
}) {
return (
<Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow>
<TableCell>{run.number}</TableCell>
<TableCell className="font-medium truncate">
{run.machine?.name}
</TableCell>
<TableCell className="truncate">
{getRelativeTime(run.created_at)}
</TableCell>
<TableCell>{run.version?.version}</TableCell>
<TableCell>
<Badge variant="outline" className="truncate">
{run.origin}
</Badge>
</TableCell>
<TableCell className="truncate">
<Tooltip>
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
<TooltipContent>
<div>Cold start: {getDuration(run.cold_start_duration)}</div>
<div>Run duration: {getDuration(run.run_duration)}</div>
</TooltipContent>
</Tooltip>
</TableCell>
<LiveStatus run={run} />
</TableRow>
</DialogTrigger>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>Run outputs</DialogTitle>
<DialogDescription>
You can view your run&apos;s outputs here
</DialogDescription>
</DialogHeader>
<div className="max-h-96 overflow-y-scroll">
<RunInputs run={run} />
<Suspense>
<RunOutputs run_id={run.id} />
</Suspense>
</div>
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
</DialogContent>
</Dialog>
);
return (
<Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow>
<TableCell>
<Tooltip>
<TooltipTrigger>{run.number}</TooltipTrigger>
<TooltipContent>{run.id}</TooltipContent>
</Tooltip>
</TableCell>
<TableCell className="font-medium truncate">
{run.machine?.name}
</TableCell>
<TableCell className="truncate">
{getRelativeTime(run.created_at)}
</TableCell>
<TableCell>{run.version?.version}</TableCell>
<TableCell>
<Badge variant="outline" className="truncate">
{run.origin}
</Badge>
</TableCell>
<TableCell className="truncate">
<Tooltip>
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
<TooltipContent>
<div>
Serverless latency: {getDuration(run.comfy_deploy_cold_start)}
</div>
<div>
GPU Cold start: {getDuration(run.cold_start_duration)}
</div>
<div>Run duration: {getDuration(run.run_duration)}</div>
</TooltipContent>
</Tooltip>
</TableCell>
<LiveStatus run={run} />
</TableRow>
</DialogTrigger>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>Run outputs</DialogTitle>
<DialogDescription>
You can view your run&apos;s outputs here
</DialogDescription>
</DialogHeader>
<div className="max-h-96 overflow-y-scroll">
<RunInputs run={run} />
<Suspense>
<RunOutputs run_id={run.id} />
</Suspense>
</div>
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
</DialogContent>
</Dialog>
);
}
+1 -1
View File
@@ -402,7 +402,7 @@ export function OpenEditButton({
onClick={async () => {
setIsLoading(true);
const url = await callServerPromise(
editWorkflowOnMachine(workflow_version_id, machine),
editWorkflowOnMachine(machine, workflow_version_id),
);
if (url && typeof url !== "object") {
window.open(url, "_blank");
+10 -1
View File
@@ -2,7 +2,13 @@
import { toast } from "sonner";
export async function callServerPromise<T>(result: Promise<T>) {
export async function callServerPromise<T>(result: Promise<T>, props?: {
loadingText: string
}) {
let id: string | number
if (props?.loadingText) {
id = toast.loading(props.loadingText)
}
return result
.then((x) => {
if ((x as { message: string })?.message !== undefined) {
@@ -15,5 +21,8 @@ export async function callServerPromise<T>(result: Promise<T>) {
.catch((error) => {
toast.error(error.message);
return null;
}).finally(() => {
if (id != undefined)
toast.dismiss(id)
});
}
@@ -157,6 +157,7 @@ type CustomNodeList = {
author: string;
title: string;
reference: string;
pip: string[];
files: string[];
install_type: string;
description: string;
@@ -237,56 +238,12 @@ function CustomNodesSelector({
key={index}
value={framework.reference}
onSelect={async (currentValue) => {
const repoName = extractRepoName(currentValue);
console.log(repoName);
const id = toast.loading(`Fetching repo info...`);
// toast.info("hi");
const repo = await fetch(
`https://api.github.com/repos/${repoName}`
)
.then((x) => x.json())
.then((x) => {
console.log(x);
return x;
})
.then((x) => RepoSchema.parse(x))
.catch((e) => {
console.error(e);
toast.dismiss(id);
toast.error(`Failed to fetch repo info ${e.message}`);
return null;
});
if (!repo) return;
const branch = repo.default_branch;
const branchInfo = await fetch(
`https://api.github.com/repos/${repoName}/branches/${branch}`
)
.then((x) => x.json())
.then((x) => BranchInfoSchema.parse(x))
.catch((e) => {
console.error(e);
toast.dismiss(id);
toast.error(
`Failed to fetch branch info ${e.message}`
);
return null;
});
toast.dismiss(id);
if (!branchInfo) return;
let nodeList: Record<
string,
{
hash: string;
disabled: boolean;
pip?: string[]
}
>;
const x = customNodeList;
@@ -296,10 +253,49 @@ function CustomNodesSelector({
delete newNodeList[currentValue];
nodeList = newNodeList;
} else {
const repoName = extractRepoName(currentValue);
const id = toast.loading(`Fetching repo info...`);
const repo = await fetch(
`https://api.github.com/repos/${repoName}`
)
.then((x) => x.json())
.then((x) => {
console.log(x);
return x;
})
.then((x) => RepoSchema.parse(x))
.catch((e) => {
console.error(e);
toast.dismiss(id);
toast.error(`Failed to fetch repo info ${e.message}`);
return null;
});
if (!repo) return;
const branch = repo.default_branch;
const branchInfo = await fetch(
`https://api.github.com/repos/${repoName}/branches/${branch}`
)
.then((x) => x.json())
.then((x) => BranchInfoSchema.parse(x))
.catch((e) => {
console.error(e);
toast.dismiss(id);
toast.error(
`Failed to fetch branch info ${e.message}`
);
return null;
});
toast.dismiss(id);
if (!branchInfo) return;
nodeList = {
[currentValue]: {
hash: branchInfo?.commit.sha,
disabled: false,
pip: framework.pip
},
...x,
};
@@ -0,0 +1,86 @@
// NOTE: this is WIP for doing client side validation for civitai model downloading
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import { FormControl, FormItem, FormLabel } from "../ui/form";
import { LoadingIcon } from "@/components/LoadingIcon";
import * as React from "react";
import AutoFormInput from "../ui/auto-form/fields/input";
import { useDebouncedCallback } from "use-debounce";
import { CivitaiModelResponse } from "@/types/civitai";
import { z } from "zod";
import { insertCivitaiModelSchema } from "@/db/schema";
function getUrl(civitai_url: string) {
// expect to be a URL to be https://civitai.com/models/36520
// possiblity with slugged name and query-param modelVersionId
const baseUrl = "https://civitai.com/api/v1/models/";
const url = new URL(civitai_url);
const pathSegments = url.pathname.split("/");
const modelId = pathSegments[pathSegments.indexOf("models") + 1];
const modelVersionId = url.searchParams.get("modelVersionId");
return { url: baseUrl + modelId, modelVersionId };
}
export default function AutoFormCheckpointInput(
props: AutoFormInputComponentProps
) {
const [loading, setLoading] = React.useState(false);
const [modelRes, setModelRes] =
React.useState<z.infer<typeof CivitaiModelResponse>>();
const [modelVersionid, setModelVersionId] = React.useState<string | null>();
const { label, isRequired, fieldProps, zodItem, fieldConfigItem } = props;
const handleSearch = useDebouncedCallback((search) => {
const validationResult =
insertCivitaiModelSchema.shape.civitai_url.safeParse(search);
if (!validationResult.success) {
console.error(validationResult.error);
// Optionally set an error state here
return;
}
setLoading(true);
const controller = new AbortController();
const { url, modelVersionId: versionId } = getUrl(search);
setModelVersionId(versionId);
fetch(url, {
signal: controller.signal,
})
.then((x) => x.json())
.then((a) => {
const res = CivitaiModelResponse.parse(a);
console.log(a);
console.log(res);
setModelRes(res);
setLoading(false);
});
return () => {
controller.abort();
setLoading(false);
};
}, 300);
const modifiedField = {
...fieldProps,
// onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
// handleSearch(event.target.value);
// },
};
return (
<FormItem>
{fieldConfigItem.inputProps?.showLabel && (
<FormLabel>
{label}
{isRequired && <span className="text-destructive">*</span>}
</FormLabel>
)}
<FormControl>
<AutoFormInput {...props} fieldProps={modifiedField} />
</FormControl>
</FormItem>
);
}
+141
View File
@@ -0,0 +1,141 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
+28
View File
@@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }
+1 -1
View File
@@ -5,7 +5,7 @@ import * as schema from "./schema";
const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
// if we're running locally
if (process.env.VERCEL_ENV !== "production") {
if (process.env.VERCEL_ENV !== "production" && process.env.VERCEL_ENV !== "preview") {
// Set the WebSocket proxy to work with the local instance
if (isDevContainer) {
// Running inside a VS Code devcontainer
+154 -1
View File
@@ -1,3 +1,4 @@
import { CivitaiModelResponse } from "@/types/civitai";
import { type InferSelectModel, relations } from "drizzle-orm";
import {
boolean,
@@ -8,9 +9,10 @@ import {
text,
timestamp,
uuid,
real,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod";
import { TypeOf, z } from "zod";
export const dbSchema = pgSchema("comfyui_deploy");
@@ -81,6 +83,7 @@ export const workflowVersionTable = dbSchema.table("workflow_versions", {
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export const workflowVersionSchema = createSelectSchema(workflowVersionTable);
export const workflowVersionRelations = relations(
@@ -99,6 +102,8 @@ export const workflowRunStatus = pgEnum("workflow_run_status", [
"uploading",
"success",
"failed",
"started",
"queued",
]);
export const deploymentEnvironment = pgEnum("deployment_environment", [
@@ -113,6 +118,8 @@ export const workflowRunOrigin = pgEnum("workflow_run_origin", [
"public-share",
]);
export const WorkflowRunStatusSchema = z.enum(workflowRunStatus.enumValues);
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
@@ -155,8 +162,16 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
origin: workflowRunOrigin("origin").notNull().default("api"),
status: workflowRunStatus("status").notNull().default("not-started"),
ended_at: timestamp("ended_at"),
// comfy deploy run created time
created_at: timestamp("created_at").defaultNow().notNull(),
// modal gpu cold start begin
queued_at: timestamp("queued_at"),
// modal gpu function actual start time
started_at: timestamp("started_at"),
gpu: machineGPUOptions("gpu"),
machine_type: machinesType("machine_type"),
user_id: text("user_id"),
org_id: text("org_id"),
});
export const workflowRunRelations = relations(
@@ -334,6 +349,19 @@ export const apiKeyTable = dbSchema.table("api_keys", {
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export const userUsageTable = dbSchema.table("user_usage", {
id: uuid("id").primaryKey().defaultRandom().notNull(),
org_id: text("org_id"),
user_id: text("user_id")
.references(() => usersTable.id, {
onDelete: "cascade",
})
.notNull(),
usage_time: real("usage_time").default(0).notNull(),
created_at: timestamp("created_at").defaultNow().notNull(),
ended_at: timestamp("ended_at").defaultNow().notNull(),
});
export const authRequestsTable = dbSchema.table("auth_requests", {
request_id: text("request_id").primaryKey().notNull(),
user_id: text("user_id"),
@@ -344,8 +372,133 @@ export const authRequestsTable = dbSchema.table("auth_requests", {
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export const resourceUpload = pgEnum("resource_upload", [
"started",
"success",
"failed",
]);
export const modelUploadType = pgEnum("model_upload_type", [
"civitai",
"huggingface",
"other",
]);
// https://www.answeroverflow.com/m/1125106227387584552
const modelTypes = ["checkpoint", "lora", "embedding", "vae"] as const;
export const modelType = pgEnum("model_type", modelTypes);
export type modelEnumType = (typeof modelTypes)[number];
export const modelTable = dbSchema.table("models", {
id: uuid("id").primaryKey().defaultRandom().notNull(),
user_id: text("user_id").references(() => usersTable.id, {}), // perhaps a "special" user_id for global models
org_id: text("org_id"),
description: text("description"),
user_volume_id: uuid("user_volume_id")
.notNull()
.references(() => userVolume.id, {
onDelete: "cascade",
})
.notNull(),
model_name: text("model_name"),
folder_path: text("folder_path"), // in volume
civitai_id: text("civitai_id"),
civitai_version_id: text("civitai_version_id"),
civitai_url: text("civitai_url"),
civitai_download_url: text("civitai_download_url"),
civitai_model_response: jsonb("civitai_model_response").$type<
z.infer<typeof CivitaiModelResponse>
>(),
hf_url: text("hf_url"),
s3_url: text("s3_url"),
user_url: text("client_url"),
is_public: boolean("is_public").notNull().default(true),
status: resourceUpload("status").notNull().default("started"),
upload_machine_id: text("upload_machine_id"), // TODO: review if actually needed
upload_type: modelUploadType("upload_type").notNull(),
model_type: modelType("model_type").notNull().default("checkpoint"),
error_log: text("error_log"),
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export const userVolume = dbSchema.table("user_volume", {
id: uuid("id").primaryKey().defaultRandom().notNull(),
user_id: text("user_id").references(() => usersTable.id, {
// onDelete: "cascade",
}),
org_id: text("org_id"),
volume_name: text("volume_name").notNull(),
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
disabled: boolean("disabled").default(false).notNull(),
});
export const modelRelations = relations(modelTable, ({ one }) => ({
user: one(usersTable, {
fields: [modelTable.user_id],
references: [usersTable.id],
}),
volume: one(userVolume, {
fields: [modelTable.user_volume_id],
references: [userVolume.id],
}),
}));
export const modalVolumeRelations = relations(userVolume, ({ many, one }) => ({
model: many(modelTable),
user: one(usersTable, {
fields: [userVolume.user_id],
references: [usersTable.id],
}),
}));
export const subscriptionPlan = pgEnum("subscription_plan", [
"basic",
"pro",
"enterprise",
]);
export const subscriptionPlanStatus = pgEnum("subscription_plan_status", [
"active",
"deleted",
"paused",
]);
export const subscriptionStatusTable = dbSchema.table("subscription_status", {
stripe_customer_id: text("stripe_customer_id").primaryKey().notNull(),
user_id: text("user_id"),
org_id: text("org_id"),
plan: subscriptionPlan("plan").notNull(),
status: subscriptionPlanStatus("status").notNull(),
subscription_id: text("subscription_id"),
subscription_item_plan_id: text("subscription_item_plan_id"),
subscription_item_api_id: text("subscription_item_api_id"),
cancel_at_period_end: boolean("cancel_at_period_end").default(false),
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export const insertCivitaiModelSchema = createInsertSchema(modelTable, {
civitai_url: (schema) =>
schema.civitai_url
.trim()
.url({ message: "URL required" })
.includes("civitai.com/models", {
message: "civitai.com/models link required",
}),
});
export type UserType = InferSelectModel<typeof usersTable>;
export type WorkflowType = InferSelectModel<typeof workflowTable>;
export type MachineType = InferSelectModel<typeof machinesTable>;
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
export type ModelType = InferSelectModel<typeof modelTable>;
export type UserVolumeType = InferSelectModel<typeof userVolume>;
export type UserUsageType = InferSelectModel<typeof userUsageTable>;
+13 -5
View File
@@ -12,13 +12,21 @@ export function getRelativeTime(time: string | Date | null | undefined) {
}
function formatDuration(seconds: number) {
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
if (minutes > 0) {
return `${minutes}.${remainingSeconds} mins`;
} else {
return `${remainingSeconds.toFixed(1)} secs`;
let result = "";
if (hours > 0) {
result += `${hours} hrs `;
}
if (minutes > 0) {
result += `${minutes} mins `;
}
if (remainingSeconds > 0) {
result += `${remainingSeconds.toFixed(1)} secs`;
}
return result.trim();
}
export function getDuration(durationInSecs: number) {
+1 -1
View File
@@ -3,7 +3,7 @@ import { z } from "zod";
export const APIKeyBodyRequest = z.object({
user_id: z.string().optional().nullable(),
org_id: z.string().optional().nullable(),
iat: z.number(),
iat: z.number().optional(),
exp: z.number().optional(),
});
+5
View File
@@ -0,0 +1,5 @@
import { insertCivitaiModelSchema } from "@/db/schema";
export const addCivitaiModelSchema = insertCivitaiModelSchema.pick({
civitai_url: true,
});
+15 -14
View File
@@ -66,7 +66,12 @@ export const createRun = withServerPromise(
throw new Error("Workflow version not found");
}
if (apiUser)
let { userId, orgId } = auth();
// If is API user, check if they have access to the workflow
if (apiUser) {
userId = apiUser.user_id ?? null;
orgId = apiUser.org_id;
if (apiUser.org_id) {
// is org api call, check org only
if (apiUser.org_id != workflow_version_data.workflow.org_id) {
@@ -81,6 +86,7 @@ export const createRun = withServerPromise(
throw new Error("Workflow not found");
}
}
}
const workflow_api = workflow_version_data.workflow_api;
@@ -114,6 +120,10 @@ export const createRun = withServerPromise(
workflow_inputs: inputs,
machine_id: machine.id,
origin: runOrigin,
org_id: orgId,
user_id: userId,
gpu: machine.gpu,
machine_type: machine.type,
})
.returning();
@@ -138,14 +148,14 @@ export const createRun = withServerPromise(
body: JSON.stringify(_data),
cache: "no-store",
});
console.log(___result);
// console.log(___result);
if (!___result.ok)
throw new Error(
`Error creating run, ${
___result.statusText
} ${await ___result.text()}`,
);
console.log(_data, ___result);
// console.log(_data, ___result);
break;
case "runpod-serverless":
const data = {
@@ -172,14 +182,14 @@ export const createRun = withServerPromise(
body: JSON.stringify(data),
cache: "no-store",
});
console.log(__result);
// console.log(__result);
if (!__result.ok)
throw new Error(
`Error creating run, ${
__result.statusText
} ${await __result.text()}`,
);
console.log(data, __result);
// console.log(data, __result);
break;
case "classic":
const body = {
@@ -219,15 +229,6 @@ export const createRun = withServerPromise(
throw e;
}
// It successfully started, update the started_at time
await db
.update(workflowRunsTable)
.set({
started_at: new Date(),
})
.where(eq(workflowRunsTable.id, workflow_run[0].id));
return {
workflow_run_id: workflow_run[0].id,
message: "Successful workflow run",
+9 -8
View File
@@ -62,10 +62,10 @@ export async function createDeployments(
const userName = workflow.org_id
? await clerkClient.organizations
.getOrganization({
organizationId: workflow.org_id,
})
.then((x) => x.name)
.getOrganization({
organizationId: workflow.org_id,
})
.then((x) => x.name)
: workflow.user.name;
await db.insert(deploymentsTable).values({
@@ -75,7 +75,8 @@ export async function createDeployments(
machine_id,
environment,
org_id: orgId,
share_slug: slugify(`${userName} ${workflow.name}`),
// only create share slug if this is public share
share_slug: environment == "public-share" ? slugify(`${userName} ${workflow.name}`) : null
});
}
revalidatePath(`/${workflow_id}`);
@@ -247,9 +248,9 @@ export async function findUserShareDeployment(share_id: string) {
orgId
? eq(deploymentsTable.org_id, orgId)
: and(
eq(deploymentsTable.user_id, userId),
isNull(deploymentsTable.org_id),
),
eq(deploymentsTable.user_id, userId),
isNull(deploymentsTable.org_id),
),
),
);
+37 -27
View File
@@ -15,6 +15,7 @@ import { headers } from "next/headers";
import { redirect } from "next/navigation";
import "server-only";
import type { z } from "zod";
import { retrieveModelVolumes } from "./curdModel";
export async function getMachines() {
const { userId, orgId } = auth();
@@ -29,10 +30,10 @@ export async function getMachines() {
: // make sure org_id is null
and(
eq(machinesTable.user_id, userId),
isNull(machinesTable.org_id)
isNull(machinesTable.org_id),
),
eq(machinesTable.disabled, false)
)
eq(machinesTable.disabled, false),
),
);
return machines;
}
@@ -49,10 +50,10 @@ export async function getMachineById(id: string) {
? eq(machinesTable.org_id, orgId)
: and(
eq(machinesTable.user_id, userId),
isNull(machinesTable.org_id)
isNull(machinesTable.org_id),
),
eq(machinesTable.id, id)
)
eq(machinesTable.id, id),
),
);
return machines[0];
}
@@ -69,7 +70,7 @@ export const addMachine = withServerPromise(
});
revalidatePath("/machines");
return { message: "Machine Added" };
}
},
);
export const updateCustomMachine = withServerPromise(
@@ -120,7 +121,7 @@ export const updateCustomMachine = withServerPromise(
}
return { message: "Machine Updated" };
}
},
);
export const buildMachine = withServerPromise(
@@ -146,7 +147,7 @@ export const buildMachine = withServerPromise(
// Perform custom build if there are changes
await _buildMachine(datas[0], currentMachine);
redirect(`/machines/${id}`);
}
},
);
export const addCustomMachine = withServerPromise(
@@ -173,12 +174,12 @@ export const addCustomMachine = withServerPromise(
redirect(`/machines/${b.id}`);
// revalidatePath("/machines");
return { message: "Machine Building" };
}
},
);
async function _buildMachine(
data: z.infer<typeof addCustomMachineSchema>,
b: MachineType
b: MachineType,
) {
const headersList = headers();
@@ -189,6 +190,7 @@ async function _buildMachine(
throw new Error("No domain");
}
const volumes = await retrieveModelVolumes();
// Call remote builder
const result = await fetch(`${process.env.MODAL_BUILDER_URL!}/create`, {
method: "POST",
@@ -202,6 +204,7 @@ async function _buildMachine(
callback_url: `${protocol}://${domain}/api/machine-built`,
models: data.models, //JSON.parse(data.models as string),
gpu: data.gpu && data.gpu.length > 0 ? data.gpu : "T4",
model_volume_name: volumes[0].volume_name,
}),
});
@@ -241,7 +244,7 @@ export const updateMachine = withServerPromise(
await db.update(machinesTable).set(data).where(eq(machinesTable.id, id));
revalidatePath("/machines");
return { message: "Machine Updated" };
}
},
);
export const deleteMachine = withServerPromise(
@@ -252,26 +255,33 @@ export const deleteMachine = withServerPromise(
if (machine?.type === "comfy-deploy-serverless") {
// Call remote builder to stop the app on modal
const result = await fetch(`${process.env.MODAL_BUILDER_URL!}/stop-app`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
machine_id: machine_id,
}),
});
// Only check for deletion error upon new migration
const newUpgradeDate = new Date("2024-01-27T14:24:23.737Z");
if (machine.created_at > newUpgradeDate) {
const result = await fetch(
`${process.env.MODAL_BUILDER_URL!}/stop-app`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
machine_id: machine_id,
}),
},
);
if (!result.ok) {
const error_log = await result.text();
throw new Error(`Error: ${result.statusText} ${error_log}`);
if (!result.ok) {
const error_log = await result.text();
throw new Error(`Error: ${result.statusText} ${error_log}`);
}
}
}
await db.delete(machinesTable).where(eq(machinesTable.id, machine_id));
revalidatePath("/machines");
return { message: "Machine Deleted" };
}
},
);
export const disableMachine = withServerPromise(
@@ -284,7 +294,7 @@ export const disableMachine = withServerPromise(
.where(eq(machinesTable.id, machine_id));
revalidatePath("/machines");
return { message: "Machine Disabled" };
}
},
);
export const enableMachine = withServerPromise(
@@ -297,5 +307,5 @@ export const enableMachine = withServerPromise(
.where(eq(machinesTable.id, machine_id));
revalidatePath("/machines");
return { message: "Machine Enabled" };
}
},
);
+273
View File
@@ -0,0 +1,273 @@
"use server";
import { auth } from "@clerk/nextjs";
import {
modelTable,
ModelType,
userVolume,
UserVolumeType,
} from "@/db/schema";
import { withServerPromise } from "./withServerPromise";
import { db } from "@/db/db";
import type { z } from "zod";
import { headers } from "next/headers";
import { addCivitaiModelSchema } from "./addCivitaiModelSchema";
import { and, eq, isNull } from "drizzle-orm";
import { CivitaiModelResponse, getModelTypeDetails } from "@/types/civitai";
export async function getModel() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const models = await db
.select()
.from(modelTable)
.where(
orgId
? eq(modelTable.org_id, orgId)
// make sure org_id is null
: and(
eq(modelTable.user_id, userId),
isNull(modelTable.org_id),
),
);
return models;
}
export async function getModelById(id: string) {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const model = await db
.select()
.from(modelTable)
.where(
and(
orgId ? eq(modelTable.org_id, orgId) : and(
eq(modelTable.user_id, userId),
isNull(modelTable.org_id),
),
eq(modelTable.id, id),
),
);
return model[0];
}
export async function getModelVolumes() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const volume = await db
.select()
.from(userVolume)
.where(
and(
orgId
? eq(userVolume.org_id, orgId)
// make sure org_id is null
: and(
eq(userVolume.user_id, userId),
isNull(userVolume.org_id),
),
eq(userVolume.disabled, false),
),
);
return volume;
}
export async function retrieveModelVolumes() {
let volumes = await getModelVolumes();
if (volumes.length === 0) {
// create volume if not already created
volumes = await addModelVolume();
}
return volumes;
}
export async function addModelVolume() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const insertedVolume = await db
.insert(userVolume)
.values({
user_id: userId,
org_id: orgId,
volume_name: `models_${orgId ? orgId: userId}`, // if orgid is avalible use as part of the volume name
disabled: false,
})
.returning();
return insertedVolume;
}
function getUrl(civitai_url: string) {
// expect to be a URL to be https://civitai.com/models/36520
// possiblity with slugged name and query-param modelVersionId
const baseUrl = "https://civitai.com/api/v1/models/";
const url = new URL(civitai_url);
const pathSegments = url.pathname.split("/");
const modelId = pathSegments[pathSegments.indexOf("models") + 1];
const modelVersionId = url.searchParams.get("modelVersionId");
return { url: baseUrl + modelId, modelVersionId };
}
export const addCivitaiModel = withServerPromise(
async (data: z.infer<typeof addCivitaiModelSchema>) => {
const { userId, orgId } = auth();
if (!data.civitai_url) return { error: "no civitai_url" };
if (!userId) return { error: "No user id" };
const { url, modelVersionId } = getUrl(data?.civitai_url);
const civitaiModelRes = await fetch(url)
.then((x) => x.json())
.then((a) => {
return CivitaiModelResponse.parse(a);
});
if (civitaiModelRes?.modelVersions?.length === 0) {
return; // no versions to download
}
let selectedModelVersion;
let selectedModelVersionId: string | null = modelVersionId;
if (!selectedModelVersionId) {
selectedModelVersion = civitaiModelRes.modelVersions[0];
selectedModelVersionId = civitaiModelRes.modelVersions[0].id.toString();
} else {
selectedModelVersion = civitaiModelRes.modelVersions.find((version) =>
version.id.toString() === selectedModelVersionId
);
if (!selectedModelVersion) {
return; // version id is wrong
}
selectedModelVersionId = selectedModelVersion?.id.toString();
}
const userVolume = await getModelVolumes();
let cVolume;
if (userVolume.length === 0) {
const volume = await addModelVolume();
cVolume = volume[0];
} else {
cVolume = userVolume[0];
}
const model_type = getModelTypeDetails(civitaiModelRes.type);
if (!model_type) {
return
}
const a = await db
.insert(modelTable)
.values({
user_id: userId,
org_id: orgId,
upload_type: "civitai",
model_name: selectedModelVersion.files[0].name,
civitai_id: civitaiModelRes.id.toString(),
civitai_version_id: selectedModelVersionId,
civitai_url: data.civitai_url,
civitai_download_url: selectedModelVersion.files[0].downloadUrl,
civitai_model_response: civitaiModelRes,
user_volume_id: cVolume.id,
model_type,
updated_at: new Date(),
})
.returning();
const b = a[0];
await uploadModel(data, b, cVolume);
},
);
// export const redownloadCheckpoint = withServerPromise(
// async (data: CheckpointItemList) => {
// const { userId } = auth();
// if (!userId) return { error: "No user id" };
//
// const checkpointVolumes = await getCheckpointVolumes();
// let cVolume;
// if (checkpointVolumes.length === 0) {
// const volume = await addCheckpointVolume();
// cVolume = volume[0];
// } else {
// cVolume = checkpointVolumes[0];
// }
//
// console.log("data");
// console.log(data);
//
// const a = await db
// .update(checkpointTable)
// .set({
// // status: "started",
// // updated_at: new Date(),
// })
// .returning();
//
// const b = a[0];
//
// console.log("b");
// console.log(b);
//
// await uploadCheckpoint(data, b, cVolume);
// // redirect(`/checkpoints/${b.id}`);
// },
// );
async function uploadModel(
data: z.infer<typeof addCivitaiModelSchema>,
c: ModelType,
v: UserVolumeType,
) {
const headersList = headers();
const domain = headersList.get("x-forwarded-host") || "";
const protocol = headersList.get("x-forwarded-proto") || "";
if (domain === "") {
throw new Error("No domain");
}
// Call remote builder
const result = await fetch(
`${process.env.MODAL_BUILDER_URL!}/upload-volume`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
download_url: c.civitai_download_url,
volume_name: v.volume_name,
volume_id: v.id,
model_id: c.id,
callback_url: `${protocol}://${domain}/api/volume-upload`,
upload_type: c.model_type,
}),
},
);
if (!result.ok) {
const error_log = await result.text();
await db
.update(modelTable)
.set({
...data,
status: "failed",
error_log: error_log,
})
.where(eq(modelTable.id, c.id));
throw new Error(`Error: ${result.statusText} ${error_log}`);
} else {
// setting the build machine id
const json = await result.json();
await db
.update(modelTable)
.set({
...data,
upload_machine_id: json.build_machine_instance_id,
})
.where(eq(modelTable.id, c.id));
}
}
+16 -11
View File
@@ -6,16 +6,13 @@ import jwt from "jsonwebtoken";
import { getOrgOrUserDisplayName } from "@/server/getOrgOrUserDisplayName";
import { withServerPromise } from "@/server/withServerPromise";
import "server-only";
import { headers } from "next/headers";
import { getUrlServerSide } from "./getUrlServerSide";
export const editWorkflowOnMachine = withServerPromise(
async (workflow_version_id: string, machine_id: string) => {
async (machine_id: string, workflow_version_id?: string) => {
const { userId, orgId } = auth();
const headersList = headers();
const host = headersList.get("host") || "";
const protocol = headersList.get("x-forwarded-proto") || "";
const domain = `${protocol}://${host}`;
const domain = getUrlServerSide();
if (!userId) {
throw new Error("No user id");
@@ -40,10 +37,18 @@ export const editWorkflowOnMachine = withServerPromise(
endpoint = machine.endpoint.replace("comfyui-api", "comfyui-app");
}
return `${endpoint}?workflow_version_id=${encodeURIComponent(
workflow_version_id,
)}&auth_token=${encodeURIComponent(token)}&org_display=${encodeURIComponent(
userName,
)}&origin=${encodeURIComponent(domain)}`;
const params = {
workflow_version_id: workflow_version_id,
auth_token: token,
org_display: userName,
origin: domain,
};
const queryString = Object.entries(params)
.filter(([key, value]) => value !== undefined)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`)
.join('&');
return `${endpoint}?${queryString}`;
},
);
+38 -36
View File
@@ -16,42 +16,44 @@ export async function findAllRuns({
offset = 0,
}: RunsSearchTypes) {
return await db.query.workflowRunsTable.findMany({
where: eq(workflowRunsTable.workflow_id, workflow_id),
orderBy: desc(workflowRunsTable.created_at),
offset: offset,
limit: limit,
extras: {
number: sql<number>`row_number() over (order by created_at)`.as(
"number",
),
total: sql<number>`count(*) over ()`.as("total"),
duration:
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
"duration",
),
cold_start_duration:
sql<number>`(extract(epoch from started_at) - extract(epoch from created_at))`.as(
"cold_start_duration",
),
run_duration:
sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
"run_duration",
),
},
with: {
machine: {
columns: {
name: true,
endpoint: true,
},
},
version: {
columns: {
version: true,
},
},
},
});
where: eq(workflowRunsTable.workflow_id, workflow_id),
orderBy: desc(workflowRunsTable.created_at),
offset: offset,
limit: limit,
extras: {
number: sql<number>`row_number() over (order by created_at)`.as("number"),
total: sql<number>`count(*) over ()`.as("total"),
duration:
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
"duration",
),
comfy_deploy_cold_start:
sql<number>`(extract(epoch from queued_at) - extract(epoch from created_at))`.as(
"cold_start_duration",
),
cold_start_duration:
sql<number>`(extract(epoch from started_at) - extract(epoch from queued_at))`.as(
"cold_start_duration",
),
run_duration:
sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
"run_duration",
),
},
with: {
machine: {
columns: {
name: true,
endpoint: true,
},
},
version: {
columns: {
version: true,
},
},
},
});
}
export async function findAllRunsWithCounts(props: RunsSearchTypes) {
+42
View File
@@ -0,0 +1,42 @@
import { db } from "@/db/db";
import {
modelTable,
} from "@/db/schema";
import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm";
export async function getAllUserModels() {
const { userId, orgId } = await auth();
if (!userId) {
return null;
}
const models = await db.query.modelTable.findMany({
with: {
user: {
columns: {
name: true,
},
},
},
columns: {
id: true,
updated_at: true,
model_name: true,
civitai_url: true,
civitai_model_response: true,
is_public: true,
upload_type: true,
model_type: true,
status: true,
},
orderBy: desc(modelTable.updated_at),
where:
orgId != undefined
? eq(modelTable.org_id, orgId)
: and(eq(modelTable.user_id, userId), isNull(modelTable.org_id)),
});
return models;
}
+32
View File
@@ -0,0 +1,32 @@
import { db } from "@/db/db";
import { and, desc, eq, isNull, or } from "drizzle-orm";
import { subscriptionStatusTable } from "@/db/schema";
import { APIKeyUserType } from "@/server/APIKeyBodyRequest";
import { auth } from "@clerk/nextjs";
export async function getCurrentPlanWithAuth() {
const { userId, orgId } = auth();
const sub = await getCurrentPlan({
org_id: orgId,
user_id: userId,
});
return sub;
}
export async function getCurrentPlan({ user_id, org_id }: APIKeyUserType) {
if (!user_id) throw new Error("No user id");
const sub = await db.query.subscriptionStatusTable.findFirst({
where: and(
eq(subscriptionStatusTable.user_id, user_id),
org_id
? eq(subscriptionStatusTable.org_id, org_id)
: or(isNull(subscriptionStatusTable.org_id), eq(subscriptionStatusTable.org_id, "")),
),
orderBy: desc(subscriptionStatusTable.created_at),
});
return sub;
}
+10
View File
@@ -0,0 +1,10 @@
import { headers } from "next/headers";
export function getUrlServerSide() {
const headersList = headers();
const host = headersList.get("host") || "";
const protocol = headersList.get("x-forwarded-proto") || "";
const domain = `${protocol}://${host}`;
return domain;
}
+3
View File
@@ -0,0 +1,3 @@
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_API_KEY!);
+158
View File
@@ -0,0 +1,158 @@
import { TypeOf, z } from "zod";
import { modelEnumType } from "@/db/schema";
// from chatgpt https://chat.openai.com/share/4985d20b-30b1-4a28-87f6-6ebf84a1040e
export const creatorSchema = z.object({
username: z.string().nullish(),
image: z.string().url().nullish(),
});
export const fileMetadataSchema = z.object({
fp: z.string().nullish(),
size: z.string().nullish(),
format: z.string().nullish(),
});
export const fileSchema = z.object({
id: z.number(),
sizeKB: z.number().nullish(),
name: z.string(),
type: z.string().nullish(),
metadata: fileMetadataSchema.nullish(),
pickleScanResult: z.string().nullish(),
pickleScanMessage: z.string().nullable(),
virusScanResult: z.string().nullish(),
virusScanMessage: z.string().nullable(),
scannedAt: z.string().nullish(),
hashes: z.record(z.string()).nullish(),
downloadUrl: z.string().url(),
primary: z.boolean().nullish(),
});
export const imageMetadataSchema = z.object({
hash: z.string(),
width: z.number(),
height: z.number(),
});
export const imageMetaSchema = z.object({
ENSD: z.string().nullish(),
Size: z.string().nullish(),
seed: z.number().nullish(),
Model: z.string().nullish(),
steps: z.number().nullish(),
hashes: z.record(z.string()).nullish(),
prompt: z.string().nullish(),
sampler: z.string().nullish(),
cfgScale: z.number().nullish(),
ClipSkip: z.number().nullish(),
resources: z.array(
z.object({
hash: z.string().nullish(),
name: z.string(),
type: z.string(),
weight: z.number().nullish(),
}),
).nullish(),
ModelHash: z.string().nullish(),
HiresSteps: z.string().nullish(),
HiresUpscale: z.string().nullish(),
HiresUpscaler: z.string().nullish(),
negativePrompt: z.string(),
DenoisingStrength: z.number().nullish(),
});
// NOTE: this definition is all over the place
// export const imageSchema = z.object({
// url: z.string().url().nullish(),
// nsfw: z.enum(["None", "Soft", "Mature"]).nullish(),
// width: z.number().nullish(),
// height: z.number().nullish(),
// hash: z.string().nullish(),
// type: z.string().nullish(),
// metadata: imageMetadataSchema.nullish(),
// meta: imageMetaSchema.nullish(),
// });
export const modelVersionSchema = z.object({
id: z.number(),
modelId: z.number(),
name: z.string(),
createdAt: z.string().nullish(),
updatedAt: z.string().nullish(),
// status: z.enum(["Published", "Unpublished"]).nullish(),
status: z.string().nullish(),
publishedAt: z.string().nullish(),
trainedWords: z.array(z.string()).nullable(),
trainingStatus: z.string().nullable(),
trainingDetails: z.string().nullable(),
baseModel: z.string().nullish(),
baseModelType: z.string().nullish(),
earlyAccessTimeFrame: z.number().nullish(),
description: z.string().nullable(),
vaeId: z.number().nullable(),
stats: z.object({
downloadCount: z.number(),
ratingCount: z.number(),
rating: z.number(),
}).nullish(),
files: z.array(fileSchema),
images: z.array(z.any()).nullish(),
downloadUrl: z.string().url(),
});
export const statsSchema = z.object({
downloadCount: z.number(),
favoriteCount: z.number(),
commentCount: z.number(),
ratingCount: z.number(),
rating: z.number(),
tippedAmountCount: z.number(),
});
const civitaiModelType = z.enum([
"Checkpoint",
"TextualInversion",
"Hypernetwork",
"AestheticGradient",
"LORA",
"Controlnet",
"Poses",
]);
export const CivitaiModelResponse = z.object({
id: z.number(),
name: z.string().nullish(),
description: z.string().nullish(),
type: civitaiModelType,
poi: z.boolean().nullish(),
nsfw: z.boolean().nullish(),
allowNoCredit: z.boolean().nullish(),
allowCommercialUse: z.string().nullish(),
allowDerivatives: z.boolean().nullish(),
allowDifferentLicense: z.boolean().nullish(),
stats: statsSchema.nullish(),
creator: creatorSchema.nullish(),
tags: z.array(z.string()).nullish(),
modelVersions: z.array(modelVersionSchema),
});
export function getModelTypeDetails(
modelType: typeof civitaiModelType["_type"],
): modelEnumType | undefined {
switch (modelType) {
case "Checkpoint":
return "checkpoint"
case "TextualInversion":
return "embedding"
case "LORA":
return "lora"
case "AestheticGradient":
case "Hypernetwork":
case "Controlnet":
case "Poses":
default:
return undefined;
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"functions": {
"src/app/**/*": {
"maxDuration": 300
}
}
}