Compare commits
73
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -12,6 +12,11 @@ RUN curl -L https://fly.io/install.sh | sh
|
||||
ENV FLYCTL_INSTALL="/root/.fly"
|
||||
ENV PATH="$FLYCTL_INSTALL/bin:$PATH"
|
||||
|
||||
RUN sudo apt-get update
|
||||
RUN sudo apt-get install python3-pip python3 -y
|
||||
|
||||
RUN pip3 install modal
|
||||
|
||||
# RUN echo 'export FLYCTL_INSTALL="/home/node/.fly"' >> ~/.bashrc
|
||||
# RUN echo 'export PATH="$FLYCTL_INSTALL/bin:$PATH"' >> ~/.bashrc
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"stivo.tailwind-fold",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"GitHub.copilot",
|
||||
"ms-azuretools.vscode-docker"
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-python.python"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.vercel
|
||||
|
||||
@@ -75,11 +75,10 @@ Major areas
|
||||
3. `bun i`
|
||||
4. Start docker
|
||||
5. `cp .env.example .env.local`
|
||||
6. Replace `JWT_SECRET` with `openssl rand -hex 32`
|
||||
6. Repace `JWT_SECRET` with `openssl rand -hex 32`
|
||||
7. Get a local clerk dev key for `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY`
|
||||
8. Keep a terminal live for `bun run db-dev`
|
||||
9. Execute the local migration to create the initial data `bun run migrate-local`
|
||||
10. Finally start the next server with `bun dev`
|
||||
9. Finally start the next server with `bun dev`
|
||||
|
||||
**Schema Changes**
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
MODAL_TOKEN_ID=
|
||||
MODAL_TOKEN_SECRET=
|
||||
CIVITAI_API_KEY=
|
||||
|
||||
# On production set to False
|
||||
DEPLOY_TEST_FLAG=True
|
||||
DEPLOY_TEST_FLAG=True
|
||||
CIVITAI_API_KEY=
|
||||
PUBLIC_MODEL_VOLUME_NAME=
|
||||
|
||||
@@ -42,6 +42,9 @@ fly secrets set MODAL_TOKEN_SECRET=
|
||||
|
||||
## To deploy
|
||||
|
||||
We have 2 `toml` files one for `production` and the other for `staging`
|
||||
it will default to `staging` if you don't include the `-c` flag to choose your config file
|
||||
|
||||
```
|
||||
// model-builder/fly.toml
|
||||
app = <APP_NAME>
|
||||
@@ -53,5 +56,5 @@ fly launch
|
||||
```
|
||||
if not, run this instead
|
||||
```
|
||||
fly deploy
|
||||
```
|
||||
fly deploy -c "toml file"
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
app = "modal-builder"
|
||||
primary_region = "sea"
|
||||
app = 'modal-builder-preview'
|
||||
primary_region = 'sea'
|
||||
|
||||
[build]
|
||||
|
||||
@@ -14,4 +14,9 @@ primary_region = "sea"
|
||||
auto_stop_machines = true
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
processes = ["app"]
|
||||
processes = ['app']
|
||||
|
||||
[[vm]]
|
||||
cpu_kind = 'shared'
|
||||
cpus = 1
|
||||
memory_mb = 1024
|
||||
|
||||
@@ -8,6 +8,7 @@ from enum import Enum
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from uuid import uuid4
|
||||
from contextlib import asynccontextmanager
|
||||
import asyncio
|
||||
import threading
|
||||
@@ -19,6 +20,7 @@ from urllib.parse import parse_qs
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.types import ASGIApp, Scope, Receive, Send
|
||||
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
# executor = ThreadPoolExecutor(max_workers=5)
|
||||
@@ -45,6 +47,8 @@ machine_id_websocket_dict = {}
|
||||
machine_id_status = {}
|
||||
|
||||
fly_instance_id = os.environ.get('FLY_ALLOC_ID', 'local').split('-')[0]
|
||||
civitai_api_key = os.environ.get('FLY_ALLOC_ID', 'local')
|
||||
public_model_volume_name = os.environ.get('PUBLIC_MODEL_VOLUME_NAME', 'local')
|
||||
|
||||
|
||||
class FlyReplayMiddleware(BaseHTTPMiddleware):
|
||||
@@ -139,6 +143,7 @@ def read_root():
|
||||
class GitCustomNodes(BaseModel):
|
||||
hash: str
|
||||
disabled: bool
|
||||
pip: Optional[List[str]] = None
|
||||
|
||||
class FileCustomNodes(BaseModel):
|
||||
filename: str
|
||||
@@ -174,6 +179,7 @@ class Item(BaseModel):
|
||||
snapshot: Snapshot
|
||||
models: List[Model]
|
||||
callback_url: str
|
||||
model_volume_name: str
|
||||
gpu: GPUType = Field(default=GPUType.T4)
|
||||
|
||||
@field_validator('gpu')
|
||||
@@ -223,6 +229,71 @@ async def websocket_endpoint(websocket: WebSocket, machine_id: str):
|
||||
|
||||
# return {"Hello": "World"}
|
||||
|
||||
# definition based on web schema
|
||||
class UploadType(str, Enum):
|
||||
checkpoint = "checkpoint"
|
||||
lora = "lora"
|
||||
embedding = "embedding"
|
||||
|
||||
class UploadBody(BaseModel):
|
||||
download_url: str
|
||||
volume_name: str
|
||||
volume_id: str
|
||||
model_id: str
|
||||
upload_type: UploadType
|
||||
callback_url: str
|
||||
|
||||
|
||||
# based on ComfyUI's model dir, and our mappings in ./src/template/data/extra_model_paths.yaml
|
||||
UPLOAD_TYPE_DIR_MAP = {
|
||||
UploadType.checkpoint: "checkpoints",
|
||||
UploadType.lora: "loras",
|
||||
UploadType.embedding: "embeddings",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/upload-volume")
|
||||
async def upload_model(body: UploadBody):
|
||||
global last_activity_time
|
||||
last_activity_time = time.time()
|
||||
logger.info(f"Extended inactivity time to {global_timeout}")
|
||||
|
||||
asyncio.create_task(upload_logic(body))
|
||||
|
||||
# check that this
|
||||
return JSONResponse(status_code=200, content={"message": "Volume uploading", "build_machine_instance_id": fly_instance_id})
|
||||
|
||||
|
||||
async def upload_logic(body: UploadBody):
|
||||
folder_path = f"/app/builds/{body.volume_id}"
|
||||
|
||||
cp_process = await asyncio.subprocess.create_subprocess_exec("cp", "-r", "/app/src/volume-builder", folder_path)
|
||||
await cp_process.wait()
|
||||
|
||||
upload_path = UPLOAD_TYPE_DIR_MAP[body.upload_type]
|
||||
config = {
|
||||
"volume_names": {
|
||||
body.volume_name: {"download_url": body.download_url, "folder_path": upload_path}
|
||||
},
|
||||
"volume_paths": {
|
||||
body.volume_name: f'/volumes/{uuid4()}'
|
||||
},
|
||||
"callback_url": body.callback_url,
|
||||
"callback_body": {
|
||||
"model_id": body.model_id,
|
||||
"volume_id": body.volume_id,
|
||||
"folder_path": upload_path,
|
||||
},
|
||||
"civitai_api_key": os.environ.get('CIVITAI_API_KEY')
|
||||
}
|
||||
with open(f"{folder_path}/config.py", "w") as f:
|
||||
f.write("config = " + json.dumps(config))
|
||||
|
||||
await asyncio.subprocess.create_subprocess_shell(
|
||||
f"modal run app.py",
|
||||
cwd=folder_path,
|
||||
env={**os.environ, "COLUMNS": "10000"}
|
||||
)
|
||||
|
||||
@app.post("/create")
|
||||
async def create_machine(item: Item):
|
||||
@@ -308,12 +379,19 @@ async def build_logic(item: Item):
|
||||
cp_process = await asyncio.subprocess.create_subprocess_exec("cp", "-r", "/app/src/template", folder_path)
|
||||
await cp_process.wait()
|
||||
|
||||
pip_modules = set()
|
||||
for git_custom_node in item.snapshot.git_custom_nodes.values():
|
||||
if git_custom_node.pip:
|
||||
pip_modules.update(git_custom_node.pip)
|
||||
|
||||
# Write the config file
|
||||
config = {
|
||||
"name": item.name,
|
||||
"deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False"),
|
||||
"gpu": item.gpu,
|
||||
"civitai_token": os.environ.get("CIVITAI_TOKEN", "")
|
||||
"public_model_volume": public_model_volume_name,
|
||||
"private_model_volume": item.model_volume_name,
|
||||
"pip": list(pip_modules)
|
||||
}
|
||||
with open(f"{folder_path}/config.py", "w") as f:
|
||||
f.write("config = " + json.dumps(config))
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from config import config
|
||||
import modal
|
||||
from modal import Image, Mount, web_endpoint, Stub, asgi_app
|
||||
from modal import Image, Mount, web_endpoint, Stub, asgi_app, method, enter, exit
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
from pydantic import BaseModel
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from volume_setup import volumes
|
||||
from datetime import datetime
|
||||
# deploy_test = False
|
||||
|
||||
import os
|
||||
@@ -27,8 +28,8 @@ deploy_test = config["deploy_test"] == "True"
|
||||
web_app = FastAPI()
|
||||
print(config)
|
||||
print("deploy_test ", deploy_test)
|
||||
print('volumes', volumes)
|
||||
stub = Stub(name=config["name"])
|
||||
# print(stub.app_id)
|
||||
|
||||
if not deploy_test:
|
||||
# dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data"))
|
||||
@@ -36,9 +37,6 @@ if not deploy_test:
|
||||
|
||||
dockerfile_image = (
|
||||
modal.Image.debian_slim()
|
||||
.env({
|
||||
"CIVITAI_TOKEN": config["civitai_token"],
|
||||
})
|
||||
.apt_install("git", "wget")
|
||||
.pip_install(
|
||||
"git+https://github.com/modal-labs/asgiproxy.git", "httpx", "tqdm"
|
||||
@@ -55,16 +53,20 @@ if not deploy_test:
|
||||
"cd /comfyui/custom_nodes/ComfyUI-Manager && pip install -r requirements.txt",
|
||||
"cd /comfyui/custom_nodes/ComfyUI-Manager && mkdir startup-scripts",
|
||||
)
|
||||
.run_commands(f"cat /comfyui/server.py")
|
||||
.run_commands(f"ls /comfyui/app")
|
||||
# .run_commands(
|
||||
# # Install comfy deploy
|
||||
# "cd /comfyui/custom_nodes && git clone https://github.com/BennyKok/comfyui-deploy.git",
|
||||
# )
|
||||
# .copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui")
|
||||
.copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui")
|
||||
|
||||
.copy_local_file(f"{current_directory}/data/start.sh", "/start.sh")
|
||||
.run_commands("chmod +x /start.sh")
|
||||
|
||||
# Restore the custom nodes first
|
||||
.pip_install(config["pip"])
|
||||
|
||||
.copy_local_file(f"{current_directory}/data/restore_snapshot.py", "/")
|
||||
.copy_local_file(f"{current_directory}/data/snapshot.json", "/comfyui/custom_nodes/ComfyUI-Manager/startup-scripts/restore-snapshot.json")
|
||||
.run_commands("python restore_snapshot.py")
|
||||
@@ -156,90 +158,110 @@ image = Image.debian_slim()
|
||||
|
||||
target_image = image if deploy_test else dockerfile_image
|
||||
|
||||
@stub.cls(image=target_image, gpu=config["gpu"] ,volumes=volumes, timeout=60 * 10, container_idle_timeout=60 * 5)
|
||||
class ComfyDeployRunner:
|
||||
|
||||
@stub.function(image=target_image, gpu=config["gpu"])
|
||||
def run(input: Input):
|
||||
import subprocess
|
||||
import time
|
||||
# Make sure that the ComfyUI API is available
|
||||
print(f"comfy-modal - check server")
|
||||
@enter()
|
||||
def setup(self):
|
||||
import subprocess
|
||||
import time
|
||||
# Make sure that the ComfyUI API is available
|
||||
print(f"comfy-modal - check server")
|
||||
|
||||
command = ["python", "main.py",
|
||||
"--disable-auto-launch", "--disable-metadata"]
|
||||
server_process = subprocess.Popen(command, cwd="/comfyui")
|
||||
command = ["python", "main.py",
|
||||
"--disable-auto-launch", "--disable-metadata"]
|
||||
|
||||
check_server(
|
||||
f"http://{COMFY_HOST}",
|
||||
COMFY_API_AVAILABLE_MAX_RETRIES,
|
||||
COMFY_API_AVAILABLE_INTERVAL_MS,
|
||||
)
|
||||
self.server_process = subprocess.Popen(command, cwd="/comfyui")
|
||||
|
||||
job_input = input
|
||||
check_server(
|
||||
f"http://{COMFY_HOST}",
|
||||
COMFY_API_AVAILABLE_MAX_RETRIES,
|
||||
COMFY_API_AVAILABLE_INTERVAL_MS,
|
||||
)
|
||||
|
||||
# print(f"comfy-modal - got input {job_input}")
|
||||
@exit()
|
||||
def cleanup(self, exc_type, exc_value, traceback):
|
||||
self.server_process.terminate()
|
||||
|
||||
# Queue the workflow
|
||||
try:
|
||||
# job_input is the json input
|
||||
queued_workflow = queue_workflow_comfy_deploy(
|
||||
job_input) # queue_workflow(workflow)
|
||||
prompt_id = queued_workflow["prompt_id"]
|
||||
print(f"comfy-modal - queued workflow with ID {prompt_id}")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
return {"error": f"Error queuing workflow: {str(e)}"}
|
||||
@method()
|
||||
def run(self, input: Input):
|
||||
data = json.dumps({
|
||||
"run_id": input.prompt_id,
|
||||
"status": "started",
|
||||
"time": datetime.now().isoformat()
|
||||
}).encode('utf-8')
|
||||
req = urllib.request.Request(input.status_endpoint, data=data, method='POST')
|
||||
urllib.request.urlopen(req)
|
||||
|
||||
# Poll for completion
|
||||
print(f"comfy-modal - wait until image generation is complete")
|
||||
retries = 0
|
||||
status = ""
|
||||
try:
|
||||
print("getting request")
|
||||
while retries < COMFY_POLLING_MAX_RETRIES:
|
||||
status_result = check_status(prompt_id=prompt_id)
|
||||
# history = get_history(prompt_id)
|
||||
job_input = input
|
||||
|
||||
# Exit the loop if we have found the history
|
||||
# if prompt_id in history and history[prompt_id].get("outputs"):
|
||||
# break
|
||||
try:
|
||||
queued_workflow = queue_workflow_comfy_deploy(job_input) # queue_workflow(workflow)
|
||||
prompt_id = queued_workflow["prompt_id"]
|
||||
print(f"comfy-modal - queued workflow with ID {prompt_id}")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
return {"error": f"Error queuing workflow: {str(e)}"}
|
||||
|
||||
# Exit the loop if we have found the status both success or failed
|
||||
if 'status' in status_result and (status_result['status'] == 'success' or status_result['status'] == 'failed'):
|
||||
status = status_result['status']
|
||||
print(status)
|
||||
break
|
||||
# Poll for completion
|
||||
print(f"comfy-modal - wait until image generation is complete")
|
||||
retries = 0
|
||||
status = ""
|
||||
try:
|
||||
print("getting request")
|
||||
while retries < COMFY_POLLING_MAX_RETRIES:
|
||||
status_result = check_status(prompt_id=prompt_id)
|
||||
# history = get_history(prompt_id)
|
||||
|
||||
# Exit the loop if we have found the history
|
||||
# if prompt_id in history and history[prompt_id].get("outputs"):
|
||||
# break
|
||||
|
||||
# Exit the loop if we have found the status both success or failed
|
||||
if 'status' in status_result and (status_result['status'] == 'success' or status_result['status'] == 'failed'):
|
||||
status = status_result['status']
|
||||
print(status)
|
||||
break
|
||||
else:
|
||||
# Wait before trying again
|
||||
time.sleep(COMFY_POLLING_INTERVAL_MS / 1000)
|
||||
retries += 1
|
||||
else:
|
||||
# Wait before trying again
|
||||
time.sleep(COMFY_POLLING_INTERVAL_MS / 1000)
|
||||
retries += 1
|
||||
else:
|
||||
return {"error": "Max retries reached while waiting for image generation"}
|
||||
except Exception as e:
|
||||
return {"error": f"Error waiting for image generation: {str(e)}"}
|
||||
return {"error": "Max retries reached while waiting for image generation"}
|
||||
except Exception as e:
|
||||
return {"error": f"Error waiting for image generation: {str(e)}"}
|
||||
|
||||
print(f"comfy-modal - Finished, turning off")
|
||||
server_process.terminate()
|
||||
print(f"comfy-modal - Finished, turning off")
|
||||
|
||||
# Get the generated image and return it as URL in an AWS bucket or as base64
|
||||
# images_result = process_output_images(history[prompt_id].get("outputs"), job["id"])
|
||||
# result = {**images_result, "refresh_worker": REFRESH_WORKER}
|
||||
result = {"status": status}
|
||||
|
||||
return result
|
||||
print("Running remotely on Modal!")
|
||||
result = {"status": status}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@web_app.post("/run")
|
||||
async def bar(request_input: RequestInput):
|
||||
# print(request_input)
|
||||
async def post_run(request_input: RequestInput):
|
||||
if not deploy_test:
|
||||
run.spawn(request_input.input)
|
||||
return {"status": "success"}
|
||||
# pass
|
||||
# print(request_input.input.prompt_id, request_input.input.status_endpoint)
|
||||
data = json.dumps({
|
||||
"run_id": request_input.input.prompt_id,
|
||||
"status": "queued",
|
||||
"time": datetime.now().isoformat()
|
||||
}).encode('utf-8')
|
||||
req = urllib.request.Request(request_input.input.status_endpoint, data=data, method='POST')
|
||||
urllib.request.urlopen(req)
|
||||
|
||||
model = ComfyDeployRunner()
|
||||
call = model.run.spawn(request_input.input)
|
||||
|
||||
@stub.function(image=image)
|
||||
# call = run.spawn()
|
||||
return {"call_id": call.object_id}
|
||||
|
||||
return {"call_id": None}
|
||||
|
||||
@stub.function(image=image
|
||||
,volumes=volumes
|
||||
)
|
||||
@asgi_app()
|
||||
def comfyui_api():
|
||||
return web_app
|
||||
@@ -289,6 +311,7 @@ def spawn_comfyui_in_background():
|
||||
# to be on a single container.
|
||||
concurrency_limit=1,
|
||||
timeout=10 * 60,
|
||||
volumes=volumes,
|
||||
)
|
||||
@asgi_app()
|
||||
def comfyui_app():
|
||||
@@ -307,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:
|
||||
base_path: /runpod-volume/ComfyUI/
|
||||
checkpoints: models/checkpoints/
|
||||
clip: models/clip/
|
||||
clip_vision: models/clip_vision/
|
||||
configs: models/configs/
|
||||
controlnet: models/controlnet/
|
||||
embeddings: models/embeddings/
|
||||
loras: models/loras/
|
||||
upscale_models: models/upscale_models/
|
||||
vae: models/vae/
|
||||
public:
|
||||
base_path: /public_models/
|
||||
checkpoints: checkpoints
|
||||
clip: clip
|
||||
clip_vision: clip_vision
|
||||
configs: configs
|
||||
controlnet: controlnet
|
||||
embeddings: embeddings
|
||||
loras: loras
|
||||
upscale_models: upscale_models
|
||||
vae: vae
|
||||
|
||||
private:
|
||||
base_path: /private_models/
|
||||
checkpoints: checkpoints
|
||||
clip: clip
|
||||
clip_vision: clip_vision
|
||||
configs: configs
|
||||
controlnet: controlnet
|
||||
embeddings: embeddings
|
||||
loras: loras
|
||||
upscale_models: upscale_models
|
||||
vae: vae
|
||||
|
||||
@@ -49,15 +49,9 @@ with open('models.json') as f:
|
||||
models = json.load(f)
|
||||
|
||||
for model in models:
|
||||
import os
|
||||
if "civitai.com/api" in model['url'] and not "token=" in model['url']:
|
||||
if "?" in model['url']:
|
||||
model['url'] += "&token=" + os.environ.get('CIVITAI_TOKEN', '')
|
||||
else:
|
||||
model['url'] += "?token=" + os.environ.get('CIVITAI_TOKEN', '')
|
||||
response = requests.request("POST", f"{root_url}/model/install", json=model, headers=headers)
|
||||
print(response.text)
|
||||
|
||||
# Close the server
|
||||
server_process.terminate()
|
||||
print("Finished installing dependencies.")
|
||||
print("Finished installing dependencies.")
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"comfyui": "d0165d819afe76bd4e6bdd710eb5f3e571b6a804",
|
||||
"git_custom_nodes": {},
|
||||
"file_custom_nodes": []
|
||||
}
|
||||
"comfyui": "d0165d819afe76bd4e6bdd710eb5f3e571b6a804",
|
||||
"git_custom_nodes": {
|
||||
"https://github.com/BennyKok/comfyui-deploy.git": {
|
||||
"hash": "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": "",
|
||||
}
|
||||
@@ -29,7 +29,9 @@ class ComfyUIDeployExternalText:
|
||||
CATEGORY = "text"
|
||||
|
||||
def run(self, input_id, default_value=None):
|
||||
return [default_value]
|
||||
if not input_id or len(input_id.strip()) == 0:
|
||||
return [default_value]
|
||||
return [input_id]
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalText": ComfyUIDeployExternalText}
|
||||
|
||||
+16
-230
@@ -22,14 +22,10 @@ from logging.handlers import RotatingFileHandler
|
||||
from enum import Enum
|
||||
from urllib.parse import quote
|
||||
import threading
|
||||
import hashlib
|
||||
import aiohttp
|
||||
|
||||
api = None
|
||||
api_task = None
|
||||
prompt_metadata = {}
|
||||
cd_enable_log = os.environ.get('CD_ENABLE_LOG', 'false').lower() == 'true'
|
||||
cd_enable_run_log = os.environ.get('CD_ENABLE_RUN_LOG', 'false').lower() == 'true'
|
||||
|
||||
def post_prompt(json_data):
|
||||
prompt_server = server.PromptServer.instance
|
||||
@@ -101,7 +97,6 @@ async def comfy_deploy_run(request):
|
||||
prompt_metadata[prompt_id] = {
|
||||
'status_endpoint': data.get('status_endpoint'),
|
||||
'file_upload_endpoint': data.get('file_upload_endpoint'),
|
||||
'workflow_api': workflow_api
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -118,8 +113,6 @@ async def comfy_deploy_run(request):
|
||||
"stack_trace": stack_trace
|
||||
}
|
||||
})
|
||||
# When there are critical errors, the prompt is actually not run
|
||||
await update_run(prompt_id, Status.FAILED)
|
||||
return web.Response(status=500, reason=f"{error_type}: {e}, {stack_trace_short}")
|
||||
|
||||
status = 200
|
||||
@@ -148,138 +141,6 @@ async def comfy_deploy_run(request):
|
||||
|
||||
sockets = dict()
|
||||
|
||||
def get_comfyui_path_from_file_path(file_path):
|
||||
file_path_parts = file_path.split("\\")
|
||||
|
||||
if file_path_parts[0] == "input":
|
||||
print("matching input")
|
||||
file_path = os.path.join(folder_paths.get_directory_by_type("input"), *file_path_parts[1:])
|
||||
elif file_path_parts[0] == "models":
|
||||
print("matching models")
|
||||
file_path = folder_paths.get_full_path(file_path_parts[1], os.path.join(*file_path_parts[2:]))
|
||||
|
||||
print(file_path)
|
||||
|
||||
return file_path
|
||||
|
||||
# Form ComfyUI Manager
|
||||
def compute_sha256_checksum(filepath):
|
||||
filepath = get_comfyui_path_from_file_path(filepath)
|
||||
"""Compute the SHA256 checksum of a file, in chunks"""
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b''):
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
# This is start uploading the files to Comfy Deploy
|
||||
@server.PromptServer.instance.routes.post('/comfyui-deploy/upload-file')
|
||||
async def upload_file(request):
|
||||
data = await request.json()
|
||||
|
||||
file_path = data.get("file_path")
|
||||
|
||||
print("Original file path", file_path)
|
||||
|
||||
file_path = get_comfyui_path_from_file_path(file_path)
|
||||
|
||||
# return web.json_response({
|
||||
# "error": f"File not uploaded"
|
||||
# }, status=500)
|
||||
|
||||
token = data.get("token")
|
||||
get_url = data.get("url")
|
||||
|
||||
try:
|
||||
base = folder_paths.base_path
|
||||
file_path = os.path.join(base, file_path)
|
||||
|
||||
if os.path.exists(file_path):
|
||||
file_size = os.path.getsize(file_path)
|
||||
file_extension = os.path.splitext(file_path)[1]
|
||||
|
||||
if file_extension in ['.jpg', '.jpeg']:
|
||||
file_type = 'image/jpeg'
|
||||
elif file_extension == '.png':
|
||||
file_type = 'image/png'
|
||||
elif file_extension == '.webp':
|
||||
file_type = 'image/webp'
|
||||
else:
|
||||
file_type = 'application/octet-stream' # Default to binary file type if unknown
|
||||
else:
|
||||
return web.json_response({
|
||||
"error": f"File not found: {file_path}"
|
||||
}, status=404)
|
||||
|
||||
except Exception as e:
|
||||
return web.json_response({
|
||||
"error": str(e)
|
||||
}, status=500)
|
||||
|
||||
if get_url:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
headers = {'Authorization': f'Bearer {token}'}
|
||||
params = {'file_size': file_size, 'type': file_type}
|
||||
async with session.get(get_url, params=params, headers=headers) as response:
|
||||
if response.status == 200:
|
||||
content = await response.json()
|
||||
upload_url = content["upload_url"]
|
||||
|
||||
with open(file_path, 'rb') as f:
|
||||
headers = {
|
||||
"Content-Type": file_type,
|
||||
"x-amz-acl": "public-read",
|
||||
"Content-Length": str(file_size)
|
||||
}
|
||||
async with session.put(upload_url, data=f, headers=headers) as upload_response:
|
||||
if upload_response.status == 200:
|
||||
return web.json_response({
|
||||
"message": "File uploaded successfully",
|
||||
"download_url": content["download_url"]
|
||||
})
|
||||
else:
|
||||
return web.json_response({
|
||||
"error": f"Failed to upload file to {upload_url}. Status code: {upload_response.status}"
|
||||
}, status=upload_response.status)
|
||||
else:
|
||||
return web.json_response({
|
||||
"error": f"Failed to fetch data from {get_url}. Status code: {response.status}"
|
||||
}, status=response.status)
|
||||
except Exception as e:
|
||||
return web.json_response({
|
||||
"error": f"An error occurred while fetching data from {get_url}: {str(e)}"
|
||||
}, status=500)
|
||||
|
||||
return web.json_response({
|
||||
"error": f"File not uploaded"
|
||||
}, status=500)
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.get('/comfyui-deploy/get-file-hash')
|
||||
async def get_file_hash(request):
|
||||
file_path = request.rel_url.query.get('file_path', '')
|
||||
|
||||
if file_path is None:
|
||||
return web.json_response({
|
||||
"error": "file_path is required"
|
||||
}, status=400)
|
||||
|
||||
try:
|
||||
base = folder_paths.base_path
|
||||
file_path = os.path.join(base, file_path)
|
||||
# print("file_path", file_path)
|
||||
file_hash = compute_sha256_checksum(
|
||||
file_path
|
||||
)
|
||||
return web.json_response({
|
||||
"file_hash": file_hash
|
||||
})
|
||||
except Exception as e:
|
||||
return web.json_response({
|
||||
"error": str(e)
|
||||
}, status=500)
|
||||
|
||||
@server.PromptServer.instance.routes.get('/comfyui-deploy/ws')
|
||||
async def websocket_handler(request):
|
||||
ws = web.WebSocketResponse()
|
||||
@@ -296,9 +157,7 @@ async def websocket_handler(request):
|
||||
try:
|
||||
# Send initial state to the new client
|
||||
await send("status", { 'sid': sid }, sid)
|
||||
|
||||
if cd_enable_log:
|
||||
await send_first_time_log(sid)
|
||||
await send_first_time_log(sid)
|
||||
|
||||
async for msg in ws:
|
||||
if msg.type == aiohttp.WSMsgType.ERROR:
|
||||
@@ -358,17 +217,6 @@ async def send_json_override(self, event, data, sid=None):
|
||||
if not have_pending_upload(prompt_id):
|
||||
update_run(prompt_id, Status.SUCCESS)
|
||||
|
||||
if event == 'executing' and data.get('node') is not None:
|
||||
node = data.get('node')
|
||||
|
||||
if 'prompt_id' in prompt_metadata:
|
||||
if 'last_updated_node' in prompt_metadata[prompt_id] and prompt_metadata[prompt_id]['last_updated_node'] == node:
|
||||
return
|
||||
prompt_metadata[prompt_id]['last_updated_node'] = node
|
||||
class_type = prompt_metadata[prompt_id]['workflow_api'][node]['class_type']
|
||||
print("updating run live status", class_type)
|
||||
await update_run_live_status(prompt_id, "Executing " + class_type)
|
||||
|
||||
if event == 'execution_error':
|
||||
# Careful this might not be fully awaited.
|
||||
await update_run_with_output(prompt_id, data)
|
||||
@@ -388,26 +236,7 @@ class Status(Enum):
|
||||
FAILED = "failed"
|
||||
UPLOADING = "uploading"
|
||||
|
||||
# Global variable to keep track of the last read line number
|
||||
last_read_line_number = 0
|
||||
|
||||
async def update_run_live_status(prompt_id, live_status):
|
||||
if prompt_id not in prompt_metadata:
|
||||
return
|
||||
|
||||
status_endpoint = prompt_metadata[prompt_id]['status_endpoint']
|
||||
body = {
|
||||
"run_id": prompt_id,
|
||||
"live_status": live_status,
|
||||
}
|
||||
# requests.post(status_endpoint, json=body)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
await session.post(status_endpoint, json=body)
|
||||
|
||||
|
||||
def update_run(prompt_id, status: Status):
|
||||
global last_read_line_number
|
||||
|
||||
if prompt_id not in prompt_metadata:
|
||||
return
|
||||
|
||||
@@ -422,50 +251,16 @@ def update_run(prompt_id, status: Status):
|
||||
"run_id": prompt_id,
|
||||
"status": status.value,
|
||||
}
|
||||
prompt_metadata[prompt_id]['status'] = status
|
||||
print(f"Status: {status.value}")
|
||||
|
||||
try:
|
||||
requests.post(status_endpoint, json=body)
|
||||
|
||||
if cd_enable_run_log and (status == Status.SUCCESS or status == Status.FAILED):
|
||||
try:
|
||||
with open(comfyui_file_path, 'r') as log_file:
|
||||
# log_data = log_file.read()
|
||||
# Move to the last read line
|
||||
all_log_data = log_file.read() # Read all log data
|
||||
print("All log data before skipping:", all_log_data) # Log all data before skipping
|
||||
log_file.seek(0) # Reset file pointer to the beginning
|
||||
|
||||
for _ in range(last_read_line_number):
|
||||
next(log_file)
|
||||
log_data = log_file.read()
|
||||
# Update the last read line number
|
||||
last_read_line_number += log_data.count('\n')
|
||||
print("last_read_line_number", last_read_line_number)
|
||||
print("log_data", log_data)
|
||||
print("log_data.count(n)", log_data.count('\n'))
|
||||
|
||||
body = {
|
||||
"run_id": prompt_id,
|
||||
"log_data": [
|
||||
{
|
||||
"logs": log_data,
|
||||
# "timestamp": time.time(),
|
||||
}
|
||||
]
|
||||
}
|
||||
requests.post(status_endpoint, json=body)
|
||||
except Exception as log_error:
|
||||
print(f"Error reading log file: {log_error}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
error_type = type(e).__name__
|
||||
stack_trace = traceback.format_exc().strip()
|
||||
print(f"Error occurred while updating run: {e} {stack_trace}")
|
||||
finally:
|
||||
prompt_metadata[prompt_id]['status'] = status
|
||||
|
||||
|
||||
|
||||
async def upload_file(prompt_id, filename, subfolder=None, content_type="image/png", type="output"):
|
||||
"""
|
||||
@@ -515,9 +310,7 @@ async def upload_file(prompt_id, filename, subfolder=None, content_type="image/p
|
||||
"Content-Length": str(len(data)),
|
||||
}
|
||||
response = requests.put(ok.get("url"), headers=headers, data=data)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.put(ok.get("url"), headers=headers, data=data) as response:
|
||||
print("upload file response", response.status)
|
||||
print("upload file response", response.status_code)
|
||||
|
||||
def have_pending_upload(prompt_id):
|
||||
if 'prompt_id' in prompt_metadata and 'uploading_nodes' in prompt_metadata[prompt_id] and len(prompt_metadata[prompt_id]['uploading_nodes']) > 0:
|
||||
@@ -594,25 +387,20 @@ async def update_file_status(prompt_id, data, uploading, have_error=False, node_
|
||||
"prompt_id": prompt_id,
|
||||
})
|
||||
|
||||
async def handle_upload(prompt_id, data, key, content_type_key, default_content_type):
|
||||
items = data.get(key, [])
|
||||
for item in items:
|
||||
await upload_file(
|
||||
prompt_id,
|
||||
item.get("filename"),
|
||||
subfolder=item.get("subfolder"),
|
||||
type=item.get("type"),
|
||||
content_type=item.get(content_type_key, default_content_type)
|
||||
)
|
||||
|
||||
|
||||
# Upload files in the background
|
||||
async def upload_in_background(prompt_id, data, node_id=None, have_upload=True):
|
||||
try:
|
||||
await handle_upload(prompt_id, data, 'images', "content_type", "image/png")
|
||||
await handle_upload(prompt_id, data, 'files', "content_type", "image/png")
|
||||
# This will also be mp4
|
||||
await handle_upload(prompt_id, data, 'gifs', "format", "image/gif")
|
||||
images = data.get('images', [])
|
||||
for image in images:
|
||||
await upload_file(prompt_id, image.get("filename"), subfolder=image.get("subfolder"), type=image.get("type"), content_type=image.get("content_type", "image/png"))
|
||||
|
||||
files = data.get('files', [])
|
||||
for file in files:
|
||||
await upload_file(prompt_id, file.get("filename"), subfolder=file.get("subfolder"), type=file.get("type"), content_type=file.get("content_type", "image/png"))
|
||||
|
||||
gifs = data.get('gifs', [])
|
||||
for gif in gifs:
|
||||
await upload_file(prompt_id, gif.get("filename"), subfolder=gif.get("subfolder"), type=gif.get("type"), content_type=gif.get("format", "image/gif"))
|
||||
|
||||
if have_upload:
|
||||
await update_file_status(prompt_id, data, False, node_id=node_id)
|
||||
@@ -653,7 +441,6 @@ prompt_server.send_json = send_json_override.__get__(prompt_server, server.Promp
|
||||
root_path = os.path.dirname(os.path.abspath(__file__))
|
||||
two_dirs_up = os.path.dirname(os.path.dirname(root_path))
|
||||
log_file_path = os.path.join(two_dirs_up, 'comfy-deploy.log')
|
||||
comfyui_file_path = os.path.join(two_dirs_up, 'comfyui.log')
|
||||
|
||||
last_read_line = 0
|
||||
|
||||
@@ -693,5 +480,4 @@ def run_in_new_thread(coroutine):
|
||||
t.start()
|
||||
asyncio.run_coroutine_threadsafe(coroutine, new_loop)
|
||||
|
||||
if cd_enable_log:
|
||||
run_in_new_thread(watch_file_changes(log_file_path, send_logs_to_websocket))
|
||||
run_in_new_thread(watch_file_changes(log_file_path, send_logs_to_websocket))
|
||||
|
||||
+32
-43
@@ -7,56 +7,45 @@ import threading
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
# Running with export CD_ENABLE_LOG=true; python main.py
|
||||
handler = RotatingFileHandler('comfy-deploy.log', maxBytes=500000, backupCount=5)
|
||||
|
||||
# Check for 'cd-enable-log' flag in input arguments
|
||||
# cd_enable_log = '--cd-enable-log' in sys.argv
|
||||
cd_enable_log = os.environ.get('CD_ENABLE_LOG', 'false').lower() == 'true'
|
||||
original_stdout = sys.stdout
|
||||
original_stderr = sys.stderr
|
||||
|
||||
def setup():
|
||||
handler = RotatingFileHandler('comfy-deploy.log', maxBytes=500000, backupCount=5)
|
||||
class StreamToLogger():
|
||||
def __init__(self, log_level):
|
||||
self.log_level = log_level
|
||||
|
||||
original_stdout = sys.stdout
|
||||
original_stderr = sys.stderr
|
||||
def write(self, buf):
|
||||
if (self.log_level == logging.INFO):
|
||||
original_stdout.write(buf)
|
||||
original_stdout.flush()
|
||||
elif (self.log_level == logging.ERROR):
|
||||
original_stderr.write(buf)
|
||||
original_stderr.flush()
|
||||
|
||||
class StreamToLogger():
|
||||
def __init__(self, log_level):
|
||||
self.log_level = log_level
|
||||
|
||||
def write(self, buf):
|
||||
if (self.log_level == logging.INFO):
|
||||
original_stdout.write(buf)
|
||||
original_stdout.flush()
|
||||
elif (self.log_level == logging.ERROR):
|
||||
original_stderr.write(buf)
|
||||
original_stderr.flush()
|
||||
|
||||
for line in buf.rstrip().splitlines():
|
||||
handler.handle(
|
||||
logging.LogRecord(
|
||||
name="comfy-deploy",
|
||||
level=self.log_level,
|
||||
pathname="prestartup_script.py",
|
||||
lineno=1,
|
||||
msg=line.rstrip(),
|
||||
args=None,
|
||||
exc_info=None
|
||||
)
|
||||
for line in buf.rstrip().splitlines():
|
||||
handler.handle(
|
||||
logging.LogRecord(
|
||||
name="comfy-deploy",
|
||||
level=self.log_level,
|
||||
pathname="prestartup_script.py",
|
||||
lineno=1,
|
||||
msg=line.rstrip(),
|
||||
args=None,
|
||||
exc_info=None
|
||||
)
|
||||
)
|
||||
|
||||
def flush(self):
|
||||
if (self.log_level == logging.INFO):
|
||||
original_stdout.flush()
|
||||
elif (self.log_level == logging.ERROR):
|
||||
original_stderr.flush()
|
||||
def flush(self):
|
||||
if (self.log_level == logging.INFO):
|
||||
original_stdout.flush()
|
||||
elif (self.log_level == logging.ERROR):
|
||||
original_stderr.flush()
|
||||
|
||||
# Redirect stdout and stderr to the logger
|
||||
sys.stdout = StreamToLogger(logging.INFO)
|
||||
sys.stderr = StreamToLogger(logging.ERROR)
|
||||
|
||||
if cd_enable_log:
|
||||
print("** Comfy Deploy logging enabled")
|
||||
setup()
|
||||
# Redirect stdout and stderr to the logger
|
||||
sys.stdout = StreamToLogger(logging.INFO)
|
||||
sys.stderr = StreamToLogger(logging.ERROR)
|
||||
|
||||
try:
|
||||
# Get the absolute path of the script's directory
|
||||
|
||||
+27
-220
@@ -1,7 +1,6 @@
|
||||
import { app } from "./app.js";
|
||||
import { api } from "./api.js";
|
||||
import { ComfyWidgets, LGraphNode } from "./widgets.js";
|
||||
import { generateDependencyGraph } from "https://esm.sh/[email protected]";
|
||||
|
||||
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
|
||||
/** @type {ComfyExtension} */
|
||||
@@ -51,22 +50,11 @@ const ext = {
|
||||
})
|
||||
.then(async (res) => {
|
||||
const data = await res.json();
|
||||
const { workflow, workflow_id, error } = data;
|
||||
const { workflow, error } = data;
|
||||
if (error) {
|
||||
infoDialog.showMessage("Unable to load this workflow", error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Adding a delay to wait for the intial graph to load
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
workflow?.nodes.forEach((x) => {
|
||||
if (x?.type === "ComfyDeploy") {
|
||||
x.widgets_values[1] = workflow_id;
|
||||
// x.widgets_values[2] = workflow_version.version;
|
||||
}
|
||||
});
|
||||
|
||||
/** @type {LGraph} */
|
||||
app.loadGraphData(workflow);
|
||||
})
|
||||
@@ -179,92 +167,6 @@ function showError(title, message) {
|
||||
);
|
||||
}
|
||||
|
||||
function createDynamicUIHtml(data) {
|
||||
console.log(data);
|
||||
let html =
|
||||
'<div style="max-width: 1024px; margin: 14px auto; display: flex; flex-direction: column; gap: 24px;">';
|
||||
const bgcolor = "var(--comfy-input-bg)";
|
||||
const evenBg = "var(--border-color)";
|
||||
const textColor = "var(--input-text)";
|
||||
|
||||
// Custom Nodes
|
||||
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
|
||||
html +=
|
||||
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Custom Nodes</h2>';
|
||||
|
||||
if (data.missing_nodes?.length > 0) {
|
||||
html += `
|
||||
<div style="border-bottom: 1px solid #e2e8f0; padding: 4px 12px; background-color: ${evenBg}">
|
||||
<h3 style="font-size: 14px; font-weight: semibold; margin-bottom: 8px;">Missing Nodes</h3>
|
||||
<p style="font-size: 12px;">These nodes are not found with any matching custom_nodes in the ComfyUI Manager Database</p>
|
||||
${data.missing_nodes
|
||||
.map((node) => {
|
||||
return `<p style="font-size: 14px; color: #d69e2e;">${node}</p>`;
|
||||
})
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
Object.values(data.custom_nodes).forEach((node) => {
|
||||
html += `
|
||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 16px;">
|
||||
<a href="${
|
||||
node.url
|
||||
}" target="_blank" style="font-size: 18px; font-weight: semibold; color: white; text-decoration: none;">${
|
||||
node.name
|
||||
}</a>
|
||||
<p style="font-size: 14px; color: #4b5563;">${node.hash}</p>
|
||||
${
|
||||
node.warning
|
||||
? `<p style="font-size: 14px; color: #d69e2e;">${node.warning}</p>`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
html += "</div>";
|
||||
|
||||
// Models
|
||||
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
|
||||
html +=
|
||||
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Models</h2>';
|
||||
|
||||
Object.entries(data.models).forEach(([section, items]) => {
|
||||
html += `
|
||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
||||
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
|
||||
section.charAt(0).toUpperCase() + section.slice(1)
|
||||
}</h3>`;
|
||||
items.forEach((item) => {
|
||||
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
||||
});
|
||||
html += "</div>";
|
||||
});
|
||||
html += "</div>";
|
||||
|
||||
// Models
|
||||
html += `<div style="background-color: ${bgcolor}; padding: 24px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">`;
|
||||
html +=
|
||||
'<h2 style="margin-top: 0px; font-size: 24px; font-weight: bold; margin-bottom: 16px;">Files</h2>';
|
||||
|
||||
Object.entries(data.files).forEach(([section, items]) => {
|
||||
html += `
|
||||
<div style="border-bottom: 1px solid #e2e8f0; padding-top: 8px; padding-bottom: 8px;">
|
||||
<h3 style="font-size: 18px; font-weight: semibold; margin-bottom: 8px;">${
|
||||
section.charAt(0).toUpperCase() + section.slice(1)
|
||||
}</h3>`;
|
||||
items.forEach((item) => {
|
||||
html += `<p style="font-size: 14px; color: ${textColor};">${item.name}</p>`;
|
||||
});
|
||||
html += "</div>";
|
||||
});
|
||||
html += "</div>";
|
||||
|
||||
html += "</div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function addButton() {
|
||||
const menu = document.querySelector(".comfy-menu");
|
||||
|
||||
@@ -276,29 +178,8 @@ function addButton() {
|
||||
/** @type {LGraph} */
|
||||
const graph = app.graph;
|
||||
|
||||
let { endpoint, apiKey, displayName } = getData();
|
||||
|
||||
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
|
||||
configDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
const ok = await confirmDialog.confirm(
|
||||
"Confirm deployment -> " + displayName,
|
||||
`A new version will be deployed, are you conform? <br><br><input id="include-deps" type="checkbox" checked>Include dependence</input>`,
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
const includeDeps = document.getElementById("include-deps").checked;
|
||||
|
||||
if (endpoint.endsWith("/")) {
|
||||
endpoint = endpoint.slice(0, -1);
|
||||
}
|
||||
loadingDialog.showLoading("Generating snapshot", "Please wait...");
|
||||
|
||||
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
|
||||
// console.log(snapshot);
|
||||
loadingDialog.close();
|
||||
|
||||
if (!snapshot) {
|
||||
showError(
|
||||
@@ -332,94 +213,32 @@ function addButton() {
|
||||
|
||||
const deployMetaNode = deployMeta[0];
|
||||
|
||||
// console.log(deployMetaNode);
|
||||
|
||||
const workflow_name = deployMetaNode.widgets[0].value;
|
||||
const workflow_id = deployMetaNode.widgets[1].value;
|
||||
|
||||
console.log(workflow_name, workflow_id);
|
||||
|
||||
const prompt = await app.graphToPrompt();
|
||||
let deps = undefined;
|
||||
console.log(graph);
|
||||
console.log(prompt);
|
||||
|
||||
if (includeDeps) {
|
||||
loadingDialog.showLoading("Fetching existing version", "Please wait...");
|
||||
// const endpoint = localStorage.getItem("endpoint") ?? "";
|
||||
// const apiKey = localStorage.getItem("apiKey");
|
||||
|
||||
const existing_workflow = await fetch(
|
||||
endpoint + "/api/workflow/" + workflow_id,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + apiKey,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.catch(() => {
|
||||
return {};
|
||||
});
|
||||
const { endpoint, apiKey, displayName } = getData();
|
||||
|
||||
loadingDialog.close();
|
||||
|
||||
loadingDialog.showLoading(
|
||||
"Generating dependency graph",
|
||||
"Please wait...",
|
||||
);
|
||||
deps = await generateDependencyGraph({
|
||||
workflow_api: prompt.output,
|
||||
snapshot: snapshot,
|
||||
computeFileHash: async (file) => {
|
||||
console.log(file);
|
||||
loadingDialog.showLoading("Generating hash", file);
|
||||
const hash = await fetch(
|
||||
`/comfyui-deploy/get-file-hash?file_path=${encodeURIComponent(
|
||||
file,
|
||||
)}`,
|
||||
).then((x) => x.json());
|
||||
loadingDialog.showLoading("Generating hash", file);
|
||||
console.log(hash);
|
||||
return hash.file_hash;
|
||||
},
|
||||
handleFileUpload: async (file, hash, prevhash) => {
|
||||
console.log("Uploading ", file);
|
||||
loadingDialog.showLoading("Uploading file", file);
|
||||
try {
|
||||
const { download_url } = await fetch(
|
||||
`/comfyui-deploy/upload-file`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
file_path: file,
|
||||
token: apiKey,
|
||||
url: endpoint + "/api/upload-url",
|
||||
}),
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.catch(() => {
|
||||
loadingDialog.close();
|
||||
confirmDialog.confirm("Error", "Unable to upload file " + file);
|
||||
});
|
||||
loadingDialog.showLoading("Uploaded file", file);
|
||||
console.log(download_url);
|
||||
return download_url;
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
existingDependencies: existing_workflow.dependencies,
|
||||
});
|
||||
|
||||
loadingDialog.close();
|
||||
|
||||
const depsOk = await confirmDialog.confirm(
|
||||
"Check dependencies",
|
||||
// JSON.stringify(deps, null, 2),
|
||||
createDynamicUIHtml(deps),
|
||||
);
|
||||
if (!depsOk) return;
|
||||
|
||||
console.log(deps);
|
||||
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
|
||||
configDialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
loadingDialog.showLoading("Deploying...");
|
||||
const ok = await confirmDialog.confirm(
|
||||
"Confirm deployment -> " + displayName,
|
||||
"A new version will be deployed, are you conform?",
|
||||
);
|
||||
if (!ok) return;
|
||||
|
||||
title.innerText = "Deploying...";
|
||||
title.style.color = "orange";
|
||||
@@ -431,8 +250,6 @@ function addButton() {
|
||||
endpoint = endpoint.slice(0, -1);
|
||||
}
|
||||
|
||||
// console.log(prompt.workflow);
|
||||
|
||||
const apiRoute = endpoint + "/api/workflow";
|
||||
// const userId = apiKey
|
||||
try {
|
||||
@@ -442,7 +259,6 @@ function addButton() {
|
||||
workflow: prompt.workflow,
|
||||
workflow_api: prompt.output,
|
||||
snapshot: snapshot,
|
||||
dependencies: deps,
|
||||
};
|
||||
console.log(body);
|
||||
let data = await fetch(apiRoute, {
|
||||
@@ -462,8 +278,6 @@ function addButton() {
|
||||
data = await data.json();
|
||||
}
|
||||
|
||||
loadingDialog.close();
|
||||
|
||||
title.textContent = "Done";
|
||||
title.style.color = "green";
|
||||
|
||||
@@ -480,7 +294,6 @@ function addButton() {
|
||||
title.style.color = "white";
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
loadingDialog.close();
|
||||
app.ui.dialog.show(e);
|
||||
console.error(e);
|
||||
title.textContent = "Error";
|
||||
@@ -557,7 +370,7 @@ export class InfoDialog extends ComfyDialog {
|
||||
|
||||
showMessage(title, message) {
|
||||
this.show(`
|
||||
<div style="width: 100%; max-width: 600px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<h3 style="margin: 0px;">${title}</h3>
|
||||
<label>
|
||||
${message}
|
||||
@@ -613,10 +426,7 @@ export class LoadingDialog extends ComfyDialog {
|
||||
showLoading(title, message) {
|
||||
this.show(`
|
||||
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 12px;">${title} ${
|
||||
this.loadingIcon
|
||||
}</h3>
|
||||
${message ? `<label>${message}</label>` : ""}
|
||||
<h3 style="margin: 0px; display: flex; align-items: center; justify-content: center; gap: 4px;">${title} ${this.loadingIcon}</h3>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
@@ -735,7 +545,7 @@ export class ConfirmDialog extends InfoDialog {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.callback = resolve;
|
||||
this.show(`
|
||||
<div style="width: 100%; max-width: 600px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<div style="width: 400px; display: flex; gap: 18px; flex-direction: column; overflow: unset">
|
||||
<h3 style="margin: 0px;">${title}</h3>
|
||||
<label>
|
||||
${message}
|
||||
@@ -872,19 +682,16 @@ export class ConfigDialog extends ComfyDialog {
|
||||
</label>
|
||||
<label style="color: white; width: 100%;">
|
||||
Endpoint:
|
||||
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${
|
||||
data.endpoint
|
||||
}">
|
||||
<input id="endpoint" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="text" value="${data.endpoint
|
||||
}">
|
||||
</label>
|
||||
<label style="color: white;">
|
||||
API Key: ${data.displayName ?? ""}
|
||||
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${
|
||||
data.apiKey
|
||||
}">
|
||||
<input id="apiKey" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;" type="password" value="${data.apiKey
|
||||
}">
|
||||
<button id="loginButton" style="margin-top: 8px; width: 100%; height:40px; box-sizing: border-box; padding: 0px 6px;">
|
||||
${
|
||||
data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
|
||||
}
|
||||
${data.apiKey ? "Re-login with ComfyDeploy" : "Login with ComfyDeploy"
|
||||
}
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -20,3 +20,14 @@ PLAUSIBLE_DOMAIN=
|
||||
|
||||
NEXT_PUBLIC_POSTHOG_KEY="your-api-key"
|
||||
NEXT_PUBLIC_POSTHOG_HOST="your-ph-address"
|
||||
|
||||
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/auth/sign-in
|
||||
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/auth/sign-up
|
||||
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
|
||||
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
|
||||
|
||||
STRIPE_API_KEY="sk_test_"
|
||||
STRIPE_PR_PRO="price_"
|
||||
STRIPE_PR_ENTERPRISE="price_"
|
||||
STRIPE_PR_API="price_"
|
||||
STRIPE_WEBHOOK_SECRET="whsec_"
|
||||
|
||||
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,
|
||||
"tag": "0034_even_lady_ursula",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 35,
|
||||
"version": "5",
|
||||
"when": 1705981452482,
|
||||
"tag": "0035_fearless_golden_guardian",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 36,
|
||||
"version": "5",
|
||||
"when": 1706081335387,
|
||||
"tag": "0036_flippant_meltdown",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 37,
|
||||
"version": "5",
|
||||
"when": 1706086271525,
|
||||
"tag": "0037_eager_cyclops",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 38,
|
||||
"version": "5",
|
||||
"when": 1706095853896,
|
||||
"tag": "0038_yummy_darkhawk",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 39,
|
||||
"version": "5",
|
||||
"when": 1706109812261,
|
||||
"tag": "0039_nostalgic_lyja",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 40,
|
||||
"version": "5",
|
||||
"when": 1706110859352,
|
||||
"tag": "0040_salty_archangel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 41,
|
||||
"version": "5",
|
||||
"when": 1706111421524,
|
||||
"tag": "0041_thick_norrin_radd",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 42,
|
||||
"version": "5",
|
||||
"when": 1706164614659,
|
||||
"tag": "0042_windy_madelyne_pryor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 43,
|
||||
"version": "5",
|
||||
"when": 1706241449348,
|
||||
"tag": "0043_wealthy_nicolaos",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 44,
|
||||
"version": "5",
|
||||
"when": 1706317908300,
|
||||
"tag": "0044_panoramic_mister_fear",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 45,
|
||||
"version": "5",
|
||||
"when": 1706336448134,
|
||||
"tag": "0045_careful_cerise",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 46,
|
||||
"version": "5",
|
||||
"when": 1706383154642,
|
||||
"tag": "0046_complex_mentallo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 47,
|
||||
"version": "5",
|
||||
"when": 1706384528895,
|
||||
"tag": "0047_gifted_starbolt",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
-1
@@ -19,23 +19,26 @@
|
||||
"@algolia/autocomplete-core": "^1.13.0",
|
||||
"@aws-sdk/client-s3": "^3.472.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.472.0",
|
||||
"@clerk/nextjs": "^4.29.3",
|
||||
"@clerk/nextjs": "4.29.5",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@hono/swagger-ui": "^0.2.1",
|
||||
"@hono/zod-openapi": "^0.9.5",
|
||||
"@hono/zod-validator": "^0.1.11",
|
||||
"@hookform/resolvers": "^3.3.2",
|
||||
"@lemonsqueezy/lemonsqueezy.js": "^1.2.5",
|
||||
"@mdx-js/loader": "^3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"@neondatabase/serverless": "^0.6.0",
|
||||
"@next/mdx": "^14.0.4",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
@@ -46,6 +49,7 @@
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@stripe/stripe-js": "^2.4.0",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@tanstack/react-table": "^8.10.7",
|
||||
"@tanstack/react-virtual": "beta",
|
||||
@@ -94,6 +98,7 @@
|
||||
"shikiji": "^0.9.3",
|
||||
"simple-functional-loader": "^1.2.1",
|
||||
"sonner": "^1.2.4",
|
||||
"stripe": "^14.13.0",
|
||||
"swagger-ui-react": "^5.11.0",
|
||||
"swr": "^2.2.4",
|
||||
"tailwind-merge": "^2.1.0",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,175 @@
|
||||
import { auth, clerkClient } from "@clerk/nextjs";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { stripe } from "../../../../../server/stripe";
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "@/db/db";
|
||||
import { subscriptionStatusTable } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const secret = process.env.STRIPE_WEBHOOK_SECRET || "";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.text();
|
||||
|
||||
const signature = headers().get("stripe-signature");
|
||||
|
||||
if (!signature)
|
||||
return new NextResponse("Signature not found.", {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const event = stripe.webhooks.constructEvent(body, signature, secret);
|
||||
|
||||
console.log(event);
|
||||
|
||||
if (event.type === "checkout.session.completed") {
|
||||
if (!event.data.object.customer_details?.email) {
|
||||
throw new Error(`missing user email, ${event.id}`);
|
||||
}
|
||||
|
||||
const userId = event.data.object.metadata?.userId;
|
||||
const plan = event.data.object.metadata?.plan;
|
||||
if (!userId) {
|
||||
throw new Error(`missing itinerary_id on metadata, ${event.id}`);
|
||||
}
|
||||
if (!plan) {
|
||||
throw new Error(`missing plan on metadata, ${event.id}`);
|
||||
}
|
||||
|
||||
const orgId = event.data.object.metadata?.orgId;
|
||||
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
const subscriptionId =
|
||||
typeof event.data.object.subscription == "string"
|
||||
? event.data.object.subscription
|
||||
: event.data.object.subscription?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
if (!subscriptionId) {
|
||||
throw new Error(`missing subscriptionId, ${event.id}`);
|
||||
}
|
||||
|
||||
const items = await stripe.subscriptionItems.list({
|
||||
subscription: subscriptionId,
|
||||
limit: 5,
|
||||
});
|
||||
|
||||
// getting the subscription item id for the api plan
|
||||
const subscription_item_api_id = items.data.find(
|
||||
(x) => x.price.id === process.env.STRIPE_PR_API,
|
||||
)?.id;
|
||||
|
||||
// the plan could be either pro or enterprise
|
||||
const subscription_item_plan_id =
|
||||
items.data.find((x) => x.price.id === process.env.STRIPE_PR_PRO)?.id ??
|
||||
items.data.find((x) => x.price.id === process.env.STRIPE_PR_ENTERPRISE)
|
||||
?.id;
|
||||
|
||||
if (!subscription_item_api_id) {
|
||||
throw new Error(
|
||||
`missing plan on subscription_item_api_id, ${event.id}`,
|
||||
);
|
||||
}
|
||||
if (!subscription_item_plan_id) {
|
||||
throw new Error(
|
||||
`missing plan on subscription_item_plan_id, ${event.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(items);
|
||||
|
||||
await db
|
||||
.insert(subscriptionStatusTable)
|
||||
.values({
|
||||
stripe_customer_id: customerId,
|
||||
org_id: orgId && orgId.length > 0 && orgId != "null" ? orgId : null,
|
||||
user_id: userId,
|
||||
subscription_id: subscriptionId,
|
||||
plan: plan as "pro" | "enterprise" | "basic",
|
||||
status: "active",
|
||||
subscription_item_api_id: subscription_item_api_id,
|
||||
subscription_item_plan_id: subscription_item_plan_id,
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
|
||||
// updateDatabase(event.data.object.metadata.itinerary_id);
|
||||
// sendEmail(event.data.object.customer_details.email);
|
||||
} else if (event.type === "customer.subscription.paused") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ status: "paused" })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
} else if (event.type === "customer.subscription.resumed") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ status: "active" })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
} else if (event.type === "customer.subscription.deleted") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ status: "deleted" })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
} else if (event.type === "customer.subscription.updated") {
|
||||
const customerId =
|
||||
typeof event.data.object.customer == "string"
|
||||
? event.data.object.customer
|
||||
: event.data.object.customer?.id;
|
||||
|
||||
if (!customerId) {
|
||||
throw new Error(`missing customerId, ${event.id}`);
|
||||
}
|
||||
|
||||
const cancel_at_period_end = event.data.object.cancel_at_period_end;
|
||||
|
||||
await db
|
||||
.update(subscriptionStatusTable)
|
||||
.set({ cancel_at_period_end: cancel_at_period_end })
|
||||
.where(eq(subscriptionStatusTable.stripe_customer_id, customerId));
|
||||
}
|
||||
|
||||
return NextResponse.json({ result: event, ok: true });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: `Something went wrong: ${error}`,
|
||||
ok: false,
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { stripe } from "@/server/stripe";
|
||||
import { auth, clerkClient } from "@clerk/nextjs";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getUrlServerSide } from "@/server/getUrlServerSide";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const { userId, orgId } = auth();
|
||||
|
||||
const plan = new URL(req.url).searchParams.get("plan");
|
||||
|
||||
if (!userId) return redirect("/");
|
||||
if (!plan) return redirect("/pricing");
|
||||
|
||||
const user = await clerkClient.users.getUser(userId);
|
||||
|
||||
const mapping = {
|
||||
pro: process.env.STRIPE_PR_PRO,
|
||||
enterprise: process.env.STRIPE_PR_ENTERPRISE,
|
||||
};
|
||||
|
||||
const api = process.env.STRIPE_PR_API;
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
success_url: getUrlServerSide(),
|
||||
line_items: [
|
||||
{
|
||||
price: mapping[plan as "pro" | "enterprise"],
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
price: api,
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
userId: userId,
|
||||
orgId: orgId ?? null,
|
||||
plan: plan,
|
||||
},
|
||||
allow_promotion_codes: true,
|
||||
client_reference_id: orgId ?? userId,
|
||||
customer_email: user.emailAddresses[0].emailAddress,
|
||||
mode: "subscription",
|
||||
});
|
||||
|
||||
if (session.url) redirect(session.url);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { stripe } from "@/server/stripe";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getUrlServerSide } from "@/server/getUrlServerSide";
|
||||
import { getCurrentPlan } from "@/server/getCurrentPlan";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const { userId, orgId } = auth();
|
||||
|
||||
if (!userId) return redirect("/");
|
||||
|
||||
const change = new URL(req.url).searchParams.get("change");
|
||||
|
||||
const sub = await getCurrentPlan({
|
||||
org_id: orgId,
|
||||
user_id: userId,
|
||||
});
|
||||
|
||||
if (!sub) return redirect("/pricing");
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: sub.stripe_customer_id,
|
||||
return_url: getUrlServerSide() + "/pricing",
|
||||
// flow_data:
|
||||
// change === "true" && sub.subscription_id
|
||||
// ? {
|
||||
// type: "subscription_update",
|
||||
// subscription_update: {
|
||||
// subscription: sub.subscription_id,
|
||||
// },
|
||||
// }
|
||||
// : undefined,
|
||||
});
|
||||
|
||||
redirect(session.url);
|
||||
// if (session.url) redirect(session.url);
|
||||
}
|
||||
@@ -1,56 +1,129 @@
|
||||
import { parseDataSafe } from "../../../../lib/parseDataSafe";
|
||||
import { db } from "@/db/db";
|
||||
import { workflowRunOutputs, workflowRunsTable } from "@/db/schema";
|
||||
import {
|
||||
WorkflowRunStatusSchema,
|
||||
userUsageTable,
|
||||
workflowRunOutputs,
|
||||
workflowRunsTable,
|
||||
workflowTable,
|
||||
} from "@/db/schema";
|
||||
import { getCurrentPlan } from "@/server/getCurrentPlan";
|
||||
import { stripe } from "@/server/stripe";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
const Request = z.object({
|
||||
run_id: z.string(),
|
||||
status: z
|
||||
.enum(["not-started", "running", "uploading", "success", "failed"])
|
||||
.optional(),
|
||||
status: WorkflowRunStatusSchema.optional(),
|
||||
time: z.coerce.date().optional(),
|
||||
output_data: z.any().optional(),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const [data, error] = await parseDataSafe(Request, request);
|
||||
if (!data || error) return error;
|
||||
try {
|
||||
const [data, error] = await parseDataSafe(Request, request);
|
||||
|
||||
const { run_id, status, output_data } = data;
|
||||
if (!data || error) return error;
|
||||
|
||||
// console.log(run_id, status, output_data);
|
||||
const { run_id, status, time, output_data } = data;
|
||||
|
||||
if (output_data) {
|
||||
const workflow_run_output = await db.insert(workflowRunOutputs).values({
|
||||
run_id: run_id,
|
||||
data: output_data,
|
||||
});
|
||||
} else if (status) {
|
||||
// console.log("status", status);
|
||||
const workflow_run = await db
|
||||
.update(workflowRunsTable)
|
||||
.set({
|
||||
status: status,
|
||||
ended_at:
|
||||
status === "success" || status === "failed" ? new Date() : null,
|
||||
})
|
||||
.where(eq(workflowRunsTable.id, run_id))
|
||||
.returning();
|
||||
}
|
||||
|
||||
// const workflow_version = await db.query.workflowVersionTable.findFirst({
|
||||
// where: eq(workflowRunsTable.id, workflow_run[0].workflow_version_id),
|
||||
// });
|
||||
|
||||
// revalidatePath(`./${workflow_version?.workflow_id}`);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: "success",
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
if (status == "started" && time != undefined) {
|
||||
// It successfully started, update the started_at time
|
||||
await db
|
||||
.update(workflowRunsTable)
|
||||
.set({
|
||||
started_at: time,
|
||||
})
|
||||
.where(eq(workflowRunsTable.id, run_id));
|
||||
}
|
||||
);
|
||||
|
||||
if (status == "queued" && time != undefined) {
|
||||
// It successfully started, update the started_at time
|
||||
await db
|
||||
.update(workflowRunsTable)
|
||||
.set({
|
||||
queued_at: time,
|
||||
})
|
||||
.where(eq(workflowRunsTable.id, run_id));
|
||||
}
|
||||
|
||||
if (output_data) {
|
||||
const workflow_run_output = await db.insert(workflowRunOutputs).values({
|
||||
run_id: run_id,
|
||||
data: output_data,
|
||||
});
|
||||
} else if (status) {
|
||||
const [workflow_run] = await db
|
||||
.update(workflowRunsTable)
|
||||
.set({
|
||||
status: status,
|
||||
ended_at:
|
||||
status === "success" || status === "failed" ? new Date() : null,
|
||||
})
|
||||
.where(eq(workflowRunsTable.id, run_id))
|
||||
.returning();
|
||||
|
||||
// Need to filter out only comfy deploy serverless
|
||||
// Also multiply with the gpu selection
|
||||
if (workflow_run.machine_type == "comfy-deploy-serverless") {
|
||||
if (
|
||||
(status === "success" || status === "failed") &&
|
||||
workflow_run.user_id
|
||||
) {
|
||||
const sub = await getCurrentPlan({
|
||||
user_id: workflow_run.user_id,
|
||||
org_id: workflow_run.org_id,
|
||||
});
|
||||
|
||||
if (sub && sub.subscription_item_api_id && workflow_run.ended_at) {
|
||||
let durationInSec = Math.abs(
|
||||
(workflow_run.ended_at.getTime() -
|
||||
workflow_run.created_at.getTime()) /
|
||||
1000,
|
||||
);
|
||||
durationInSec = Math.ceil(durationInSec);
|
||||
switch (workflow_run.gpu) {
|
||||
case "A100":
|
||||
durationInSec *= 7;
|
||||
break;
|
||||
case "A10G":
|
||||
durationInSec *= 4;
|
||||
break;
|
||||
}
|
||||
await stripe.subscriptionItems.createUsageRecord(
|
||||
sub.subscription_item_api_id,
|
||||
{
|
||||
quantity: durationInSec,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: "success",
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
console.log("An error here");
|
||||
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error";
|
||||
console.log(errorMessage);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: errorMessage,
|
||||
},
|
||||
{
|
||||
status: 500,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { parseDataSafe } from "../../../../lib/parseDataSafe";
|
||||
import { db } from "@/db/db";
|
||||
import { modelTable } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
const Request = z.object({
|
||||
volume_id: z.string(),
|
||||
model_id: z.string(),
|
||||
folder_path: z.string().optional(),
|
||||
status: z.enum(["success", "failed"]),
|
||||
error_log: z.string().optional(),
|
||||
timeout: z.number().optional(),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const [data, error] = await parseDataSafe(Request, request);
|
||||
if (!data || error) return error;
|
||||
|
||||
const { model_id, error_log, status, folder_path } = data;
|
||||
console.log(model_id, error_log, status, folder_path);
|
||||
|
||||
if (status === "success") {
|
||||
await db
|
||||
.update(modelTable)
|
||||
.set({
|
||||
status: "success",
|
||||
folder_path,
|
||||
updated_at: new Date(),
|
||||
// build_log: build_log,
|
||||
})
|
||||
.where(eq(modelTable.id, model_id));
|
||||
} else {
|
||||
await db
|
||||
.update(modelTable)
|
||||
.set({
|
||||
status: "failed",
|
||||
error_log,
|
||||
updated_at: new Date(),
|
||||
// status: "error",
|
||||
// build_log: build_log,
|
||||
})
|
||||
.where(eq(modelTable.id, model_id));
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: "success",
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SignIn } from "@clerk/nextjs";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="w-full h-full items-center flex justify-center">
|
||||
<SignIn />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SignUp } from "@clerk/nextjs";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="w-full h-full items-center flex justify-center">
|
||||
<SignUp />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Page() {
|
||||
return <Examples />;
|
||||
}
|
||||
|
||||
|
||||
type exampleWorkflow = {
|
||||
title: string;
|
||||
description: string;
|
||||
previewURL: string;
|
||||
image: {
|
||||
src: string,
|
||||
alt: string,
|
||||
};
|
||||
};
|
||||
|
||||
const exampleWorkflows: exampleWorkflow[] = [
|
||||
{
|
||||
title: "Txt2Img SDXL",
|
||||
description: "The basic workflow, type a prompt and generate images based on that.",
|
||||
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-txt2img-sdxl',
|
||||
image: {
|
||||
src: '/example-workflows/txt2img.webp',
|
||||
alt: 'IPAdapter workflow',
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Txt2Img LCM SDXL",
|
||||
description: "Images in a couple of seconds, increase the speed of each generation using LCM Lora.",
|
||||
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-lcm-sdxl',
|
||||
image: {
|
||||
src: '/example-workflows/txt2img-lcm.webp',
|
||||
alt: 'txt2img LCM SDXL',
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "IPAdapter SDXL",
|
||||
description: "Load images and use them as reference for new generations.",
|
||||
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-ip-adapter-sdxl',
|
||||
image: {
|
||||
src: '/example-workflows/ipadapter.webp',
|
||||
alt: 'IPAdapter workflow',
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Upscale and Add Detail SDXL",
|
||||
description: "Upscale and Add Details to your creations",
|
||||
previewURL: 'https://www.comfydeploy.com/share/comfy-deploy-example-upscale-and-add-detail-sdxl',
|
||||
image: {
|
||||
src: '/example-workflows/upscale.webp',
|
||||
alt: 'Upscale and Add Detail SDXL',
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
async function Examples() {
|
||||
return (
|
||||
<div className="w-full py-4">
|
||||
<section className="mx-auto flex max-w-[980px] flex-col items-center gap-2 py-8 md:py-12 md:pb-8 lg:py-24 lg:pb-20">
|
||||
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl text-center">
|
||||
Check out some examples
|
||||
</h1>
|
||||
<p className="max-w-[560px] text-center text-lg text-muted-foreground">Text to Image, Image to Image, IPAdapter, and more. Here are some examples that you can use to deploy your workflow.</p>
|
||||
</section>
|
||||
<section className="flex justify-center flex-wrap gap-4">
|
||||
{exampleWorkflows.map(workflow => {
|
||||
return <Card className="w-[350px]">
|
||||
<CardHeader>
|
||||
<CardTitle>{workflow.title}</CardTitle>
|
||||
<CardDescription>{workflow.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Image src={workflow.image.src} alt={workflow.image.alt} width={350} height={230} />
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end gap-2">
|
||||
<Button asChild>
|
||||
<Link href={workflow.previewURL}>View Workflow</Link>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>;
|
||||
})}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { AccessType } from "../../../lib/AccessType";
|
||||
import { MachineList } from "@/components/MachineList";
|
||||
import { db } from "@/db/db";
|
||||
import { machinesTable } from "@/db/schema";
|
||||
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { clerkClient } from "@clerk/nextjs/server";
|
||||
import { desc, eq, isNull, and } from "drizzle-orm";
|
||||
@@ -19,6 +20,8 @@ async function MachineListServer() {
|
||||
|
||||
const user = await clerkClient.users.getUser(userId);
|
||||
|
||||
const sub = await getCurrentPlanWithAuth();
|
||||
|
||||
const machines = await db.query.machinesTable.findMany({
|
||||
orderBy: desc(machinesTable.updated_at),
|
||||
where:
|
||||
@@ -31,6 +34,7 @@ async function MachineListServer() {
|
||||
<div className="w-full">
|
||||
{/* <div>Machines</div> */}
|
||||
<MachineList
|
||||
sub={sub}
|
||||
data={machines}
|
||||
userMetadata={AccessType.parse(user.privateMetadata ?? {})}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import PricingList from "@/components/PricingPlan";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<PricingList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
// app/providers.tsx
|
||||
'use client'
|
||||
import posthog from 'posthog-js'
|
||||
import { PostHogProvider } from 'posthog-js/react'
|
||||
"use client";
|
||||
import posthog from "posthog-js";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
if (typeof window !== "undefined") {
|
||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
capture_pageview: false // Disable automatic pageview capture, as we capture manually
|
||||
})
|
||||
capture_pageview: false, // Disable automatic pageview capture, as we capture manually
|
||||
});
|
||||
}
|
||||
|
||||
export function PHProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
|
||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
|
||||
}
|
||||
|
||||
@@ -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 { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
|
||||
|
||||
export const maxDuration = 300; // 5 minutes
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const maxDuration = 300; // 5 minutes
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
deployment,
|
||||
|
||||
@@ -6,6 +6,8 @@ import { usersTable } from "@/db/schema";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const maxDuration = 300; // 5 minutes
|
||||
|
||||
export default function Home() {
|
||||
return <WorkflowServer />;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ import type { UnknownKeysParam, ZodObject, ZodRawShape, z } from "zod";
|
||||
export function InsertModal<
|
||||
K extends ZodRawShape,
|
||||
Y extends UnknownKeysParam,
|
||||
Z extends ZodObject<K, Y>
|
||||
Z extends ZodObject<K, Y>,
|
||||
>(props: {
|
||||
tooltip?: string;
|
||||
disabled?: boolean;
|
||||
title: string;
|
||||
title: React.ReactNode;
|
||||
description: string;
|
||||
dialogClassName?: string;
|
||||
serverAction: (data: z.infer<Z>) => Promise<unknown>;
|
||||
@@ -105,7 +105,7 @@ export function InsertModal<
|
||||
export function UpdateModal<
|
||||
K extends ZodRawShape,
|
||||
Y extends UnknownKeysParam,
|
||||
Z extends ZodObject<K, Y>
|
||||
Z extends ZodObject<K, Y>,
|
||||
>(props: {
|
||||
open?: boolean;
|
||||
setOpen?: (open: boolean) => void;
|
||||
@@ -118,7 +118,7 @@ export function UpdateModal<
|
||||
serverAction: (
|
||||
data: z.infer<Z> & {
|
||||
id: string;
|
||||
}
|
||||
},
|
||||
) => Promise<unknown>;
|
||||
formSchema: Z;
|
||||
fieldConfig?: FieldConfig<z.infer<Z>>;
|
||||
@@ -165,7 +165,7 @@ export function UpdateModal<
|
||||
props.serverAction({
|
||||
...data,
|
||||
id: props.data.id,
|
||||
})
|
||||
}),
|
||||
);
|
||||
setIsLoading(false);
|
||||
setOpen(false);
|
||||
|
||||
@@ -6,6 +6,19 @@ import { getConnectionStatus } from "@/components/getConnectionStatus";
|
||||
import { useEffect, useState } from "react";
|
||||
import useWebSocket from "react-use-websocket";
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export function MachineBuildLog({
|
||||
machine_id,
|
||||
endpoint,
|
||||
@@ -17,6 +30,7 @@ export function MachineBuildLog({
|
||||
}) {
|
||||
const [logs, setLogs] = useState<LogsType>([]);
|
||||
const [finished, setFinished] = useState(false);
|
||||
const [status, setStatus] = useState<"failed" | "succuss">();
|
||||
|
||||
const wsEndpoint = endpoint.replace(/^http/, "ws");
|
||||
const query = { fly_instance_id: instance_id };
|
||||
@@ -43,13 +57,59 @@ export function MachineBuildLog({
|
||||
setLogs((logs) => [...(logs ?? []), message.data]);
|
||||
} else if (message?.event === "FINISHED") {
|
||||
setFinished(true);
|
||||
setStatus(message.data.status)
|
||||
}
|
||||
}, [lastMessage]);
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{connectionStatus}
|
||||
<LogsViewer logs={logs} />
|
||||
|
||||
<AlertDialog open={finished}>
|
||||
<AlertDialogContent>
|
||||
{
|
||||
status == "succuss" ? (
|
||||
<>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Machine Built</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Your machine is built, you can now integrate your API, or directly run to check this machines.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogAction onClick={() => {
|
||||
router.push("/workflows")
|
||||
}}>See Workflows</AlertDialogAction>
|
||||
<AlertDialogAction onClick={() => {
|
||||
router.push("/machines")
|
||||
}}>See All Machines</AlertDialogAction>
|
||||
</AlertDialogFooter></>
|
||||
) : (
|
||||
<>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Machine Failed</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Something went wrong with the machine build, please check the log.
|
||||
Possible cause could be conflits with custom nodes, build got stuck, timeout, or too many custom nodes installed.
|
||||
Please attempt a rebuild or remove some of the custom nodes.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>See logs</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => {
|
||||
router.push("/machines")
|
||||
}}>Back to machines</AlertDialogAction>
|
||||
</AlertDialogFooter></>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
updateCustomMachine,
|
||||
updateMachine,
|
||||
} from "@/server/curdMachine";
|
||||
import { editWorkflowOnMachine } from "@/server/editWorkflowOnMachine";
|
||||
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||
import type {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
@@ -54,9 +56,10 @@ import {
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||
import { ArrowUpDown, Lock, MoreHorizontal, Plus } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type { z } from "zod";
|
||||
|
||||
export type Machine = MachineType;
|
||||
@@ -113,16 +116,18 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
{row.original.status} <LoadingIcon />
|
||||
</Badge>
|
||||
)}
|
||||
{!row.original.disabled && row.original.status && (
|
||||
<Badge
|
||||
variant={
|
||||
row.original.status == "ready" ? "success" : "destructive"
|
||||
}
|
||||
className="capitalize"
|
||||
>
|
||||
{row.original.status}
|
||||
</Badge>
|
||||
)}
|
||||
{!row.original.disabled &&
|
||||
row.original.status &&
|
||||
row.original.status != "building" && (
|
||||
<Badge
|
||||
variant={
|
||||
row.original.status == "ready" ? "success" : "destructive"
|
||||
}
|
||||
className="capitalize"
|
||||
>
|
||||
{row.original.status}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
// </a>
|
||||
);
|
||||
@@ -192,7 +197,9 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={async () => {
|
||||
callServerPromise(deleteMachine(machine.id));
|
||||
callServerPromise(deleteMachine(machine.id), {
|
||||
loadingText: "Deleting machine",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Delete Machine
|
||||
@@ -200,7 +207,9 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
{machine.disabled ? (
|
||||
<DropdownMenuItem
|
||||
onClick={async () => {
|
||||
callServerPromise(enableMachine(machine.id));
|
||||
callServerPromise(enableMachine(machine.id), {
|
||||
loadingText: "Enabling machine",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Enable Machine
|
||||
@@ -209,7 +218,9 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
<DropdownMenuItem
|
||||
className="text-destructive"
|
||||
onClick={async () => {
|
||||
callServerPromise(disableMachine(machine.id));
|
||||
callServerPromise(disableMachine(machine.id), {
|
||||
loadingText: "Disabling machine",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Disable Machine
|
||||
@@ -217,22 +228,33 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
)}
|
||||
{machine.type === "comfy-deploy-serverless" && (
|
||||
<>
|
||||
<DropdownMenuItem asChild>
|
||||
<a
|
||||
target="_blank"
|
||||
href={machine.endpoint.replace(
|
||||
"comfyui-api",
|
||||
"comfyui-app"
|
||||
)} rel="noreferrer"
|
||||
>
|
||||
Open ComfyUI
|
||||
</a>
|
||||
<DropdownMenuItem
|
||||
onClick={async () => {
|
||||
const url = await callServerPromise(
|
||||
editWorkflowOnMachine(machine.id),
|
||||
{
|
||||
loadingText: "Getting machine url...",
|
||||
},
|
||||
);
|
||||
if (url && typeof url !== "object") {
|
||||
window.open(url, "_blank");
|
||||
} else if (url && typeof url === "object" && url.error) {
|
||||
console.error(url.error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Open ComfyUI
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
buildMachine({
|
||||
id: machine.id,
|
||||
});
|
||||
callServerPromise(
|
||||
buildMachine({
|
||||
id: machine.id,
|
||||
}),
|
||||
{
|
||||
loadingText: "Starting machine build process",
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
Rebuild
|
||||
@@ -300,13 +322,15 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
export function MachineList({
|
||||
data,
|
||||
userMetadata,
|
||||
sub,
|
||||
}: {
|
||||
data: Machine[];
|
||||
userMetadata: z.infer<typeof AccessType>;
|
||||
sub: Awaited<ReturnType<typeof getCurrentPlanWithAuth>>;
|
||||
}) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
[],
|
||||
);
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({});
|
||||
@@ -331,6 +355,21 @@ export function MachineList({
|
||||
},
|
||||
});
|
||||
|
||||
let machineMaxCount = 2;
|
||||
|
||||
// Temp fixes for machine count
|
||||
if (userMetadata.betaFeaturesAccess) machineMaxCount = 5;
|
||||
|
||||
if (sub?.plan == "pro") {
|
||||
machineMaxCount = 10;
|
||||
} else if (sub?.plan == "enterprise") {
|
||||
machineMaxCount = 99;
|
||||
}
|
||||
|
||||
const locked =
|
||||
data.some((machine) => machine.type === "modal-serverless") &&
|
||||
data.length >= machineMaxCount;
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center py-4">
|
||||
@@ -345,17 +384,17 @@ export function MachineList({
|
||||
<div className="ml-auto flex gap-2">
|
||||
<InsertModal
|
||||
dialogClassName="sm:max-w-[600px]"
|
||||
disabled={
|
||||
data.some(
|
||||
(machine) => machine.type === "comfy-deploy-serverless"
|
||||
) && !userMetadata.betaFeaturesAccess
|
||||
}
|
||||
disabled={locked}
|
||||
tooltip={
|
||||
data.some((machine) => machine.type === "comfy-deploy-serverless")
|
||||
? "Only one hosted machine at preview stage"
|
||||
: undefined
|
||||
locked
|
||||
? `Max ${machineMaxCount} ComfyUI machine for your account, upgrade to unlock more cnfiguration.`
|
||||
: `Max ${machineMaxCount} ComfyUI machine for your account`
|
||||
}
|
||||
title={
|
||||
<>
|
||||
New Machine {locked ? <Lock size={14} /> : <Plus size={14} />}
|
||||
</>
|
||||
}
|
||||
title="New Machine"
|
||||
description="Add custom ComfyUI machines to your account."
|
||||
serverAction={addCustomMachine}
|
||||
formSchema={addCustomMachineSchema}
|
||||
@@ -410,7 +449,7 @@ export function MachineList({
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
@@ -429,7 +468,7 @@ export function MachineList({
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
"use client";
|
||||
|
||||
import { getRelativeTime } from "../lib/getRelativeTime";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { InsertModal } from "./InsertModal";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import type { getAllUserModels as getAllUserModels } from "@/server/getAllUserModel";
|
||||
import type {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { ArrowUpDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { 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: "fallback",
|
||||
inputProps: { required: true },
|
||||
description: (
|
||||
<>
|
||||
Pick a model from{" "}
|
||||
<a
|
||||
href="https://www.civitai.com/models"
|
||||
target="_blank"
|
||||
className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"
|
||||
>
|
||||
civitai.com
|
||||
</a>{" "}
|
||||
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";
|
||||
import { Github, Menu } from "lucide-react";
|
||||
import meta from "next-gen/config";
|
||||
import { useFeatureFlagEnabled } from "posthog-js/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
@@ -29,15 +30,17 @@ export function Navbar() {
|
||||
const { organization } = useOrganization();
|
||||
const _isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||
const [isDesktop, setIsDesktop] = useState(true);
|
||||
const [isSheetOpen, setSheetOpen] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setIsDesktop(_isDesktop);
|
||||
}, [_isDesktop]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
{!isDesktop && (
|
||||
<Sheet open={isSheetOpen} onOpenChange={(open) => setSheetOpen(open)}>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<button className="flex items-center justify-center w-8 h-8 p-2">
|
||||
<Menu />
|
||||
@@ -48,17 +51,7 @@ export function Navbar() {
|
||||
<SheetTitle className="text-start">Comfy Deploy</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="grid h-full grid-rows-[1fr_auto]">
|
||||
<NavbarMenu
|
||||
className=" h-full"
|
||||
closeSheet={() => setSheetOpen(false)}
|
||||
/>
|
||||
{/* <OrganizationSwitcher
|
||||
appearance={{
|
||||
elements: {
|
||||
rootBox: "flex items-center justify-center z-[50]",
|
||||
},
|
||||
}}
|
||||
/> */}
|
||||
<NavbarMenu className=" h-full" />
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">
|
||||
@@ -89,20 +82,26 @@ export function Navbar() {
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{isDesktop && <NavbarMenu />}
|
||||
<Button
|
||||
{/* <Button
|
||||
asChild
|
||||
variant="link"
|
||||
className="rounded-full aspect-square p-2 mr-4"
|
||||
>
|
||||
<a href="/docs">Docs</a>
|
||||
</Button>
|
||||
<UserButton />
|
||||
</Button> */}
|
||||
<UserButton
|
||||
afterSignOutUrl="/"
|
||||
/>
|
||||
<Button
|
||||
asChild
|
||||
variant="outline"
|
||||
className="rounded-full aspect-square p-2"
|
||||
>
|
||||
<a target="_blank" href="https://github.com/BennyKok/comfyui-deploy" rel="noreferrer">
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/BennyKok/comfyui-deploy"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Github />
|
||||
</a>
|
||||
</Button>
|
||||
|
||||
@@ -6,16 +6,11 @@ import { cn } from "@/lib/utils";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useFeatureFlagEnabled } from "posthog-js/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
export function NavbarMenu({
|
||||
className,
|
||||
closeSheet,
|
||||
}: {
|
||||
className?: string;
|
||||
closeSheet?: () => void;
|
||||
}) {
|
||||
export function NavbarMenu({ className }: { className?: string }) {
|
||||
const _isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||
const [isDesktop, setIsDesktop] = useState(true);
|
||||
useEffect(() => {
|
||||
@@ -27,6 +22,8 @@ export function NavbarMenu({
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const pricingPlanFlagEnable = useFeatureFlagEnabled("pricing-plan");
|
||||
|
||||
const pages = [
|
||||
{
|
||||
name: "Workflows",
|
||||
@@ -41,11 +38,27 @@ export function NavbarMenu({
|
||||
path: "/api-keys",
|
||||
},
|
||||
{
|
||||
name: "Examples",
|
||||
path: "/examples"
|
||||
}
|
||||
name: "Storage",
|
||||
path: "/storage",
|
||||
},
|
||||
];
|
||||
|
||||
if (pricingPlanFlagEnable) {
|
||||
pages.push({
|
||||
name: "Pricing",
|
||||
path: "/pricing",
|
||||
})
|
||||
pages.push({
|
||||
name: "Usage",
|
||||
path: "/usage",
|
||||
})
|
||||
}
|
||||
|
||||
pages.push({
|
||||
name: "Docs",
|
||||
path: "/docs",
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn("mr-2", className)}>
|
||||
{/* <div className="w-full h-full absolute inset-x-0 top-0 flex items-center justify-center pointer-events-none"> */}
|
||||
@@ -54,7 +67,7 @@ export function NavbarMenu({
|
||||
defaultValue={pathname}
|
||||
className="w-fit flex pointer-events-auto"
|
||||
>
|
||||
<TabsList className="w-full">
|
||||
<TabsList className=" w-full ">
|
||||
{pages.map((page) => (
|
||||
<TabsTrigger
|
||||
key={page.name}
|
||||
@@ -78,9 +91,6 @@ export function NavbarMenu({
|
||||
<Link
|
||||
key={page.name}
|
||||
href={page.path}
|
||||
onClick={() => {
|
||||
if (!!closeSheet) closeSheet();
|
||||
}}
|
||||
className="p-2 hover:bg-gray-100/20 hover:underline"
|
||||
>
|
||||
{page.name}
|
||||
|
||||
@@ -5,20 +5,6 @@ export async function OutputRender(props: {
|
||||
run_id: string;
|
||||
filename: string;
|
||||
}) {
|
||||
if (props.filename.endsWith(".mp4") || props.filename.endsWith(".webm")) {
|
||||
const url = await getFileDownloadUrl(
|
||||
`outputs/runs/${props.run_id}/${props.filename}`,
|
||||
);
|
||||
|
||||
return (
|
||||
<video controls autoPlay className="w-[400px]">
|
||||
<source src={url} type="video/mp4" />
|
||||
<source src={url} type="video/webm" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
props.filename.endsWith(".png") ||
|
||||
props.filename.endsWith(".gif") ||
|
||||
@@ -26,13 +12,13 @@ export async function OutputRender(props: {
|
||||
props.filename.endsWith(".jpeg")
|
||||
) {
|
||||
const url = await getFileDownloadUrl(
|
||||
`outputs/runs/${props.run_id}/${props.filename}`,
|
||||
`outputs/runs/${props.run_id}/${props.filename}`
|
||||
);
|
||||
|
||||
return <img className="max-w-[200px]" alt={props.filename} src={url} />;
|
||||
} else {
|
||||
const url = await getFileDownloadUrl(
|
||||
`outputs/runs/${props.run_id}/${props.filename}`,
|
||||
`outputs/runs/${props.run_id}/${props.filename}`
|
||||
);
|
||||
// console.log(url);
|
||||
|
||||
|
||||
@@ -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 { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
|
||||
import { type findAllRuns } from "@/server/findAllRuns";
|
||||
@@ -21,54 +21,64 @@ import { Suspense } from "react";
|
||||
import { LiveStatus } from "./LiveStatus";
|
||||
|
||||
export async function RunDisplay({
|
||||
run,
|
||||
run,
|
||||
}: {
|
||||
run: Awaited<ReturnType<typeof findAllRuns>>[0];
|
||||
run: Awaited<ReturnType<typeof findAllRuns>>[0];
|
||||
}) {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
||||
<TableRow>
|
||||
<TableCell>{run.number}</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{run.machine?.name}
|
||||
</TableCell>
|
||||
<TableCell className="truncate">
|
||||
{getRelativeTime(run.created_at)}
|
||||
</TableCell>
|
||||
<TableCell>{run.version?.version}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="truncate">
|
||||
{run.origin}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="truncate">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div>Cold start: {getDuration(run.cold_start_duration)}</div>
|
||||
<div>Run duration: {getDuration(run.run_duration)}</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<LiveStatus run={run} />
|
||||
</TableRow>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Run outputs</DialogTitle>
|
||||
<DialogDescription>
|
||||
You can view your run's outputs here
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="max-h-96 overflow-y-scroll">
|
||||
<RunInputs run={run} />
|
||||
<Suspense>
|
||||
<RunOutputs run_id={run.id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>{run.number}</TooltipTrigger>
|
||||
<TooltipContent>{run.id}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{run.machine?.name}
|
||||
</TableCell>
|
||||
<TableCell className="truncate">
|
||||
{getRelativeTime(run.created_at)}
|
||||
</TableCell>
|
||||
<TableCell>{run.version?.version}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="truncate">
|
||||
{run.origin}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="truncate">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div>
|
||||
Serverless latency: {getDuration(run.comfy_deploy_cold_start)}
|
||||
</div>
|
||||
<div>
|
||||
GPU Cold start: {getDuration(run.cold_start_duration)}
|
||||
</div>
|
||||
<div>Run duration: {getDuration(run.run_duration)}</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<LiveStatus run={run} />
|
||||
</TableRow>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Run outputs</DialogTitle>
|
||||
<DialogDescription>
|
||||
You can view your run's outputs here
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="max-h-96 overflow-y-scroll">
|
||||
<RunInputs run={run} />
|
||||
<Suspense>
|
||||
<RunOutputs run_id={run.id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,48 +124,14 @@ export function MachineSelect({
|
||||
);
|
||||
}
|
||||
|
||||
type SelectedMachineStore = {
|
||||
selectedMachine: string | undefined;
|
||||
setSelectedMachine: (machine: string) => void;
|
||||
};
|
||||
|
||||
export const selectedMachineStore = create<SelectedMachineStore>((set) => ({
|
||||
selectedMachine: undefined,
|
||||
setSelectedMachine: (machine) => set(() => ({ selectedMachine: machine })),
|
||||
}));
|
||||
|
||||
export function useSelectedMachine(
|
||||
machines: Awaited<ReturnType<typeof getMachines>>,
|
||||
): [string, (v: string) => void] {
|
||||
const { selectedMachine, setSelectedMachine } = selectedMachineStore();
|
||||
return [selectedMachine ?? machines?.[0]?.id ?? "", setSelectedMachine];
|
||||
) {
|
||||
const a = useQueryState("machine", {
|
||||
defaultValue: machines?.[0]?.id ?? "",
|
||||
});
|
||||
|
||||
// const searchParams = useSearchParams();
|
||||
// const pathname = usePathname();
|
||||
// const router = useRouter();
|
||||
|
||||
// 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) => {
|
||||
// // window.history.pushState(
|
||||
// // "new url",
|
||||
// // "",
|
||||
// // pathname + "?" + createQueryString("machine", v),
|
||||
// // );
|
||||
// // router.push(pathname + "?" + createQueryString("machine", v));
|
||||
// router.replace(pathname + "?" + createQueryString("machine", v));
|
||||
// },
|
||||
// ];
|
||||
return a;
|
||||
}
|
||||
|
||||
type PublicRunStore = {
|
||||
@@ -436,7 +402,7 @@ export function OpenEditButton({
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
const url = await callServerPromise(
|
||||
editWorkflowOnMachine(workflow_version_id, machine),
|
||||
editWorkflowOnMachine(machine, workflow_version_id),
|
||||
);
|
||||
if (url && typeof url !== "object") {
|
||||
window.open(url, "_blank");
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
import { toast } from "sonner";
|
||||
|
||||
export async function callServerPromise<T>(result: Promise<T>) {
|
||||
export async function callServerPromise<T>(result: Promise<T>, props?: {
|
||||
loadingText: string
|
||||
}) {
|
||||
let id: string | number
|
||||
if (props?.loadingText) {
|
||||
id = toast.loading(props.loadingText)
|
||||
}
|
||||
return result
|
||||
.then((x) => {
|
||||
if ((x as { message: string })?.message !== undefined) {
|
||||
@@ -15,5 +21,8 @@ export async function callServerPromise<T>(result: Promise<T>) {
|
||||
.catch((error) => {
|
||||
toast.error(error.message);
|
||||
return null;
|
||||
}).finally(() => {
|
||||
if (id != undefined)
|
||||
toast.dismiss(id)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,6 +157,7 @@ type CustomNodeList = {
|
||||
author: string;
|
||||
title: string;
|
||||
reference: string;
|
||||
pip: string[];
|
||||
files: string[];
|
||||
install_type: string;
|
||||
description: string;
|
||||
@@ -237,56 +238,12 @@ function CustomNodesSelector({
|
||||
key={index}
|
||||
value={framework.reference}
|
||||
onSelect={async (currentValue) => {
|
||||
const repoName = extractRepoName(currentValue);
|
||||
|
||||
console.log(repoName);
|
||||
|
||||
const id = toast.loading(`Fetching repo info...`);
|
||||
// toast.info("hi");
|
||||
|
||||
const repo = await fetch(
|
||||
`https://api.github.com/repos/${repoName}`
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.then((x) => {
|
||||
console.log(x);
|
||||
return x;
|
||||
})
|
||||
.then((x) => RepoSchema.parse(x))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.dismiss(id);
|
||||
toast.error(`Failed to fetch repo info ${e.message}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!repo) return;
|
||||
|
||||
const branch = repo.default_branch;
|
||||
|
||||
const branchInfo = await fetch(
|
||||
`https://api.github.com/repos/${repoName}/branches/${branch}`
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.then((x) => BranchInfoSchema.parse(x))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.dismiss(id);
|
||||
toast.error(
|
||||
`Failed to fetch branch info ${e.message}`
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
toast.dismiss(id);
|
||||
|
||||
if (!branchInfo) return;
|
||||
|
||||
let nodeList: Record<
|
||||
string,
|
||||
{
|
||||
hash: string;
|
||||
disabled: boolean;
|
||||
pip?: string[]
|
||||
}
|
||||
>;
|
||||
const x = customNodeList;
|
||||
@@ -296,10 +253,49 @@ function CustomNodesSelector({
|
||||
delete newNodeList[currentValue];
|
||||
nodeList = newNodeList;
|
||||
} else {
|
||||
const repoName = extractRepoName(currentValue);
|
||||
const id = toast.loading(`Fetching repo info...`);
|
||||
const repo = await fetch(
|
||||
`https://api.github.com/repos/${repoName}`
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.then((x) => {
|
||||
console.log(x);
|
||||
return x;
|
||||
})
|
||||
.then((x) => RepoSchema.parse(x))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.dismiss(id);
|
||||
toast.error(`Failed to fetch repo info ${e.message}`);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!repo) return;
|
||||
const branch = repo.default_branch;
|
||||
const branchInfo = await fetch(
|
||||
`https://api.github.com/repos/${repoName}/branches/${branch}`
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.then((x) => BranchInfoSchema.parse(x))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.dismiss(id);
|
||||
toast.error(
|
||||
`Failed to fetch branch info ${e.message}`
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
toast.dismiss(id);
|
||||
|
||||
if (!branchInfo) return;
|
||||
|
||||
nodeList = {
|
||||
[currentValue]: {
|
||||
hash: branchInfo?.commit.sha,
|
||||
disabled: false,
|
||||
pip: framework.pip
|
||||
},
|
||||
...x,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,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,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Progress = React.forwardRef<
|
||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||
>(({ className, value, ...props }, ref) => (
|
||||
<ProgressPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
className="h-full w-full flex-1 bg-primary transition-all"
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
))
|
||||
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||
|
||||
export { Progress }
|
||||
+1
-1
@@ -5,7 +5,7 @@ import * as schema from "./schema";
|
||||
const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
|
||||
|
||||
// if we're running locally
|
||||
if (process.env.VERCEL_ENV !== "production") {
|
||||
if (process.env.VERCEL_ENV !== "production" && process.env.VERCEL_ENV !== "preview") {
|
||||
// Set the WebSocket proxy to work with the local instance
|
||||
if (isDevContainer) {
|
||||
// Running inside a VS Code devcontainer
|
||||
|
||||
+151
-1
@@ -1,3 +1,4 @@
|
||||
import { CivitaiModelResponse } from "@/types/civitai";
|
||||
import { type InferSelectModel, relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
@@ -8,9 +9,10 @@ import {
|
||||
text,
|
||||
timestamp,
|
||||
uuid,
|
||||
real,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
import { TypeOf, z } from "zod";
|
||||
|
||||
export const dbSchema = pgSchema("comfyui_deploy");
|
||||
|
||||
@@ -81,6 +83,7 @@ export const workflowVersionTable = dbSchema.table("workflow_versions", {
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const workflowVersionSchema = createSelectSchema(workflowVersionTable);
|
||||
|
||||
export const workflowVersionRelations = relations(
|
||||
@@ -99,6 +102,8 @@ export const workflowRunStatus = pgEnum("workflow_run_status", [
|
||||
"uploading",
|
||||
"success",
|
||||
"failed",
|
||||
"started",
|
||||
"queued",
|
||||
]);
|
||||
|
||||
export const deploymentEnvironment = pgEnum("deployment_environment", [
|
||||
@@ -113,6 +118,8 @@ export const workflowRunOrigin = pgEnum("workflow_run_origin", [
|
||||
"public-share",
|
||||
]);
|
||||
|
||||
export const WorkflowRunStatusSchema = z.enum(workflowRunStatus.enumValues);
|
||||
|
||||
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
|
||||
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
|
||||
|
||||
@@ -155,8 +162,16 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
|
||||
origin: workflowRunOrigin("origin").notNull().default("api"),
|
||||
status: workflowRunStatus("status").notNull().default("not-started"),
|
||||
ended_at: timestamp("ended_at"),
|
||||
// comfy deploy run created time
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
// modal gpu cold start begin
|
||||
queued_at: timestamp("queued_at"),
|
||||
// modal gpu function actual start time
|
||||
started_at: timestamp("started_at"),
|
||||
gpu: machineGPUOptions("gpu"),
|
||||
machine_type: machinesType("machine_type"),
|
||||
user_id: text("user_id"),
|
||||
org_id: text("org_id"),
|
||||
});
|
||||
|
||||
export const workflowRunRelations = relations(
|
||||
@@ -334,6 +349,19 @@ export const apiKeyTable = dbSchema.table("api_keys", {
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const userUsageTable = dbSchema.table("user_usage", {
|
||||
id: uuid("id").primaryKey().defaultRandom().notNull(),
|
||||
org_id: text("org_id"),
|
||||
user_id: text("user_id")
|
||||
.references(() => usersTable.id, {
|
||||
onDelete: "cascade",
|
||||
})
|
||||
.notNull(),
|
||||
usage_time: real("usage_time").default(0).notNull(),
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
ended_at: timestamp("ended_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const authRequestsTable = dbSchema.table("auth_requests", {
|
||||
request_id: text("request_id").primaryKey().notNull(),
|
||||
user_id: text("user_id"),
|
||||
@@ -344,8 +372,130 @@ export const authRequestsTable = dbSchema.table("auth_requests", {
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const resourceUpload = pgEnum("resource_upload", [
|
||||
"started",
|
||||
"success",
|
||||
"failed",
|
||||
]);
|
||||
|
||||
export const modelUploadType = pgEnum("model_upload_type", [
|
||||
"civitai",
|
||||
"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 WorkflowType = InferSelectModel<typeof workflowTable>;
|
||||
export type MachineType = InferSelectModel<typeof machinesTable>;
|
||||
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
|
||||
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
|
||||
export type ModelType = InferSelectModel<typeof modelTable>;
|
||||
export type UserVolumeType = InferSelectModel<typeof userVolume>;
|
||||
export type UserUsageType = InferSelectModel<typeof userUsageTable>;
|
||||
|
||||
@@ -12,13 +12,21 @@ export function getRelativeTime(time: string | Date | null | undefined) {
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
if (minutes > 0) {
|
||||
return `${minutes}.${remainingSeconds} mins`;
|
||||
} else {
|
||||
return `${remainingSeconds.toFixed(1)} secs`;
|
||||
|
||||
let result = "";
|
||||
if (hours > 0) {
|
||||
result += `${hours} hrs `;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
result += `${minutes} mins `;
|
||||
}
|
||||
if (remainingSeconds > 0) {
|
||||
result += `${remainingSeconds.toFixed(1)} secs`;
|
||||
}
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
export function getDuration(durationInSecs: number) {
|
||||
|
||||
@@ -113,7 +113,7 @@ export const registerWorkflowUploadRoute = (app: App) => {
|
||||
workflow_id = _workflow_id;
|
||||
version = _version;
|
||||
} else if (workflow_id) {
|
||||
const _workflow = await db
|
||||
const workflow = await db
|
||||
.select()
|
||||
.from(workflowTable)
|
||||
.where(
|
||||
@@ -126,7 +126,7 @@ export const registerWorkflowUploadRoute = (app: App) => {
|
||||
),
|
||||
);
|
||||
|
||||
if (_workflow.length === 0) {
|
||||
if (workflow.length === 0) {
|
||||
return c.json(
|
||||
{
|
||||
error: "Invalid workflow_id",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
export const APIKeyBodyRequest = z.object({
|
||||
user_id: z.string().optional().nullable(),
|
||||
org_id: z.string().optional().nullable(),
|
||||
iat: z.number(),
|
||||
iat: z.number().optional(),
|
||||
exp: z.number().optional(),
|
||||
});
|
||||
|
||||
|
||||
@@ -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")
|
||||
});
|
||||
|
||||
+15
-19
@@ -66,7 +66,12 @@ export const createRun = withServerPromise(
|
||||
throw new Error("Workflow version not found");
|
||||
}
|
||||
|
||||
if (apiUser)
|
||||
let { userId, orgId } = auth();
|
||||
|
||||
// If is API user, check if they have access to the workflow
|
||||
if (apiUser) {
|
||||
userId = apiUser.user_id ?? null;
|
||||
orgId = apiUser.org_id;
|
||||
if (apiUser.org_id) {
|
||||
// is org api call, check org only
|
||||
if (apiUser.org_id != workflow_version_data.workflow.org_id) {
|
||||
@@ -81,6 +86,7 @@ export const createRun = withServerPromise(
|
||||
throw new Error("Workflow not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const workflow_api = workflow_version_data.workflow_api;
|
||||
|
||||
@@ -91,11 +97,6 @@ export const createRun = withServerPromise(
|
||||
if (node.inputs["input_id"] === key) {
|
||||
node.inputs["input_id"] = inputs[key];
|
||||
}
|
||||
|
||||
// Fix for external text default value
|
||||
if (node.class_type == "ComfyUIDeployExternalText") {
|
||||
node.inputs["default_value"] = inputs[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -119,6 +120,10 @@ export const createRun = withServerPromise(
|
||||
workflow_inputs: inputs,
|
||||
machine_id: machine.id,
|
||||
origin: runOrigin,
|
||||
org_id: orgId,
|
||||
user_id: userId,
|
||||
gpu: machine.gpu,
|
||||
machine_type: machine.type,
|
||||
})
|
||||
.returning();
|
||||
|
||||
@@ -143,14 +148,14 @@ export const createRun = withServerPromise(
|
||||
body: JSON.stringify(_data),
|
||||
cache: "no-store",
|
||||
});
|
||||
console.log(___result);
|
||||
// console.log(___result);
|
||||
if (!___result.ok)
|
||||
throw new Error(
|
||||
`Error creating run, ${
|
||||
___result.statusText
|
||||
} ${await ___result.text()}`,
|
||||
);
|
||||
console.log(_data, ___result);
|
||||
// console.log(_data, ___result);
|
||||
break;
|
||||
case "runpod-serverless":
|
||||
const data = {
|
||||
@@ -177,14 +182,14 @@ export const createRun = withServerPromise(
|
||||
body: JSON.stringify(data),
|
||||
cache: "no-store",
|
||||
});
|
||||
console.log(__result);
|
||||
// console.log(__result);
|
||||
if (!__result.ok)
|
||||
throw new Error(
|
||||
`Error creating run, ${
|
||||
__result.statusText
|
||||
} ${await __result.text()}`,
|
||||
);
|
||||
console.log(data, __result);
|
||||
// console.log(data, __result);
|
||||
break;
|
||||
case "classic":
|
||||
const body = {
|
||||
@@ -224,15 +229,6 @@ export const createRun = withServerPromise(
|
||||
throw e;
|
||||
}
|
||||
|
||||
// It successfully started, update the started_at time
|
||||
|
||||
await db
|
||||
.update(workflowRunsTable)
|
||||
.set({
|
||||
started_at: new Date(),
|
||||
})
|
||||
.where(eq(workflowRunsTable.id, workflow_run[0].id));
|
||||
|
||||
return {
|
||||
workflow_run_id: workflow_run[0].id,
|
||||
message: "Successful workflow run",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import "server-only";
|
||||
import type { z } from "zod";
|
||||
import { retrieveModelVolumes } from "./curdModel";
|
||||
|
||||
export async function getMachines() {
|
||||
const { userId, orgId } = auth();
|
||||
@@ -29,10 +30,10 @@ export async function getMachines() {
|
||||
: // make sure org_id is null
|
||||
and(
|
||||
eq(machinesTable.user_id, userId),
|
||||
isNull(machinesTable.org_id)
|
||||
isNull(machinesTable.org_id),
|
||||
),
|
||||
eq(machinesTable.disabled, false)
|
||||
)
|
||||
eq(machinesTable.disabled, false),
|
||||
),
|
||||
);
|
||||
return machines;
|
||||
}
|
||||
@@ -49,10 +50,10 @@ export async function getMachineById(id: string) {
|
||||
? eq(machinesTable.org_id, orgId)
|
||||
: and(
|
||||
eq(machinesTable.user_id, userId),
|
||||
isNull(machinesTable.org_id)
|
||||
isNull(machinesTable.org_id),
|
||||
),
|
||||
eq(machinesTable.id, id)
|
||||
)
|
||||
eq(machinesTable.id, id),
|
||||
),
|
||||
);
|
||||
return machines[0];
|
||||
}
|
||||
@@ -69,7 +70,7 @@ export const addMachine = withServerPromise(
|
||||
});
|
||||
revalidatePath("/machines");
|
||||
return { message: "Machine Added" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const updateCustomMachine = withServerPromise(
|
||||
@@ -120,7 +121,7 @@ export const updateCustomMachine = withServerPromise(
|
||||
}
|
||||
|
||||
return { message: "Machine Updated" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const buildMachine = withServerPromise(
|
||||
@@ -146,7 +147,7 @@ export const buildMachine = withServerPromise(
|
||||
// Perform custom build if there are changes
|
||||
await _buildMachine(datas[0], currentMachine);
|
||||
redirect(`/machines/${id}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const addCustomMachine = withServerPromise(
|
||||
@@ -173,12 +174,12 @@ export const addCustomMachine = withServerPromise(
|
||||
redirect(`/machines/${b.id}`);
|
||||
// revalidatePath("/machines");
|
||||
return { message: "Machine Building" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
async function _buildMachine(
|
||||
data: z.infer<typeof addCustomMachineSchema>,
|
||||
b: MachineType
|
||||
b: MachineType,
|
||||
) {
|
||||
const headersList = headers();
|
||||
|
||||
@@ -189,6 +190,7 @@ async function _buildMachine(
|
||||
throw new Error("No domain");
|
||||
}
|
||||
|
||||
const volumes = await retrieveModelVolumes();
|
||||
// Call remote builder
|
||||
const result = await fetch(`${process.env.MODAL_BUILDER_URL!}/create`, {
|
||||
method: "POST",
|
||||
@@ -202,6 +204,7 @@ async function _buildMachine(
|
||||
callback_url: `${protocol}://${domain}/api/machine-built`,
|
||||
models: data.models, //JSON.parse(data.models as string),
|
||||
gpu: data.gpu && data.gpu.length > 0 ? data.gpu : "T4",
|
||||
model_volume_name: volumes[0].volume_name,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -241,7 +244,7 @@ export const updateMachine = withServerPromise(
|
||||
await db.update(machinesTable).set(data).where(eq(machinesTable.id, id));
|
||||
revalidatePath("/machines");
|
||||
return { message: "Machine Updated" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const deleteMachine = withServerPromise(
|
||||
@@ -252,26 +255,33 @@ export const deleteMachine = withServerPromise(
|
||||
|
||||
if (machine?.type === "comfy-deploy-serverless") {
|
||||
// Call remote builder to stop the app on modal
|
||||
const result = await fetch(`${process.env.MODAL_BUILDER_URL!}/stop-app`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
machine_id: machine_id,
|
||||
}),
|
||||
});
|
||||
// Only check for deletion error upon new migration
|
||||
const newUpgradeDate = new Date("2024-01-27T14:24:23.737Z");
|
||||
if (machine.created_at > newUpgradeDate) {
|
||||
const result = await fetch(
|
||||
`${process.env.MODAL_BUILDER_URL!}/stop-app`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
machine_id: machine_id,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!result.ok) {
|
||||
const error_log = await result.text();
|
||||
throw new Error(`Error: ${result.statusText} ${error_log}`);
|
||||
if (!result.ok) {
|
||||
const error_log = await result.text();
|
||||
throw new Error(`Error: ${result.statusText} ${error_log}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await db.delete(machinesTable).where(eq(machinesTable.id, machine_id));
|
||||
revalidatePath("/machines");
|
||||
return { message: "Machine Deleted" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const disableMachine = withServerPromise(
|
||||
@@ -284,7 +294,7 @@ export const disableMachine = withServerPromise(
|
||||
.where(eq(machinesTable.id, machine_id));
|
||||
revalidatePath("/machines");
|
||||
return { message: "Machine Disabled" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const enableMachine = withServerPromise(
|
||||
@@ -297,5 +307,5 @@ export const enableMachine = withServerPromise(
|
||||
.where(eq(machinesTable.id, machine_id));
|
||||
revalidatePath("/machines");
|
||||
return { message: "Machine Enabled" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
"use server";
|
||||
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import {
|
||||
modelEnumType,
|
||||
modelTable,
|
||||
ModelType,
|
||||
userVolume,
|
||||
UserVolumeType,
|
||||
} from "@/db/schema";
|
||||
import { withServerPromise } from "./withServerPromise";
|
||||
import { db } from "@/db/db";
|
||||
import type { z } from "zod";
|
||||
import { headers } from "next/headers";
|
||||
import { downloadUrlModelSchema } from "./addCivitaiModelSchema";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { CivitaiModelResponse, getModelTypeDetails } from "@/types/civitai";
|
||||
|
||||
export async function getModel() {
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) throw new Error("No user id");
|
||||
const models = await db
|
||||
.select()
|
||||
.from(modelTable)
|
||||
.where(
|
||||
orgId
|
||||
? eq(modelTable.org_id, orgId)
|
||||
// make sure org_id is null
|
||||
: and(
|
||||
eq(modelTable.user_id, userId),
|
||||
isNull(modelTable.org_id),
|
||||
),
|
||||
);
|
||||
return models;
|
||||
}
|
||||
|
||||
export async function getModelById(id: string) {
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) throw new Error("No user id");
|
||||
const model = await db
|
||||
.select()
|
||||
.from(modelTable)
|
||||
.where(
|
||||
and(
|
||||
orgId ? eq(modelTable.org_id, orgId) : and(
|
||||
eq(modelTable.user_id, userId),
|
||||
isNull(modelTable.org_id),
|
||||
),
|
||||
eq(modelTable.id, id),
|
||||
),
|
||||
);
|
||||
return model[0];
|
||||
}
|
||||
|
||||
export async function getModelVolumes() {
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) throw new Error("No user id");
|
||||
const volume = await db
|
||||
.select()
|
||||
.from(userVolume)
|
||||
.where(
|
||||
and(
|
||||
orgId
|
||||
? eq(userVolume.org_id, orgId)
|
||||
// make sure org_id is null
|
||||
: and(
|
||||
eq(userVolume.user_id, userId),
|
||||
isNull(userVolume.org_id),
|
||||
),
|
||||
eq(userVolume.disabled, false),
|
||||
),
|
||||
);
|
||||
return volume;
|
||||
}
|
||||
|
||||
export async function retrieveModelVolumes() {
|
||||
let volumes = await getModelVolumes();
|
||||
if (volumes.length === 0) {
|
||||
// create volume if not already created
|
||||
volumes = await addModelVolume();
|
||||
}
|
||||
return volumes;
|
||||
}
|
||||
|
||||
export async function addModelVolume() {
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) throw new Error("No user id");
|
||||
|
||||
const insertedVolume = await db
|
||||
.insert(userVolume)
|
||||
.values({
|
||||
user_id: userId,
|
||||
org_id: orgId,
|
||||
volume_name: `models_${orgId ? orgId : userId}`, // if orgid is avalible use as part of the volume name
|
||||
disabled: false,
|
||||
})
|
||||
.returning();
|
||||
return insertedVolume;
|
||||
}
|
||||
|
||||
function getUrl(civitai_url: string) {
|
||||
// expect to be a URL to be https://civitai.com/models/36520
|
||||
// possiblity with slugged name and query-param modelVersionId
|
||||
const baseUrl = "https://civitai.com/api/v1/models/";
|
||||
const url = new URL(civitai_url);
|
||||
const pathSegments = url.pathname.split("/");
|
||||
const modelId = pathSegments[pathSegments.indexOf("models") + 1];
|
||||
const modelVersionId = url.searchParams.get("modelVersionId");
|
||||
|
||||
return { url: baseUrl + modelId, modelVersionId };
|
||||
}
|
||||
|
||||
// Helper function to make a HEAD request and follow redirects
|
||||
async function fetchFinalUrl(
|
||||
url: string,
|
||||
): Promise<{ finalUrl: string; dispositionFilename?: string }> {
|
||||
console.log("fetching");
|
||||
const response = await fetch(url, { method: "HEAD", redirect: "follow" });
|
||||
if (!response.ok) {
|
||||
console.log("response not ok");
|
||||
throw new Error(`Request failed with status ${response.status}`);
|
||||
}
|
||||
const contentDisposition = response.headers.get("content-disposition");
|
||||
let filename;
|
||||
if (contentDisposition) {
|
||||
const matches = contentDisposition.match(
|
||||
/filename\*?=['"]?(?:UTF-8'')?([^;'"\n]*)['"]?;?/i,
|
||||
);
|
||||
filename = matches && matches[1]
|
||||
? decodeURIComponent(matches[1])
|
||||
: undefined;
|
||||
}
|
||||
return { finalUrl: response.url, dispositionFilename: filename };
|
||||
}
|
||||
|
||||
// The main function for validation
|
||||
export const addModel = withServerPromise(
|
||||
async (data: z.infer<typeof downloadUrlModelSchema>) => {
|
||||
const { url } = data;
|
||||
|
||||
if (url.includes("civitai.com/models/")) {
|
||||
// Make a HEAD request to check for 200 OK
|
||||
const response = await fetch(url, { method: "HEAD" });
|
||||
if (!response.ok) {
|
||||
createModelErrorRecord(
|
||||
url,
|
||||
`civitai gave non-ok response`,
|
||||
"civitai",
|
||||
data.model_type,
|
||||
);
|
||||
}
|
||||
addCivitaiModel(data);
|
||||
} else {
|
||||
const { finalUrl, dispositionFilename } = await fetchFinalUrl(url);
|
||||
console.log("finished fetching");
|
||||
console.log(finalUrl, dispositionFilename);
|
||||
|
||||
if (!dispositionFilename) {
|
||||
console.log("no file name");
|
||||
createModelErrorRecord(
|
||||
url,
|
||||
`Could not find a filename from resolved Url: ${finalUrl}`,
|
||||
"download-url",
|
||||
data.model_type,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const validExtensions = [".ckpt", ".pt", ".bin", ".pth", ".safetensors"];
|
||||
const extension = dispositionFilename.slice(
|
||||
dispositionFilename.lastIndexOf("."),
|
||||
);
|
||||
if (!validExtensions.includes(extension)) {
|
||||
console.log("invalid extension");
|
||||
createModelErrorRecord(
|
||||
url,
|
||||
`file ext ${extension} is invalid. Valid extensions: ${validExtensions}`,
|
||||
"download-url",
|
||||
data.model_type,
|
||||
);
|
||||
}
|
||||
addModelDownloadUrl(data, dispositionFilename);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const addModelDownloadUrl = withServerPromise(
|
||||
async (data: z.infer<typeof downloadUrlModelSchema>, filename: string) => {
|
||||
console.log("adding model download");
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) return { error: "No user id" };
|
||||
const volumes = await retrieveModelVolumes();
|
||||
|
||||
const a = await db
|
||||
.insert(modelTable)
|
||||
.values({
|
||||
user_id: userId,
|
||||
org_id: orgId,
|
||||
upload_type: "download-url",
|
||||
model_name: filename,
|
||||
user_url: data.url,
|
||||
user_volume_id: volumes[0].id,
|
||||
model_type: data.model_type,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const b = a[0];
|
||||
console.log("download url about to upload");
|
||||
await uploadModel(data, b, volumes[0]);
|
||||
},
|
||||
);
|
||||
|
||||
export const getCivitaiModelRes = async (civitaiUrl: string) => {
|
||||
const { url, modelVersionId } = getUrl(civitaiUrl);
|
||||
const civitaiModelRes = await fetch(url)
|
||||
.then((x) => x.json())
|
||||
.then((a) => {
|
||||
return CivitaiModelResponse.parse(a);
|
||||
});
|
||||
return { civitaiModelRes, url, modelVersionId };
|
||||
};
|
||||
|
||||
const createModelErrorRecord = async (
|
||||
url: string,
|
||||
errorMessage: string,
|
||||
upload_type: "civitai" | "download-url",
|
||||
model_type: modelEnumType,
|
||||
) => {
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) return { error: "No user id" };
|
||||
const volumes = await retrieveModelVolumes();
|
||||
|
||||
const a = await db
|
||||
.insert(modelTable)
|
||||
.values({
|
||||
user_id: userId,
|
||||
org_id: orgId,
|
||||
user_volume_id: volumes[0].id,
|
||||
upload_type: "civitai",
|
||||
model_type,
|
||||
civitai_url: upload_type === "civitai" ? url : undefined,
|
||||
user_url: upload_type === "download-url" ? url : undefined,
|
||||
error_log: errorMessage,
|
||||
status: "failed",
|
||||
})
|
||||
.returning();
|
||||
return a;
|
||||
};
|
||||
|
||||
export const addCivitaiModel = withServerPromise(
|
||||
async (data: z.infer<typeof downloadUrlModelSchema>) => {
|
||||
const { userId, orgId } = auth();
|
||||
|
||||
if (!userId) return { error: "No user id" };
|
||||
|
||||
const { url, modelVersionId } = getUrl(data.url);
|
||||
const civitaiModelRes = await fetch(url)
|
||||
.then((x) => x.json())
|
||||
.then((a) => {
|
||||
return CivitaiModelResponse.parse(a);
|
||||
});
|
||||
|
||||
if (civitaiModelRes?.modelVersions?.length === 0) {
|
||||
return; // no versions to download
|
||||
}
|
||||
|
||||
let selectedModelVersion;
|
||||
let selectedModelVersionId: string | null = modelVersionId;
|
||||
if (!selectedModelVersionId) {
|
||||
selectedModelVersion = civitaiModelRes.modelVersions[0];
|
||||
selectedModelVersionId = civitaiModelRes.modelVersions[0].id.toString();
|
||||
} else {
|
||||
selectedModelVersion = civitaiModelRes.modelVersions.find((version) =>
|
||||
version.id.toString() === selectedModelVersionId
|
||||
);
|
||||
if (!selectedModelVersion) {
|
||||
return; // version id is wrong
|
||||
}
|
||||
selectedModelVersionId = selectedModelVersion?.id.toString();
|
||||
}
|
||||
|
||||
const volumes = await retrieveModelVolumes();
|
||||
|
||||
const model_type = getModelTypeDetails(civitaiModelRes.type);
|
||||
if (!model_type) {
|
||||
createModelErrorRecord(
|
||||
url,
|
||||
`Civitai model type ${civitaiModelRes.type} is not currently supported`,
|
||||
"civitai",
|
||||
data.model_type,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const a = await db
|
||||
.insert(modelTable)
|
||||
.values({
|
||||
user_id: userId,
|
||||
org_id: orgId,
|
||||
upload_type: "civitai",
|
||||
model_name: selectedModelVersion.files[0].name,
|
||||
civitai_id: civitaiModelRes.id.toString(),
|
||||
civitai_version_id: selectedModelVersionId,
|
||||
civitai_url: data.url, // TODO: need to confirm
|
||||
civitai_download_url: selectedModelVersion.files[0].downloadUrl,
|
||||
civitai_model_response: civitaiModelRes,
|
||||
user_volume_id: volumes[0].id,
|
||||
model_type,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const b = a[0];
|
||||
|
||||
await uploadModel(data, b, volumes[0]);
|
||||
},
|
||||
);
|
||||
|
||||
// export const redownloadCheckpoint = withServerPromise(
|
||||
// async (data: CheckpointItemList) => {
|
||||
// const { userId } = auth();
|
||||
// if (!userId) return { error: "No user id" };
|
||||
//
|
||||
// const checkpointVolumes = await getCheckpointVolumes();
|
||||
// let cVolume;
|
||||
// if (checkpointVolumes.length === 0) {
|
||||
// const volume = await addCheckpointVolume();
|
||||
// cVolume = volume[0];
|
||||
// } else {
|
||||
// cVolume = checkpointVolumes[0];
|
||||
// }
|
||||
//
|
||||
// console.log("data");
|
||||
// console.log(data);
|
||||
//
|
||||
// const a = await db
|
||||
// .update(checkpointTable)
|
||||
// .set({
|
||||
// // status: "started",
|
||||
// // updated_at: new Date(),
|
||||
// })
|
||||
// .returning();
|
||||
//
|
||||
// const b = a[0];
|
||||
//
|
||||
// console.log("b");
|
||||
// console.log(b);
|
||||
//
|
||||
// await uploadCheckpoint(data, b, cVolume);
|
||||
// // redirect(`/checkpoints/${b.id}`);
|
||||
// },
|
||||
// );
|
||||
|
||||
async function uploadModel(
|
||||
data: z.infer<typeof downloadUrlModelSchema>,
|
||||
c: ModelType,
|
||||
v: UserVolumeType,
|
||||
) {
|
||||
const headersList = headers();
|
||||
|
||||
const domain = headersList.get("x-forwarded-host") || "";
|
||||
const protocol = headersList.get("x-forwarded-proto") || "";
|
||||
|
||||
if (domain === "") {
|
||||
throw new Error("No domain");
|
||||
}
|
||||
|
||||
// Call remote builder
|
||||
const result = await fetch(
|
||||
`${process.env.MODAL_BUILDER_URL!}/upload-volume`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
download_url: c.upload_type === "civitai"
|
||||
? c.civitai_download_url
|
||||
: c.user_url,
|
||||
volume_name: v.volume_name,
|
||||
volume_id: v.id,
|
||||
model_id: c.id,
|
||||
callback_url: `${protocol}://${domain}/api/volume-upload`,
|
||||
upload_type: c.model_type,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (!result.ok) {
|
||||
const error_log = await result.text();
|
||||
await db
|
||||
.update(modelTable)
|
||||
.set({
|
||||
status: "failed",
|
||||
error_log: error_log,
|
||||
})
|
||||
.where(eq(modelTable.id, c.id));
|
||||
throw new Error(`Error: ${result.statusText} ${error_log}`);
|
||||
} else {
|
||||
// setting the build machine id
|
||||
const json = await result.json();
|
||||
await db
|
||||
.update(modelTable)
|
||||
.set({
|
||||
...data,
|
||||
upload_machine_id: json.build_machine_instance_id,
|
||||
})
|
||||
.where(eq(modelTable.id, c.id));
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,13 @@ import jwt from "jsonwebtoken";
|
||||
import { getOrgOrUserDisplayName } from "@/server/getOrgOrUserDisplayName";
|
||||
import { withServerPromise } from "@/server/withServerPromise";
|
||||
import "server-only";
|
||||
import { headers } from "next/headers";
|
||||
import { getUrlServerSide } from "./getUrlServerSide";
|
||||
|
||||
export const editWorkflowOnMachine = withServerPromise(
|
||||
async (workflow_version_id: string, machine_id: string) => {
|
||||
async (machine_id: string, workflow_version_id?: string) => {
|
||||
const { userId, orgId } = auth();
|
||||
|
||||
const headersList = headers();
|
||||
const host = headersList.get("host") || "";
|
||||
const protocol = headersList.get("x-forwarded-proto") || "";
|
||||
const domain = `${protocol}://${host}`;
|
||||
const domain = getUrlServerSide();
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("No user id");
|
||||
@@ -40,10 +37,18 @@ export const editWorkflowOnMachine = withServerPromise(
|
||||
endpoint = machine.endpoint.replace("comfyui-api", "comfyui-app");
|
||||
}
|
||||
|
||||
return `${endpoint}?workflow_version_id=${encodeURIComponent(
|
||||
workflow_version_id,
|
||||
)}&auth_token=${encodeURIComponent(token)}&org_display=${encodeURIComponent(
|
||||
userName,
|
||||
)}&origin=${encodeURIComponent(domain)}`;
|
||||
const params = {
|
||||
workflow_version_id: workflow_version_id,
|
||||
auth_token: token,
|
||||
org_display: userName,
|
||||
origin: domain,
|
||||
};
|
||||
|
||||
const queryString = Object.entries(params)
|
||||
.filter(([key, value]) => value !== undefined)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`)
|
||||
.join('&');
|
||||
|
||||
return `${endpoint}?${queryString}`;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,42 +16,44 @@ export async function findAllRuns({
|
||||
offset = 0,
|
||||
}: RunsSearchTypes) {
|
||||
return await db.query.workflowRunsTable.findMany({
|
||||
where: eq(workflowRunsTable.workflow_id, workflow_id),
|
||||
orderBy: desc(workflowRunsTable.created_at),
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
extras: {
|
||||
number: sql<number>`row_number() over (order by created_at)`.as(
|
||||
"number",
|
||||
),
|
||||
total: sql<number>`count(*) over ()`.as("total"),
|
||||
duration:
|
||||
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
|
||||
"duration",
|
||||
),
|
||||
cold_start_duration:
|
||||
sql<number>`(extract(epoch from started_at) - extract(epoch from created_at))`.as(
|
||||
"cold_start_duration",
|
||||
),
|
||||
run_duration:
|
||||
sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
|
||||
"run_duration",
|
||||
),
|
||||
},
|
||||
with: {
|
||||
machine: {
|
||||
columns: {
|
||||
name: true,
|
||||
endpoint: true,
|
||||
},
|
||||
},
|
||||
version: {
|
||||
columns: {
|
||||
version: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
where: eq(workflowRunsTable.workflow_id, workflow_id),
|
||||
orderBy: desc(workflowRunsTable.created_at),
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
extras: {
|
||||
number: sql<number>`row_number() over (order by created_at)`.as("number"),
|
||||
total: sql<number>`count(*) over ()`.as("total"),
|
||||
duration:
|
||||
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
|
||||
"duration",
|
||||
),
|
||||
comfy_deploy_cold_start:
|
||||
sql<number>`(extract(epoch from queued_at) - extract(epoch from created_at))`.as(
|
||||
"cold_start_duration",
|
||||
),
|
||||
cold_start_duration:
|
||||
sql<number>`(extract(epoch from started_at) - extract(epoch from queued_at))`.as(
|
||||
"cold_start_duration",
|
||||
),
|
||||
run_duration:
|
||||
sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as(
|
||||
"run_duration",
|
||||
),
|
||||
},
|
||||
with: {
|
||||
machine: {
|
||||
columns: {
|
||||
name: true,
|
||||
endpoint: true,
|
||||
},
|
||||
},
|
||||
version: {
|
||||
columns: {
|
||||
version: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function findAllRunsWithCounts(props: RunsSearchTypes) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { db } from "@/db/db";
|
||||
import {
|
||||
modelTable,
|
||||
} from "@/db/schema";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { and, desc, eq, isNull } from "drizzle-orm";
|
||||
|
||||
export async function getAllUserModels() {
|
||||
const { userId, orgId } = await auth();
|
||||
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const models = await db.query.modelTable.findMany({
|
||||
with: {
|
||||
user: {
|
||||
columns: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: true,
|
||||
updated_at: true,
|
||||
model_name: true,
|
||||
civitai_url: true,
|
||||
civitai_model_response: true,
|
||||
is_public: true,
|
||||
upload_type: true,
|
||||
model_type: true,
|
||||
status: true,
|
||||
},
|
||||
orderBy: desc(modelTable.updated_at),
|
||||
where:
|
||||
orgId != undefined
|
||||
? eq(modelTable.org_id, orgId)
|
||||
: and(eq(modelTable.user_id, userId), isNull(modelTable.org_id)),
|
||||
});
|
||||
|
||||
return models;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user