Compare commits
89
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0b23783ba | ||
|
|
eeb7310955 | ||
|
|
e40cc5373f | ||
|
|
debdaf418c | ||
|
|
90107ebe1b | ||
|
|
a90c6c1db4 | ||
|
|
1bf3c1dcd0 | ||
|
|
24e95a1954 | ||
|
|
194715920e | ||
|
|
0cb1e92f4f | ||
|
|
d96811a0c3 | ||
|
|
757c587901 | ||
|
|
3b7db4480b | ||
|
|
10bbb393a7 | ||
|
|
c1fc06fd39 | ||
|
|
9de266fbab | ||
|
|
42aaf1acb9 | ||
|
|
852d889397 | ||
|
|
cb01c896a0 | ||
|
|
fbb7b18273 | ||
|
|
5443dcd20b | ||
|
|
e5097a5ed5 | ||
|
|
9a1ab21e91 | ||
|
|
d2dbe3410f | ||
|
|
0ce07c88f8 | ||
|
|
c7727fc1be | ||
|
|
72325a4217 | ||
|
|
5eba792579 | ||
|
|
fe8f5a49ff | ||
|
|
f4d93a82e2 | ||
|
|
18c410ba4e | ||
|
|
6aa9a44f4b | ||
|
|
d3e1ca1b0b | ||
|
|
8f7f06470c | ||
|
|
2a55b20887 | ||
|
|
dd0af2a3e9 | ||
|
|
9a715e9766 | ||
|
|
ac5aba2aa9 | ||
|
|
95e454e687 | ||
|
|
8b790fe7c5 | ||
|
|
9e07ccadb4 | ||
|
|
954de12d08 | ||
|
|
731bdf3982 | ||
|
|
97aa88eac8 | ||
|
|
d7ee49aa49 | ||
|
|
2bace09f5e | ||
|
|
78721b3129 | ||
|
|
112c6bb0f6 | ||
|
|
0311638ab1 | ||
|
|
15e1b1ccb4 | ||
|
|
4bca9a7376 | ||
|
|
e95811474e | ||
|
|
cc03c780e8 | ||
|
|
93b1fa9c79 | ||
|
|
85477aba9d | ||
|
|
62a69dba06 | ||
|
|
8305134a8e | ||
|
|
6f0499c657 | ||
|
|
b968fded32 | ||
|
|
aa419c80f1 | ||
|
|
3c1e49b451 | ||
|
|
72312c7a40 | ||
|
|
f22f5eef4e | ||
|
|
982ef0780d | ||
|
|
a295df973f | ||
|
|
d8df580339 | ||
|
|
0d88907cfd | ||
|
|
4c047aea62 | ||
|
|
db04d02d34 | ||
|
|
3d2eacccc9 | ||
|
|
85e4219ea7 | ||
|
|
df4dfba31d | ||
|
|
911cc8d16b | ||
|
|
224b006ec2 | ||
|
|
dfd139294f | ||
|
|
eb04f246a4 | ||
|
|
d644edd3c4 | ||
|
|
1143b469ad | ||
|
|
0f9e0c9c76 | ||
|
|
b1e9bcc4e6 | ||
|
|
14c3ca6bf5 | ||
|
|
7a7ced3e08 | ||
|
|
a9f46b0846 | ||
|
|
f948fca78c | ||
|
|
0ba1a6d1f0 | ||
|
|
ac8f6e2808 | ||
|
|
8a134ed39e | ||
|
|
9cbf0760a0 | ||
|
|
931b4e144a |
@@ -4,4 +4,20 @@ FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:${VARIANT}
|
|||||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
# && 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
|
RUN npm install -g bun
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
"service": "app",
|
"service": "app",
|
||||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||||
"postCreateCommand": "cd web && bun install && bun run migrate-local",
|
"postCreateCommand": "cd web && bun install && bun run migrate-local",
|
||||||
|
"remoteUser": "root",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
@@ -13,7 +14,9 @@
|
|||||||
"stivo.tailwind-fold",
|
"stivo.tailwind-fold",
|
||||||
"streetsidesoftware.code-spell-checker",
|
"streetsidesoftware.code-spell-checker",
|
||||||
"GitHub.copilot",
|
"GitHub.copilot",
|
||||||
|
"ms-azuretools.vscode-docker",
|
||||||
|
"ms-python.python"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -1,2 +1,3 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vercel
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
MODAL_TOKEN_ID=
|
MODAL_TOKEN_ID=
|
||||||
MODAL_TOKEN_SECRET=
|
MODAL_TOKEN_SECRET=
|
||||||
|
CIVITAI_API_KEY=
|
||||||
|
|
||||||
# On production set to False
|
# On production set to False
|
||||||
DEPLOY_TEST_FLAG=True
|
DEPLOY_TEST_FLAG=True
|
||||||
|
CIVITAI_API_KEY=
|
||||||
|
PUBLIC_MODEL_VOLUME_NAME=
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ fly secrets set MODAL_TOKEN_SECRET=
|
|||||||
|
|
||||||
## To deploy
|
## 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
|
// model-builder/fly.toml
|
||||||
app = <APP_NAME>
|
app = <APP_NAME>
|
||||||
@@ -53,5 +56,5 @@ fly launch
|
|||||||
```
|
```
|
||||||
if not, run this instead
|
if not, run this instead
|
||||||
```
|
```
|
||||||
fly deploy
|
fly deploy -c "toml 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
|
||||||
@@ -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.
|
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||||
#
|
#
|
||||||
|
|
||||||
app = "modal-builder"
|
app = 'modal-builder-preview'
|
||||||
primary_region = "sea"
|
primary_region = 'sea'
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
|
||||||
@@ -14,4 +14,9 @@ primary_region = "sea"
|
|||||||
auto_stop_machines = true
|
auto_stop_machines = true
|
||||||
auto_start_machines = true
|
auto_start_machines = true
|
||||||
min_machines_running = 0
|
min_machines_running = 0
|
||||||
processes = ["app"]
|
processes = ['app']
|
||||||
|
|
||||||
|
[[vm]]
|
||||||
|
cpu_kind = 'shared'
|
||||||
|
cpus = 1
|
||||||
|
memory_mb = 1024
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from enum import Enum
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
from uuid import uuid4
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
import asyncio
|
import asyncio
|
||||||
import threading
|
import threading
|
||||||
@@ -19,6 +20,7 @@ from urllib.parse import parse_qs
|
|||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
from starlette.types import ASGIApp, Scope, Receive, Send
|
from starlette.types import ASGIApp, Scope, Receive, Send
|
||||||
|
|
||||||
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
# executor = ThreadPoolExecutor(max_workers=5)
|
# executor = ThreadPoolExecutor(max_workers=5)
|
||||||
@@ -45,6 +47,8 @@ machine_id_websocket_dict = {}
|
|||||||
machine_id_status = {}
|
machine_id_status = {}
|
||||||
|
|
||||||
fly_instance_id = os.environ.get('FLY_ALLOC_ID', 'local').split('-')[0]
|
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):
|
class FlyReplayMiddleware(BaseHTTPMiddleware):
|
||||||
@@ -139,6 +143,7 @@ def read_root():
|
|||||||
class GitCustomNodes(BaseModel):
|
class GitCustomNodes(BaseModel):
|
||||||
hash: str
|
hash: str
|
||||||
disabled: bool
|
disabled: bool
|
||||||
|
pip: Optional[List[str]] = None
|
||||||
|
|
||||||
class FileCustomNodes(BaseModel):
|
class FileCustomNodes(BaseModel):
|
||||||
filename: str
|
filename: str
|
||||||
@@ -174,6 +179,7 @@ class Item(BaseModel):
|
|||||||
snapshot: Snapshot
|
snapshot: Snapshot
|
||||||
models: List[Model]
|
models: List[Model]
|
||||||
callback_url: str
|
callback_url: str
|
||||||
|
model_volume_name: str
|
||||||
gpu: GPUType = Field(default=GPUType.T4)
|
gpu: GPUType = Field(default=GPUType.T4)
|
||||||
|
|
||||||
@field_validator('gpu')
|
@field_validator('gpu')
|
||||||
@@ -223,6 +229,71 @@ async def websocket_endpoint(websocket: WebSocket, machine_id: str):
|
|||||||
|
|
||||||
# return {"Hello": "World"}
|
# 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")
|
@app.post("/create")
|
||||||
async def create_machine(item: Item):
|
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)
|
cp_process = await asyncio.subprocess.create_subprocess_exec("cp", "-r", "/app/src/template", folder_path)
|
||||||
await cp_process.wait()
|
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
|
# Write the config file
|
||||||
config = {
|
config = {
|
||||||
"name": item.name,
|
"name": item.name,
|
||||||
"deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False"),
|
"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:
|
with open(f"{folder_path}/config.py", "w") as f:
|
||||||
f.write("config = " + json.dumps(config))
|
f.write("config = " + json.dumps(config))
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
from config import config
|
from config import config
|
||||||
import modal
|
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 json
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
|
from volume_setup import volumes
|
||||||
|
from datetime import datetime
|
||||||
# deploy_test = False
|
# deploy_test = False
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -27,8 +28,8 @@ deploy_test = config["deploy_test"] == "True"
|
|||||||
web_app = FastAPI()
|
web_app = FastAPI()
|
||||||
print(config)
|
print(config)
|
||||||
print("deploy_test ", deploy_test)
|
print("deploy_test ", deploy_test)
|
||||||
|
print('volumes', volumes)
|
||||||
stub = Stub(name=config["name"])
|
stub = Stub(name=config["name"])
|
||||||
# print(stub.app_id)
|
|
||||||
|
|
||||||
if not deploy_test:
|
if not deploy_test:
|
||||||
# dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data"))
|
# dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data"))
|
||||||
@@ -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 && pip install -r requirements.txt",
|
||||||
"cd /comfyui/custom_nodes/ComfyUI-Manager && mkdir startup-scripts",
|
"cd /comfyui/custom_nodes/ComfyUI-Manager && mkdir startup-scripts",
|
||||||
)
|
)
|
||||||
|
.run_commands(f"cat /comfyui/server.py")
|
||||||
|
.run_commands(f"ls /comfyui/app")
|
||||||
# .run_commands(
|
# .run_commands(
|
||||||
# # Install comfy deploy
|
# # Install comfy deploy
|
||||||
# "cd /comfyui/custom_nodes && git clone https://github.com/BennyKok/comfyui-deploy.git",
|
# "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")
|
.copy_local_file(f"{current_directory}/data/start.sh", "/start.sh")
|
||||||
.run_commands("chmod +x /start.sh")
|
.run_commands("chmod +x /start.sh")
|
||||||
|
|
||||||
# Restore the custom nodes first
|
# 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/restore_snapshot.py", "/")
|
||||||
.copy_local_file(f"{current_directory}/data/snapshot.json", "/comfyui/custom_nodes/ComfyUI-Manager/startup-scripts/restore-snapshot.json")
|
.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")
|
.run_commands("python restore_snapshot.py")
|
||||||
@@ -153,89 +158,110 @@ image = Image.debian_slim()
|
|||||||
|
|
||||||
target_image = image if deploy_test else dockerfile_image
|
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)
|
||||||
|
class ComfyDeployRunner:
|
||||||
|
|
||||||
@stub.function(image=target_image, gpu=config["gpu"])
|
@enter()
|
||||||
def run(input: Input):
|
def setup(self):
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
# Make sure that the ComfyUI API is available
|
# Make sure that the ComfyUI API is available
|
||||||
print(f"comfy-modal - check server")
|
print(f"comfy-modal - check server")
|
||||||
|
|
||||||
command = ["python", "main.py",
|
command = ["python", "main.py",
|
||||||
"--disable-auto-launch", "--disable-metadata"]
|
"--disable-auto-launch", "--disable-metadata"]
|
||||||
server_process = subprocess.Popen(command, cwd="/comfyui")
|
|
||||||
|
|
||||||
check_server(
|
self.server_process = subprocess.Popen(command, cwd="/comfyui")
|
||||||
f"http://{COMFY_HOST}",
|
|
||||||
COMFY_API_AVAILABLE_MAX_RETRIES,
|
|
||||||
COMFY_API_AVAILABLE_INTERVAL_MS,
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
@method()
|
||||||
try:
|
def run(self, input: Input):
|
||||||
# job_input is the json input
|
data = json.dumps({
|
||||||
queued_workflow = queue_workflow_comfy_deploy(
|
"run_id": input.prompt_id,
|
||||||
job_input) # queue_workflow(workflow)
|
"status": "started",
|
||||||
prompt_id = queued_workflow["prompt_id"]
|
"time": datetime.now().isoformat()
|
||||||
print(f"comfy-modal - queued workflow with ID {prompt_id}")
|
}).encode('utf-8')
|
||||||
except Exception as e:
|
req = urllib.request.Request(input.status_endpoint, data=data, method='POST')
|
||||||
import traceback
|
urllib.request.urlopen(req)
|
||||||
print(traceback.format_exc())
|
|
||||||
return {"error": f"Error queuing workflow: {str(e)}"}
|
|
||||||
|
|
||||||
# Poll for completion
|
job_input = input
|
||||||
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
|
try:
|
||||||
# if prompt_id in history and history[prompt_id].get("outputs"):
|
queued_workflow = queue_workflow_comfy_deploy(job_input) # queue_workflow(workflow)
|
||||||
# break
|
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
|
# Poll for completion
|
||||||
if 'status' in status_result and (status_result['status'] == 'success' or status_result['status'] == 'failed'):
|
print(f"comfy-modal - wait until image generation is complete")
|
||||||
status = status_result['status']
|
retries = 0
|
||||||
print(status)
|
status = ""
|
||||||
break
|
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:
|
else:
|
||||||
# Wait before trying again
|
return {"error": "Max retries reached while waiting for image generation"}
|
||||||
time.sleep(COMFY_POLLING_INTERVAL_MS / 1000)
|
except Exception as e:
|
||||||
retries += 1
|
return {"error": f"Error waiting for image generation: {str(e)}"}
|
||||||
else:
|
|
||||||
return {"error": "Max retries reached while waiting for image generation"}
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": f"Error waiting for image generation: {str(e)}"}
|
|
||||||
|
|
||||||
print(f"comfy-modal - Finished, turning off")
|
print(f"comfy-modal - Finished, turning off")
|
||||||
server_process.terminate()
|
|
||||||
|
|
||||||
# Get the generated image and return it as URL in an AWS bucket or as base64
|
result = {"status": status}
|
||||||
# images_result = process_output_images(history[prompt_id].get("outputs"), job["id"])
|
|
||||||
# result = {**images_result, "refresh_worker": REFRESH_WORKER}
|
|
||||||
result = {"status": status}
|
|
||||||
|
|
||||||
return result
|
|
||||||
print("Running remotely on Modal!")
|
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@web_app.post("/run")
|
@web_app.post("/run")
|
||||||
async def bar(request_input: RequestInput):
|
async def post_run(request_input: RequestInput):
|
||||||
# print(request_input)
|
|
||||||
if not deploy_test:
|
if not deploy_test:
|
||||||
return run.remote(request_input.input)
|
# print(request_input.input.prompt_id, request_input.input.status_endpoint)
|
||||||
# pass
|
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()
|
@asgi_app()
|
||||||
def comfyui_api():
|
def comfyui_api():
|
||||||
return web_app
|
return web_app
|
||||||
@@ -285,6 +311,7 @@ def spawn_comfyui_in_background():
|
|||||||
# to be on a single container.
|
# to be on a single container.
|
||||||
concurrency_limit=1,
|
concurrency_limit=1,
|
||||||
timeout=10 * 60,
|
timeout=10 * 60,
|
||||||
|
volumes=volumes,
|
||||||
)
|
)
|
||||||
@asgi_app()
|
@asgi_app()
|
||||||
def comfyui_app():
|
def comfyui_app():
|
||||||
@@ -303,4 +330,4 @@ def comfyui_app():
|
|||||||
},
|
},
|
||||||
)()
|
)()
|
||||||
|
|
||||||
return make_simple_proxy_app(ProxyContext(config))
|
return make_simple_proxy_app(ProxyContext(config))
|
||||||
|
|||||||
@@ -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:
|
public:
|
||||||
base_path: /runpod-volume/ComfyUI/
|
base_path: /public_models/
|
||||||
checkpoints: models/checkpoints/
|
checkpoints: checkpoints
|
||||||
clip: models/clip/
|
clip: clip
|
||||||
clip_vision: models/clip_vision/
|
clip_vision: clip_vision
|
||||||
configs: models/configs/
|
configs: configs
|
||||||
controlnet: models/controlnet/
|
controlnet: controlnet
|
||||||
embeddings: models/embeddings/
|
embeddings: embeddings
|
||||||
loras: models/loras/
|
loras: loras
|
||||||
upscale_models: models/upscale_models/
|
upscale_models: upscale_models
|
||||||
vae: models/vae/
|
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
|
||||||
|
|||||||
@@ -54,4 +54,4 @@ for model in models:
|
|||||||
|
|
||||||
# Close the server
|
# Close the server
|
||||||
server_process.terminate()
|
server_process.terminate()
|
||||||
print("Finished installing dependencies.")
|
print("Finished installing dependencies.")
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"comfyui": "d0165d819afe76bd4e6bdd710eb5f3e571b6a804",
|
"comfyui": "d0165d819afe76bd4e6bdd710eb5f3e571b6a804",
|
||||||
"git_custom_nodes": {},
|
"git_custom_nodes": {
|
||||||
"file_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 # civitai requires auth
|
||||||
|
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
@@ -15,27 +15,28 @@ const ext = {
|
|||||||
const auth_token = queryParams.get("auth_token");
|
const auth_token = queryParams.get("auth_token");
|
||||||
const org_display = queryParams.get("org_display");
|
const org_display = queryParams.get("org_display");
|
||||||
const origin = queryParams.get("origin");
|
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) {
|
if (!workflow_version_id) {
|
||||||
console.error("No workflow_version_id provided in query parameters.");
|
console.error("No workflow_version_id provided in query parameters.");
|
||||||
} else {
|
} 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(
|
loadingDialog.showLoading(
|
||||||
"Loading workflow from " + org_display,
|
"Loading workflow from " + org_display,
|
||||||
"Please wait...",
|
"Please wait...",
|
||||||
@@ -681,19 +682,16 @@ export class ConfigDialog extends ComfyDialog {
|
|||||||
</label>
|
</label>
|
||||||
<label style="color: white; width: 100%;">
|
<label style="color: white; width: 100%;">
|
||||||
Endpoint:
|
Endpoint:
|
||||||
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${
|
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${data.endpoint
|
||||||
data.endpoint
|
}">
|
||||||
}">
|
|
||||||
</label>
|
</label>
|
||||||
<label style="color: white;">
|
<label style="color: white;">
|
||||||
API Key: ${data.displayName ?? ""}
|
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="${
|
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${data.apiKey
|
||||||
data.apiKey
|
}">
|
||||||
}">
|
|
||||||
<button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;">
|
<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>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,3 +20,14 @@ PLAUSIBLE_DOMAIN=
|
|||||||
|
|
||||||
NEXT_PUBLIC_POSTHOG_KEY="your-api-key"
|
NEXT_PUBLIC_POSTHOG_KEY="your-api-key"
|
||||||
NEXT_PUBLIC_POSTHOG_HOST="your-ph-address"
|
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_"
|
||||||
|
|||||||
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 $$;
|
||||||
@@ -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
|
||||||
|
);
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "comfyui_deploy"."subscription_status" ADD COLUMN "cancel_at_period_end" boolean DEFAULT false;
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "gpu" "machine_gpu";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "machine_type" "machine_type";
|
||||||
@@ -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 $$;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TYPE "model_upload_type" ADD VALUE 'download_url';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TYPE "model_upload_type" ADD VALUE 'download-url';
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,989 @@
|
|||||||
|
{
|
||||||
|
"id": "bd893271-b9ea-4832-a3e8-a6970b9f20d9",
|
||||||
|
"prevId": "cde8f758-0055-4326-981d-293ac84db54a",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"api_keys": {
|
||||||
|
"name": "api_keys",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"revoked": {
|
||||||
|
"name": "revoked",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"api_keys_user_id_users_id_fk": {
|
||||||
|
"name": "api_keys_user_id_users_id_fk",
|
||||||
|
"tableFrom": "api_keys",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"api_keys_key_unique": {
|
||||||
|
"name": "api_keys_key_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth_requests": {
|
||||||
|
"name": "auth_requests",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"request_id": {
|
||||||
|
"name": "request_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"api_hash": {
|
||||||
|
"name": "api_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"expired_date": {
|
||||||
|
"name": "expired_date",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"deployments": {
|
||||||
|
"name": "deployments",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"workflow_version_id": {
|
||||||
|
"name": "workflow_version_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workflow_id": {
|
||||||
|
"name": "workflow_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"machine_id": {
|
||||||
|
"name": "machine_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"share_slug": {
|
||||||
|
"name": "share_slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"showcase_media": {
|
||||||
|
"name": "showcase_media",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"name": "environment",
|
||||||
|
"type": "deployment_environment",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"deployments_user_id_users_id_fk": {
|
||||||
|
"name": "deployments_user_id_users_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"deployments_workflow_version_id_workflow_versions_id_fk": {
|
||||||
|
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "workflow_versions",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workflow_version_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"deployments_workflow_id_workflows_id_fk": {
|
||||||
|
"name": "deployments_workflow_id_workflows_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "workflows",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workflow_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"deployments_machine_id_machines_id_fk": {
|
||||||
|
"name": "deployments_machine_id_machines_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "machines",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"deployments_share_slug_unique": {
|
||||||
|
"name": "deployments_share_slug_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"share_slug"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"machines": {
|
||||||
|
"name": "machines",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"name": "endpoint",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"name": "disabled",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"auth_token": {
|
||||||
|
"name": "auth_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "machine_type",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'classic'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "machine_status",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'ready'"
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"name": "snapshot",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"models": {
|
||||||
|
"name": "models",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"gpu": {
|
||||||
|
"name": "gpu",
|
||||||
|
"type": "machine_gpu",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"build_machine_instance_id": {
|
||||||
|
"name": "build_machine_instance_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"build_log": {
|
||||||
|
"name": "build_log",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"machines_user_id_users_id_fk": {
|
||||||
|
"name": "machines_user_id_users_id_fk",
|
||||||
|
"tableFrom": "machines",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"subscription_status": {
|
||||||
|
"name": "subscription_status",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"stripe_customer_id": {
|
||||||
|
"name": "stripe_customer_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"plan": {
|
||||||
|
"name": "plan",
|
||||||
|
"type": "subscription_plan",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "subscription_plan_status",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"subscription_id": {
|
||||||
|
"name": "subscription_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"subscription_item_plan_id": {
|
||||||
|
"name": "subscription_item_plan_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"subscription_item_api_id": {
|
||||||
|
"name": "subscription_item_api_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"cancel_at_period_end": {
|
||||||
|
"name": "cancel_at_period_end",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"user_usage": {
|
||||||
|
"name": "user_usage",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"usage_time": {
|
||||||
|
"name": "usage_time",
|
||||||
|
"type": "real",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"ended_at": {
|
||||||
|
"name": "ended_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_usage_user_id_users_id_fk": {
|
||||||
|
"name": "user_usage_user_id_users_id_fk",
|
||||||
|
"tableFrom": "user_usage",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"workflow_run_outputs": {
|
||||||
|
"name": "workflow_run_outputs",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"run_id": {
|
||||||
|
"name": "run_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"name": "data",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
|
||||||
|
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
|
||||||
|
"tableFrom": "workflow_run_outputs",
|
||||||
|
"tableTo": "workflow_runs",
|
||||||
|
"columnsFrom": [
|
||||||
|
"run_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"workflow_runs": {
|
||||||
|
"name": "workflow_runs",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"workflow_version_id": {
|
||||||
|
"name": "workflow_version_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"workflow_inputs": {
|
||||||
|
"name": "workflow_inputs",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"workflow_id": {
|
||||||
|
"name": "workflow_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"machine_id": {
|
||||||
|
"name": "machine_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"name": "origin",
|
||||||
|
"type": "workflow_run_origin",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'api'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "workflow_run_status",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'not-started'"
|
||||||
|
},
|
||||||
|
"ended_at": {
|
||||||
|
"name": "ended_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"name": "started_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
|
||||||
|
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
|
||||||
|
"tableFrom": "workflow_runs",
|
||||||
|
"tableTo": "workflow_versions",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workflow_version_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"workflow_runs_workflow_id_workflows_id_fk": {
|
||||||
|
"name": "workflow_runs_workflow_id_workflows_id_fk",
|
||||||
|
"tableFrom": "workflow_runs",
|
||||||
|
"tableTo": "workflows",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workflow_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"workflow_runs_machine_id_machines_id_fk": {
|
||||||
|
"name": "workflow_runs_machine_id_machines_id_fk",
|
||||||
|
"tableFrom": "workflow_runs",
|
||||||
|
"tableTo": "machines",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "set null",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"workflows": {
|
||||||
|
"name": "workflows",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"workflows_user_id_users_id_fk": {
|
||||||
|
"name": "workflows_user_id_users_id_fk",
|
||||||
|
"tableFrom": "workflows",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"workflow_versions": {
|
||||||
|
"name": "workflow_versions",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"workflow_id": {
|
||||||
|
"name": "workflow_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"workflow": {
|
||||||
|
"name": "workflow",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"workflow_api": {
|
||||||
|
"name": "workflow_api",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"name": "version",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"name": "snapshot",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"workflow_versions_workflow_id_workflows_id_fk": {
|
||||||
|
"name": "workflow_versions_workflow_id_workflows_id_fk",
|
||||||
|
"tableFrom": "workflow_versions",
|
||||||
|
"tableTo": "workflows",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workflow_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {
|
||||||
|
"deployment_environment": {
|
||||||
|
"name": "deployment_environment",
|
||||||
|
"values": {
|
||||||
|
"staging": "staging",
|
||||||
|
"production": "production",
|
||||||
|
"public-share": "public-share"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"machine_gpu": {
|
||||||
|
"name": "machine_gpu",
|
||||||
|
"values": {
|
||||||
|
"T4": "T4",
|
||||||
|
"A10G": "A10G",
|
||||||
|
"A100": "A100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"machine_status": {
|
||||||
|
"name": "machine_status",
|
||||||
|
"values": {
|
||||||
|
"ready": "ready",
|
||||||
|
"building": "building",
|
||||||
|
"error": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"machine_type": {
|
||||||
|
"name": "machine_type",
|
||||||
|
"values": {
|
||||||
|
"classic": "classic",
|
||||||
|
"runpod-serverless": "runpod-serverless",
|
||||||
|
"modal-serverless": "modal-serverless",
|
||||||
|
"comfy-deploy-serverless": "comfy-deploy-serverless"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subscription_plan": {
|
||||||
|
"name": "subscription_plan",
|
||||||
|
"values": {
|
||||||
|
"basic": "basic",
|
||||||
|
"pro": "pro",
|
||||||
|
"enterprise": "enterprise"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subscription_plan_status": {
|
||||||
|
"name": "subscription_plan_status",
|
||||||
|
"values": {
|
||||||
|
"active": "active",
|
||||||
|
"deleted": "deleted",
|
||||||
|
"paused": "paused"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workflow_run_origin": {
|
||||||
|
"name": "workflow_run_origin",
|
||||||
|
"values": {
|
||||||
|
"manual": "manual",
|
||||||
|
"api": "api",
|
||||||
|
"public-share": "public-share"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workflow_run_status": {
|
||||||
|
"name": "workflow_run_status",
|
||||||
|
"values": {
|
||||||
|
"not-started": "not-started",
|
||||||
|
"running": "running",
|
||||||
|
"uploading": "uploading",
|
||||||
|
"success": "success",
|
||||||
|
"failed": "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemas": {
|
||||||
|
"comfyui_deploy": "comfyui_deploy"
|
||||||
|
},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -246,6 +246,97 @@
|
|||||||
"when": 1705902960991,
|
"when": 1705902960991,
|
||||||
"tag": "0034_even_lady_ursula",
|
"tag": "0034_even_lady_ursula",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 35,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1705981452482,
|
||||||
|
"tag": "0035_fearless_golden_guardian",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 36,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706081335387,
|
||||||
|
"tag": "0036_flippant_meltdown",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 37,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706086271525,
|
||||||
|
"tag": "0037_eager_cyclops",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 38,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706095853896,
|
||||||
|
"tag": "0038_yummy_darkhawk",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 39,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706109812261,
|
||||||
|
"tag": "0039_nostalgic_lyja",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 40,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706110859352,
|
||||||
|
"tag": "0040_salty_archangel",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 41,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706111421524,
|
||||||
|
"tag": "0041_thick_norrin_radd",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 42,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706164614659,
|
||||||
|
"tag": "0042_windy_madelyne_pryor",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 43,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706241449348,
|
||||||
|
"tag": "0043_wealthy_nicolaos",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 44,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706317908300,
|
||||||
|
"tag": "0044_panoramic_mister_fear",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 45,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706336448134,
|
||||||
|
"tag": "0045_careful_cerise",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 46,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706383154642,
|
||||||
|
"tag": "0046_complex_mentallo",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 47,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1706384528895,
|
||||||
|
"tag": "0047_gifted_starbolt",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+6
-1
@@ -19,23 +19,26 @@
|
|||||||
"@algolia/autocomplete-core": "^1.13.0",
|
"@algolia/autocomplete-core": "^1.13.0",
|
||||||
"@aws-sdk/client-s3": "^3.472.0",
|
"@aws-sdk/client-s3": "^3.472.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^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/react": "^1.7.17",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@hono/swagger-ui": "^0.2.1",
|
"@hono/swagger-ui": "^0.2.1",
|
||||||
"@hono/zod-openapi": "^0.9.5",
|
"@hono/zod-openapi": "^0.9.5",
|
||||||
"@hono/zod-validator": "^0.1.11",
|
"@hono/zod-validator": "^0.1.11",
|
||||||
"@hookform/resolvers": "^3.3.2",
|
"@hookform/resolvers": "^3.3.2",
|
||||||
|
"@lemonsqueezy/lemonsqueezy.js": "^1.2.5",
|
||||||
"@mdx-js/loader": "^3.0.0",
|
"@mdx-js/loader": "^3.0.0",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"@neondatabase/serverless": "^0.6.0",
|
"@neondatabase/serverless": "^0.6.0",
|
||||||
"@next/mdx": "^14.0.4",
|
"@next/mdx": "^14.0.4",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@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-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@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-radio-group": "^1.1.3",
|
||||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
@@ -46,6 +49,7 @@
|
|||||||
"@radix-ui/react-toggle": "^1.0.3",
|
"@radix-ui/react-toggle": "^1.0.3",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
|
"@stripe/stripe-js": "^2.4.0",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@tanstack/react-table": "^8.10.7",
|
"@tanstack/react-table": "^8.10.7",
|
||||||
"@tanstack/react-virtual": "beta",
|
"@tanstack/react-virtual": "beta",
|
||||||
@@ -94,6 +98,7 @@
|
|||||||
"shikiji": "^0.9.3",
|
"shikiji": "^0.9.3",
|
||||||
"simple-functional-loader": "^1.2.1",
|
"simple-functional-loader": "^1.2.1",
|
||||||
"sonner": "^1.2.4",
|
"sonner": "^1.2.4",
|
||||||
|
"stripe": "^14.13.0",
|
||||||
"swagger-ui-react": "^5.11.0",
|
"swagger-ui-react": "^5.11.0",
|
||||||
"swr": "^2.2.4",
|
"swr": "^2.2.4",
|
||||||
"tailwind-merge": "^2.1.0",
|
"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,46 @@
|
|||||||
|
import { stripe } from "@/server/stripe";
|
||||||
|
import { auth, clerkClient } from "@clerk/nextjs";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { getUrlServerSide } from "@/server/getUrlServerSide";
|
||||||
|
|
||||||
|
export async function GET(req: Request) {
|
||||||
|
const { userId, orgId } = auth();
|
||||||
|
|
||||||
|
const plan = new URL(req.url).searchParams.get("plan");
|
||||||
|
|
||||||
|
if (!userId) return redirect("/");
|
||||||
|
if (!plan) return redirect("/pricing");
|
||||||
|
|
||||||
|
const user = await clerkClient.users.getUser(userId);
|
||||||
|
|
||||||
|
const mapping = {
|
||||||
|
pro: process.env.STRIPE_PR_PRO,
|
||||||
|
enterprise: process.env.STRIPE_PR_ENTERPRISE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const api = process.env.STRIPE_PR_API;
|
||||||
|
|
||||||
|
const session = await stripe.checkout.sessions.create({
|
||||||
|
success_url: getUrlServerSide(),
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
price: mapping[plan as "pro" | "enterprise"],
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: api,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
metadata: {
|
||||||
|
userId: userId,
|
||||||
|
orgId: orgId ?? null,
|
||||||
|
plan: plan,
|
||||||
|
},
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
client_reference_id: orgId ?? userId,
|
||||||
|
customer_email: user.emailAddresses[0].emailAddress,
|
||||||
|
mode: "subscription",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (session.url) redirect(session.url);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { stripe } from "@/server/stripe";
|
||||||
|
import { auth } from "@clerk/nextjs";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { getUrlServerSide } from "@/server/getUrlServerSide";
|
||||||
|
import { getCurrentPlan } from "@/server/getCurrentPlan";
|
||||||
|
|
||||||
|
export async function GET(req: Request) {
|
||||||
|
const { userId, orgId } = auth();
|
||||||
|
|
||||||
|
if (!userId) return redirect("/");
|
||||||
|
|
||||||
|
const change = new URL(req.url).searchParams.get("change");
|
||||||
|
|
||||||
|
const sub = await getCurrentPlan({
|
||||||
|
org_id: orgId,
|
||||||
|
user_id: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sub) return redirect("/pricing");
|
||||||
|
|
||||||
|
const session = await stripe.billingPortal.sessions.create({
|
||||||
|
customer: sub.stripe_customer_id,
|
||||||
|
return_url: getUrlServerSide() + "/pricing",
|
||||||
|
// flow_data:
|
||||||
|
// change === "true" && sub.subscription_id
|
||||||
|
// ? {
|
||||||
|
// type: "subscription_update",
|
||||||
|
// subscription_update: {
|
||||||
|
// subscription: sub.subscription_id,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
redirect(session.url);
|
||||||
|
// if (session.url) redirect(session.url);
|
||||||
|
}
|
||||||
@@ -1,56 +1,129 @@
|
|||||||
import { parseDataSafe } from "../../../../lib/parseDataSafe";
|
import { parseDataSafe } from "../../../../lib/parseDataSafe";
|
||||||
import { db } from "@/db/db";
|
import { db } from "@/db/db";
|
||||||
import { workflowRunOutputs, workflowRunsTable } from "@/db/schema";
|
import {
|
||||||
|
WorkflowRunStatusSchema,
|
||||||
|
userUsageTable,
|
||||||
|
workflowRunOutputs,
|
||||||
|
workflowRunsTable,
|
||||||
|
workflowTable,
|
||||||
|
} from "@/db/schema";
|
||||||
|
import { getCurrentPlan } from "@/server/getCurrentPlan";
|
||||||
|
import { stripe } from "@/server/stripe";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const Request = z.object({
|
const Request = z.object({
|
||||||
run_id: z.string(),
|
run_id: z.string(),
|
||||||
status: z
|
status: WorkflowRunStatusSchema.optional(),
|
||||||
.enum(["not-started", "running", "uploading", "success", "failed"])
|
time: z.coerce.date().optional(),
|
||||||
.optional(),
|
|
||||||
output_data: z.any().optional(),
|
output_data: z.any().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const [data, error] = await parseDataSafe(Request, request);
|
try {
|
||||||
if (!data || error) return error;
|
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) {
|
if (status == "started" && time != undefined) {
|
||||||
const workflow_run_output = await db.insert(workflowRunOutputs).values({
|
// It successfully started, update the started_at time
|
||||||
run_id: run_id,
|
await db
|
||||||
data: output_data,
|
.update(workflowRunsTable)
|
||||||
});
|
.set({
|
||||||
} else if (status) {
|
started_at: time,
|
||||||
// console.log("status", status);
|
})
|
||||||
const workflow_run = await db
|
.where(eq(workflowRunsTable.id, run_id));
|
||||||
.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 == "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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { AccessType } from "../../../lib/AccessType";
|
import { AccessType } from "../../../lib/AccessType";
|
||||||
import { MachineList } from "@/components/MachineList";
|
import { MachineList } from "@/components/MachineList";
|
||||||
|
import { SubscriptionProvider } from "@/components/useCurrentPlan";
|
||||||
import { db } from "@/db/db";
|
import { db } from "@/db/db";
|
||||||
import { machinesTable } from "@/db/schema";
|
import { machinesTable } from "@/db/schema";
|
||||||
|
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
import { auth } from "@clerk/nextjs";
|
import { auth } from "@clerk/nextjs";
|
||||||
import { clerkClient } from "@clerk/nextjs/server";
|
import { clerkClient } from "@clerk/nextjs/server";
|
||||||
import { desc, eq, isNull, and } from "drizzle-orm";
|
import { desc, eq, isNull, and } from "drizzle-orm";
|
||||||
@@ -19,6 +21,8 @@ async function MachineListServer() {
|
|||||||
|
|
||||||
const user = await clerkClient.users.getUser(userId);
|
const user = await clerkClient.users.getUser(userId);
|
||||||
|
|
||||||
|
const sub = await getCurrentPlanWithAuth();
|
||||||
|
|
||||||
const machines = await db.query.machinesTable.findMany({
|
const machines = await db.query.machinesTable.findMany({
|
||||||
orderBy: desc(machinesTable.updated_at),
|
orderBy: desc(machinesTable.updated_at),
|
||||||
where:
|
where:
|
||||||
@@ -29,11 +33,12 @@ async function MachineListServer() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{/* <div>Machines</div> */}
|
<SubscriptionProvider sub={sub}>
|
||||||
<MachineList
|
<MachineList
|
||||||
data={machines}
|
data={machines}
|
||||||
userMetadata={AccessType.parse(user.privateMetadata ?? {})}
|
userMetadata={AccessType.parse(user.privateMetadata ?? {})}
|
||||||
/>
|
/>
|
||||||
|
</SubscriptionProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import PricingList from "@/components/PricingPlan";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PricingList />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
// app/providers.tsx
|
// app/providers.tsx
|
||||||
'use client'
|
"use client";
|
||||||
import posthog from 'posthog-js'
|
import posthog from "posthog-js";
|
||||||
import { PostHogProvider } from 'posthog-js/react'
|
import { PostHogProvider } from "posthog-js/react";
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||||
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
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({
|
export function PHProvider({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
|
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()} />;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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()} />;
|
||||||
|
}
|
||||||
@@ -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 { getMachines } from "@/server/curdMachine";
|
||||||
import { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
|
import { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
|
||||||
|
|
||||||
|
export const maxDuration = 300; // 5 minutes
|
||||||
|
|
||||||
export default async function Page({
|
export default async function Page({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export const maxDuration = 300; // 5 minutes
|
||||||
|
|
||||||
export default async function Layout({
|
export default async function Layout({
|
||||||
children,
|
children,
|
||||||
deployment,
|
deployment,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { usersTable } from "@/db/schema";
|
|||||||
import { auth } from "@clerk/nextjs";
|
import { auth } from "@clerk/nextjs";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
export const maxDuration = 300; // 5 minutes
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return <WorkflowServer />;
|
return <WorkflowServer />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Quickstart',
|
title: 'Quickstart',
|
||||||
description:
|
description:
|
||||||
'This guide will get you all set up and ready to use the Protocol API. We’ll cover how to get started an API client and how to make your first API request.',
|
'This guide will get you all set up and ready to use Comfy Deploy. We’ll cover how to get started an API client and how to make your first API request.',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Getting stated
|
# Getting started
|
||||||
|
|
||||||
Install Comfy Deploy's plugin on your local machine to get started with deploying workflow.
|
Install Comfy Deploy's plugin on your local ComfyUI to get started with deploying workflow.
|
||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
export const metadata = {
|
||||||
|
title: 'Comfy Deploy Video tutorials',
|
||||||
|
description:
|
||||||
|
'Get started with API integration to run any deploy ComfyUI workflow',
|
||||||
|
}
|
||||||
|
|
||||||
|
**This is a collection of video tutorials on questions that have come up in our [discord channel](https://discord.gg/Zrar7yP6MJ).**
|
||||||
|
|
||||||
|
# Comfy Deploy workflow walkthrough (comprehensive)
|
||||||
|
|
||||||
|
Local workflow, to production API
|
||||||
|
- Set inputs with Comfy Deploy `External Text` nodes
|
||||||
|
- Deploy the workflow
|
||||||
|
- Create a machine
|
||||||
|
- Deploy an API endpoint for the workflow
|
||||||
|
- Test the workflow in Comfy Deploy
|
||||||
|
|
||||||
|
<video controls>
|
||||||
|
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy_base-sd-setup.mp4" type="video/mp4"/>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Test with Docs
|
||||||
|
Shows how you can use the <a href='/docs/endpoints'>endpoints docs</a> to test out the API!
|
||||||
|
|
||||||
|
<video controls>
|
||||||
|
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy_interactive_docs.mp4" type="video/mp4"/>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
## Intermediate IPAdaptor example run
|
||||||
|
Shows off a semi-complex workflow working on Comfy Deploy
|
||||||
|
|
||||||
|
<video controls>
|
||||||
|
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/ipadapters-test.mp4" type="video/mp4"/>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
## Animate diff on Comfy Deploy
|
||||||
|
Shows off animate diff working on Comfy Deploy
|
||||||
|
|
||||||
|
<video controls>
|
||||||
|
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy-animediff.mp4" type="video/mp4"/>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
## Cloning workflows
|
||||||
|
What does cloning a workflow look like
|
||||||
|
|
||||||
|
<video controls>
|
||||||
|
<source src="https://pub-1a2bca0642c24fcfb84ce8d8415958d3.r2.dev/comfydeploy-clone-template.mp4" type="video/mp4"/>
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
## [Install custom nodes from any github repo (Loom)](https://www.loom.com/share/c2025c5060e348839ade6a3a9d96441d?sid=6a57f779-0209-4341-a5fd-f99769cdb162)
|
||||||
|
- This is only neccessary if the custom node is not in the ComfyUI Manager node list
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"use client";
|
||||||
|
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
|
import * as React from "react";
|
||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export type CurrentPlanContextType = Awaited<
|
||||||
|
ReturnType<typeof getCurrentPlanWithAuth>
|
||||||
|
>;
|
||||||
|
export const CurrentPlanContext = createContext<
|
||||||
|
CurrentPlanContextType | undefined
|
||||||
|
>(undefined);
|
||||||
|
export function SubscriptionProvider({
|
||||||
|
sub,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
sub: CurrentPlanContextType;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CurrentPlanContext.Provider value={sub}>
|
||||||
|
{children}
|
||||||
|
</CurrentPlanContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -118,15 +118,12 @@ export function DeploymentDisplay({
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent className="flex flex-col gap-2 !mt-0" value="client">
|
<TabsContent className="flex flex-col gap-2 !mt-0" value="client">
|
||||||
<div>
|
<div>
|
||||||
Copy and paste the ComfyDeployClient form
|
Install the node comfydeploy SDK
|
||||||
<a
|
<CodeBlock
|
||||||
href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts"
|
lang="bash"
|
||||||
className="text-blue-500 hover:underline"
|
code={`npm i comfydeploy`}
|
||||||
target="_blank"
|
/>
|
||||||
rel="noreferrer"
|
Initialize your client
|
||||||
>
|
|
||||||
here
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
lang="js"
|
lang="js"
|
||||||
@@ -200,18 +197,20 @@ function formatCode(
|
|||||||
if (inputs && inputs.length > 0) {
|
if (inputs && inputs.length > 0) {
|
||||||
codeTemplate = codeTemplate.replace(
|
codeTemplate = codeTemplate.replace(
|
||||||
"inputs: {}",
|
"inputs: {}",
|
||||||
`inputs: ${JSON.stringify(
|
`inputs: ${
|
||||||
Object.fromEntries(
|
JSON.stringify(
|
||||||
inputs.map((x) => {
|
Object.fromEntries(
|
||||||
return [x?.input_id, ""];
|
inputs.map((x) => {
|
||||||
}),
|
return [x?.input_id, ""];
|
||||||
),
|
}),
|
||||||
null,
|
),
|
||||||
2,
|
null,
|
||||||
)
|
2,
|
||||||
.split("\n")
|
)
|
||||||
.map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line
|
.split("\n")
|
||||||
.join("\n")}`,
|
.map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line
|
||||||
|
.join("\n")
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
codeTemplate = codeTemplate.replace(
|
codeTemplate = codeTemplate.replace(
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ import type { UnknownKeysParam, ZodObject, ZodRawShape, z } from "zod";
|
|||||||
export function InsertModal<
|
export function InsertModal<
|
||||||
K extends ZodRawShape,
|
K extends ZodRawShape,
|
||||||
Y extends UnknownKeysParam,
|
Y extends UnknownKeysParam,
|
||||||
Z extends ZodObject<K, Y>
|
Z extends ZodObject<K, Y>,
|
||||||
>(props: {
|
>(props: {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
title: string;
|
title: React.ReactNode;
|
||||||
|
buttonTitle?: React.ReactNode;
|
||||||
description: string;
|
description: string;
|
||||||
dialogClassName?: string;
|
dialogClassName?: string;
|
||||||
serverAction: (data: z.infer<Z>) => Promise<unknown>;
|
serverAction: (data: z.infer<Z>) => Promise<unknown>;
|
||||||
@@ -54,7 +55,7 @@ export function InsertModal<
|
|||||||
setOpen(true);
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.title}
|
{props.buttonTitle ?? props.title}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -105,7 +106,7 @@ export function InsertModal<
|
|||||||
export function UpdateModal<
|
export function UpdateModal<
|
||||||
K extends ZodRawShape,
|
K extends ZodRawShape,
|
||||||
Y extends UnknownKeysParam,
|
Y extends UnknownKeysParam,
|
||||||
Z extends ZodObject<K, Y>
|
Z extends ZodObject<K, Y>,
|
||||||
>(props: {
|
>(props: {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
setOpen?: (open: boolean) => void;
|
setOpen?: (open: boolean) => void;
|
||||||
@@ -118,7 +119,7 @@ export function UpdateModal<
|
|||||||
serverAction: (
|
serverAction: (
|
||||||
data: z.infer<Z> & {
|
data: z.infer<Z> & {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
},
|
||||||
) => Promise<unknown>;
|
) => Promise<unknown>;
|
||||||
formSchema: Z;
|
formSchema: Z;
|
||||||
fieldConfig?: FieldConfig<z.infer<Z>>;
|
fieldConfig?: FieldConfig<z.infer<Z>>;
|
||||||
@@ -165,7 +166,7 @@ export function UpdateModal<
|
|||||||
props.serverAction({
|
props.serverAction({
|
||||||
...data,
|
...data,
|
||||||
id: props.data.id,
|
id: props.data.id,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ import { getConnectionStatus } from "@/components/getConnectionStatus";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useWebSocket from "react-use-websocket";
|
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({
|
export function MachineBuildLog({
|
||||||
machine_id,
|
machine_id,
|
||||||
endpoint,
|
endpoint,
|
||||||
@@ -17,6 +30,7 @@ export function MachineBuildLog({
|
|||||||
}) {
|
}) {
|
||||||
const [logs, setLogs] = useState<LogsType>([]);
|
const [logs, setLogs] = useState<LogsType>([]);
|
||||||
const [finished, setFinished] = useState(false);
|
const [finished, setFinished] = useState(false);
|
||||||
|
const [status, setStatus] = useState<"failed" | "succuss">();
|
||||||
|
|
||||||
const wsEndpoint = endpoint.replace(/^http/, "ws");
|
const wsEndpoint = endpoint.replace(/^http/, "ws");
|
||||||
const query = { fly_instance_id: instance_id };
|
const query = { fly_instance_id: instance_id };
|
||||||
@@ -43,13 +57,59 @@ export function MachineBuildLog({
|
|||||||
setLogs((logs) => [...(logs ?? []), message.data]);
|
setLogs((logs) => [...(logs ?? []), message.data]);
|
||||||
} else if (message?.event === "FINISHED") {
|
} else if (message?.event === "FINISHED") {
|
||||||
setFinished(true);
|
setFinished(true);
|
||||||
|
setStatus(message.data.status)
|
||||||
}
|
}
|
||||||
}, [lastMessage]);
|
}, [lastMessage]);
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{connectionStatus}
|
{connectionStatus}
|
||||||
<LogsViewer logs={logs} />
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import {
|
|||||||
updateCustomMachine,
|
updateCustomMachine,
|
||||||
updateMachine,
|
updateMachine,
|
||||||
} from "@/server/curdMachine";
|
} from "@/server/curdMachine";
|
||||||
|
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
|
||||||
|
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
import type {
|
import type {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
@@ -54,9 +56,10 @@ import {
|
|||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
import { ArrowUpDown, Lock, MoreHorizontal, Plus } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
|
||||||
export type Machine = MachineType;
|
export type Machine = MachineType;
|
||||||
@@ -113,16 +116,18 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
{row.original.status} <LoadingIcon />
|
{row.original.status} <LoadingIcon />
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{!row.original.disabled && row.original.status && (
|
{!row.original.disabled &&
|
||||||
<Badge
|
row.original.status &&
|
||||||
variant={
|
row.original.status != "building" && (
|
||||||
row.original.status == "ready" ? "success" : "destructive"
|
<Badge
|
||||||
}
|
variant={
|
||||||
className="capitalize"
|
row.original.status == "ready" ? "success" : "destructive"
|
||||||
>
|
}
|
||||||
{row.original.status}
|
className="capitalize"
|
||||||
</Badge>
|
>
|
||||||
)}
|
{row.original.status}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
// </a>
|
// </a>
|
||||||
);
|
);
|
||||||
@@ -144,9 +149,16 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
header: () => <div className="text-left">Type</div>,
|
header: () => <div className="text-left">Type</div>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<div className="text-left font-medium truncate">
|
<Badge
|
||||||
|
className="text-left font-medium truncate"
|
||||||
|
variant={
|
||||||
|
row.original.type == "comfy-deploy-serverless"
|
||||||
|
? "success"
|
||||||
|
: "outline"
|
||||||
|
}
|
||||||
|
>
|
||||||
{row.original.type}
|
{row.original.type}
|
||||||
</div>
|
</Badge>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -178,6 +190,7 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const machine = row.original;
|
const machine = row.original;
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const sub = useCurrentPlan();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -192,7 +205,9 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="text-destructive"
|
className="text-destructive"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
callServerPromise(deleteMachine(machine.id));
|
callServerPromise(deleteMachine(machine.id), {
|
||||||
|
loadingText: "Deleting machine",
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete Machine
|
Delete Machine
|
||||||
@@ -200,7 +215,9 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
{machine.disabled ? (
|
{machine.disabled ? (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
callServerPromise(enableMachine(machine.id));
|
callServerPromise(enableMachine(machine.id), {
|
||||||
|
loadingText: "Enabling machine",
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Enable Machine
|
Enable Machine
|
||||||
@@ -209,7 +226,9 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="text-destructive"
|
className="text-destructive"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
callServerPromise(disableMachine(machine.id));
|
callServerPromise(disableMachine(machine.id), {
|
||||||
|
loadingText: "Disabling machine",
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Disable Machine
|
Disable Machine
|
||||||
@@ -217,22 +236,33 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
)}
|
)}
|
||||||
{machine.type === "comfy-deploy-serverless" && (
|
{machine.type === "comfy-deploy-serverless" && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem
|
||||||
<a
|
onClick={async () => {
|
||||||
target="_blank"
|
const url = await callServerPromise(
|
||||||
href={machine.endpoint.replace(
|
editWorkflowOnMachine(machine.id),
|
||||||
"comfyui-api",
|
{
|
||||||
"comfyui-app"
|
loadingText: "Getting machine url...",
|
||||||
)} rel="noreferrer"
|
},
|
||||||
>
|
);
|
||||||
Open ComfyUI
|
if (url && typeof url !== "object") {
|
||||||
</a>
|
window.open(url, "_blank");
|
||||||
|
} else if (url && typeof url === "object" && url.error) {
|
||||||
|
console.error(url.error);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Open ComfyUI
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
buildMachine({
|
callServerPromise(
|
||||||
id: machine.id,
|
buildMachine({
|
||||||
});
|
id: machine.id,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
loadingText: "Starting machine build process",
|
||||||
|
},
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Rebuild
|
Rebuild
|
||||||
@@ -269,7 +299,10 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
fieldType: "models",
|
fieldType: "models",
|
||||||
},
|
},
|
||||||
gpu: {
|
gpu: {
|
||||||
inputProps: {},
|
fieldType: "gpuPicker",
|
||||||
|
inputProps: {
|
||||||
|
sub: sub,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -297,6 +330,8 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
import { useCurrentPlan } from "./useCurrentPlan";
|
||||||
|
|
||||||
export function MachineList({
|
export function MachineList({
|
||||||
data,
|
data,
|
||||||
userMetadata,
|
userMetadata,
|
||||||
@@ -306,12 +341,14 @@ export function MachineList({
|
|||||||
}) {
|
}) {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
[]
|
[],
|
||||||
);
|
);
|
||||||
const [columnVisibility, setColumnVisibility] =
|
const [columnVisibility, setColumnVisibility] =
|
||||||
React.useState<VisibilityState>({});
|
React.useState<VisibilityState>({});
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
|
||||||
|
const sub = useCurrentPlan();
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
@@ -331,6 +368,21 @@ export function MachineList({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let machineMaxCount = 2;
|
||||||
|
|
||||||
|
// Temp fixes for machine count
|
||||||
|
if (userMetadata.betaFeaturesAccess) machineMaxCount = 5;
|
||||||
|
|
||||||
|
if (sub?.plan == "pro") {
|
||||||
|
machineMaxCount = 10;
|
||||||
|
} else if (sub?.plan == "enterprise") {
|
||||||
|
machineMaxCount = 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
const locked =
|
||||||
|
data.some((machine) => machine.type === "modal-serverless") &&
|
||||||
|
data.length >= machineMaxCount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex items-center py-4">
|
<div className="flex items-center py-4">
|
||||||
@@ -345,17 +397,18 @@ export function MachineList({
|
|||||||
<div className="ml-auto flex gap-2">
|
<div className="ml-auto flex gap-2">
|
||||||
<InsertModal
|
<InsertModal
|
||||||
dialogClassName="sm:max-w-[600px]"
|
dialogClassName="sm:max-w-[600px]"
|
||||||
disabled={
|
disabled={locked}
|
||||||
data.some(
|
|
||||||
(machine) => machine.type === "comfy-deploy-serverless"
|
|
||||||
) && !userMetadata.betaFeaturesAccess
|
|
||||||
}
|
|
||||||
tooltip={
|
tooltip={
|
||||||
data.some((machine) => machine.type === "comfy-deploy-serverless")
|
locked
|
||||||
? "Only one hosted machine at preview stage"
|
? `Max ${machineMaxCount} ComfyUI machine for your account, upgrade to unlock more cnfiguration.`
|
||||||
: undefined
|
: `Max ${machineMaxCount} ComfyUI machine for your account`
|
||||||
}
|
}
|
||||||
title="New Machine"
|
buttonTitle={
|
||||||
|
<>
|
||||||
|
New Machine {locked ? <Lock size={14} /> : <Plus size={14} />}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
title={"New Machine"}
|
||||||
description="Add custom ComfyUI machines to your account."
|
description="Add custom ComfyUI machines to your account."
|
||||||
serverAction={addCustomMachine}
|
serverAction={addCustomMachine}
|
||||||
formSchema={addCustomMachineSchema}
|
formSchema={addCustomMachineSchema}
|
||||||
@@ -381,11 +434,9 @@ export function MachineList({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gpu: {
|
gpu: {
|
||||||
fieldType: !userMetadata.betaFeaturesAccess
|
fieldType: "gpuPicker",
|
||||||
? "fallback"
|
|
||||||
: "select",
|
|
||||||
inputProps: {
|
inputProps: {
|
||||||
disabled: !userMetadata.betaFeaturesAccess,
|
sub: sub,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -410,7 +461,7 @@ export function MachineList({
|
|||||||
? null
|
? null
|
||||||
: flexRender(
|
: flexRender(
|
||||||
header.column.columnDef.header,
|
header.column.columnDef.header,
|
||||||
header.getContext()
|
header.getContext(),
|
||||||
)}
|
)}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
);
|
);
|
||||||
@@ -429,7 +480,7 @@ export function MachineList({
|
|||||||
<TableCell key={cell.id}>
|
<TableCell key={cell.id}>
|
||||||
{flexRender(
|
{flexRender(
|
||||||
cell.column.columnDef.cell,
|
cell.column.columnDef.cell,
|
||||||
cell.getContext()
|
cell.getContext(),
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -0,0 +1,416 @@
|
|||||||
|
"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 } 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 { addModel } from "@/server/curdModel";
|
||||||
|
import { downloadUrlModelSchema } 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
|
||||||
|
}
|
||||||
|
title="Add a Model"
|
||||||
|
description="using a link to a model"
|
||||||
|
serverAction={addModel}
|
||||||
|
formSchema={downloadUrlModelSchema}
|
||||||
|
fieldConfig={{
|
||||||
|
url: {
|
||||||
|
fieldType: "modelUrlPicker",
|
||||||
|
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"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
civitai.com
|
||||||
|
</a>{" "}
|
||||||
|
or a url we can download a model from
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
model_type: {
|
||||||
|
fieldType: "select",
|
||||||
|
inputProps: { required: true },
|
||||||
|
description: (
|
||||||
|
<>We'll figure this out if you pick a civitai model</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
} from "@clerk/nextjs";
|
} from "@clerk/nextjs";
|
||||||
import { Github, Menu } from "lucide-react";
|
import { Github, Menu } from "lucide-react";
|
||||||
import meta from "next-gen/config";
|
import meta from "next-gen/config";
|
||||||
|
import { useFeatureFlagEnabled } from "posthog-js/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useMediaQuery } from "usehooks-ts";
|
import { useMediaQuery } from "usehooks-ts";
|
||||||
|
|
||||||
@@ -29,9 +30,12 @@ export function Navbar() {
|
|||||||
const { organization } = useOrganization();
|
const { organization } = useOrganization();
|
||||||
const _isDesktop = useMediaQuery("(min-width: 1024px)");
|
const _isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||||
const [isDesktop, setIsDesktop] = useState(true);
|
const [isDesktop, setIsDesktop] = useState(true);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsDesktop(_isDesktop);
|
setIsDesktop(_isDesktop);
|
||||||
}, [_isDesktop]);
|
}, [_isDesktop]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row items-center gap-4">
|
<div className="flex flex-row items-center gap-4">
|
||||||
@@ -48,13 +52,6 @@ export function Navbar() {
|
|||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="grid h-full grid-rows-[1fr_auto]">
|
<div className="grid h-full grid-rows-[1fr_auto]">
|
||||||
<NavbarMenu className=" h-full" />
|
<NavbarMenu className=" h-full" />
|
||||||
{/* <OrganizationSwitcher
|
|
||||||
appearance={{
|
|
||||||
elements: {
|
|
||||||
rootBox: "flex items-center justify-center z-[50]",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
@@ -85,20 +82,26 @@ export function Navbar() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
{isDesktop && <NavbarMenu />}
|
{isDesktop && <NavbarMenu />}
|
||||||
<Button
|
{/* <Button
|
||||||
asChild
|
asChild
|
||||||
variant="link"
|
variant="link"
|
||||||
className="rounded-full aspect-square p-2 mr-4"
|
className="rounded-full aspect-square p-2 mr-4"
|
||||||
>
|
>
|
||||||
<a href="/docs">Docs</a>
|
<a href="/docs">Docs</a>
|
||||||
</Button>
|
</Button> */}
|
||||||
<UserButton />
|
<UserButton
|
||||||
|
afterSignOutUrl="/"
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="rounded-full aspect-square p-2"
|
className="rounded-full aspect-square p-2"
|
||||||
>
|
>
|
||||||
<a target="_blank" href="https://github.com/BennyKok/comfyui-deploy" rel="noreferrer">
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/BennyKok/comfyui-deploy"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
<Github />
|
<Github />
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useFeatureFlagEnabled } from "posthog-js/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useMediaQuery } from "usehooks-ts";
|
import { useMediaQuery } from "usehooks-ts";
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ export function NavbarMenu({ className }: { className?: string }) {
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const pricingPlanFlagEnable = useFeatureFlagEnabled("pricing-plan");
|
||||||
|
|
||||||
const pages = [
|
const pages = [
|
||||||
{
|
{
|
||||||
name: "Workflows",
|
name: "Workflows",
|
||||||
@@ -34,17 +37,37 @@ export function NavbarMenu({ className }: { className?: string }) {
|
|||||||
name: "API Keys",
|
name: "API Keys",
|
||||||
path: "/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 (
|
return (
|
||||||
<div className={cn("mr-2", className)}>
|
<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"> */}
|
{/* <div className="w-full h-full absolute inset-x-0 top-0 flex items-center justify-center pointer-events-none"> */}
|
||||||
{isDesktop && (
|
{isDesktop && (
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue={pathname}
|
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) => (
|
{pages.map((page) => (
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
key={page.name}
|
key={page.name}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,18 +2,18 @@ import { RunInputs } from "@/components/RunInputs";
|
|||||||
import { RunOutputs } from "@/components/RunOutputs";
|
import { RunOutputs } from "@/components/RunOutputs";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { TableCell, TableRow } from "@/components/ui/table";
|
import { TableCell, TableRow } from "@/components/ui/table";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
|
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
|
||||||
import { type findAllRuns } from "@/server/findAllRuns";
|
import { type findAllRuns } from "@/server/findAllRuns";
|
||||||
@@ -21,54 +21,64 @@ import { Suspense } from "react";
|
|||||||
import { LiveStatus } from "./LiveStatus";
|
import { LiveStatus } from "./LiveStatus";
|
||||||
|
|
||||||
export async function RunDisplay({
|
export async function RunDisplay({
|
||||||
run,
|
run,
|
||||||
}: {
|
}: {
|
||||||
run: Awaited<ReturnType<typeof findAllRuns>>[0];
|
run: Awaited<ReturnType<typeof findAllRuns>>[0];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>{run.number}</TableCell>
|
<TableCell>
|
||||||
<TableCell className="font-medium truncate">
|
<Tooltip>
|
||||||
{run.machine?.name}
|
<TooltipTrigger>{run.number}</TooltipTrigger>
|
||||||
</TableCell>
|
<TooltipContent>{run.id}</TooltipContent>
|
||||||
<TableCell className="truncate">
|
</Tooltip>
|
||||||
{getRelativeTime(run.created_at)}
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell className="font-medium truncate">
|
||||||
<TableCell>{run.version?.version}</TableCell>
|
{run.machine?.name}
|
||||||
<TableCell>
|
</TableCell>
|
||||||
<Badge variant="outline" className="truncate">
|
<TableCell className="truncate">
|
||||||
{run.origin}
|
{getRelativeTime(run.created_at)}
|
||||||
</Badge>
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell>{run.version?.version}</TableCell>
|
||||||
<TableCell className="truncate">
|
<TableCell>
|
||||||
<Tooltip>
|
<Badge variant="outline" className="truncate">
|
||||||
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
|
{run.origin}
|
||||||
<TooltipContent>
|
</Badge>
|
||||||
<div>Cold start: {getDuration(run.cold_start_duration)}</div>
|
</TableCell>
|
||||||
<div>Run duration: {getDuration(run.run_duration)}</div>
|
<TableCell className="truncate">
|
||||||
</TooltipContent>
|
<Tooltip>
|
||||||
</Tooltip>
|
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
|
||||||
</TableCell>
|
<TooltipContent>
|
||||||
<LiveStatus run={run} />
|
<div>
|
||||||
</TableRow>
|
Serverless latency: {getDuration(run.comfy_deploy_cold_start)}
|
||||||
</DialogTrigger>
|
</div>
|
||||||
<DialogContent className="max-w-3xl">
|
<div>
|
||||||
<DialogHeader>
|
GPU Cold start: {getDuration(run.cold_start_duration)}
|
||||||
<DialogTitle>Run outputs</DialogTitle>
|
</div>
|
||||||
<DialogDescription>
|
<div>Run duration: {getDuration(run.run_duration)}</div>
|
||||||
You can view your run's outputs here
|
</TooltipContent>
|
||||||
</DialogDescription>
|
</Tooltip>
|
||||||
</DialogHeader>
|
</TableCell>
|
||||||
<div className="max-h-96 overflow-y-scroll">
|
<LiveStatus run={run} />
|
||||||
<RunInputs run={run} />
|
</TableRow>
|
||||||
<Suspense>
|
</DialogTrigger>
|
||||||
<RunOutputs run_id={run.id} />
|
<DialogContent className="max-w-3xl">
|
||||||
</Suspense>
|
<DialogHeader>
|
||||||
</div>
|
<DialogTitle>Run outputs</DialogTitle>
|
||||||
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
|
<DialogDescription>
|
||||||
</DialogContent>
|
You can view your run's outputs here
|
||||||
</Dialog>
|
</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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import {
|
|||||||
Play,
|
Play,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { parseAsInteger, useQueryState } from "next-usequerystate";
|
import { parseAsInteger, useQueryState } from "next-usequerystate";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
@@ -60,6 +60,7 @@ import { callServerPromise } from "./callServerPromise";
|
|||||||
import fetcher from "./fetcher";
|
import fetcher from "./fetcher";
|
||||||
import { ButtonAction } from "@/components/ButtonActionLoader";
|
import { ButtonAction } from "@/components/ButtonActionLoader";
|
||||||
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
|
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
|
||||||
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
export function VersionSelect({
|
export function VersionSelect({
|
||||||
workflow,
|
workflow,
|
||||||
@@ -126,12 +127,27 @@ export function MachineSelect({
|
|||||||
|
|
||||||
export function useSelectedMachine(
|
export function useSelectedMachine(
|
||||||
machines: Awaited<ReturnType<typeof getMachines>>,
|
machines: Awaited<ReturnType<typeof getMachines>>,
|
||||||
) {
|
): [string, (v: string) => void] {
|
||||||
const a = useQueryState("machine", {
|
const searchParams = useSearchParams();
|
||||||
defaultValue: machines?.[0]?.id ?? "",
|
const pathname = usePathname();
|
||||||
});
|
const router = useRouter();
|
||||||
|
|
||||||
return a;
|
const createQueryString = useCallback(
|
||||||
|
(name: string, value: string) => {
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
params.set(name, value);
|
||||||
|
|
||||||
|
return params.toString();
|
||||||
|
},
|
||||||
|
[searchParams],
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
searchParams.get("machine") ?? machines?.[0]?.id ?? "",
|
||||||
|
(v: string) => {
|
||||||
|
router.push(pathname + "?" + createQueryString("machine", v));
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublicRunStore = {
|
type PublicRunStore = {
|
||||||
@@ -402,7 +418,7 @@ export function OpenEditButton({
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const url = await callServerPromise(
|
const url = await callServerPromise(
|
||||||
editWorkflowOnMachine(workflow_version_id, machine),
|
editWorkflowOnMachine(machine, workflow_version_id),
|
||||||
);
|
);
|
||||||
if (url && typeof url !== "object") {
|
if (url && typeof url !== "object") {
|
||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
|
|||||||
@@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
import { toast } from "sonner";
|
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
|
return result
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
if ((x as { message: string })?.message !== undefined) {
|
if ((x as { message: string })?.message !== undefined) {
|
||||||
@@ -15,5 +21,8 @@ export async function callServerPromise<T>(result: Promise<T>) {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
return null;
|
return null;
|
||||||
|
}).finally(() => {
|
||||||
|
if (id != undefined)
|
||||||
|
toast.dismiss(id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
"use client";
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { CivitalModelSchema, ModelListWrapper } from "./CivitalModelSchema";
|
||||||
|
import { getUrl, mapModelsList } from "./getUrl";
|
||||||
|
import { ModelSelector } from "./ModelSelector";
|
||||||
|
|
||||||
|
export function CivitaiModelRegistry({
|
||||||
|
field,
|
||||||
|
selectMultiple = true,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field"> & {
|
||||||
|
selectMultiple?: boolean;
|
||||||
|
}) {
|
||||||
|
const [modelList, setModelList] =
|
||||||
|
React.useState<z.infer<typeof ModelListWrapper>>();
|
||||||
|
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
const handleSearch = useDebouncedCallback((search) => {
|
||||||
|
console.log(`Searching... ${search}`);
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
fetch(getUrl(search), {
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
.then((x) => x.json())
|
||||||
|
.then((a) => {
|
||||||
|
const list = CivitalModelSchema.parse(a);
|
||||||
|
console.log(a);
|
||||||
|
|
||||||
|
setModelList(mapModelsList(list));
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
fetch(getUrl(), {
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
.then((x) => x.json())
|
||||||
|
.then((a) => {
|
||||||
|
const list = CivitalModelSchema.parse(a);
|
||||||
|
setModelList(mapModelsList(list));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelSelector
|
||||||
|
selectMultiple={selectMultiple}
|
||||||
|
field={field}
|
||||||
|
modelList={modelList}
|
||||||
|
label="Civitai"
|
||||||
|
onSearch={handleSearch}
|
||||||
|
shouldFilter={false}
|
||||||
|
isLoading={loading}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
"use client";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const Model = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
base: z.string(),
|
||||||
|
save_path: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
reference: z.string(),
|
||||||
|
filename: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CivitalModelSchema = z.object({
|
||||||
|
items: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
creator: z
|
||||||
|
.object({
|
||||||
|
username: z.string().nullable(),
|
||||||
|
image: z.string().nullable().default(null),
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
|
tags: z.array(z.string()),
|
||||||
|
modelVersions: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
modelId: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
status: z.string(),
|
||||||
|
publishedAt: z.string(),
|
||||||
|
trainedWords: z.array(z.unknown()),
|
||||||
|
trainingStatus: z.string().nullable(),
|
||||||
|
trainingDetails: z.string().nullable(),
|
||||||
|
baseModel: z.string(),
|
||||||
|
baseModelType: z.string().nullable(),
|
||||||
|
earlyAccessTimeFrame: z.number(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
vaeId: z.number().nullable(),
|
||||||
|
stats: z.object({
|
||||||
|
downloadCount: z.number(),
|
||||||
|
ratingCount: z.number(),
|
||||||
|
rating: z.number(),
|
||||||
|
}),
|
||||||
|
files: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
sizeKB: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
downloadUrl: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
images: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.number(),
|
||||||
|
url: z.string(),
|
||||||
|
nsfw: z.string(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
hash: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
metadata: z.object({
|
||||||
|
hash: z.string(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
}),
|
||||||
|
meta: z.any(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
downloadUrl: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
metadata: z.object({
|
||||||
|
totalItems: z.number(),
|
||||||
|
currentPage: z.number(),
|
||||||
|
pageSize: z.number(),
|
||||||
|
totalPages: z.number(),
|
||||||
|
nextPage: z.string().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
export const ModelList = z.array(Model);
|
||||||
|
|
||||||
|
export const ModelListWrapper = z.object({
|
||||||
|
models: ModelList,
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import * as React from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ModelListWrapper } from "./CivitalModelSchema";
|
||||||
|
import { ModelSelector } from "./ModelSelector";
|
||||||
|
|
||||||
|
export function ComfyUIManagerModelRegistry({
|
||||||
|
field,
|
||||||
|
selectMultiple = true,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field"> & {
|
||||||
|
selectMultiple?: boolean;
|
||||||
|
}) {
|
||||||
|
const [modelList, setModelList] =
|
||||||
|
React.useState<z.infer<typeof ModelListWrapper>>();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
fetch(
|
||||||
|
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
|
||||||
|
{
|
||||||
|
signal: controller.signal,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then((x) => x.json())
|
||||||
|
.then((a) => {
|
||||||
|
setModelList(ModelListWrapper.parse(a));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelSelector
|
||||||
|
selectMultiple={selectMultiple}
|
||||||
|
field={field}
|
||||||
|
modelList={modelList}
|
||||||
|
label="ComfyUI Manager"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,160 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
CommandList,
|
|
||||||
} from "@/components/ui/command";
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { Check, ChevronsUpDown } from "lucide-react";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useRef } from "react";
|
import { CivitaiModelRegistry } from "./CivitaiModelRegistry";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { ComfyUIManagerModelRegistry } from "./ComfyUIManagerModelRegistry";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const Model = z.object({
|
|
||||||
name: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
base: z.string(),
|
|
||||||
save_path: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
reference: z.string(),
|
|
||||||
filename: z.string(),
|
|
||||||
url: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CivitalModelSchema = z.object({
|
|
||||||
items: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
// poi: z.boolean(),
|
|
||||||
// nsfw: z.boolean(),
|
|
||||||
// allowNoCredit: z.boolean(),
|
|
||||||
// allowCommercialUse: z.string(),
|
|
||||||
// allowDerivatives: z.boolean(),
|
|
||||||
// allowDifferentLicense: z.boolean(),
|
|
||||||
// stats: z.object({
|
|
||||||
// downloadCount: z.number(),
|
|
||||||
// favoriteCount: z.number(),
|
|
||||||
// commentCount: z.number(),
|
|
||||||
// ratingCount: z.number(),
|
|
||||||
// rating: z.number(),
|
|
||||||
// tippedAmountCount: z.number(),
|
|
||||||
// }),
|
|
||||||
creator: z
|
|
||||||
.object({
|
|
||||||
username: z.string().nullable(),
|
|
||||||
image: z.string().nullable().default(null),
|
|
||||||
})
|
|
||||||
.nullable(),
|
|
||||||
tags: z.array(z.string()),
|
|
||||||
modelVersions: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
modelId: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
createdAt: z.string(),
|
|
||||||
updatedAt: z.string(),
|
|
||||||
status: z.string(),
|
|
||||||
publishedAt: z.string(),
|
|
||||||
trainedWords: z.array(z.unknown()),
|
|
||||||
trainingStatus: z.string().nullable(),
|
|
||||||
trainingDetails: z.string().nullable(),
|
|
||||||
baseModel: z.string(),
|
|
||||||
baseModelType: z.string().nullable(),
|
|
||||||
earlyAccessTimeFrame: z.number(),
|
|
||||||
description: z.string().nullable(),
|
|
||||||
vaeId: z.number().nullable(),
|
|
||||||
stats: z.object({
|
|
||||||
downloadCount: z.number(),
|
|
||||||
ratingCount: z.number(),
|
|
||||||
rating: z.number(),
|
|
||||||
}),
|
|
||||||
files: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
sizeKB: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
// metadata: z.object({
|
|
||||||
// fp: z.string().nullable().optional(),
|
|
||||||
// size: z.string().nullable().optional(),
|
|
||||||
// format: z.string().nullable().optional(),
|
|
||||||
// }),
|
|
||||||
// pickleScanResult: z.string(),
|
|
||||||
// pickleScanMessage: z.string(),
|
|
||||||
// virusScanResult: z.string(),
|
|
||||||
// virusScanMessage: z.string().nullable(),
|
|
||||||
// scannedAt: z.string(),
|
|
||||||
// hashes: z.object({
|
|
||||||
// AutoV1: z.string().nullable().optional(),
|
|
||||||
// AutoV2: z.string().nullable().optional(),
|
|
||||||
// SHA256: z.string().nullable().optional(),
|
|
||||||
// CRC32: z.string().nullable().optional(),
|
|
||||||
// BLAKE3: z.string().nullable().optional(),
|
|
||||||
// }),
|
|
||||||
downloadUrl: z.string(),
|
|
||||||
// primary: z.boolean().default(false),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
images: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.number(),
|
|
||||||
url: z.string(),
|
|
||||||
nsfw: z.string(),
|
|
||||||
width: z.number(),
|
|
||||||
height: z.number(),
|
|
||||||
hash: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
metadata: z.object({
|
|
||||||
hash: z.string(),
|
|
||||||
width: z.number(),
|
|
||||||
height: z.number(),
|
|
||||||
}),
|
|
||||||
meta: z.any(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
downloadUrl: z.string(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
metadata: z.object({
|
|
||||||
totalItems: z.number(),
|
|
||||||
currentPage: z.number(),
|
|
||||||
pageSize: z.number(),
|
|
||||||
totalPages: z.number(),
|
|
||||||
nextPage: z.string().optional(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ModelList = z.array(Model);
|
|
||||||
|
|
||||||
export const ModelListWrapper = z.object({
|
|
||||||
models: ModelList,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ModelPickerView({
|
export function ModelPickerView({
|
||||||
field,
|
field,
|
||||||
@@ -187,240 +44,3 @@ export function ModelPickerView({
|
|||||||
</Accordion>
|
</Accordion>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapType(type: string) {
|
|
||||||
switch (type) {
|
|
||||||
case "checkpoint":
|
|
||||||
return "checkpoints";
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapModelsList(
|
|
||||||
models: z.infer<typeof CivitalModelSchema>
|
|
||||||
): z.infer<typeof ModelListWrapper> {
|
|
||||||
return {
|
|
||||||
models: models.items.flatMap((item) => {
|
|
||||||
return item.modelVersions.map((v) => {
|
|
||||||
return {
|
|
||||||
name: `${item.name} ${v.name} (${v.files[0].name})`,
|
|
||||||
type: mapType(item.type.toLowerCase()),
|
|
||||||
base: v.baseModel,
|
|
||||||
save_path: "default",
|
|
||||||
description: item.description,
|
|
||||||
reference: "",
|
|
||||||
filename: v.files[0].name,
|
|
||||||
url: v.files[0].downloadUrl,
|
|
||||||
} as z.infer<typeof Model>;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUrl(search?: string) {
|
|
||||||
const baseUrl = "https://civitai.com/api/v1/models";
|
|
||||||
const searchParams = {
|
|
||||||
limit: 5,
|
|
||||||
} as any;
|
|
||||||
searchParams["sort"] = "Most Downloaded";
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
searchParams["query"] = search;
|
|
||||||
} else {
|
|
||||||
// sort: "Highest Rated",
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(baseUrl);
|
|
||||||
Object.keys(searchParams).forEach((key) =>
|
|
||||||
url.searchParams.append(key, searchParams[key])
|
|
||||||
);
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CivitaiModelRegistry({
|
|
||||||
field,
|
|
||||||
}: Pick<AutoFormInputComponentProps, "field">) {
|
|
||||||
const [modelList, setModelList] =
|
|
||||||
React.useState<z.infer<typeof ModelListWrapper>>();
|
|
||||||
|
|
||||||
const [loading, setLoading] = React.useState(false);
|
|
||||||
|
|
||||||
const handleSearch = useDebouncedCallback((search) => {
|
|
||||||
console.log(`Searching... ${search}`);
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
fetch(getUrl(search), {
|
|
||||||
signal: controller.signal,
|
|
||||||
})
|
|
||||||
.then((x) => x.json())
|
|
||||||
.then((a) => {
|
|
||||||
const list = CivitalModelSchema.parse(a);
|
|
||||||
console.log(a);
|
|
||||||
|
|
||||||
setModelList(mapModelsList(list));
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
controller.abort();
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const controller = new AbortController();
|
|
||||||
fetch(getUrl(), {
|
|
||||||
signal: controller.signal,
|
|
||||||
})
|
|
||||||
.then((x) => x.json())
|
|
||||||
.then((a) => {
|
|
||||||
const list = CivitalModelSchema.parse(a);
|
|
||||||
setModelList(mapModelsList(list));
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
controller.abort();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModelSelector
|
|
||||||
field={field}
|
|
||||||
modelList={modelList}
|
|
||||||
label="Civitai"
|
|
||||||
onSearch={handleSearch}
|
|
||||||
shouldFilter={false}
|
|
||||||
isLoading={loading}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ComfyUIManagerModelRegistry({
|
|
||||||
field,
|
|
||||||
}: Pick<AutoFormInputComponentProps, "field">) {
|
|
||||||
const [modelList, setModelList] =
|
|
||||||
React.useState<z.infer<typeof ModelListWrapper>>();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const controller = new AbortController();
|
|
||||||
fetch(
|
|
||||||
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
|
|
||||||
{
|
|
||||||
signal: controller.signal,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((x) => x.json())
|
|
||||||
.then((a) => {
|
|
||||||
setModelList(ModelListWrapper.parse(a));
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
controller.abort();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModelSelector
|
|
||||||
field={field}
|
|
||||||
modelList={modelList}
|
|
||||||
label="ComfyUI Manager"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ModelSelector({
|
|
||||||
field,
|
|
||||||
modelList,
|
|
||||||
label,
|
|
||||||
onSearch,
|
|
||||||
shouldFilter = true,
|
|
||||||
isLoading,
|
|
||||||
}: Pick<AutoFormInputComponentProps, "field"> & {
|
|
||||||
modelList?: z.infer<typeof ModelListWrapper>;
|
|
||||||
label: string;
|
|
||||||
onSearch?: (search: string) => void;
|
|
||||||
shouldFilter?: boolean;
|
|
||||||
isLoading?: boolean;
|
|
||||||
}) {
|
|
||||||
const value = (field.value as z.infer<typeof ModelList>) ?? [];
|
|
||||||
const [open, setOpen] = React.useState(false);
|
|
||||||
|
|
||||||
function toggleModel(model: z.infer<typeof Model>) {
|
|
||||||
const prevSelectedModels = value;
|
|
||||||
if (
|
|
||||||
prevSelectedModels.some(
|
|
||||||
(selectedModel) =>
|
|
||||||
selectedModel.url + selectedModel.name === model.url + model.name
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
field.onChange(
|
|
||||||
prevSelectedModels.filter(
|
|
||||||
(selectedModel) =>
|
|
||||||
selectedModel.url + selectedModel.name !== model.url + model.name
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
field.onChange([...prevSelectedModels, model]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="" ref={containerRef}>
|
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
aria-expanded={open}
|
|
||||||
className="w-full justify-between flex"
|
|
||||||
>
|
|
||||||
Add from {label}
|
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-[375px] p-0" side="bottom">
|
|
||||||
<Command shouldFilter={shouldFilter}>
|
|
||||||
<CommandInput
|
|
||||||
placeholder="Search models..."
|
|
||||||
className="h-9"
|
|
||||||
onValueChange={onSearch}
|
|
||||||
>
|
|
||||||
{isLoading && <LoadingIcon />}
|
|
||||||
</CommandInput>
|
|
||||||
<CommandEmpty>No models found.</CommandEmpty>
|
|
||||||
<CommandList className="pointer-events-auto">
|
|
||||||
<CommandGroup>
|
|
||||||
{modelList?.models.map((model) => (
|
|
||||||
<CommandItem
|
|
||||||
key={model.url + model.name}
|
|
||||||
value={model.url}
|
|
||||||
onSelect={() => {
|
|
||||||
toggleModel(model);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{model.name}
|
|
||||||
<Check
|
|
||||||
className={cn(
|
|
||||||
"ml-auto h-4 w-4",
|
|
||||||
value.some(
|
|
||||||
(selectedModel) => selectedModel.url === model.url
|
|
||||||
)
|
|
||||||
? "opacity-100"
|
|
||||||
: "opacity-0"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
"use client";
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ModelListWrapper, Model, ModelList } from "./CivitalModelSchema";
|
||||||
|
|
||||||
|
export function ModelSelector({
|
||||||
|
field,
|
||||||
|
modelList,
|
||||||
|
label,
|
||||||
|
onSearch,
|
||||||
|
shouldFilter = true,
|
||||||
|
isLoading,
|
||||||
|
selectMultiple = true,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field"> & {
|
||||||
|
modelList?: z.infer<typeof ModelListWrapper>;
|
||||||
|
label: string;
|
||||||
|
onSearch?: (search: string) => void;
|
||||||
|
shouldFilter?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
selectMultiple?: boolean;
|
||||||
|
}) {
|
||||||
|
const value = (field.value as z.infer<typeof ModelList>) ?? [];
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
|
function toggleModel(model: z.infer<typeof Model>) {
|
||||||
|
const prevSelectedModels = value;
|
||||||
|
if (
|
||||||
|
prevSelectedModels.some(
|
||||||
|
(selectedModel) =>
|
||||||
|
selectedModel.url + selectedModel.name === model.url + model.name,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
field.onChange(
|
||||||
|
prevSelectedModels.filter(
|
||||||
|
(selectedModel) =>
|
||||||
|
selectedModel.url + selectedModel.name !== model.url + model.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (!selectMultiple) {
|
||||||
|
field.onChange([model]);
|
||||||
|
} else {
|
||||||
|
field.onChange([...prevSelectedModels, model]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="" ref={containerRef}>
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between flex"
|
||||||
|
>
|
||||||
|
Add from {label}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[375px] p-0" side="bottom">
|
||||||
|
<Command shouldFilter={shouldFilter}>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Search models..."
|
||||||
|
className="h-9"
|
||||||
|
onValueChange={onSearch}
|
||||||
|
>
|
||||||
|
{isLoading && <LoadingIcon />}
|
||||||
|
</CommandInput>
|
||||||
|
<CommandEmpty>No models found.</CommandEmpty>
|
||||||
|
<CommandList className="pointer-events-auto">
|
||||||
|
<CommandGroup>
|
||||||
|
{modelList?.models.map((model) => (
|
||||||
|
<CommandItem
|
||||||
|
key={model.url + model.name}
|
||||||
|
value={model.url}
|
||||||
|
onSelect={() => {
|
||||||
|
toggleModel(model);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{model.name}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
value.some(
|
||||||
|
(selectedModel) => selectedModel.url === model.url,
|
||||||
|
)
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,9 @@ import {
|
|||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
@@ -17,6 +19,22 @@ import {
|
|||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "@/components/ui/command";
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
@@ -25,7 +43,15 @@ import {
|
|||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { findAllDeployments } from "@/server/curdDeploments";
|
import { findAllDeployments } from "@/server/curdDeploments";
|
||||||
import { Check, ChevronsUpDown } from "lucide-react";
|
import {
|
||||||
|
Check,
|
||||||
|
ChevronsUpDown,
|
||||||
|
Edit,
|
||||||
|
ExternalLink,
|
||||||
|
FolderInput,
|
||||||
|
MoreVertical,
|
||||||
|
Plus,
|
||||||
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@@ -39,19 +65,95 @@ export function SnapshotPickerView({
|
|||||||
<AccordionItem value="item-1">
|
<AccordionItem value="item-1">
|
||||||
<AccordionTrigger className="text-sm">Custom Nodes</AccordionTrigger>
|
<AccordionTrigger className="text-sm">Custom Nodes</AccordionTrigger>
|
||||||
<AccordionContent className="flex gap-2 flex-col px-1">
|
<AccordionContent className="flex gap-2 flex-col px-1">
|
||||||
<SnapshotPresetPicker field={field} />
|
<div className="flex flex-wrap gap-2 justify-end">
|
||||||
<CustomNodesSelector field={field} />
|
<CustomNodesSelector field={field} />
|
||||||
|
<SnapshotPresetPicker field={field} />
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="secondary" className="w-fit">
|
||||||
|
Edit <Edit size={14}></Edit>
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[600px] h-full max-h-[600px] grid grid-rows-[auto,1fr,auto]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit custom nodes</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Make advacne changes to the snapshots
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<Textarea
|
||||||
|
className="h-full p-2 max-h-[600px] rounded-md text-xs w-full"
|
||||||
|
value={JSON.stringify(field.value, null, 2)}
|
||||||
|
onChange={(e) => {
|
||||||
|
// Update field.onChange to pass the array of selected models
|
||||||
|
field.onChange(JSON.parse(e.target.value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose>
|
||||||
|
<Button type="button" variant="secondary">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
{field.value && (
|
{field.value && (
|
||||||
// <ScrollArea className="w-full bg-gray-100 mx-auto max-w-[500px] rounded-lg">
|
<div className="flex gap-2 flex-col">
|
||||||
<Textarea
|
{Object.entries(field.value.git_custom_nodes).map(
|
||||||
className="min-h-[150px] max-h-[300px] p-2 rounded-md text-xs w-full"
|
([key, item]: [string, any], index) => (
|
||||||
value={JSON.stringify(field.value, null, 2)}
|
<Card className="p-4 flex gap-4 items-center justify-between">
|
||||||
onChange={(e) => {
|
<div className="flex gap-4 items-center">
|
||||||
// Update field.onChange to pass the array of selected models
|
<div className="bg-gray-200 aspect-square w-6 h-6 rounded-full text-center">
|
||||||
field.onChange(JSON.parse(e.target.value));
|
{index + 1}
|
||||||
}}
|
</div>
|
||||||
/>
|
<div>
|
||||||
// </ScrollArea>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href={key}
|
||||||
|
className="hover:underline flex items-center gap-2"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<ExternalLink size={12} /> {key}
|
||||||
|
</a>
|
||||||
|
<div className="text-2xs text-primary/50">
|
||||||
|
{item.hash}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild type="button">
|
||||||
|
<Button type="button" variant={"ghost"}>
|
||||||
|
<MoreVertical size={12} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
disabled={key.endsWith("comfyui-deploy.git")}
|
||||||
|
// className="opacity-50"
|
||||||
|
onClick={() => {
|
||||||
|
const newNodeList = {
|
||||||
|
...field.value.git_custom_nodes,
|
||||||
|
};
|
||||||
|
delete newNodeList[key];
|
||||||
|
const nodeList = newNodeList;
|
||||||
|
const newValue = {
|
||||||
|
...field.value,
|
||||||
|
git_custom_nodes: nodeList,
|
||||||
|
};
|
||||||
|
field.onChange(newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Card>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
@@ -65,13 +167,14 @@ function SnapshotPresetPicker({
|
|||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [selected, setSelected] = React.useState<string | null>(null);
|
const [selected, setSelected] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const [frameworks, setFramework] = React.useState<
|
const [frameworks, setFramework] =
|
||||||
{
|
React.useState<
|
||||||
id: string;
|
{
|
||||||
label: string;
|
id: string;
|
||||||
value: string;
|
label: string;
|
||||||
}[]
|
value: string;
|
||||||
>();
|
}[]
|
||||||
|
>();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
findAllDeployments().then((a) => {
|
findAllDeployments().then((a) => {
|
||||||
@@ -108,12 +211,13 @@ function SnapshotPresetPicker({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
className="w-full justify-between flex"
|
className="w-fit justify-between flex"
|
||||||
>
|
>
|
||||||
{selected
|
<FolderInput size={14} />
|
||||||
|
Import
|
||||||
|
{/* {selected
|
||||||
? findItem(selected)?.label
|
? findItem(selected)?.label
|
||||||
: "Select snapshot (From deployments)"}
|
: "Select snapshot (From deployments)"} */}
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-[375px] p-0">
|
<PopoverContent className="w-[375px] p-0">
|
||||||
@@ -140,7 +244,7 @@ function SnapshotPresetPicker({
|
|||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
field.value === framework.value
|
field.value === framework.value
|
||||||
? "opacity-100"
|
? "opacity-100"
|
||||||
: "opacity-0"
|
: "opacity-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
@@ -157,6 +261,7 @@ type CustomNodeList = {
|
|||||||
author: string;
|
author: string;
|
||||||
title: string;
|
title: string;
|
||||||
reference: string;
|
reference: string;
|
||||||
|
pip: string[];
|
||||||
files: string[];
|
files: string[];
|
||||||
install_type: string;
|
install_type: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -198,12 +303,12 @@ function CustomNodesSelector({
|
|||||||
|
|
||||||
const { data, error, isLoading } = useSWR<CustomNodeList>(
|
const { data, error, isLoading } = useSWR<CustomNodeList>(
|
||||||
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json",
|
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json",
|
||||||
fetcher
|
fetcher,
|
||||||
);
|
);
|
||||||
|
|
||||||
const keys = React.useMemo(
|
const keys = React.useMemo(
|
||||||
() => Object.keys(customNodeList),
|
() => Object.keys(customNodeList),
|
||||||
[customNodeList, data]
|
[customNodeList, data],
|
||||||
);
|
);
|
||||||
|
|
||||||
function findItem(value: string) {
|
function findItem(value: string) {
|
||||||
@@ -212,6 +317,11 @@ function CustomNodesSelector({
|
|||||||
return included;
|
return included;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onChangeRef = React.useRef(field.onChange);
|
||||||
|
React.useEffect(() => {
|
||||||
|
onChangeRef.current = field.onChange;
|
||||||
|
}, [field.onChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
@@ -219,10 +329,10 @@ function CustomNodesSelector({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
className="w-full justify-between flex"
|
className="w-fit justify-between flex"
|
||||||
>
|
>
|
||||||
Add custom nodes - {keys.length} selected
|
<Plus size={14}></Plus> <Badge>{keys.length} </Badge>
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
{/* <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> */}
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-[375px] p-0" side="bottom">
|
<PopoverContent className="w-[375px] p-0" side="bottom">
|
||||||
@@ -237,56 +347,12 @@ function CustomNodesSelector({
|
|||||||
key={index}
|
key={index}
|
||||||
value={framework.reference}
|
value={framework.reference}
|
||||||
onSelect={async (currentValue) => {
|
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<
|
let nodeList: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
hash: string;
|
hash: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
pip?: string[];
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
const x = customNodeList;
|
const x = customNodeList;
|
||||||
@@ -296,18 +362,67 @@ function CustomNodesSelector({
|
|||||||
delete newNodeList[currentValue];
|
delete newNodeList[currentValue];
|
||||||
nodeList = newNodeList;
|
nodeList = newNodeList;
|
||||||
} else {
|
} 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;
|
||||||
|
|
||||||
|
const value: Record<string, any> = {
|
||||||
|
hash: branchInfo?.commit.sha,
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (framework.pip) {
|
||||||
|
value["pip"] = framework.pip;
|
||||||
|
}
|
||||||
|
|
||||||
nodeList = {
|
nodeList = {
|
||||||
[currentValue]: {
|
|
||||||
hash: branchInfo?.commit.sha,
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
...x,
|
...x,
|
||||||
|
[currentValue]: value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
field.onChange({
|
|
||||||
|
const newValue = {
|
||||||
...field.value,
|
...field.value,
|
||||||
git_custom_nodes: nodeList,
|
git_custom_nodes: nodeList,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
field.onChange(newValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{framework.title}
|
{framework.title}
|
||||||
@@ -316,7 +431,7 @@ function CustomNodesSelector({
|
|||||||
"ml-auto h-4 w-4",
|
"ml-auto h-4 w-4",
|
||||||
findItem(framework.reference)
|
findItem(framework.reference)
|
||||||
? "opacity-100"
|
? "opacity-100"
|
||||||
: "opacity-0"
|
: "opacity-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
"use client";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
CivitalModelSchema,
|
||||||
|
ModelListWrapper,
|
||||||
|
Model,
|
||||||
|
} from "./CivitalModelSchema";
|
||||||
|
|
||||||
|
function mapType(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case "checkpoint":
|
||||||
|
return "checkpoints";
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
export function mapModelsList(
|
||||||
|
models: z.infer<typeof CivitalModelSchema>,
|
||||||
|
): z.infer<typeof ModelListWrapper> {
|
||||||
|
return {
|
||||||
|
models: models.items.flatMap((item) => {
|
||||||
|
return item.modelVersions.map((v) => {
|
||||||
|
return {
|
||||||
|
name: `${item.name} ${v.name} (${v.files[0].name})`,
|
||||||
|
type: mapType(item.type.toLowerCase()),
|
||||||
|
base: v.baseModel,
|
||||||
|
save_path: "default",
|
||||||
|
description: item.description,
|
||||||
|
reference: "",
|
||||||
|
filename: v.files[0].name,
|
||||||
|
// Quick hack to get the download url back as normal url
|
||||||
|
url: `https://civitai.com/models/${v.modelId}?modelVersionId=${v.id}`, //v.files[0].downloadUrl,
|
||||||
|
} as z.infer<typeof Model>;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function getUrl(search?: string) {
|
||||||
|
const baseUrl = "https://civitai.com/api/v1/models";
|
||||||
|
const searchParams = {
|
||||||
|
limit: 5,
|
||||||
|
} as any;
|
||||||
|
searchParams["sort"] = "Most Downloaded";
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
searchParams["query"] = search;
|
||||||
|
} else {
|
||||||
|
// sort: "Highest Rated",
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
Object.keys(searchParams).forEach((key) =>
|
||||||
|
url.searchParams.append(key, searchParams[key]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { AutoFormInputComponentProps } from "@/components/ui/auto-form/types";
|
||||||
|
import { getBaseSchema } from "@/components/ui/auto-form/utils";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Lock } from "lucide-react";
|
||||||
|
import * as z from "zod";
|
||||||
|
|
||||||
|
export default function AutoFormGPUPicker({
|
||||||
|
label,
|
||||||
|
isRequired,
|
||||||
|
field,
|
||||||
|
fieldConfigItem,
|
||||||
|
zodItem,
|
||||||
|
}: AutoFormInputComponentProps) {
|
||||||
|
const baseValues = (getBaseSchema(zodItem) as unknown as z.ZodEnum<any>)._def
|
||||||
|
.values;
|
||||||
|
|
||||||
|
let values: [string, string][] = [];
|
||||||
|
if (!Array.isArray(baseValues)) {
|
||||||
|
values = Object.entries(baseValues);
|
||||||
|
} else {
|
||||||
|
values = baseValues.map((value) => [value, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findItem(value: any) {
|
||||||
|
return values.find((item) => item[0] === value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = fieldConfigItem.inputProps?.sub?.plan;
|
||||||
|
const enabledGPU = ["T4"];
|
||||||
|
|
||||||
|
const planMapping: Record<string, string> = {
|
||||||
|
A10G: "pro",
|
||||||
|
A100: "enterprise",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (plan == "pro") {
|
||||||
|
enabledGPU.push("A10G");
|
||||||
|
} else if (plan == "enterprise") {
|
||||||
|
enabledGPU.push("A10G");
|
||||||
|
enabledGPU.push("A100");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{label}
|
||||||
|
{isRequired && <span className="text-destructive"> *</span>}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
className="w-full"
|
||||||
|
placeholder={fieldConfigItem.inputProps?.placeholder}
|
||||||
|
>
|
||||||
|
{field.value ? findItem(field.value)?.[1] : "Select an option"}
|
||||||
|
</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{values.map(([value, label]) => {
|
||||||
|
const enabled = enabledGPU.includes(value);
|
||||||
|
return (
|
||||||
|
<SelectItem value={label} key={value} disabled={!enabled}>
|
||||||
|
{label}
|
||||||
|
{!enabled && (
|
||||||
|
<span className="mx-2 inline-flex items-center justify-center gap-2">
|
||||||
|
<Badge className="capitalize">{planMapping[value]}</Badge>{" "}
|
||||||
|
plan required
|
||||||
|
<Lock size={14}></Lock>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{fieldConfigItem.description && (
|
||||||
|
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "../ui/form";
|
||||||
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
|
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { CivitaiModelRegistry } from "./CivitaiModelRegistry";
|
||||||
|
import { ComfyUIManagerModelRegistry } from "./ComfyUIManagerModelRegistry";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ModelList } from "@/components/custom-form/CivitalModelSchema";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export default function AutoFormModelsPickerUrl({
|
||||||
|
label,
|
||||||
|
isRequired,
|
||||||
|
field,
|
||||||
|
fieldConfigItem,
|
||||||
|
zodItem,
|
||||||
|
fieldProps,
|
||||||
|
}: AutoFormInputComponentProps) {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
{fieldConfigItem.inputProps?.showLabel && (
|
||||||
|
<FormLabel>
|
||||||
|
{label}
|
||||||
|
{isRequired && <span className="text-destructive"> *</span>}
|
||||||
|
</FormLabel>
|
||||||
|
)}
|
||||||
|
<FormControl>
|
||||||
|
<Suspense fallback={<LoadingIcon />}>
|
||||||
|
<ModelPickerView field={field} fieldProps={fieldProps} />
|
||||||
|
</Suspense>
|
||||||
|
</FormControl>
|
||||||
|
{fieldConfigItem.description && (
|
||||||
|
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModelPickerView({
|
||||||
|
field,
|
||||||
|
fieldProps,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field" | "fieldProps">) {
|
||||||
|
const customOverride = React.useMemo(() => {
|
||||||
|
const customOnChange = (value: z.infer<typeof ModelList>) => {
|
||||||
|
const model = value[0];
|
||||||
|
field.onChange(model?.url);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...field,
|
||||||
|
onChange: customOnChange,
|
||||||
|
value: field.value
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
url: field.value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
}, [field]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 flex-col px-1">
|
||||||
|
<ComfyUIManagerModelRegistry
|
||||||
|
field={customOverride}
|
||||||
|
selectMultiple={false}
|
||||||
|
/>
|
||||||
|
<CivitaiModelRegistry field={customOverride} selectMultiple={false} />
|
||||||
|
<Input
|
||||||
|
// className="min-h-[150px] max-h-[300px] p-2 rounded-lg text-xs w-full"
|
||||||
|
value={field.value ?? ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(e.target.value);
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -7,10 +9,19 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "../ui/form";
|
} from "../ui/form";
|
||||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
import { ModelPickerView } from "@/components/custom-form/ModelPickerView";
|
|
||||||
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { ComfyUIManagerModelRegistry } from "./ComfyUIManagerModelRegistry";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
|
|
||||||
export default function AutoFormModelsPicker({
|
export default function AutoFormModelsPicker({
|
||||||
label,
|
label,
|
||||||
@@ -27,6 +38,7 @@ export default function AutoFormModelsPicker({
|
|||||||
{isRequired && <span className="text-destructive"> *</span>}
|
{isRequired && <span className="text-destructive"> *</span>}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Suspense fallback={<LoadingIcon />}>
|
<Suspense fallback={<LoadingIcon />}>
|
||||||
<ModelPickerView field={field} />
|
<ModelPickerView field={field} />
|
||||||
@@ -35,7 +47,54 @@ export default function AutoFormModelsPicker({
|
|||||||
{fieldConfigItem.description && (
|
{fieldConfigItem.description && (
|
||||||
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
||||||
)}
|
)}
|
||||||
|
<FormDescription>
|
||||||
|
{" "}
|
||||||
|
<div className="text-sm">
|
||||||
|
Models are moving to{" "}
|
||||||
|
<a
|
||||||
|
href="/storage"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="inline-flex items-center gap-1 underline"
|
||||||
|
>
|
||||||
|
<ExternalLink size={12} />
|
||||||
|
Storage
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ModelPickerView({
|
||||||
|
field,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field">) {
|
||||||
|
return (
|
||||||
|
<Accordion type="single" collapsible>
|
||||||
|
<AccordionItem value="item-1">
|
||||||
|
<AccordionTrigger className="text-sm">
|
||||||
|
Models (ComfyUI Manager)
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="flex gap-2 flex-col px-1">
|
||||||
|
<ComfyUIManagerModelRegistry field={field} />
|
||||||
|
{/* <CivitaiModelRegistry field={field} /> */}
|
||||||
|
{/* <span>{field.value.length} selected</span> */}
|
||||||
|
{field.value && (
|
||||||
|
<ScrollArea className="w-full bg-gray-100 mx-auto rounded-lg mt-2">
|
||||||
|
<Textarea
|
||||||
|
className="min-h-[150px] max-h-[300px] p-2 rounded-lg text-xs w-full"
|
||||||
|
value={JSON.stringify(field.value, null, 2)}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(JSON.parse(e.target.value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ export const navigation: Array<NavGroup> = [
|
|||||||
title: "API",
|
title: "API",
|
||||||
links: [{ title: "Endpoints", href: "/docs/endpoints" }],
|
links: [{ title: "Endpoints", href: "/docs/endpoints" }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Video Tutorials",
|
||||||
|
links: [{ title: "Archive", href: "/docs/videos" }],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Navigation(props: React.ComponentPropsWithoutRef<"nav">) {
|
export function Navigation(props: React.ComponentPropsWithoutRef<"nav">) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AutoFormGPUPicker from "@/components/custom-form/gpu-picker";
|
||||||
import AutoFormCheckbox from "./fields/checkbox";
|
import AutoFormCheckbox from "./fields/checkbox";
|
||||||
import AutoFormDate from "./fields/date";
|
import AutoFormDate from "./fields/date";
|
||||||
import AutoFormEnum from "./fields/enum";
|
import AutoFormEnum from "./fields/enum";
|
||||||
@@ -8,6 +9,7 @@ import AutoFormSwitch from "./fields/switch";
|
|||||||
import AutoFormTextarea from "./fields/textarea";
|
import AutoFormTextarea from "./fields/textarea";
|
||||||
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
|
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
|
||||||
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
|
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
|
||||||
|
import AutoFormModelsPickerUrl from "@/components/custom-form/model-picker-url-only";
|
||||||
|
|
||||||
export const INPUT_COMPONENTS = {
|
export const INPUT_COMPONENTS = {
|
||||||
checkbox: AutoFormCheckbox,
|
checkbox: AutoFormCheckbox,
|
||||||
@@ -22,6 +24,8 @@ export const INPUT_COMPONENTS = {
|
|||||||
// Customs
|
// Customs
|
||||||
snapshot: AutoFormSnapshotPicker,
|
snapshot: AutoFormSnapshotPicker,
|
||||||
models: AutoFormModelsPicker,
|
models: AutoFormModelsPicker,
|
||||||
|
gpuPicker: AutoFormGPUPicker,
|
||||||
|
modelUrlPicker: AutoFormModelsPickerUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
import type { INPUT_COMPONENTS } from "./config";
|
import type { INPUT_COMPONENTS } from "./config";
|
||||||
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
|
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
|
||||||
import type * as z from "zod";
|
import type * as z from "zod";
|
||||||
@@ -6,6 +7,7 @@ export type FieldConfigItem = {
|
|||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & {
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & {
|
||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
|
sub?: Awaited<ReturnType<typeof getCurrentPlanWithAuth>>;
|
||||||
};
|
};
|
||||||
fieldType?:
|
fieldType?:
|
||||||
| keyof typeof INPUT_COMPONENTS
|
| keyof typeof INPUT_COMPONENTS
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
|
import * as React from "react";
|
||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
type CurrentPlanContextType = Awaited<
|
||||||
|
ReturnType<typeof getCurrentPlanWithAuth>
|
||||||
|
>;
|
||||||
|
const CurrentPlanContext = createContext<CurrentPlanContextType | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
export function SubscriptionProvider({
|
||||||
|
sub,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
sub: CurrentPlanContextType;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CurrentPlanContext.Provider value={sub}>
|
||||||
|
{children}
|
||||||
|
</CurrentPlanContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCurrentPlan = (): CurrentPlanContextType => {
|
||||||
|
const context = useContext(CurrentPlanContext);
|
||||||
|
|
||||||
|
// if (context === undefined) {
|
||||||
|
// throw new Error("useCurrentPlan must be used within a CurrentPlanProvider");
|
||||||
|
// }
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import * as schema from "./schema";
|
|||||||
const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
|
const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
|
||||||
|
|
||||||
// if we're running locally
|
// if we're running locally
|
||||||
if (process.env.VERCEL_ENV !== "production") {
|
if (process.env.VERCEL_ENV !== "production" && process.env.VERCEL_ENV !== "preview") {
|
||||||
// Set the WebSocket proxy to work with the local instance
|
// Set the WebSocket proxy to work with the local instance
|
||||||
if (isDevContainer) {
|
if (isDevContainer) {
|
||||||
// Running inside a VS Code devcontainer
|
// Running inside a VS Code devcontainer
|
||||||
|
|||||||
+151
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import { CivitaiModelResponse } from "@/types/civitai";
|
||||||
import { type InferSelectModel, relations } from "drizzle-orm";
|
import { type InferSelectModel, relations } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
boolean,
|
boolean,
|
||||||
@@ -8,9 +9,10 @@ import {
|
|||||||
text,
|
text,
|
||||||
timestamp,
|
timestamp,
|
||||||
uuid,
|
uuid,
|
||||||
|
real,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||||
import { z } from "zod";
|
import { TypeOf, z } from "zod";
|
||||||
|
|
||||||
export const dbSchema = pgSchema("comfyui_deploy");
|
export const dbSchema = pgSchema("comfyui_deploy");
|
||||||
|
|
||||||
@@ -81,6 +83,7 @@ export const workflowVersionTable = dbSchema.table("workflow_versions", {
|
|||||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const workflowVersionSchema = createSelectSchema(workflowVersionTable);
|
export const workflowVersionSchema = createSelectSchema(workflowVersionTable);
|
||||||
|
|
||||||
export const workflowVersionRelations = relations(
|
export const workflowVersionRelations = relations(
|
||||||
@@ -99,6 +102,8 @@ export const workflowRunStatus = pgEnum("workflow_run_status", [
|
|||||||
"uploading",
|
"uploading",
|
||||||
"success",
|
"success",
|
||||||
"failed",
|
"failed",
|
||||||
|
"started",
|
||||||
|
"queued",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const deploymentEnvironment = pgEnum("deployment_environment", [
|
export const deploymentEnvironment = pgEnum("deployment_environment", [
|
||||||
@@ -113,6 +118,8 @@ export const workflowRunOrigin = pgEnum("workflow_run_origin", [
|
|||||||
"public-share",
|
"public-share",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const WorkflowRunStatusSchema = z.enum(workflowRunStatus.enumValues);
|
||||||
|
|
||||||
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
|
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
|
||||||
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
|
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
|
||||||
|
|
||||||
@@ -155,8 +162,16 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
|
|||||||
origin: workflowRunOrigin("origin").notNull().default("api"),
|
origin: workflowRunOrigin("origin").notNull().default("api"),
|
||||||
status: workflowRunStatus("status").notNull().default("not-started"),
|
status: workflowRunStatus("status").notNull().default("not-started"),
|
||||||
ended_at: timestamp("ended_at"),
|
ended_at: timestamp("ended_at"),
|
||||||
|
// comfy deploy run created time
|
||||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
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"),
|
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(
|
export const workflowRunRelations = relations(
|
||||||
@@ -334,6 +349,19 @@ export const apiKeyTable = dbSchema.table("api_keys", {
|
|||||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const userUsageTable = dbSchema.table("user_usage", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom().notNull(),
|
||||||
|
org_id: text("org_id"),
|
||||||
|
user_id: text("user_id")
|
||||||
|
.references(() => usersTable.id, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
})
|
||||||
|
.notNull(),
|
||||||
|
usage_time: real("usage_time").default(0).notNull(),
|
||||||
|
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
ended_at: timestamp("ended_at").defaultNow().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
export const authRequestsTable = dbSchema.table("auth_requests", {
|
export const authRequestsTable = dbSchema.table("auth_requests", {
|
||||||
request_id: text("request_id").primaryKey().notNull(),
|
request_id: text("request_id").primaryKey().notNull(),
|
||||||
user_id: text("user_id"),
|
user_id: text("user_id"),
|
||||||
@@ -344,8 +372,130 @@ export const authRequestsTable = dbSchema.table("auth_requests", {
|
|||||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const resourceUpload = pgEnum("resource_upload", [
|
||||||
|
"started",
|
||||||
|
"success",
|
||||||
|
"failed",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const modelUploadType = pgEnum("model_upload_type", [
|
||||||
|
"civitai",
|
||||||
|
"download-url",
|
||||||
|
"huggingface", // remove?
|
||||||
|
"other",
|
||||||
|
]);
|
||||||
|
|
||||||
|
// https://www.answeroverflow.com/m/1125106227387584552
|
||||||
|
export 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",
|
||||||
|
}),
|
||||||
|
|
||||||
|
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>
|
||||||
|
>(),
|
||||||
|
|
||||||
|
// for our own storage
|
||||||
|
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 type UserType = InferSelectModel<typeof usersTable>;
|
export type UserType = InferSelectModel<typeof usersTable>;
|
||||||
export type WorkflowType = InferSelectModel<typeof workflowTable>;
|
export type WorkflowType = InferSelectModel<typeof workflowTable>;
|
||||||
export type MachineType = InferSelectModel<typeof machinesTable>;
|
export type MachineType = InferSelectModel<typeof machinesTable>;
|
||||||
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
|
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
|
||||||
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
|
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
|
||||||
|
export type ModelType = InferSelectModel<typeof modelTable>;
|
||||||
|
export type UserVolumeType = InferSelectModel<typeof userVolume>;
|
||||||
|
export type UserUsageType = InferSelectModel<typeof userUsageTable>;
|
||||||
|
|||||||
@@ -12,13 +12,21 @@ export function getRelativeTime(time: string | Date | null | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatDuration(seconds: number) {
|
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;
|
const remainingSeconds = seconds % 60;
|
||||||
if (minutes > 0) {
|
|
||||||
return `${minutes}.${remainingSeconds} mins`;
|
let result = "";
|
||||||
} else {
|
if (hours > 0) {
|
||||||
return `${remainingSeconds.toFixed(1)} secs`;
|
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) {
|
export function getDuration(durationInSecs: number) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|||||||
export const APIKeyBodyRequest = z.object({
|
export const APIKeyBodyRequest = z.object({
|
||||||
user_id: z.string().optional().nullable(),
|
user_id: z.string().optional().nullable(),
|
||||||
org_id: z.string().optional().nullable(),
|
org_id: z.string().optional().nullable(),
|
||||||
iat: z.number(),
|
iat: z.number().optional(),
|
||||||
exp: z.number().optional(),
|
exp: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { modelTypes } from "@/db/schema";
|
||||||
|
|
||||||
|
export const downloadUrlModelSchema = z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
model_type: z.enum(modelTypes).default("checkpoint")
|
||||||
|
});
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user