Compare commits

..
Author SHA1 Message Date
Nicholas Koben Kao ed659ebdb8 schema 2024-01-22 19:05:24 -08:00
Nicholas Koben Kao f6a1b88dda rest 2024-01-22 19:05:18 -08:00
Nicholas Koben Kao fed7b380b6 work 2024-01-22 17:23:35 -08:00
Nicholas Koben Kao 90cec6b778 schema 2024-01-20 22:01:11 -08:00
Nicholas Koben Kao 7b61fea849 volume work 2024-01-20 19:54:35 -08:00
Nicholas Koben Kao 60471a8e01 model in voluems 2024-01-20 12:53:48 -08:00
75 changed files with 3010 additions and 3506 deletions
-7
View File
@@ -1,7 +0,0 @@
ARG VARIANT=18-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:${VARIANT}
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
RUN npm install -g bun
-17
View File
@@ -1,17 +0,0 @@
{
"name": "Comfy Deploy Dev",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"postCreateCommand": "cd web && bun install && bun run migrate-local",
"customizations": {
"vscode": {
"extensions": [
"biomejs.biome",
"formulahendry.auto-rename-tag",
"bradlc.vscode-tailwindcss",
"stivo.tailwind-fold"
]
}
}
}
-48
View File
@@ -1,48 +0,0 @@
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
environment:
VSCODE_DEV_CONTAINER: true
volumes:
# Forwards the local Docker socket to the container.
- /var/run/docker.sock:/var/run/docker-host.sock
# Update this to wherever you want VS Code to mount the folder of your project
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
# entrypoint: /usr/local/share/docker-init.sh
command: sleep infinity
postgres:
image: "postgres:15.2-alpine"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: verceldb
ports:
- "5480:5432"
pg_proxy:
image: ghcr.io/neondatabase/wsproxy:latest
environment:
APPEND_PORT: "postgres:5432"
ALLOW_ADDR_REGEX: ".*"
LOG_TRAFFIC: "true"
ports:
- "5481:80"
depends_on:
- postgres
localstack:
image: localstack/localstack:latest
environment:
SERVICES: s3
ports:
- 4566:4566
volumes:
- ../web/aws:/etc/localstack/init/ready.d
- ../web/aws:/app/web/aws
+3 -2
View File
@@ -1,13 +1,14 @@
{ {
"recommendations": [ "recommendations": [
"DavidAnson.vscode-markdownlint", // markdown linting "DavidAnson.vscode-markdownlint", // markdown linting
"yzhang.markdown-all-in-one", // nicer markdown support
"esbenp.prettier-vscode", // prettier plugin "esbenp.prettier-vscode", // prettier plugin
"dbaeumer.vscode-eslint", // eslint plugin "dbaeumer.vscode-eslint", // eslint plugin
"bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind "bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind
"ban.spellright", // Spell check for docs "ban.spellright", // Spell check for docs
"stripe.vscode-stripe", // stripe VSCode extension "stripe.vscode-stripe", // stripe VSCode extension
"Prisma.prisma", // syntax|format|completion for prisma
"rebornix.project-snippets", // Share useful snippets between collaborators "rebornix.project-snippets", // Share useful snippets between collaborators
"inlang.vs-code-extension", "inlang.vs-code-extension" // improved i18n DX
"biomejs.biome" // improved i18n DX
] ]
} }
+2 -3
View File
@@ -1,9 +1,8 @@
{ {
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true, "editor.formatOnSave": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"quickfix.biome": "explicit", "source.fixAll.eslint": true
"source.organizeImports.biome": "explicit"
}, },
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"spellright.language": ["en"], "spellright.language": ["en"],
+51 -1
View File
@@ -8,6 +8,7 @@ from enum import Enum
import json import json
import subprocess import subprocess
import time import time
from uuid import uuid4
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import asyncio import asyncio
import threading import threading
@@ -19,6 +20,7 @@ from urllib.parse import parse_qs
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp, Scope, Receive, Send from starlette.types import ASGIApp, Scope, Receive, Send
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
# executor = ThreadPoolExecutor(max_workers=5) # executor = ThreadPoolExecutor(max_workers=5)
@@ -224,6 +226,52 @@ async def websocket_endpoint(websocket: WebSocket, machine_id: str):
# return {"Hello": "World"} # return {"Hello": "World"}
class UploadBody(BaseModel):
download_url: str
volume_name: str
volume_id: str
# callback_url: str
@app.post("/upload_volume")
async def upload_checkpoint(body: UploadBody):
global last_activity_time
last_activity_time = time.time()
logger.info(f"Extended inactivity time to {global_timeout}")
download_url = body.download_url
volume_name = body.volume_name
# callback_url = body.callback_url
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()
# Write the config file
config = {
"volume_names": {
volume_name: download_url
},
"paths": {
volume_name: f'/volumes/{uuid4()}'
},
}
await asyncio.subprocess.create_subprocess_shell(
f"modal run app.py",
# stdout=asyncio.subprocess.PIPE,
# stderr=asyncio.subprocess.PIPE,
cwd=folder_path,
env={**os.environ, "COLUMNS": "10000"}
)
with open(f"{folder_path}/config.py", "w") as f:
f.write("config = " + json.dumps(config))
# check that thi
return JSONResponse(status_code=200, content={"message": "Volume uploading", "build_machine_instance_id": fly_instance_id})
@app.post("/create") @app.post("/create")
async def create_machine(item: Item): async def create_machine(item: Item):
global last_activity_time global last_activity_time
@@ -312,7 +360,9 @@ async def build_logic(item: Item):
config = { config = {
"name": item.name, "name": item.name,
"deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False"), "deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False"),
"gpu": item.gpu "gpu": item.gpu,
"public_checkpoint_volume": "model-store",
"private_checkpoint_volume": "private-model-store"
} }
with open(f"{folder_path}/config.py", "w") as f: with open(f"{folder_path}/config.py", "w") as f:
f.write("config = " + json.dumps(config)) f.write("config = " + json.dumps(config))
+12 -7
View File
@@ -1,12 +1,13 @@
from config import config from config import config
import modal import modal
from modal import Image, Mount, web_endpoint, Stub, asgi_app from modal import Image, Mount, web_endpoint, Stub, asgi_app, Volume
import json import json
import urllib.request import urllib.request
import urllib.parse import urllib.parse
from pydantic import BaseModel from pydantic import BaseModel
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from volume import volumes
# deploy_test = False # deploy_test = False
@@ -28,7 +29,6 @@ web_app = FastAPI()
print(config) print(config)
print("deploy_test ", deploy_test) print("deploy_test ", deploy_test)
stub = Stub(name=config["name"]) stub = Stub(name=config["name"])
# print(stub.app_id)
if not deploy_test: if not deploy_test:
# dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data")) # dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data"))
@@ -56,7 +56,7 @@ if not deploy_test:
# # Install comfy deploy # # Install comfy deploy
# "cd /comfyui/custom_nodes && git clone https://github.com/BennyKok/comfyui-deploy.git", # "cd /comfyui/custom_nodes && git clone https://github.com/BennyKok/comfyui-deploy.git",
# ) # )
# .copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui") .copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui")
.copy_local_file(f"{current_directory}/data/start.sh", "/start.sh") .copy_local_file(f"{current_directory}/data/start.sh", "/start.sh")
.run_commands("chmod +x /start.sh") .run_commands("chmod +x /start.sh")
@@ -153,8 +153,9 @@ image = Image.debian_slim()
target_image = image if deploy_test else dockerfile_image target_image = image if deploy_test else dockerfile_image
@stub.function(image=target_image, gpu=config["gpu"]
@stub.function(image=target_image, gpu=config["gpu"]) ,volumes=volumes
)
def run(input: Input): def run(input: Input):
import subprocess import subprocess
import time import time
@@ -163,6 +164,7 @@ def run(input: Input):
command = ["python", "main.py", command = ["python", "main.py",
"--disable-auto-launch", "--disable-metadata"] "--disable-auto-launch", "--disable-metadata"]
server_process = subprocess.Popen(command, cwd="/comfyui") server_process = subprocess.Popen(command, cwd="/comfyui")
check_server( check_server(
@@ -235,7 +237,9 @@ async def bar(request_input: RequestInput):
# pass # pass
@stub.function(image=image) @stub.function(image=image
,volumes=volumes
)
@asgi_app() @asgi_app()
def comfyui_api(): def comfyui_api():
return web_app return web_app
@@ -285,6 +289,7 @@ def spawn_comfyui_in_background():
# to be on a single container. # to be on a single container.
concurrency_limit=1, concurrency_limit=1,
timeout=10 * 60, timeout=10 * 60,
volumes=volumes,
) )
@asgi_app() @asgi_app()
def comfyui_app(): def comfyui_app():
@@ -303,4 +308,4 @@ def comfyui_app():
}, },
)() )()
return make_simple_proxy_app(ProxyContext(config)) return make_simple_proxy_app(ProxyContext(config))
+7 -1
View File
@@ -1 +1,7 @@
config = {"name": "my-app", "deploy_test": "True", "gpu": "T4"} config = {
"name": "my-app",
"deploy_test": "True",
"gpu": "T4",
"public_checkpoint_volume": "model-store",
"private_checkpoint_volume": "private-model-store"
}
@@ -1,11 +1,30 @@
comfyui: comfyui:
base_path: /runpod-volume/ComfyUI/ base_path: /extra_models/
checkpoints: models/checkpoints/ checkpoints: |
clip: models/clip/ checkpoints
clip_vision: models/clip_vision/ private_checkpoints
configs: models/configs/ clip: |
controlnet: models/controlnet/ clip
embeddings: models/embeddings/ private_clip
loras: models/loras/ clip_vision: |
upscale_models: models/upscale_models/ clip_vision
vae: models/vae/ private_clip_vision
configs: |
configs
private_configs
controlnet: |
controlnet
private_controlnet
embeddings: |
embeddings
private_embeddings
loras: |
loras
private_loras
upscale_models: |
upscale_models
private_upscale_models
vae: |
vae
private_vae
@@ -0,0 +1,101 @@
"""
This is a standalone script to download models into a modal Volume using civitai
Example Usage
`modal run insert_models::insert_model --civitai-url https://civitai.com/models/36520/ghostmix`
This inserts an individual model from a civitai url
`modal run insert_models::insert_models_civitai_api`
This inserts a bunch of models based on the models retrieved by civitai
civitai's API reference https://github.com/civitai/civitai/wiki/REST-API-Reference
"""
import modal
import subprocess
import requests
import json
stub = modal.Stub()
# NOTE: volume name can be variable
volume = modal.Volume.persisted("rah")
model_store_path = "/vol/models"
MODEL_ROUTE = "models"
image = (
modal.Image.debian_slim().apt_install("wget").pip_install("requests")
)
@stub.function(volumes={model_store_path: volume}, image=image, timeout=50000, gpu=None)
def download_model(download_url):
print(download_url)
subprocess.run(["wget", download_url, "--content-disposition", "-P", model_store_path])
subprocess.run(["ls", "-la", model_store_path])
volume.commit()
# file is raw output from Civitai API https://github.com/civitai/civitai/wiki/REST-API-Reference
@stub.function()
def get_civitai_models(model_type: str, sort: str = "Highest Rated", page: int = 1):
"""Fetch models from CivitAI API based on type."""
try:
response = requests.get(f"https://civitai.com/api/v1/models", params={"types": model_type, "page": page, "sort": sort})
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"Error fetching models: {e}")
return None
@stub.function()
def get_civitai_model_url(civitai_url: str):
# Validate the URL
if civitai_url.startswith("https://civitai.com/api/"):
api_url = civitai_url
elif civitai_url.startswith("https://civitai.com/models/"):
try:
model_id = civitai_url.split("/")[4]
int(model_id)
except (IndexError, ValueError):
return None
api_url = f"https://civitai.com/api/v1/models/{model_id}"
else:
return "Error: URL must be from civitai.com and contain /models/"
response = requests.get(api_url)
# Check for successful response
if response.status_code != 200:
return f"Error: Unable to fetch data from {api_url}"
# Return the response data
return response.json()
@stub.local_entrypoint()
def insert_models_civitai_api(type: str = "Checkpoint", sort = "Highest Rated", page: int = 1):
civitai_models = get_civitai_models.local(type, sort, page)
if civitai_models:
for _ in download_model.map(map(lambda model: model['modelVersions'][0]['downloadUrl'], civitai_models['items'])):
pass
else:
print("Failed to retrieve models.")
@stub.local_entrypoint()
def insert_model(civitai_url: str):
if civitai_url.startswith("'https://civitai.com/api/download/models/"):
download_url = civitai_url
else:
civitai_model = get_civitai_model_url.local(civitai_url)
if civitai_model:
download_url = civitai_model['modelVersions'][0]['downloadUrl']
else:
return "invalid URL"
download_model.remote(download_url)
@stub.local_entrypoint()
def simple_download():
download_urls = ['https://civitai.com/api/download/models/119057', 'https://civitai.com/api/download/models/130090', 'https://civitai.com/api/download/models/31859', 'https://civitai.com/api/download/models/128713', 'https://civitai.com/api/download/models/179657', 'https://civitai.com/api/download/models/143906', 'https://civitai.com/api/download/models/9208', 'https://civitai.com/api/download/models/136078', 'https://civitai.com/api/download/models/134065', 'https://civitai.com/api/download/models/288775', 'https://civitai.com/api/download/models/95263', 'https://civitai.com/api/download/models/288982', 'https://civitai.com/api/download/models/87153', 'https://civitai.com/api/download/models/10638', 'https://civitai.com/api/download/models/263809', 'https://civitai.com/api/download/models/130072', 'https://civitai.com/api/download/models/117019', 'https://civitai.com/api/download/models/95256', 'https://civitai.com/api/download/models/197181', 'https://civitai.com/api/download/models/256915', 'https://civitai.com/api/download/models/118945', 'https://civitai.com/api/download/models/125843', 'https://civitai.com/api/download/models/179015', 'https://civitai.com/api/download/models/245598', 'https://civitai.com/api/download/models/223670', 'https://civitai.com/api/download/models/90072', 'https://civitai.com/api/download/models/290817', 'https://civitai.com/api/download/models/154097', 'https://civitai.com/api/download/models/143497', 'https://civitai.com/api/download/models/5637']
for _ in download_model.map(download_urls):
pass
@@ -45,13 +45,13 @@ for package in packages:
response = requests.request("POST", f"{root_url}/customnode/install", json=package, headers=headers) response = requests.request("POST", f"{root_url}/customnode/install", json=package, headers=headers)
print(response.text) print(response.text)
with open('models.json') as f: # with open('models.json') as f:
models = json.load(f) # models = json.load(f)
#
for model in models: # for model in models:
response = requests.request("POST", f"{root_url}/model/install", json=model, headers=headers) # response = requests.request("POST", f"{root_url}/model/install", json=model, headers=headers)
print(response.text) # print(response.text)
# Close the server # Close the server
server_process.terminate() server_process.terminate()
print("Finished installing dependencies.") print("Finished installing dependencies.")
@@ -0,0 +1,10 @@
import modal
from config import config
public_model_volume = modal.Volume.persisted(config["public_checkpoint_volume"])
private_volume = modal.Volume.persisted(config["private_checkpoint_volume"])
BASEMODEL_DIR = "/extra_models/"
MODEL_DIR = BASEMODEL_DIR + "checkpoints"
PRIVATE_MODEL_DIR = BASEMODEL_DIR + "private_checkpoints"
volumes = {MODEL_DIR: public_model_volume, PRIVATE_MODEL_DIR: private_volume}
@@ -0,0 +1,45 @@
import modal
from config import config
import os
import subprocess
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["paths"]
volumes = create_volumes(vol_name_to_links, vol_name_to_path)
image = (
modal.Image.debian_slim().apt_install("wget").pip_install("requests")
)
print(vol_name_to_links)
print(vol_name_to_path)
print(volumes)
@stub.function(volumes=volumes, image=image, timeout=5000, gpu=None)
def download_model(volume_name, download_url):
model_store_path = vol_name_to_path[volume_name]
subprocess.run(["wget", download_url, "--content-disposition", "-P", model_store_path])
subprocess.run(["ls", "-la", model_store_path])
volumes[model_store_path].commit()
@stub.local_entrypoint()
def simple_download():
print(vol_name_to_links)
print([(vol_name, link) for vol_name,link in vol_name_to_links.items()])
list(download_model.starmap([(vol_name, link) for vol_name,link in vol_name_to_links.items()]))
@@ -0,0 +1,8 @@
config = {
"volume_names": {
"test": "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose (1).png"
},
"paths": {
"test": "/volumes/something"
}
}
-56
View File
@@ -1,56 +0,0 @@
import folder_paths
from PIL import Image, ImageOps
import numpy as np
import torch
import folder_paths
class ComfyUIDeployExternalCheckpoints:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_checkpoints"},
),
},
"optional": {
"default_checkpoints_name": (folder_paths.get_filename_list("checkpoints"), ),
}
}
RETURN_TYPES = (folder_paths.get_filename_list("checkpoints"),)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "deploy"
def run(self, input_id, default_checkpoints_name=None):
import requests
import os
import uuid
if input_id and input_id.startswith('http'):
unique_filename = str(uuid.uuid4()) + ".safetensors"
print(unique_filename)
print(folder_paths.folder_names_and_paths["checkpoints"][0][0])
destination_path = os.path.join(
folder_paths.folder_names_and_paths["checkpoints"][0][0], unique_filename)
print(destination_path)
print("Downloading external checkpoints - " +
input_id + " to " + destination_path)
response = requests.get(
input_id, headers={'User-Agent': 'Mozilla/5.0'}, allow_redirects=True)
with open(destination_path, 'wb') as out_file:
out_file.write(response.content)
return (unique_filename,)
else:
return (default_checkpoints_name,)
NODE_CLASS_MAPPINGS = {
"ComfyUIDeployExternalCheckpoints": ComfyUIDeployExternalCheckpoints}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalCheckpoints": "External Checkpoints (ComfyUI Deploy)"}
+6
View File
@@ -0,0 +1,6 @@
node_modules
**/node_modules
**/.next
**/public
packages/prisma/zod
apps/web/public/embed
+95
View File
@@ -0,0 +1,95 @@
/** @type {import("eslint").Linter.Config} */
module.exports = {
root: true,
extends: [
// "plugin:playwright/playwright-test",
"next",
// "next/core-web-vitals",
"plugin:prettier/recommended",
// "turbo",
// "plugin:you-dont-need-lodash-underscore/compatible-warn",
],
plugins: ["unused-imports"],
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
// project: ["./apps/*/tsconfig.json", "./packages/*/tsconfig.json"],
},
settings: {
next: {
// rootDir: ["apps/*/", "packages/*/"],
rootDir: ["src"],
},
},
rules: {
"@next/next/no-img-element": "off",
"@next/next/no-html-link-for-pages": "off",
"jsx-a11y/role-supports-aria-props": "off", // @see https://github.com/vercel/next.js/issues/27989#issuecomment-897638654
// "playwright/no-page-pause": "error",
"react/jsx-curly-brace-presence": [
"error",
{ props: "never", children: "never" },
],
"react/self-closing-comp": ["error", { component: true, html: true }],
"@typescript-eslint/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
},
],
"unused-imports/no-unused-imports": "error",
"no-restricted-imports": [
"error",
{
patterns: ["lodash"],
},
],
"prefer-template": "error",
},
overrides: [
{
files: ["*.ts", "*.tsx"],
extends: [
"plugin:@typescript-eslint/recommended",
// "plugin:@calcom/eslint/recommended",
],
plugins: [
"@typescript-eslint",
// "@calcom/eslint"
],
parser: "@typescript-eslint/parser",
rules: {
"@typescript-eslint/consistent-type-imports": [
"error",
{
prefer: "type-imports",
// TODO: enable this once prettier supports it
// fixStyle: "inline-type-imports",
fixStyle: "separate-type-imports",
disallowTypeAnnotations: false,
},
],
},
// overrides: [
// {
// files: ["**/playwright/**/*.{tsx,ts}"],
// rules: {
// "@typescript-eslint/no-unused-vars": "off",
// "no-undef": "off",
// },
// },
// ],
},
// {
// files: ["**/playwright/**/*.{js,jsx}"],
// rules: {
// "@typescript-eslint/no-unused-vars": "off",
// "no-undef": "off",
// },
// },
],
};
-23
View File
@@ -1,23 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/1.5.2/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"json": {
"parser": {
"allowComments": true
}
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
}
}
BIN
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
ALTER TABLE "comfyui_deploy"."workflow_runs" ADD COLUMN "started_at" timestamp;
+62
View File
@@ -0,0 +1,62 @@
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', 'failed', 'succeded');
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,
"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,
"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_workflow_runs_id_fk" FOREIGN KEY ("checkpoint_volume_id") REFERENCES "comfyui_deploy"."workflow_runs"("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 $$;
+2
View File
@@ -0,0 +1,2 @@
ALTER TYPE "resource_upload" ADD VALUE 'error';--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."checkpoints" ADD COLUMN "build_log" text;
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "share_slug" text;--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."deployments" ADD CONSTRAINT "deployments_share_slug_unique" UNIQUE("share_slug");
-13
View File
@@ -1,13 +0,0 @@
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."user_usage" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text NOT NULL,
"usage_time" real DEFAULT 0 NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."user_usage" ADD CONSTRAINT "user_usage_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE "comfyui_deploy"."user_usage" RENAME COLUMN "updated_at" TO "ended_at";--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."user_usage" ADD COLUMN "org_id" text;
+249 -7
View File
@@ -1,5 +1,5 @@
{ {
"id": "1ca4fdb7-c0c4-4c39-8b47-f40282293da0", "id": "4d5b29d0-848f-4c2e-a2cd-2932f1fa38c6",
"prevId": "db06ea66-92c2-4ebe-93c1-6cb8a90ccd8b", "prevId": "db06ea66-92c2-4ebe-93c1-6cb8a90ccd8b",
"version": "5", "version": "5",
"dialect": "pg", "dialect": "pg",
@@ -88,6 +88,238 @@
} }
} }
}, },
"checkpoints": {
"name": "checkpoints",
"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": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"checkpoint_volume_id": {
"name": "checkpoint_volume_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"model_name": {
"name": "model_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_id": {
"name": "civitai_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_version_id": {
"name": "civitai_version_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_url": {
"name": "civitai_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_download_url": {
"name": "civitai_download_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_model_response": {
"name": "civitai_model_response",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"hf_url": {
"name": "hf_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"s3_url": {
"name": "s3_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_url": {
"name": "client_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_public": {
"name": "is_public",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"status": {
"name": "status",
"type": "resource_upload",
"primaryKey": false,
"notNull": true,
"default": "'started'"
},
"upload_machine_id": {
"name": "upload_machine_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"upload_type": {
"name": "upload_type",
"type": "model_upload_type",
"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": {
"checkpoints_user_id_users_id_fk": {
"name": "checkpoints_user_id_users_id_fk",
"tableFrom": "checkpoints",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"checkpoints_checkpoint_volume_id_workflow_runs_id_fk": {
"name": "checkpoints_checkpoint_volume_id_workflow_runs_id_fk",
"tableFrom": "checkpoints",
"tableTo": "workflow_runs",
"columnsFrom": [
"checkpoint_volume_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"checkpoint_volume": {
"name": "checkpoint_volume",
"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": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"volume_name": {
"name": "volume_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()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"indexes": {},
"foreignKeys": {
"checkpoint_volume_user_id_users_id_fk": {
"name": "checkpoint_volume_user_id_users_id_fk",
"tableFrom": "checkpoint_volume",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"deployments": { "deployments": {
"name": "deployments", "name": "deployments",
"schema": "comfyui_deploy", "schema": "comfyui_deploy",
@@ -506,12 +738,6 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"default": "now()" "default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
} }
}, },
"indexes": {}, "indexes": {},
@@ -732,6 +958,22 @@
"comfy-deploy-serverless": "comfy-deploy-serverless" "comfy-deploy-serverless": "comfy-deploy-serverless"
} }
}, },
"model_upload_type": {
"name": "model_upload_type",
"values": {
"civitai": "civitai",
"huggingface": "huggingface",
"other": "other"
}
},
"resource_upload": {
"name": "resource_upload",
"values": {
"started": "started",
"failed": "failed",
"succeded": "succeded"
}
},
"workflow_run_origin": { "workflow_run_origin": {
"name": "workflow_run_origin", "name": "workflow_run_origin",
"values": { "values": {
+257 -23
View File
@@ -1,6 +1,6 @@
{ {
"id": "1425ee00-66fb-4541-8da7-19b217944545", "id": "fed3e81d-c0d7-4deb-a63a-370039fb5edc",
"prevId": "1ca4fdb7-c0c4-4c39-8b47-f40282293da0", "prevId": "4d5b29d0-848f-4c2e-a2cd-2932f1fa38c6",
"version": "5", "version": "5",
"dialect": "pg", "dialect": "pg",
"tables": { "tables": {
@@ -88,6 +88,244 @@
} }
} }
}, },
"checkpoints": {
"name": "checkpoints",
"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": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"checkpoint_volume_id": {
"name": "checkpoint_volume_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"model_name": {
"name": "model_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_id": {
"name": "civitai_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_version_id": {
"name": "civitai_version_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_url": {
"name": "civitai_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_download_url": {
"name": "civitai_download_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"civitai_model_response": {
"name": "civitai_model_response",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"hf_url": {
"name": "hf_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"s3_url": {
"name": "s3_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_url": {
"name": "client_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_public": {
"name": "is_public",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"status": {
"name": "status",
"type": "resource_upload",
"primaryKey": false,
"notNull": true,
"default": "'started'"
},
"upload_machine_id": {
"name": "upload_machine_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"upload_type": {
"name": "upload_type",
"type": "model_upload_type",
"primaryKey": false,
"notNull": true
},
"build_log": {
"name": "build_log",
"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": {
"checkpoints_user_id_users_id_fk": {
"name": "checkpoints_user_id_users_id_fk",
"tableFrom": "checkpoints",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"checkpoints_checkpoint_volume_id_workflow_runs_id_fk": {
"name": "checkpoints_checkpoint_volume_id_workflow_runs_id_fk",
"tableFrom": "checkpoints",
"tableTo": "workflow_runs",
"columnsFrom": [
"checkpoint_volume_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"checkpoint_volume": {
"name": "checkpoint_volume",
"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": false
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"volume_name": {
"name": "volume_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()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
}
},
"indexes": {},
"foreignKeys": {
"checkpoint_volume_user_id_users_id_fk": {
"name": "checkpoint_volume_user_id_users_id_fk",
"tableFrom": "checkpoint_volume",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"deployments": { "deployments": {
"name": "deployments", "name": "deployments",
"schema": "comfyui_deploy", "schema": "comfyui_deploy",
@@ -129,12 +367,6 @@
"primaryKey": false, "primaryKey": false,
"notNull": true "notNull": true
}, },
"share_slug": {
"name": "share_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": { "description": {
"name": "description", "name": "description",
"type": "text", "type": "text",
@@ -224,15 +456,7 @@
} }
}, },
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": { "uniqueConstraints": {}
"deployments_share_slug_unique": {
"name": "deployments_share_slug_unique",
"nullsNotDistinct": false,
"columns": [
"share_slug"
]
}
}
}, },
"machines": { "machines": {
"name": "machines", "name": "machines",
@@ -520,12 +744,6 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"default": "now()" "default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
} }
}, },
"indexes": {}, "indexes": {},
@@ -746,6 +964,22 @@
"comfy-deploy-serverless": "comfy-deploy-serverless" "comfy-deploy-serverless": "comfy-deploy-serverless"
} }
}, },
"model_upload_type": {
"name": "model_upload_type",
"values": {
"civitai": "civitai",
"huggingface": "huggingface",
"other": "other"
}
},
"resource_upload": {
"name": "resource_upload",
"values": {
"started": "started",
"error": "error",
"succeded": "succeded"
}
},
"workflow_run_origin": { "workflow_run_origin": {
"name": "workflow_run_origin", "name": "workflow_run_origin",
"values": { "values": {
-834
View File
@@ -1,834 +0,0 @@
{
"id": "91bb0461-452a-4e59-abf4-8757fcd75a89",
"prevId": "1425ee00-66fb-4541-8da7-19b217944545",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"share_slug": {
"name": "share_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"deployments_share_slug_unique": {
"name": "deployments_share_slug_unique",
"nullsNotDistinct": false,
"columns": [
"share_slug"
]
}
}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user_usage": {
"name": "user_usage",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"usage_time": {
"name": "usage_time",
"type": "real",
"primaryKey": false,
"notNull": true,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"user_usage_user_id_users_id_fk": {
"name": "user_usage_user_id_users_id_fk",
"tableFrom": "user_usage",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
-842
View File
@@ -1,842 +0,0 @@
{
"id": "fad17dc9-86c5-4081-8e73-47c113f48936",
"prevId": "91bb0461-452a-4e59-abf4-8757fcd75a89",
"version": "5",
"dialect": "pg",
"tables": {
"api_keys": {
"name": "api_keys",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"revoked": {
"name": "revoked",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"api_keys_user_id_users_id_fk": {
"name": "api_keys_user_id_users_id_fk",
"tableFrom": "api_keys",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"api_keys_key_unique": {
"name": "api_keys_key_unique",
"nullsNotDistinct": false,
"columns": [
"key"
]
}
}
},
"deployments": {
"name": "deployments",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"share_slug": {
"name": "share_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"showcase_media": {
"name": "showcase_media",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"environment": {
"name": "environment",
"type": "deployment_environment",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"deployments_user_id_users_id_fk": {
"name": "deployments_user_id_users_id_fk",
"tableFrom": "deployments",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_workflow_version_id_workflow_versions_id_fk": {
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "deployments",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"deployments_workflow_id_workflows_id_fk": {
"name": "deployments_workflow_id_workflows_id_fk",
"tableFrom": "deployments",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"deployments_machine_id_machines_id_fk": {
"name": "deployments_machine_id_machines_id_fk",
"tableFrom": "deployments",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"deployments_share_slug_unique": {
"name": "deployments_share_slug_unique",
"nullsNotDistinct": false,
"columns": [
"share_slug"
]
}
}
},
"machines": {
"name": "machines",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"endpoint": {
"name": "endpoint",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"disabled": {
"name": "disabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"auth_token": {
"name": "auth_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"type": {
"name": "type",
"type": "machine_type",
"primaryKey": false,
"notNull": true,
"default": "'classic'"
},
"status": {
"name": "status",
"type": "machine_status",
"primaryKey": false,
"notNull": true,
"default": "'ready'"
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"models": {
"name": "models",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"gpu": {
"name": "gpu",
"type": "machine_gpu",
"primaryKey": false,
"notNull": false
},
"build_machine_instance_id": {
"name": "build_machine_instance_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"build_log": {
"name": "build_log",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"machines_user_id_users_id_fk": {
"name": "machines_user_id_users_id_fk",
"tableFrom": "machines",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user_usage": {
"name": "user_usage",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"usage_time": {
"name": "usage_time",
"type": "real",
"primaryKey": false,
"notNull": true,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"user_usage_user_id_users_id_fk": {
"name": "user_usage_user_id_users_id_fk",
"tableFrom": "user_usage",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users": {
"name": "users",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_run_outputs": {
"name": "workflow_run_outputs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"data": {
"name": "data",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_run_outputs_run_id_workflow_runs_id_fk": {
"name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
"tableFrom": "workflow_run_outputs",
"tableTo": "workflow_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_runs": {
"name": "workflow_runs",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow_version_id": {
"name": "workflow_version_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"workflow_inputs": {
"name": "workflow_inputs",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"machine_id": {
"name": "machine_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"origin": {
"name": "origin",
"type": "workflow_run_origin",
"primaryKey": false,
"notNull": true,
"default": "'api'"
},
"status": {
"name": "status",
"type": "workflow_run_status",
"primaryKey": false,
"notNull": true,
"default": "'not-started'"
},
"ended_at": {
"name": "ended_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"started_at": {
"name": "started_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflow_versions",
"columnsFrom": [
"workflow_version_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"workflow_runs_workflow_id_workflows_id_fk": {
"name": "workflow_runs_workflow_id_workflows_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"workflow_runs_machine_id_machines_id_fk": {
"name": "workflow_runs_machine_id_machines_id_fk",
"tableFrom": "workflow_runs",
"tableTo": "machines",
"columnsFrom": [
"machine_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflows": {
"name": "workflows",
"schema": "comfyui_deploy",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflows_user_id_users_id_fk": {
"name": "workflows_user_id_users_id_fk",
"tableFrom": "workflows",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"workflow_versions": {
"name": "workflow_versions",
"schema": "comfyui_deploy",
"columns": {
"workflow_id": {
"name": "workflow_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"workflow": {
"name": "workflow",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"workflow_api": {
"name": "workflow_api",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"snapshot": {
"name": "snapshot",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"workflow_versions_workflow_id_workflows_id_fk": {
"name": "workflow_versions_workflow_id_workflows_id_fk",
"tableFrom": "workflow_versions",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {
"deployment_environment": {
"name": "deployment_environment",
"values": {
"staging": "staging",
"production": "production",
"public-share": "public-share"
}
},
"machine_gpu": {
"name": "machine_gpu",
"values": {
"T4": "T4",
"A10G": "A10G",
"A100": "A100"
}
},
"machine_status": {
"name": "machine_status",
"values": {
"ready": "ready",
"building": "building",
"error": "error"
}
},
"machine_type": {
"name": "machine_type",
"values": {
"classic": "classic",
"runpod-serverless": "runpod-serverless",
"modal-serverless": "modal-serverless",
"comfy-deploy-serverless": "comfy-deploy-serverless"
}
},
"workflow_run_origin": {
"name": "workflow_run_origin",
"values": {
"manual": "manual",
"api": "api",
"public-share": "public-share"
}
},
"workflow_run_status": {
"name": "workflow_run_status",
"values": {
"not-started": "not-started",
"running": "running",
"uploading": "uploading",
"success": "success",
"failed": "failed"
}
}
},
"schemas": {
"comfyui_deploy": "comfyui_deploy"
},
"_meta": {
"schemas": {},
"tables": {},
"columns": {
"\"comfyui_deploy\".\"user_usage\".\"updated_at\"": "\"comfyui_deploy\".\"user_usage\".\"ended_at\""
}
}
}
+4 -18
View File
@@ -222,29 +222,15 @@
{ {
"idx": 31, "idx": 31,
"version": "5", "version": "5",
"when": 1705763980972, "when": 1705975916818,
"tag": "0031_fast_lyja", "tag": "0031_safe_multiple_man",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 32, "idx": 32,
"version": "5", "version": "5",
"when": 1705806921697, "when": 1705979098372,
"tag": "0032_shallow_vermin", "tag": "0032_material_wallflower",
"breakpoints": true
},
{
"idx": 33,
"version": "5",
"when": 1705824362978,
"tag": "0033_fantastic_marvel_boy",
"breakpoints": true
},
{
"idx": 34,
"version": "5",
"when": 1705840184127,
"tag": "0034_previous_viper",
"breakpoints": true "breakpoints": true
} }
] ]
+3 -4
View File
@@ -12,11 +12,10 @@ let sslMode: string | boolean = process.env.SSL || "require";
if (sslMode === "false") sslMode = false; if (sslMode === "false") sslMode = false;
let connectionString = process.env.POSTGRES_URL!; console.log(migrationsFolderName, sslMode);
const isDevContainer = process.env.VSCODE_DEV_CONTAINER !== undefined;
if (isDevContainer) connectionString = connectionString.replace("localhost","host.docker.internal")
const connectionString = process.env.POSTGRES_URL!;
console.log(connectionString);
const sql = postgres(connectionString, { max: 1, ssl: sslMode as any }); const sql = postgres(connectionString, { max: 1, ssl: sslMode as any });
const db = drizzle(sql, { const db = drizzle(sql, {
logger: true, logger: true,
+11 -4
View File
@@ -12,8 +12,7 @@
"migrate-production": "bun run migrate.mts", "migrate-production": "bun run migrate.mts",
"migrate-local": "SSL=false LOCAL=true bun run migrate.mts", "migrate-local": "SSL=false LOCAL=true bun run migrate.mts",
"db-up": "docker-compose up", "db-up": "docker-compose up",
"db-dev": "bun run db-up && bun run migrate-local", "db-dev": "bun run db-up && bun run migrate-local"
"lint:fix": "bunx @biomejs/biome lint --apply ./src"
}, },
"dependencies": { "dependencies": {
"@algolia/autocomplete-core": "^1.13.0", "@algolia/autocomplete-core": "^1.13.0",
@@ -26,7 +25,6 @@
"@hono/zod-openapi": "^0.9.5", "@hono/zod-openapi": "^0.9.5",
"@hono/zod-validator": "^0.1.11", "@hono/zod-validator": "^0.1.11",
"@hookform/resolvers": "^3.3.2", "@hookform/resolvers": "^3.3.2",
"@lemonsqueezy/lemonsqueezy.js": "^1.2.5",
"@mdx-js/loader": "^3.0.0", "@mdx-js/loader": "^3.0.0",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.0",
"@neondatabase/serverless": "^0.6.0", "@neondatabase/serverless": "^0.6.0",
@@ -107,17 +105,26 @@
"zustand": "^4.4.7" "zustand": "^4.4.7"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.5.2", "@trivago/prettier-plugin-sort-imports": "4.1.1",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"autoprefixer": "^10.0.1", "autoprefixer": "^10.0.1",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"drizzle-kit": "^0.20.6", "drizzle-kit": "^0.20.6",
"eslint": "8.34.0", "eslint": "8.34.0",
"eslint-config-next": "^14.0.4",
"eslint-config-prettier": "^8.6.0",
"eslint-config-turbo": "latest",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-unused-imports": "^3.0.0",
"postcss": "^8", "postcss": "^8",
"postgres": "^3.4.3", "postgres": "^3.4.3",
"prettier": "2.8.6",
"prettier-plugin-tailwindcss": "0.2.5",
"sharp": "^0.33.1", "sharp": "^0.33.1",
"tailwindcss": "^3.3.0", "tailwindcss": "^3.3.0",
"typescript": "^5" "typescript": "^5"
+4 -61
View File
@@ -1,13 +1,6 @@
import { parseDataSafe } from "../../../../lib/parseDataSafe"; import { parseDataSafe } from "../../../../lib/parseDataSafe";
import { db } from "@/db/db"; import { db } from "@/db/db";
import { import { workflowRunOutputs, workflowRunsTable } from "@/db/schema";
userUsageTable,
workflowRunOutputs,
workflowRunsTable,
workflowTable,
} from "@/db/schema";
import { getDuration } from "@/lib/getRelativeTime";
import { getSubscription, setUsage } from "@/server/linkToPricing";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { z } from "zod"; import { z } from "zod";
@@ -34,6 +27,7 @@ export async function POST(request: Request) {
data: output_data, data: output_data,
}); });
} else if (status) { } else if (status) {
// console.log("status", status);
const workflow_run = await db const workflow_run = await db
.update(workflowRunsTable) .update(workflowRunsTable)
.set({ .set({
@@ -41,15 +35,8 @@ export async function POST(request: Request) {
ended_at: ended_at:
status === "success" || status === "failed" ? new Date() : null, status === "success" || status === "failed" ? new Date() : null,
}) })
.where(eq(workflowRunsTable.id, run_id)); .where(eq(workflowRunsTable.id, run_id))
.returning();
// get data from workflowRunsTable
const userUsageTime = await importUserUsageData(run_id);
if (userUsageTime) {
// get the usage_time from userUsage
await addSubscriptionUnit(userUsageTime);
}
} }
// const workflow_version = await db.query.workflowVersionTable.findFirst({ // const workflow_version = await db.query.workflowVersionTable.findFirst({
@@ -67,47 +54,3 @@ export async function POST(request: Request) {
} }
); );
} }
async function addSubscriptionUnit(userUsageTime: number) {
const subscription = await getSubscription();
// round up userUsageTime to the nearest integer
const roundedUsageTime = Math.ceil(userUsageTime);
if (subscription) {
const usage = await setUsage(
subscription.data[0].attributes.first_subscription_item.id,
roundedUsageTime
);
}
}
async function importUserUsageData(run_id: string) {
const workflowRuns = await db.query.workflowRunsTable.findFirst({
where: eq(workflowRunsTable.id, run_id),
});
if (!workflowRuns?.workflow_id) return;
// find if workflowTable id column contains workflowRunsTable workflow_id
const workflow = await db.query.workflowTable.findFirst({
where: eq(workflowTable.id, workflowRuns.workflow_id),
});
if (workflowRuns?.ended_at === null || workflow == null) return;
const usageTime = parseFloat(
getDuration((workflowRuns?.ended_at - workflowRuns?.started_at) / 1000)
);
// add data to userUsageTable
const user_usage = await db.insert(userUsageTable).values({
user_id: workflow.user_id,
created_at: workflowRuns.ended_at,
org_id: workflow.org_id,
ended_at: workflowRuns.ended_at,
usage_time: usageTime,
});
return usageTime;
}
@@ -0,0 +1,50 @@
import { parseDataSafe } from "../../../../lib/parseDataSafe";
import { db } from "@/db/db";
import { checkpointTable, machinesTable } from "@/db/schema";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";
import { z } from "zod";
const Request = z.object({
machine_id: z.string(),
endpoint: z.string().optional(),
build_log: z.string().optional(),
});
export async function POST(request: Request) {
const [data, error] = await parseDataSafe(Request, request);
if (!data || error) return error;
// console.log(data);
const { machine_id, endpoint, build_log } = data;
if (endpoint) {
await db
.update(checkpointTable)
.set({
// status: "ready",
// endpoint: endpoint,
// build_log: build_log,
})
.where(eq(machinesTable.id, machine_id));
} else {
// console.log(data);
await db
.update(machinesTable)
.set({
// status: "error",
// build_log: build_log,
})
.where(eq(machinesTable.id, machine_id));
}
return NextResponse.json(
{
message: "success",
},
{
status: 200,
}
);
}
@@ -1,81 +0,0 @@
const people = [
{
name: "Nvidia T4 GPU",
gpu: "1x",
ram: "16GB",
price: "$0.000225/sec",
},
{
name: "Nvidia A40 GPU",
gpu: "1x",
ram: "48GB",
price: "$0.000575/sec",
},
];
export function GpuPricingPlan() {
return (
<div className="flex justify-center w-full py-8">
<div className="w-full max-w-4xl">
<table className="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th
scope="col"
className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6"
>
GPU
</th>
<th
scope="col"
className="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell"
>
No.
</th>
<th
scope="col"
className="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell"
>
RAM
</th>
<th
scope="col"
className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
>
Price
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{people.map((person) => (
<tr key={person.ram} className="even:bg-gray-50">
<td className="w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6">
{person.name}
<dl className="font-normal lg:hidden">
<dt className="sr-only">No.</dt>
<dd className="mt-1 truncate text-gray-700">
{person.gpu}
</dd>
<dt className="sr-only sm:hidden">RAM</dt>
<dd className="mt-1 truncate text-gray-500 sm:hidden">
{person.ram}
</dd>
</dl>
</td>
<td className="hidden px-3 py-4 text-sm text-gray-500 lg:table-cell">
{person.gpu}
</td>
<td className="hidden px-3 py-4 text-sm text-gray-500 sm:table-cell">
{person.ram}
</td>
<td className="px-3 py-4 text-sm text-gray-500">
{person.price}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
@@ -1,157 +0,0 @@
import { checkMarkIcon, crossMarkIcon } from "../const/Icon";
import { cn } from "@/lib/utils";
import { getPricing } from "@/server/linkToPricing";
import { useEffect, useState } from "react";
type Tier = {
name: string;
id: string;
href: string;
priceMonthly: string;
description: string;
features: string[];
featured: boolean;
priority?: TierPriority;
};
enum TierPriority {
Free = "free",
Pro = "pro",
Enterprise = "enterprise",
}
export default function PricingList() {
const [productTiers, setProductTiers] = useState<Tier[]>();
useEffect(() => {
(async () => {
const product = await getPricing();
if (!product) return;
const newProductTiers: Tier[] = product.data.map((item) => {
// Create a new DOMParser instance
const parser = new DOMParser();
// Parse the description HTML string to a new document
const doc = parser.parseFromString(
item.attributes.description,
"text/html"
);
// Extract the description and features
const description = doc.querySelector("p")?.textContent || "";
const features = Array.from(doc.querySelectorAll("ul > li")).map(
(li) => li.textContent || ""
);
return {
name: item.attributes.name,
id: item.id,
href: item.attributes.buy_now_url,
priceMonthly:
item.attributes.price_formatted.split("/")[0] == "Usage-based"
? "$20.00"
: item.attributes.price_formatted.split("/")[0],
description: description,
features: features,
// if name contains pro, it's featured
featured: item.attributes.name.toLowerCase().includes("pro"),
// give priority if name contain in enum
priority: Object.values(TierPriority).find((priority) =>
item.attributes.name.toLowerCase().includes(priority)
),
};
});
// sort newProductTiers by priority
newProductTiers.sort((a, b) => {
if (!a.priority) return 1;
if (!b.priority) return -1;
return (
Object.values(TierPriority).indexOf(a.priority) -
Object.values(TierPriority).indexOf(b.priority)
);
});
setProductTiers(newProductTiers);
})();
}, []);
return (
<div className="relative isolate px-6 py-24 lg:px-8">
<div className="mx-auto max-w-2xl text-center lg:max-w-4xl">
<h2 className="text-base font-semibold leading-7 text-indigo-600">
Pricing
</h2>
<p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
The right price for you, whoever you are
</p>
</div>
<p className="mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600">
Qui iusto aut est earum eos quae. Eligendi est at nam aliquid ad quo
reprehenderit in aliquid fugiat dolorum voluptatibus.
</p>
<div className="mx-auto mt-16 grid max-w-lg grid-cols-1 items-center gap-y-6 sm:mt-20 sm:gap-y-0 lg:max-w-4xl lg:grid-cols-2 xl:max-w-6xl xl:grid-cols-3">
{productTiers &&
productTiers.map((tier, tierIdx) => (
<div
key={tier.id}
className={cn(
tier.featured
? "relative bg-white shadow-2xl"
: "bg-white/60 sm:mx-8 lg:mx-0",
tier.featured
? ""
: tierIdx === 0
? "rounded-t-3xl sm:rounded-b-none lg:rounded-tr-none lg:rounded-bl-3xl"
: "sm:rounded-t-none lg:rounded-tr-3xl lg:rounded-bl-none",
"rounded-3xl p-8 ring-1 ring-gray-900/10 sm:p-10"
)}
>
<h3
id={tier.id}
className="text-base font-semibold leading-7 text-indigo-600"
>
{tier.name}
</h3>
<p className="mt-4 flex items-baseline gap-x-2">
<span className="text-5xl font-bold tracking-tight text-gray-900">
{tier.priceMonthly}
</span>
<span className="text-base text-gray-500">/month</span>
</p>
<p className="mt-6 text-base leading-7 text-gray-600">
{tier.description}
</p>
<ul
role="list"
className="mt-8 space-y-3 text-sm leading-6 text-gray-600 sm:mt-10"
>
{tier.features.map((feature) => (
<li key={feature} className="flex gap-x-3">
<div className="flex justify-center items-center">
{feature.includes("[x]") ? crossMarkIcon : checkMarkIcon}
</div>
{feature.replace("[x]", "")}
</li>
))}
</ul>
<a
href={tier.href}
aria-describedby={tier.id}
className={cn(
tier.featured
? "bg-indigo-600 text-white shadow hover:bg-indigo-500"
: "text-indigo-600 ring-1 ring-inset ring-indigo-200 hover:ring-indigo-300",
"mt-8 block rounded-md py-2.5 px-3.5 text-center text-sm font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:mt-10"
)}
>
Get started today
</a>
</div>
))}
</div>
</div>
);
}
-37
View File
@@ -1,37 +0,0 @@
export const checkMarkIcon = (
<svg
className="h-5 w-5 flex-shrink-0 text-green-500"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clipRule="evenodd"
/>
</svg>
);
export const crossMarkIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="20"
height="20"
viewBox="0 0 48 48"
>
<path
fill="#F44336"
d="M21.5 4.5H26.501V43.5H21.5z"
transform="rotate(45.001 24 24)"
/>
<path
fill="#F44336"
d="M21.5 4.5H26.5V43.501H21.5z"
transform="rotate(135.008 24 24)"
/>
</svg>
);
-13
View File
@@ -1,13 +0,0 @@
"use client";
import { GpuPricingPlan } from "@/app/(app)/pricing/components/gpuPricingTable";
import PricingList from "@/app/(app)/pricing/components/pricePlanList";
export default function Home() {
return (
<div>
<PricingList />
<GpuPricingPlan />
</div>
);
}
+81 -79
View File
@@ -2,11 +2,11 @@ import { ButtonActionMenu } from "@/components/ButtonActionLoader";
import { RunWorkflowInline } from "@/components/RunWorkflowInline"; import { RunWorkflowInline } from "@/components/RunWorkflowInline";
import { PublicRunOutputs } from "@/components/VersionSelect"; import { PublicRunOutputs } from "@/components/VersionSelect";
import { import {
Card, Card,
CardContent, CardContent,
CardDescription, CardDescription,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { db } from "@/db/db"; import { db } from "@/db/db";
import { usersTable } from "@/db/schema"; import { usersTable } from "@/db/schema";
@@ -14,9 +14,9 @@ import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
import { getRelativeTime } from "@/lib/getRelativeTime"; import { getRelativeTime } from "@/lib/getRelativeTime";
import { setInitialUserData } from "@/lib/setInitialUserData"; import { setInitialUserData } from "@/lib/setInitialUserData";
import { import {
cloneMachine, cloneMachine,
cloneWorkflow, cloneWorkflow,
findSharedDeployment, findSharedDeployment,
} from "@/server/curdDeploments"; } from "@/server/curdDeploments";
import { auth, clerkClient } from "@clerk/nextjs/server"; import { auth, clerkClient } from "@clerk/nextjs/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
@@ -25,87 +25,89 @@ import { redirect } from "next/navigation";
export const maxDuration = 300; // 5 minutes export const maxDuration = 300; // 5 minutes
export default async function Page({ export default async function Page({
params, params,
}: { }: {
params: { share_id: string }; params: { share_id: string };
}) { }) {
const { userId } = await auth(); const { userId } = await auth();
// If there is user, check if the user data is present // If there is user, check if the user data is present
if (userId) { if (userId) {
const user = await db.query.usersTable.findFirst({ const user = await db.query.usersTable.findFirst({
where: eq(usersTable.id, userId), where: eq(usersTable.id, userId),
}); });
if (!user) { if (!user) {
await setInitialUserData(userId); await setInitialUserData(userId);
} }
} }
const sharedDeployment = await findSharedDeployment(params.share_id); const sharedDeployment = await findSharedDeployment(params.share_id);
if (!sharedDeployment) return redirect("/"); if (!sharedDeployment) return redirect("/");
const userName = sharedDeployment.workflow.org_id const userName = sharedDeployment.workflow.org_id
? await clerkClient.organizations ? await clerkClient.organizations
.getOrganization({ .getOrganization({
organizationId: sharedDeployment.workflow.org_id, organizationId: sharedDeployment.workflow.org_id,
}) })
.then((x) => x.name) .then((x) => x.name)
: sharedDeployment.user.name; : sharedDeployment.user.name;
const inputs = getInputsFromWorkflow(sharedDeployment.version); const inputs = getInputsFromWorkflow(sharedDeployment.version);
return ( return (
<div className="mt-4 w-full grid grid-rows-[1fr,1fr] lg:grid-cols-[minmax(auto,500px),1fr] gap-4 max-h-[calc(100dvh-100px)]"> <div className="mt-4 w-full grid grid-rows-[1fr,1fr] lg:grid-cols-[minmax(auto,500px),1fr] gap-4 max-h-[calc(100dvh-100px)]">
<Card className="w-full h-fit mt-4"> <Card className="w-full h-fit mt-4">
<CardHeader> <CardHeader>
<CardTitle className="flex justify-between items-center"> <CardTitle className="flex justify-between items-center">
<div> <div>
{userName} {userName}
{" / "} {" / "}
{sharedDeployment.workflow.name} {sharedDeployment.workflow.name}
</div> </div>
<ButtonActionMenu <ButtonActionMenu
title="Clone" title="Clone"
actions={[ actions={[
{ {
title: "Workflow", title: "Workflow",
action: cloneWorkflow.bind(null, sharedDeployment.id), action: cloneWorkflow.bind(null, sharedDeployment.id),
}, },
{ {
title: "Machine", title: "Machine",
action: cloneMachine.bind(null, sharedDeployment.id), action: cloneMachine.bind(null, sharedDeployment.id),
}, },
]} ]}
/> />
</CardTitle> </CardTitle>
<CardDescription suppressHydrationWarning={true}> <CardDescription suppressHydrationWarning={true}>
{getRelativeTime(sharedDeployment?.updated_at)} {getRelativeTime(sharedDeployment?.updated_at)}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div> <div>
{sharedDeployment?.description && sharedDeployment?.description} {sharedDeployment?.description && (
</div> <>{sharedDeployment?.description}</>
<RunWorkflowInline )}
inputs={inputs} </div>
machine_id={sharedDeployment.machine_id} <RunWorkflowInline
workflow_version_id={sharedDeployment.workflow_version_id} inputs={inputs}
/> machine_id={sharedDeployment.machine_id}
</CardContent> workflow_version_id={sharedDeployment.workflow_version_id}
</Card> />
<Card className="w-full h-fit mt-4"> </CardContent>
<CardHeader> </Card>
<CardDescription>Run outputs</CardDescription> <Card className="w-full h-fit mt-4">
</CardHeader> <CardHeader>
<CardDescription>Run outputs</CardDescription>
</CardHeader>
<CardContent> <CardContent>
<PublicRunOutputs preview={sharedDeployment.showcase_media} /> <PublicRunOutputs preview={sharedDeployment.showcase_media} />
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
); );
} }
+35
View File
@@ -0,0 +1,35 @@
import { setInitialUserData } from "../../../lib/setInitialUserData";
import { auth } from "@clerk/nextjs";
import { clerkClient } from "@clerk/nextjs/server";
import { CheckpointList } from "@/components/CheckpointList"
import { getAllUserCheckpoints } from "@/server/getAllUserCheckpoints";
export default function Page() {
return <CheckpointListServer />;
}
async function CheckpointListServer() {
const { userId } = auth();
if (!userId) {
return <div>No auth</div>;
}
const user = await clerkClient.users.getUser(userId);
if (!user) {
await setInitialUserData(userId);
}
const checkpoints = await getAllUserCheckpoints()
if (!checkpoints) {
return <div>No checkpoints found</div>;
}
return (
<div className="w-full">
<CheckpointList data={checkpoints}/>
</div>
);
}
@@ -1,13 +1,13 @@
import { CreateShareButton } from "@/components/CreateShareButton";
import { MachinesWSMain } from "@/components/MachinesWS"; import { MachinesWSMain } from "@/components/MachinesWS";
import { VersionDetails } from "@/components/VersionDetails"; import { VersionDetails } from "@/components/VersionDetails";
import { import {
CopyWorkflowVersion, CopyWorkflowVersion,
CreateDeploymentButton, CreateDeploymentButton,
MachineSelect, CreateShareButton,
RunWorkflowButton, MachineSelect,
VersionSelect, RunWorkflowButton,
ViewWorkflowDetailsButton, VersionSelect,
ViewWorkflowDetailsButton,
} from "@/components/VersionSelect"; } from "@/components/VersionSelect";
import { import {
Card, Card,
+63 -69
View File
@@ -4,10 +4,10 @@ import { LoadingIcon } from "@/components/LoadingIcon";
import { callServerPromise } from "@/components/callServerPromise"; import { callServerPromise } from "@/components/callServerPromise";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useAuth, useClerk } from "@clerk/nextjs"; import { useAuth, useClerk } from "@clerk/nextjs";
import { MoreVertical } from "lucide-react"; import { MoreVertical } from "lucide-react";
@@ -15,80 +15,74 @@ import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
export function ButtonAction({ export function ButtonAction({
action, action,
children, children,
routerAction = "back", ...rest
...rest
}: { }: {
action: () => Promise<any>; action: () => Promise<any>;
routerAction?: "refresh" | "back"; children: React.ReactNode;
children: React.ReactNode;
}) { }) {
const [pending, setPending] = useState(false); const [pending, setPending] = useState(false);
const router = useRouter(); const router = useRouter();
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (pending) return; if (pending) return;
setPending(true); setPending(true);
await callServerPromise(action()); await callServerPromise(action());
setPending(false); setPending(false);
if (routerAction === "back") { router.refresh();
router.back(); }}
router.refresh(); {...rest}
} >
else if (routerAction === "refresh") router.refresh(); {children} {pending && <LoadingIcon />}
}} </button>
{...rest} );
>
{children} {pending && <LoadingIcon />}
</button>
);
} }
export function ButtonActionMenu(props: { export function ButtonActionMenu(props: {
title?: string; title?: string;
actions: { actions: {
title: string; title: string;
action: () => Promise<any>; action: () => Promise<any>;
}[]; }[];
}) { }) {
const user = useAuth(); const user = useAuth();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const clerk = useClerk(); const clerk = useClerk();
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button className="gap-2" variant="outline" disabled={isLoading}> <Button className="gap-2" variant="outline" disabled={isLoading}>
{props.title} {props.title}
{isLoading ? <LoadingIcon /> : <MoreVertical size={14} />} {isLoading ? <LoadingIcon /> : <MoreVertical size={14} />}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-56"> <DropdownMenuContent className="w-56">
{props.actions.map((action) => ( {props.actions.map((action) => (
<DropdownMenuItem <DropdownMenuItem
key={action.title} key={action.title}
onClick={async () => { onClick={async () => {
if (!user.isSignedIn) { if (!user.isSignedIn) {
clerk.openSignIn({ clerk.openSignIn({
redirectUrl: window.location.href, redirectUrl: window.location.href,
}); });
return; return;
} }
setIsLoading(true); setIsLoading(true);
await callServerPromise(action.action()); await callServerPromise(action.action());
setIsLoading(false); setIsLoading(false);
}} }}
> >
{action.title} {action.title}
</DropdownMenuItem> </DropdownMenuItem>
))} ))}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );
} }
+315
View File
@@ -0,0 +1,315 @@
"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, UpdateModal } 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 { getAllUserCheckpoints } from "@/server/getAllUserCheckpoints";
import type {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
} from "@tanstack/react-table";
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
import * as React from "react";
import { insertCivitaiCheckpointSchema } from "@/db/schema";
import { addCivitaiCheckpoint } from "@/server/curdCheckpoint";
import { addCivitaiCheckpointSchema } from "@/server/addCheckpointSchema";
export type CheckpointItemList = NonNullable<
Awaited<ReturnType<typeof getAllUserCheckpoints>>
>[0];
export const columns: ColumnDef<CheckpointItemList>[] = [
{
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: "name",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
const checkpoint = row.original;
return (
<a
className="hover:underline flex gap-2"
href={`/storage/${checkpoint.id}`} // TODO
>
<span className="truncate max-w-[200px]">{row.original.model_name}</span>
<Badge variant="default">{}</Badge>
{checkpoint.is_public
? <Badge variant="success">Public</Badge>
: <Badge variant="teal">Private</Badge>}
</a>
);
},
},
{
accessorKey: "creator",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Creator
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
// return <Badge variant="cyan">{row?.original?.user?.name ? row.original.user.name : "Public"}</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>
),
},
// {
// 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 CheckpointList({ data }: { data: CheckpointItemList[] }) {
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("name")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("name")?.setFilterValue(event.target.value)}
className="max-w-sm"
/>
<div className="ml-auto flex gap-2">
<InsertModal
dialogClassName="sm:max-w-[600px]"
disabled={
false
// TODO: limitations based on plan
}
tooltip={"Add models using their civitai url!"}
title="Civitai Checkpoint"
description="Pick a model from civitai"
serverAction={addCivitaiCheckpoint}
formSchema={addCivitaiCheckpointSchema}
fieldConfig={{
civitai_url: {
fieldType: "fallback",
// fieldType: "fallback",
inputProps: { required: true },
description: (
<>
Pick a checkpoint from{" "}
<a
href="https://www.civitai.com/models"
target="_blank"
className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"
>
civitai.com
</a>{" "}
and place it's url here
</>
),
},
}}
/>
</div>
</div>
<ScrollArea className="h-full w-full rounded-md border">
<Table>
<TableHeader className="bg-background top-0 sticky">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length
? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
)
: (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</ScrollArea>
<div className="flex flex-row items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
);
}
-66
View File
@@ -1,66 +0,0 @@
"use client";
import { LoadingIcon } from "@/components/LoadingIcon";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine";
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
import { Share } from "lucide-react";
import { parseAsInteger, useQueryState } from "next-usequerystate";
import { useState } from "react";
import { useSelectedMachine } from "./VersionSelect";
import { callServerPromise } from "./callServerPromise";
export function CreateShareButton({
workflow,
machines,
}: {
workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
machines: Awaited<ReturnType<typeof getMachines>>;
}) {
const [version] = useQueryState("version", {
defaultValue: workflow?.versions[0].version ?? 1,
...parseAsInteger,
});
const [machine] = useSelectedMachine(machines);
const [isLoading, setIsLoading] = useState(false);
const workflow_version_id = workflow?.versions.find(
(x) => x.version === version,
)?.id;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-2" disabled={isLoading} variant="outline">
Share {isLoading ? <LoadingIcon /> : <Share size={14} />}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem
onClick={async () => {
if (!workflow_version_id) return;
setIsLoading(true);
await callServerPromise(
createDeployments(
workflow.id,
workflow_version_id,
machine,
"public-share",
),
);
setIsLoading(false);
}}
>
Public
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
+138 -139
View File
@@ -1,18 +1,18 @@
import { DeploymentRow, SharePageDeploymentRow } from "./DeploymentRow";
import { CodeBlock } from "@/components/CodeBlock"; import { CodeBlock } from "@/components/CodeBlock";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { TableRow } from "@/components/ui/table"; import { TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow"; import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
import type { findAllDeployments } from "@/server/findAllRuns"; import type { findAllDeployments } from "@/server/findAllRuns";
import { DeploymentRow, SharePageDeploymentRow } from "./DeploymentRow";
const curlTemplate = ` const curlTemplate = `
curl --request POST \ curl --request POST \
@@ -83,145 +83,144 @@ const run = await client.getRun(run_id);
`; `;
export function DeploymentDisplay({ export function DeploymentDisplay({
deployment, deployment,
domain, domain,
}: { }: {
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0]; deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
domain: string; domain: string;
}) { }) {
const workflowInput = getInputsFromWorkflow(deployment.version); const workflowInput = getInputsFromWorkflow(deployment.version);
if (deployment.environment === "public-share") { if (deployment.environment == "public-share") {
return <SharePageDeploymentRow deployment={deployment} />; return <SharePageDeploymentRow deployment={deployment} />;
} }
return ( return (
<Dialog> <Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer"> <DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow> <TableRow>
<DeploymentRow deployment={deployment} /> <DeploymentRow deployment={deployment} />
</TableRow> </TableRow>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-w-3xl"> <DialogContent className="max-w-3xl">
<DialogHeader> <DialogHeader>
<DialogTitle className="capitalize"> <DialogTitle className="capitalize">
{deployment.environment} Deployment {deployment.environment} Deployment
</DialogTitle> </DialogTitle>
<DialogDescription>Code for your deployment client</DialogDescription> <DialogDescription>Code for your deployment client</DialogDescription>
</DialogHeader> </DialogHeader>
<ScrollArea className="max-h-[600px] pr-4"> <ScrollArea className="max-h-[600px] pr-4">
<Tabs defaultValue="client" className="w-full gap-2 text-sm"> <Tabs defaultValue="client" className="w-full gap-2 text-sm">
<TabsList className="grid w-fit grid-cols-3 mb-2"> <TabsList className="grid w-fit grid-cols-3 mb-2">
<TabsTrigger value="client">Server Client</TabsTrigger> <TabsTrigger value="client">Server Client</TabsTrigger>
<TabsTrigger value="js">NodeJS Fetch</TabsTrigger> <TabsTrigger value="js">NodeJS Fetch</TabsTrigger>
<TabsTrigger value="curl">CURL</TabsTrigger> <TabsTrigger value="curl">CURL</TabsTrigger>
</TabsList> </TabsList>
<TabsContent className="flex flex-col gap-2 !mt-0" value="client"> <TabsContent className="flex flex-col gap-2 !mt-0" value="client">
<div> <div>
Copy and paste the ComfyDeployClient form&nbsp; Copy and paste the ComfyDeployClient form&nbsp;
<a <a
href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts" href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts"
className="text-blue-500 hover:underline" className="text-blue-500 hover:underline"
target="_blank" target="_blank"
rel="noreferrer" >
> here
here </a>
</a> </div>
</div> <CodeBlock
<CodeBlock lang="js"
lang="js" code={formatCode(
code={formatCode( domain == "https://www.comfydeploy.com"
domain == "https://www.comfydeploy.com" ? jsClientSetupTemplateHostedVersion
? jsClientSetupTemplateHostedVersion : jsClientSetupTemplate,
: jsClientSetupTemplate, deployment,
deployment, domain,
domain, workflowInput
workflowInput, )}
)} />
/> Create a run via deployment id
Create a run via deployment id <CodeBlock
<CodeBlock lang="js"
lang="js" code={formatCode(
code={formatCode( workflowInput && workflowInput.length > 0
workflowInput && workflowInput.length > 0 ? jsClientCreateRunTemplate
? jsClientCreateRunTemplate : jsClientCreateRunNoInputsTemplate,
: jsClientCreateRunNoInputsTemplate, deployment,
deployment, domain,
domain, workflowInput
workflowInput, )}
)} />
/> Check the status of the run, and retrieve the outputs
Check the status of the run, and retrieve the outputs <CodeBlock
<CodeBlock lang="js"
lang="js" code={formatCode(
code={formatCode( clientTemplate_checkStatus,
clientTemplate_checkStatus, deployment,
deployment, domain
domain, )}
)} />
/> </TabsContent>
</TabsContent> <TabsContent className="flex flex-col gap-2 !mt-0" value="js">
<TabsContent className="flex flex-col gap-2 !mt-0" value="js"> Trigger the workflow
Trigger the workflow <CodeBlock
<CodeBlock lang="js"
lang="js" code={formatCode(jsTemplate, deployment, domain, workflowInput)}
code={formatCode(jsTemplate, deployment, domain, workflowInput)} />
/> Check the status of the run, and retrieve the outputs
Check the status of the run, and retrieve the outputs <CodeBlock
<CodeBlock lang="js"
lang="js" code={formatCode(jsTemplate_checkStatus, deployment, domain)}
code={formatCode(jsTemplate_checkStatus, deployment, domain)} />
/> </TabsContent>
</TabsContent> <TabsContent className="flex flex-col gap-2 !mt-2" value="curl">
<TabsContent className="flex flex-col gap-2 !mt-2" value="curl"> <CodeBlock
<CodeBlock lang="bash"
lang="bash" code={formatCode(curlTemplate, deployment, domain)}
code={formatCode(curlTemplate, deployment, domain)} />
/> <CodeBlock
<CodeBlock lang="bash"
lang="bash" code={formatCode(curlTemplate_checkStatus, deployment, domain)}
code={formatCode(curlTemplate_checkStatus, deployment, domain)} />
/> </TabsContent>
</TabsContent> </Tabs>
</Tabs> </ScrollArea>
</ScrollArea> </DialogContent>
</DialogContent> </Dialog>
</Dialog> );
);
} }
function formatCode( function formatCode(
codeTemplate: string, codeTemplate: string,
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0], deployment: Awaited<ReturnType<typeof findAllDeployments>>[0],
domain: string, domain: string,
inputs?: ReturnType<typeof getInputsFromWorkflow>, inputs?: ReturnType<typeof getInputsFromWorkflow>,
inputsTabs?: number, inputsTabs?: number
) { ) {
if (inputs && inputs.length > 0) { if (inputs && inputs.length > 0) {
codeTemplate = codeTemplate.replace( codeTemplate = codeTemplate.replace(
"inputs: {}", "inputs: {}",
`inputs: ${JSON.stringify( `inputs: ${JSON.stringify(
Object.fromEntries( Object.fromEntries(
inputs.map((x) => { inputs.map((x) => {
return [x?.input_id, ""]; return [x?.input_id, ""];
}), })
), ),
null, null,
2, 2
) )
.split("\n") .split("\n")
.map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line .map((line, index) => (index === 0 ? line : ` ${line}`)) // Add two spaces indentation except for the first line
.join("\n")}`, .join("\n")}`
); );
} else { } else {
codeTemplate = codeTemplate.replace( codeTemplate = codeTemplate.replace(
` `
inputs: {}`, inputs: {}`,
"", ""
); );
} }
return codeTemplate return codeTemplate
.replace("<URL>", `${domain ?? "http://localhost:3000"}/api/run`) .replace("<URL>", `${domain ?? "http://localhost:3000"}/api/run`)
.replace("<ID>", deployment.id) .replace("<ID>", deployment.id)
.replace("<URLONLY>", domain ?? "http://localhost:3000"); .replace("<URLONLY>", domain ?? "http://localhost:3000");
} }
+44 -46
View File
@@ -6,57 +6,55 @@ import type { findAllDeployments } from "@/server/findAllRuns";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
export function SharePageDeploymentRow({ export function SharePageDeploymentRow({
deployment, deployment,
}: { }: {
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0]; deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
}) { }) {
const router = useRouter(); const router = useRouter();
return ( return (
<TableRow <TableRow
className="appearance-none hover:cursor-pointer" className="appearance-none hover:cursor-pointer"
onClick={() => { onClick={() => {
if (deployment.environment === "public-share") { if (deployment.environment == "public-share") {
router.push( router.push(`/share/${deployment.id}/settings`);
`/share/${deployment.share_slug ?? deployment.id}/settings`, }
); }}
} >
}} <TableCell className="capitalize truncate">
> {deployment.environment}
<TableCell className="capitalize truncate"> </TableCell>
{deployment.environment} <TableCell className="font-medium truncate">
</TableCell> {deployment.version?.version}
<TableCell className="font-medium truncate"> </TableCell>
{deployment.version?.version} <TableCell className="font-medium truncate">
</TableCell> {deployment.machine?.name}
<TableCell className="font-medium truncate"> </TableCell>
{deployment.machine?.name} <TableCell className="text-right truncate">
</TableCell> {getRelativeTime(deployment.updated_at)}
<TableCell className="text-right truncate"> </TableCell>
{getRelativeTime(deployment.updated_at)} </TableRow>
</TableCell> );
</TableRow>
);
} }
export function DeploymentRow({ export function DeploymentRow({
deployment, deployment,
}: { }: {
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0]; deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
}) { }) {
return ( return (
<> <>
<TableCell className="capitalize truncate"> <TableCell className="capitalize truncate">
{deployment.environment} {deployment.environment}
</TableCell> </TableCell>
<TableCell className="font-medium truncate"> <TableCell className="font-medium truncate">
{deployment.version?.version} {deployment.version?.version}
</TableCell> </TableCell>
<TableCell className="font-medium truncate"> <TableCell className="font-medium truncate">
{deployment.machine?.name} {deployment.machine?.name}
</TableCell> </TableCell>
<TableCell className="text-right truncate"> <TableCell className="text-right truncate">
{getRelativeTime(deployment.updated_at)} {getRelativeTime(deployment.updated_at)}
</TableCell> </TableCell>
</> </>
); );
} }
+1 -1
View File
@@ -223,7 +223,7 @@ export const columns: ColumnDef<Machine>[] = [
href={machine.endpoint.replace( href={machine.endpoint.replace(
"comfyui-api", "comfyui-api",
"comfyui-app" "comfyui-app"
)} rel="noreferrer" )}
> >
Open ComfyUI Open ComfyUI
</a> </a>
+1 -19
View File
@@ -22,7 +22,6 @@ import {
} from "@clerk/nextjs"; } from "@clerk/nextjs";
import { Github, Menu } from "lucide-react"; import { Github, Menu } from "lucide-react";
import meta from "next-gen/config"; import meta from "next-gen/config";
import { useFeatureFlagEnabled } from "posthog-js/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useMediaQuery } from "usehooks-ts"; import { useMediaQuery } from "usehooks-ts";
@@ -30,13 +29,9 @@ export function Navbar() {
const { organization } = useOrganization(); const { organization } = useOrganization();
const _isDesktop = useMediaQuery("(min-width: 1024px)"); const _isDesktop = useMediaQuery("(min-width: 1024px)");
const [isDesktop, setIsDesktop] = useState(true); const [isDesktop, setIsDesktop] = useState(true);
const pricingPlanFlagEnable = useFeatureFlagEnabled("pricing-plan");
useEffect(() => { useEffect(() => {
setIsDesktop(_isDesktop); setIsDesktop(_isDesktop);
}, [_isDesktop]); }, [_isDesktop]);
return ( return (
<> <>
<div className="flex flex-row items-center gap-4"> <div className="flex flex-row items-center gap-4">
@@ -90,15 +85,6 @@ export function Navbar() {
</div> </div>
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
{isDesktop && <NavbarMenu />} {isDesktop && <NavbarMenu />}
{pricingPlanFlagEnable && (
<Button
asChild
variant="link"
className="rounded-full aspect-square p-2 mr-4"
>
<a href="/pricing">Pricing</a>
</Button>
)}
<Button <Button
asChild asChild
variant="link" variant="link"
@@ -112,11 +98,7 @@ export function Navbar() {
variant="outline" variant="outline"
className="rounded-full aspect-square p-2" className="rounded-full aspect-square p-2"
> >
<a <a target="_blank" href="https://github.com/BennyKok/comfyui-deploy">
target="_blank"
href="https://github.com/BennyKok/comfyui-deploy"
rel="noreferrer"
>
<Github /> <Github />
</a> </a>
</Button> </Button>
+6 -2
View File
@@ -34,6 +34,10 @@ export function NavbarMenu({ className }: { className?: string }) {
name: "API Keys", name: "API Keys",
path: "/api-keys", path: "/api-keys",
}, },
{
name: "Storage",
path: "/storage",
},
]; ];
return ( return (
@@ -42,9 +46,9 @@ export function NavbarMenu({ className }: { className?: string }) {
{isDesktop && ( {isDesktop && (
<Tabs <Tabs
defaultValue={pathname} defaultValue={pathname}
className="w-[300px] flex pointer-events-auto" className="w-[400px] flex pointer-events-auto"
> >
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-4">
{pages.map((page) => ( {pages.map((page) => (
<TabsTrigger <TabsTrigger
key={page.name} key={page.name}
+49 -60
View File
@@ -1,74 +1,63 @@
import { LiveStatus } from "./LiveStatus";
import { RunInputs } from "@/components/RunInputs"; import { RunInputs } from "@/components/RunInputs";
import { RunOutputs } from "@/components/RunOutputs"; import { RunOutputs } from "@/components/RunOutputs";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { TableCell, TableRow } from "@/components/ui/table"; import { TableCell, TableRow } from "@/components/ui/table";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime"; import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
import { type findAllRuns } from "@/server/findAllRuns"; import { type findAllRuns } from "@/server/findAllRuns";
import { Suspense } from "react"; import { Suspense } from "react";
import { LiveStatus } from "./LiveStatus";
export async function RunDisplay({ export async function RunDisplay({
run, run,
}: { }: {
run: Awaited<ReturnType<typeof findAllRuns>>[0]; run: Awaited<ReturnType<typeof findAllRuns>>[0];
}) { }) {
return ( return (
<Dialog> <Dialog>
<DialogTrigger asChild className="appearance-none hover:cursor-pointer"> <DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow> <TableRow>
<TableCell>{run.number}</TableCell> <TableCell>{run.number}</TableCell>
<TableCell className="font-medium truncate"> <TableCell className="font-medium truncate">
{run.machine?.name} {run.machine?.name}
</TableCell> </TableCell>
<TableCell className="truncate"> <TableCell className="truncate">
{getRelativeTime(run.created_at)} {getRelativeTime(run.created_at)}
</TableCell> </TableCell>
<TableCell>{run.version?.version}</TableCell> <TableCell>{run.version?.version}</TableCell>
<TableCell> <TableCell>
<Badge variant="outline" className="truncate"> <Badge variant="outline" className="truncate">
{run.origin} {run.origin}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell className="truncate"> <TableCell className="truncate">
<Tooltip> {getDuration(run.duration)}
<TooltipTrigger>{getDuration(run.duration)}</TooltipTrigger> </TableCell>
<TooltipContent> <LiveStatus run={run} />
<div>Cold start: {getDuration(run.cold_start_duration)}</div> </TableRow>
<div>Run duration: {getDuration(run.run_duration)}</div> </DialogTrigger>
</TooltipContent> <DialogContent className="max-w-3xl">
</Tooltip> <DialogHeader>
</TableCell> <DialogTitle>Run outputs</DialogTitle>
<LiveStatus run={run} /> <DialogDescription>
</TableRow> You can view your run&apos;s outputs here
</DialogTrigger> </DialogDescription>
<DialogContent className="max-w-3xl"> </DialogHeader>
<DialogHeader> <div className="max-h-96 overflow-y-scroll">
<DialogTitle>Run outputs</DialogTitle> <RunInputs run={run} />
<DialogDescription> <Suspense>
You can view your run&apos;s outputs here <RunOutputs run_id={run.id} />
</DialogDescription> </Suspense>
</DialogHeader> </div>
<div className="max-h-96 overflow-y-scroll"> {/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
<RunInputs run={run} /> </DialogContent>
<Suspense> </Dialog>
<RunOutputs run_id={run.id} /> );
</Suspense>
</div>
{/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
</DialogContent>
</Dialog>
);
} }
+40 -40
View File
@@ -1,3 +1,10 @@
import {
findAllDeployments,
findAllRunsWithCounts,
} from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
import { PaginationControl } from "./PaginationControl";
import { RunDisplay } from "./RunDisplay";
import { import {
Table, Table,
TableBody, TableBody,
@@ -8,13 +15,6 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import { parseAsInteger } from "next-usequerystate"; import { parseAsInteger } from "next-usequerystate";
import { headers } from "next/headers"; import { headers } from "next/headers";
import {
findAllDeployments,
findAllRunsWithCounts,
} from "../server/findAllRuns";
import { DeploymentDisplay } from "./DeploymentDisplay";
import { PaginationControl } from "./PaginationControl";
import { RunDisplay } from "./RunDisplay";
const itemPerPage = 6; const itemPerPage = 6;
const pageParser = parseAsInteger.withDefault(1); const pageParser = parseAsInteger.withDefault(1);
@@ -33,40 +33,40 @@ export async function RunsTable(props: {
offset: (page - 1) * itemPerPage, offset: (page - 1) * itemPerPage,
}); });
return ( return (
<div> <div>
<div className="overflow-auto h-fit w-full"> <div className="overflow-auto h-fit w-full">
<Table className=""> <Table className="">
{allRuns.length === 0 && ( {allRuns.length == 0 && (
<TableCaption>A list of your recent runs.</TableCaption> <TableCaption>A list of your recent runs.</TableCaption>
)} )}
<TableHeader className="bg-background top-0 sticky"> <TableHeader className="bg-background top-0 sticky">
<TableRow> <TableRow>
<TableHead className="truncate">Number</TableHead> <TableHead className="truncate">Number</TableHead>
<TableHead className="truncate">Machine</TableHead> <TableHead className="truncate">Machine</TableHead>
<TableHead className="truncate">Time</TableHead> <TableHead className="truncate">Time</TableHead>
<TableHead className="truncate">Version</TableHead> <TableHead className="truncate">Version</TableHead>
<TableHead className="truncate">Origin</TableHead> <TableHead className="truncate">Origin</TableHead>
<TableHead className="truncate">Duration</TableHead> <TableHead className="truncate">Duration</TableHead>
<TableHead className="truncate">Live Status</TableHead> <TableHead className="truncate">Live Status</TableHead>
<TableHead className="text-right">Status</TableHead> <TableHead className="text-right">Status</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{allRuns.map((run) => ( {allRuns.map((run) => (
<RunDisplay run={run} key={run.id} /> <RunDisplay run={run} key={run.id} />
))} ))}
</TableBody> </TableBody>
</Table> </Table>
</div> </div>
{Math.ceil(total / itemPerPage) > 0 && ( {Math.ceil(total / itemPerPage) > 0 && (
<PaginationControl <PaginationControl
totalPage={Math.ceil(total / itemPerPage)} totalPage={Math.ceil(total / itemPerPage)}
currentPage={page} currentPage={page}
/> />
)} )}
</div> </div>
); );
} }
export async function DeploymentsTable(props: { workflow_id: string }) { export async function DeploymentsTable(props: { workflow_id: string }) {
+2 -3
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import { useServerActionData } from "./useServerActionData";
import { ButtonAction } from "@/components/ButtonActionLoader"; import { ButtonAction } from "@/components/ButtonActionLoader";
import { UpdateModal } from "@/components/InsertModal"; import { UpdateModal } from "@/components/InsertModal";
import { LoadingPageWrapper } from "@/components/LoadingWrapper"; import { LoadingPageWrapper } from "@/components/LoadingWrapper";
@@ -14,7 +15,6 @@ import { ExternalLink } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { useServerActionData } from "./useServerActionData";
export function SharePageSettings({ export function SharePageSettings({
deployment_id, deployment_id,
@@ -58,14 +58,13 @@ export function SharePageSettings({
type="button" type="button"
> >
<ButtonAction <ButtonAction
routerAction="back"
action={removePublicShareDeployment.bind(null, deployment.id)} action={removePublicShareDeployment.bind(null, deployment.id)}
> >
Remove Remove
</ButtonAction> </ButtonAction>
</Button> </Button>
<Button asChild className="gap-2 truncate" type="button"> <Button asChild className="gap-2 truncate" type="button">
<Link href={`/share/${deployment.share_slug ?? deployment.id}`} target="_blank"> <Link href={`/share/${deployment.id}`} target="_blank">
View Share Page <ExternalLink size={14} /> View Share Page <ExternalLink size={14} />
</Link> </Link>
</Button> </Button>
+66 -12
View File
@@ -1,5 +1,8 @@
"use client"; "use client";
import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod";
import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher";
import { LoadingIcon } from "@/components/LoadingIcon"; import { LoadingIcon } from "@/components/LoadingIcon";
import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form"; import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@@ -41,16 +44,20 @@ import { checkStatus, createRun } from "@/server/createRun";
import { createDeployments } from "@/server/curdDeploments"; import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine"; import type { getMachines } from "@/server/curdMachine";
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion"; import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
import { Copy, ExternalLink, Info, MoreVertical, Play } from "lucide-react"; import {
Copy,
ExternalLink,
Info,
MoreVertical,
Play,
Share,
} from "lucide-react";
import { parseAsInteger, useQueryState } from "next-usequerystate"; import { parseAsInteger, useQueryState } from "next-usequerystate";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import useSWR from "swr"; import useSWR from "swr";
import type { z } from "zod"; import type { z } from "zod";
import { create } from "zustand"; import { create } from "zustand";
import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod";
import { callServerPromise } from "./callServerPromise";
import fetcher from "./fetcher";
export function VersionSelect({ export function VersionSelect({
workflow, workflow,
@@ -115,14 +122,12 @@ export function MachineSelect({
); );
} }
export function useSelectedMachine( function useSelectedMachine(machines: Awaited<ReturnType<typeof getMachines>>) {
machines: Awaited<ReturnType<typeof getMachines>>, const a = useQueryState("machine", {
) { defaultValue: machines?.[0]?.id ?? "",
const a = useQueryState("machine", { });
defaultValue: machines?.[0]?.id ?? "",
});
return a; return a;
} }
type PublicRunStore = { type PublicRunStore = {
@@ -368,6 +373,55 @@ export function CreateDeploymentButton({
); );
} }
export function CreateShareButton({
workflow,
machines,
}: {
workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
machines: Awaited<ReturnType<typeof getMachines>>;
}) {
const [version] = useQueryState("version", {
defaultValue: workflow?.versions[0].version ?? 1,
...parseAsInteger,
});
const [machine] = useSelectedMachine(machines);
const [isLoading, setIsLoading] = useState(false);
const workflow_version_id = workflow?.versions.find(
(x) => x.version === version
)?.id;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="gap-2" disabled={isLoading} variant="outline">
Share {isLoading ? <LoadingIcon /> : <Share size={14} />}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuItem
onClick={async () => {
if (!workflow_version_id) return;
setIsLoading(true);
await callServerPromise(
createDeployments(
workflow.id,
workflow_version_id,
machine,
"public-share"
)
);
setIsLoading(false);
}}
>
Public
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
export function CopyWorkflowVersion({ export function CopyWorkflowVersion({
workflow, workflow,
}: { }: {
@@ -544,7 +598,7 @@ export function ViewWorkflowDetailsButton({
<a <a
href={group.url} href={group.url}
target="_blank" target="_blank"
className="hover:underline" rel="noreferrer" className="hover:underline"
> >
{key} {key}
<ExternalLink <ExternalLink
+104 -102
View File
@@ -42,105 +42,105 @@ const Model = z.object({
url: z.string(), url: z.string(),
}); });
export const CivitalModelSchema = z.object({ export const CivitaiModel = z.object({
items: z.array( id: z.number(),
name: z.string(),
description: z.string(),
type: z.string(),
// poi: z.boolean(),
// nsfw: z.boolean(),
// allowNoCredit: z.boolean(),
// allowCommercialUse: z.string(),
// allowDerivatives: z.boolean(),
// allowDifferentLicense: z.boolean(),
// stats: z.object({
// downloadCount: z.number(),
// favoriteCount: z.number(),
// commentCount: z.number(),
// ratingCount: z.number(),
// rating: z.number(),
// tippedAmountCount: z.number(),
// }),
creator: z
.object({
username: z.string().nullable(),
image: z.string().nullable().default(null),
})
.nullable(),
tags: z.array(z.string()),
modelVersions: z.array(
z.object({ z.object({
id: z.number(), id: z.number(),
modelId: z.number(),
name: z.string(), name: z.string(),
description: z.string(), createdAt: z.string(),
type: z.string(), updatedAt: z.string(),
// poi: z.boolean(), status: z.string(),
// nsfw: z.boolean(), publishedAt: z.string(),
// allowNoCredit: z.boolean(), trainedWords: z.array(z.unknown()),
// allowCommercialUse: z.string(), trainingStatus: z.string().nullable(),
// allowDerivatives: z.boolean(), trainingDetails: z.string().nullable(),
// allowDifferentLicense: z.boolean(), baseModel: z.string(),
// stats: z.object({ baseModelType: z.string().nullable(),
// downloadCount: z.number(), earlyAccessTimeFrame: z.number(),
// favoriteCount: z.number(), description: z.string().nullable(),
// commentCount: z.number(), vaeId: z.number().nullable(),
// ratingCount: z.number(), stats: z.object({
// rating: z.number(), downloadCount: z.number(),
// tippedAmountCount: z.number(), ratingCount: z.number(),
// }), rating: z.number(),
creator: z }),
.object({ files: z.array(
username: z.string().nullable(),
image: z.string().nullable().default(null),
})
.nullable(),
tags: z.array(z.string()),
modelVersions: z.array(
z.object({ z.object({
id: z.number(), id: z.number(),
modelId: z.number(), sizeKB: z.number(),
name: z.string(), name: z.string(),
createdAt: z.string(), type: z.string(),
updatedAt: z.string(), // metadata: z.object({
status: z.string(), // fp: z.string().nullable().optional(),
publishedAt: z.string(), // size: z.string().nullable().optional(),
trainedWords: z.array(z.unknown()), // format: z.string().nullable().optional(),
trainingStatus: z.string().nullable(), // }),
trainingDetails: z.string().nullable(), // pickleScanResult: z.string(),
baseModel: z.string(), // pickleScanMessage: z.string(),
baseModelType: z.string().nullable(), // virusScanResult: z.string(),
earlyAccessTimeFrame: z.number(), // virusScanMessage: z.string().nullable(),
description: z.string().nullable(), // scannedAt: z.string(),
vaeId: z.number().nullable(), // hashes: z.object({
stats: z.object({ // AutoV1: z.string().nullable().optional(),
downloadCount: z.number(), // AutoV2: z.string().nullable().optional(),
ratingCount: z.number(), // SHA256: z.string().nullable().optional(),
rating: z.number(), // CRC32: z.string().nullable().optional(),
}), // BLAKE3: z.string().nullable().optional(),
files: z.array( // }),
z.object({
id: z.number(),
sizeKB: z.number(),
name: z.string(),
type: z.string(),
// metadata: z.object({
// fp: z.string().nullable().optional(),
// size: z.string().nullable().optional(),
// format: z.string().nullable().optional(),
// }),
// pickleScanResult: z.string(),
// pickleScanMessage: z.string(),
// virusScanResult: z.string(),
// virusScanMessage: z.string().nullable(),
// scannedAt: z.string(),
// hashes: z.object({
// AutoV1: z.string().nullable().optional(),
// AutoV2: z.string().nullable().optional(),
// SHA256: z.string().nullable().optional(),
// CRC32: z.string().nullable().optional(),
// BLAKE3: z.string().nullable().optional(),
// }),
downloadUrl: z.string(),
// primary: z.boolean().default(false),
})
),
images: z.array(
z.object({
id: z.number(),
url: z.string(),
nsfw: z.string(),
width: z.number(),
height: z.number(),
hash: z.string(),
type: z.string(),
metadata: z.object({
hash: z.string(),
width: z.number(),
height: z.number(),
}),
meta: z.any(),
})
),
downloadUrl: z.string(), downloadUrl: z.string(),
}) // primary: z.boolean().default(false),
}),
), ),
}) images: z.array(
z.object({
id: z.number(),
url: z.string(),
nsfw: z.string(),
width: z.number(),
height: z.number(),
hash: z.string(),
type: z.string(),
metadata: z.object({
hash: z.string(),
width: z.number(),
height: z.number(),
}),
meta: z.any(),
}),
),
downloadUrl: z.string(),
}),
), ),
});
export const CivitalModelSchema = z.object({
items: z.array(CivitaiModel),
metadata: z.object({ metadata: z.object({
totalItems: z.number(), totalItems: z.number(),
currentPage: z.number(), currentPage: z.number(),
@@ -197,7 +197,7 @@ function mapType(type: string) {
} }
function mapModelsList( function mapModelsList(
models: z.infer<typeof CivitalModelSchema> models: z.infer<typeof CivitalModelSchema>,
): z.infer<typeof ModelListWrapper> { ): z.infer<typeof ModelListWrapper> {
return { return {
models: models.items.flatMap((item) => { models: models.items.flatMap((item) => {
@@ -241,8 +241,9 @@ function getUrl(search?: string) {
export function CivitaiModelRegistry({ export function CivitaiModelRegistry({
field, field,
}: Pick<AutoFormInputComponentProps, "field">) { }: Pick<AutoFormInputComponentProps, "field">) {
const [modelList, setModelList] = const [modelList, setModelList] = React.useState<
React.useState<z.infer<typeof ModelListWrapper>>(); z.infer<typeof ModelListWrapper>
>();
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
@@ -301,8 +302,9 @@ export function CivitaiModelRegistry({
export function ComfyUIManagerModelRegistry({ export function ComfyUIManagerModelRegistry({
field, field,
}: Pick<AutoFormInputComponentProps, "field">) { }: Pick<AutoFormInputComponentProps, "field">) {
const [modelList, setModelList] = const [modelList, setModelList] = React.useState<
React.useState<z.infer<typeof ModelListWrapper>>(); z.infer<typeof ModelListWrapper>
>();
React.useEffect(() => { React.useEffect(() => {
const controller = new AbortController(); const controller = new AbortController();
@@ -310,7 +312,7 @@ export function ComfyUIManagerModelRegistry({
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json", "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
{ {
signal: controller.signal, signal: controller.signal,
} },
) )
.then((x) => x.json()) .then((x) => x.json())
.then((a) => { .then((a) => {
@@ -353,14 +355,14 @@ export function ModelSelector({
if ( if (
prevSelectedModels.some( prevSelectedModels.some(
(selectedModel) => (selectedModel) =>
selectedModel.url + selectedModel.name === model.url + model.name selectedModel.url + selectedModel.name === model.url + model.name,
) )
) { ) {
field.onChange( field.onChange(
prevSelectedModels.filter( prevSelectedModels.filter(
(selectedModel) => (selectedModel) =>
selectedModel.url + selectedModel.name !== model.url + model.name selectedModel.url + selectedModel.name !== model.url + model.name,
) ),
); );
} else { } else {
field.onChange([...prevSelectedModels, model]); field.onChange([...prevSelectedModels, model]);
@@ -408,10 +410,10 @@ export function ModelSelector({
className={cn( className={cn(
"ml-auto h-4 w-4", "ml-auto h-4 w-4",
value.some( value.some(
(selectedModel) => selectedModel.url === model.url (selectedModel) => selectedModel.url === model.url,
) )
? "opacity-100" ? "opacity-100"
: "opacity-0" : "opacity-0",
)} )}
/> />
</CommandItem> </CommandItem>
@@ -0,0 +1,89 @@
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import { FormControl, FormItem, FormLabel } from "../ui/form";
import { LoadingIcon } from "@/components/LoadingIcon";
import * as React from "react";
import AutoFormInput from "../ui/auto-form/fields/input";
import { useDebouncedCallback } from "use-debounce";
import { CivitaiModel } from "./ModelPickerView";
import { z } from "zod";
import { insertCivitaiCheckpointSchema } from "@/db/schema";
function getUrl(civitai_url: string) {
// expect to be a URL to be https://civitai.com/models/36520
// possiblity with slugged name and query-param modelVersionId
const baseUrl = "https://civitai.com/api/v1/models/";
const url = new URL(civitai_url);
const pathSegments = url.pathname.split("/");
const modelId = pathSegments[pathSegments.indexOf("models") + 1];
const modelVersionId = url.searchParams.get("modelVersionId");
return { url: baseUrl + modelId, modelVersionId };
}
export default function AutoFormCheckpointInput(
props: AutoFormInputComponentProps,
) {
const [loading, setLoading] = React.useState(false);
const [modelRes, setModelRes] = React.useState<
z.infer<typeof CivitaiModel>
>();
const [modelVersionid, setModelVersionId] = React.useState<string | null>();
const { label, isRequired, fieldProps, zodItem, fieldConfigItem } = props;
const handleSearch = useDebouncedCallback((search) => {
const validationResult = insertCivitaiCheckpointSchema.shape.civitai_url
.safeParse(search);
if (!validationResult.success) {
console.error(validationResult.error);
// Optionally set an error state here
return;
}
setLoading(true);
const controller = new AbortController();
const { url, modelVersionId: versionId } = getUrl(search);
setModelVersionId(versionId);
fetch(url, {
signal: controller.signal,
})
.then((x) => x.json())
.then((a) => {
const res = CivitaiModel.parse(a);
console.log(a);
console.log(res);
setModelRes(res);
setLoading(false);
});
return () => {
controller.abort();
setLoading(false);
};
}, 300);
const modifiedField = {
...fieldProps,
// onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
// handleSearch(event.target.value);
// },
};
return (
<FormItem>
{fieldConfigItem.inputProps?.showLabel && (
<FormLabel>
{label}
{isRequired && <span className="text-destructive">*</span>}
</FormLabel>
)}
<FormControl>
<AutoFormInput
{...props}
fieldProps={modifiedField}
/>
</FormControl>
</FormItem>
);
}
+6 -8
View File
@@ -1,10 +1,8 @@
export const customInputNodes: Record<string, string> = { export const customInputNodes: Record<string, string> = {
ComfyUIDeployExternalText: "string", ComfyUIDeployExternalText: "string",
ComfyUIDeployExternalImage: "string - (public image url)", ComfyUIDeployExternalImage: "string - (public image url)",
ComfyUIDeployExternalImageAlpha: "string - (public image url)", ComfyUIDeployExternalImageAlpha: "string - (public image url)",
ComfyUIDeployExternalNumber: "float", ComfyUIDeployExternalNumber: "float",
ComfyUIDeployExternalNumberInt: "integer", ComfyUIDeployExternalNumberInt: "integer",
ComfyUIDeployExternalLora: "string - (public lora download url)", ComfyUIDeployExternalLora: "string - (public lora download url)",
ComfyUIDeployExternalCheckpoints:
"string - (public checkpoints download url)",
}; };
+1 -1
View File
@@ -69,7 +69,7 @@ const FeedbackThanks = forwardRef<React.ElementRef<'div'>>(
) )
export function Feedback() { export function Feedback() {
const [submitted, setSubmitted] = useState(false) let [submitted, setSubmitted] = useState(false)
function onSubmit(event: React.FormEvent<HTMLFormElement>) { function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault() event.preventDefault()
+1 -1
View File
@@ -8,7 +8,7 @@ export function Prose<T extends React.ElementType = 'div'>({
as?: T as?: T
className?: string className?: string
}) { }) {
const Component = as ?? 'div' let Component = as ?? 'div'
return ( return (
<Component <Component
+3 -3
View File
@@ -22,9 +22,9 @@ function MoonIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
} }
export function ThemeToggle() { export function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme() let { resolvedTheme, setTheme } = useTheme()
const otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark' let otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark'
const [mounted, setMounted] = useState(false) let [mounted, setMounted] = useState(false)
useEffect(() => { useEffect(() => {
setMounted(true) setMounted(true)
@@ -8,6 +8,7 @@ import AutoFormSwitch from "./fields/switch";
import AutoFormTextarea from "./fields/textarea"; import AutoFormTextarea from "./fields/textarea";
import AutoFormModelsPicker from "@/components/custom-form/model-picker"; import AutoFormModelsPicker from "@/components/custom-form/model-picker";
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker"; import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
import AutoFormCheckpointInput from "@/components/custom-form/checkpoint-input";
export const INPUT_COMPONENTS = { export const INPUT_COMPONENTS = {
checkbox: AutoFormCheckbox, checkbox: AutoFormCheckbox,
@@ -22,6 +23,7 @@ export const INPUT_COMPONENTS = {
// Customs // Customs
snapshot: AutoFormSnapshotPicker, snapshot: AutoFormSnapshotPicker,
models: AutoFormModelsPicker, models: AutoFormModelsPicker,
checkpoints: AutoFormCheckpointInput,
}; };
/** /**
+1 -9
View File
@@ -2,18 +2,10 @@ import * as schema from "./schema";
import { neonConfig, Pool } from "@neondatabase/serverless"; import { neonConfig, Pool } from "@neondatabase/serverless";
import { drizzle as neonDrizzle } from "drizzle-orm/neon-serverless"; import { drizzle as neonDrizzle } from "drizzle-orm/neon-serverless";
const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
// if we're running locally // if we're running locally
if (process.env.VERCEL_ENV !== "production") { if (process.env.VERCEL_ENV !== "production") {
// Set the WebSocket proxy to work with the local instance // Set the WebSocket proxy to work with the local instance
if (isDevContainer) { neonConfig.wsProxy = (host) => `${host}:5481/v1`;
// Running inside a VS Code devcontainer
neonConfig.wsProxy = (host) => `host.docker.internal:5481/v1`;
} else {
// Not running inside a VS Code devcontainer
neonConfig.wsProxy = (host) => `${host}:5481/v1`;
}
// Disable all authentication and encryption // Disable all authentication and encryption
neonConfig.useSecureWebSocket = false; neonConfig.useSecureWebSocket = false;
neonConfig.pipelineTLS = false; neonConfig.pipelineTLS = false;
+106 -21
View File
@@ -1,3 +1,4 @@
import { CivitaiModelResponse } from "@/types/civitai";
import { type InferSelectModel, relations } from "drizzle-orm"; import { type InferSelectModel, relations } from "drizzle-orm";
import { import {
boolean, boolean,
@@ -8,7 +9,6 @@ import {
text, text,
timestamp, timestamp,
uuid, uuid,
real,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
@@ -88,7 +88,7 @@ export const workflowVersionRelations = relations(
fields: [workflowVersionTable.workflow_id], fields: [workflowVersionTable.workflow_id],
references: [workflowTable.id], references: [workflowTable.id],
}), }),
}) }),
); );
export const workflowRunStatus = pgEnum("workflow_run_status", [ export const workflowRunStatus = pgEnum("workflow_run_status", [
@@ -137,10 +137,11 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
() => workflowVersionTable.id, () => workflowVersionTable.id,
{ {
onDelete: "set null", onDelete: "set null",
} },
), ),
workflow_inputs: workflow_inputs: jsonb("workflow_inputs").$type<
jsonb("workflow_inputs").$type<Record<string, string | number>>(), Record<string, string | number>
>(),
workflow_id: uuid("workflow_id") workflow_id: uuid("workflow_id")
.notNull() .notNull()
.references(() => workflowTable.id, { .references(() => workflowTable.id, {
@@ -154,7 +155,6 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
status: workflowRunStatus("status").notNull().default("not-started"), status: workflowRunStatus("status").notNull().default("not-started"),
ended_at: timestamp("ended_at"), ended_at: timestamp("ended_at"),
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
started_at: timestamp("started_at"),
}); });
export const workflowRunRelations = relations( export const workflowRunRelations = relations(
@@ -173,7 +173,7 @@ export const workflowRunRelations = relations(
fields: [workflowRunsTable.workflow_id], fields: [workflowRunsTable.workflow_id],
references: [workflowTable.id], references: [workflowTable.id],
}), }),
}) }),
); );
// We still want to keep the workflow run record. // We still want to keep the workflow run record.
@@ -197,7 +197,7 @@ export const workflowOutputRelations = relations(
fields: [workflowRunOutputs.run_id], fields: [workflowRunOutputs.run_id],
references: [workflowRunsTable.id], references: [workflowRunsTable.id],
}), }),
}) }),
); );
// when user delete, also delete all the workflow versions // when user delete, also delete all the workflow versions
@@ -230,7 +230,7 @@ export const snapshotType = z.object({
z.object({ z.object({
hash: z.string(), hash: z.string(),
disabled: z.boolean(), disabled: z.boolean(),
}) }),
), ),
file_custom_nodes: z.array(z.any()), file_custom_nodes: z.array(z.any()),
}); });
@@ -245,7 +245,7 @@ export const showcaseMedia = z.array(
z.object({ z.object({
url: z.string(), url: z.string(),
isCover: z.boolean().default(false), isCover: z.boolean().default(false),
}) }),
); );
export const showcaseMediaNullable = z export const showcaseMediaNullable = z
@@ -253,7 +253,7 @@ export const showcaseMediaNullable = z
z.object({ z.object({
url: z.string(), url: z.string(),
isCover: z.boolean().default(false), isCover: z.boolean().default(false),
}) }),
) )
.nullable(); .nullable();
@@ -276,10 +276,10 @@ export const deploymentsTable = dbSchema.table("deployments", {
machine_id: uuid("machine_id") machine_id: uuid("machine_id")
.notNull() .notNull()
.references(() => machinesTable.id), .references(() => machinesTable.id),
share_slug: text("share_slug").unique(),
description: text("description"), description: text("description"),
showcase_media: showcase_media: jsonb("showcase_media").$type<
jsonb("showcase_media").$type<z.infer<typeof showcaseMedia>>(), z.infer<typeof showcaseMedia>
>(),
environment: deploymentEnvironment("environment").notNull(), environment: deploymentEnvironment("environment").notNull(),
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
@@ -332,22 +332,107 @@ export const apiKeyTable = dbSchema.table("api_keys", {
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
}); });
export const userUsageTable = dbSchema.table("user_usage", { export const resourceUpload = pgEnum("resource_upload", [
"started",
"error",
"succeded",
]);
export const modelUploadType = pgEnum("model_upload_type", [
"civitai",
"huggingface",
"other",
]);
export const checkpointTable = dbSchema.table("checkpoints", {
id: uuid("id").primaryKey().defaultRandom().notNull(), id: uuid("id").primaryKey().defaultRandom().notNull(),
user_id: text("user_id")
.references(() => usersTable.id, {}), // perhaps a "special" user_id for global checkpoints
org_id: text("org_id"), org_id: text("org_id"),
description: text("description"),
checkpoint_volume_id: uuid("checkpoint_volume_id")
.notNull()
.references(() => workflowRunsTable.id, {
onDelete: "cascade",
}).notNull(),
model_name: text("model_name"),
civitai_id: text("civitai_id"),
civitai_version_id: text("civitai_version_id"),
civitai_url: text("civitai_url"),
civitai_download_url: text("civitai_download_url"),
civitai_model_response: jsonb("civitai_model_response").$type<
z.infer<typeof CivitaiModelResponse>
>(),
hf_url: text("hf_url"),
s3_url: text("s3_url"),
user_url: text("client_url"),
is_public: boolean("is_public").notNull().default(false),
status: resourceUpload("status").notNull().default("started"),
upload_machine_id: text("upload_machine_id"),
upload_type: modelUploadType("upload_type").notNull(),
build_log: text("build_log"),
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export const insertCivitaiCheckpointSchema = createInsertSchema(
checkpointTable,
{
civitai_url: (schema) =>
schema.civitai_url.trim().url({ message: "URL required" }).includes(
"civitai.com/models",
{ message: "civitai.com/models link required" },
),
},
);
export const checkpointVolumeTable = dbSchema.table("checkpoint_volume", {
id: uuid("id").primaryKey().defaultRandom().notNull(),
user_id: text("user_id") user_id: text("user_id")
.references(() => usersTable.id, { .references(() => usersTable.id, {
onDelete: "cascade", // onDelete: "cascade",
}) }),
.notNull(), org_id: text("org_id"),
usage_time: real("usage_time").default(0).notNull(), volume_name: text("volume_name").notNull(),
created_at: timestamp("created_at").defaultNow().notNull(), created_at: timestamp("created_at").defaultNow().notNull(),
ended_at: timestamp("ended_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
disabled: boolean("disabled").default(false).notNull(),
}); });
export const checkpointRelations = relations(checkpointTable, ({ one }) => ({
user: one(usersTable, {
fields: [checkpointTable.user_id],
references: [usersTable.id],
}),
volume: one(checkpointVolumeTable, {
fields: [checkpointTable.checkpoint_volume_id],
references: [checkpointVolumeTable.id],
}),
}));
export const checkpointVolumeRelations = relations(
checkpointVolumeTable,
({ many, one }) => ({
checkpoint: many(checkpointTable),
user: one(usersTable, {
fields: [checkpointVolumeTable.user_id],
references: [usersTable.id],
}),
}),
);
export type UserType = InferSelectModel<typeof usersTable>; export type UserType = InferSelectModel<typeof usersTable>;
export type WorkflowType = InferSelectModel<typeof workflowTable>; export type WorkflowType = InferSelectModel<typeof workflowTable>;
export type MachineType = InferSelectModel<typeof machinesTable>; export type MachineType = InferSelectModel<typeof machinesTable>;
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>; export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
export type DeploymentType = InferSelectModel<typeof deploymentsTable>; export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
export type UserUsageType = InferSelectModel<typeof userUsageTable>; export type CheckpointType = InferSelectModel<typeof checkpointTable>;
export type CheckpointVolumeType = InferSelectModel<
typeof checkpointVolumeTable
>;
+4 -6
View File
@@ -1,10 +1,8 @@
export function remToPx(remValue: number) { export function remToPx(remValue: number) {
const rootFontSize = let rootFontSize =
typeof window === "undefined" typeof window === 'undefined'
? 16 ? 16
: parseFloat( : parseFloat(window.getComputedStyle(document.documentElement).fontSize)
window.getComputedStyle(document.documentElement).fontSize,
);
return remValue * rootFontSize return remValue * rootFontSize
} }
+10 -10
View File
@@ -27,13 +27,13 @@ function rehypeShiki() {
visit(tree, "element", (node) => { visit(tree, "element", (node) => {
if (node.tagName === "pre" && node.children[0]?.tagName === "code") { if (node.tagName === "pre" && node.children[0]?.tagName === "code") {
const codeNode = node.children[0]; let codeNode = node.children[0];
const textNode = codeNode.children[0]; let textNode = codeNode.children[0];
node.properties.code = textNode.value; node.properties.code = textNode.value;
if (node.properties.language) { if (node.properties.language) {
const tokens = highlighter.codeToThemedTokens( let tokens = highlighter.codeToThemedTokens(
textNode.value, textNode.value,
node.properties.language node.properties.language
); );
@@ -53,7 +53,7 @@ function rehypeShiki() {
function rehypeSlugify() { function rehypeSlugify() {
return (tree) => { return (tree) => {
const slugify = slugifyWithCounter(); let slugify = slugifyWithCounter();
visit(tree, "element", (node) => { visit(tree, "element", (node) => {
if (node.tagName === "h2" && !node.properties.id) { if (node.tagName === "h2" && !node.properties.id) {
node.properties.id = slugify(toString(node)); node.properties.id = slugify(toString(node));
@@ -64,10 +64,10 @@ function rehypeSlugify() {
function rehypeAddMDXExports(getExports) { function rehypeAddMDXExports(getExports) {
return (tree) => { return (tree) => {
const exports = Object.entries(getExports(tree)); let exports = Object.entries(getExports(tree));
for (const [name, value] of exports) { for (let [name, value] of exports) {
for (const node of tree.children) { for (let node of tree.children) {
if ( if (
node.type === "mdxjsEsm" && node.type === "mdxjsEsm" &&
new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value) new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value)
@@ -76,7 +76,7 @@ function rehypeAddMDXExports(getExports) {
} }
} }
const exportStr = `export const ${name} = ${value}`; let exportStr = `export const ${name} = ${value}`;
tree.children.push({ tree.children.push({
type: "mdxjsEsm", type: "mdxjsEsm",
@@ -93,9 +93,9 @@ function rehypeAddMDXExports(getExports) {
} }
function getSections(node) { function getSections(node) {
const sections = []; let sections = [];
for (const child of node.children ?? []) { for (let child of node.children ?? []) {
if (child.type === "element" && child.tagName === "h2") { if (child.type === "element" && child.tagName === "h2") {
sections.push(`{ sections.push(`{
title: ${JSON.stringify(toString(child))}, title: ${JSON.stringify(toString(child))},
+8 -8
View File
@@ -31,9 +31,9 @@ function extractSections() {
visit(tree, (node) => { visit(tree, (node) => {
if (node.type === "heading" || node.type === "paragraph") { if (node.type === "heading" || node.type === "paragraph") {
const content = toString(excludeObjectExpressions(node)); let content = toString(excludeObjectExpressions(node));
if (node.type === "heading" && node.depth <= 2) { if (node.type === "heading" && node.depth <= 2) {
const hash = node.depth === 1 ? null : slugify(content); let hash = node.depth === 1 ? null : slugify(content);
sections.push([content, hash, []]); sections.push([content, hash, []]);
} else { } else {
sections.at(-1)?.[2].push(content); sections.at(-1)?.[2].push(content);
@@ -45,7 +45,7 @@ function extractSections() {
} }
export default function (nextConfig = {}) { export default function (nextConfig = {}) {
const cache = new Map(); let cache = new Map();
return Object.assign({}, nextConfig, { return Object.assign({}, nextConfig, {
webpack(config, options) { webpack(config, options) {
@@ -53,20 +53,20 @@ export default function (nextConfig = {}) {
test: __filename, test: __filename,
use: [ use: [
createLoader(function () { createLoader(function () {
const appDir = path.resolve("./src/app/(docs)/docs"); let appDir = path.resolve("./src/app/(docs)/docs");
this.addContextDependency(appDir); this.addContextDependency(appDir);
const files = glob.sync("**/*.mdx", { cwd: appDir }); let files = glob.sync("**/*.mdx", { cwd: appDir });
const data = files.map((file) => { let data = files.map((file) => {
let url = `/${file.replace(/(^|\/)page\.mdx$/, "")}`; let url = `/${file.replace(/(^|\/)page\.mdx$/, "")}`;
const mdx = fs.readFileSync(path.join(appDir, file), "utf8"); let mdx = fs.readFileSync(path.join(appDir, file), "utf8");
let sections = []; let sections = [];
if (cache.get(file)?.[0] === mdx) { if (cache.get(file)?.[0] === mdx) {
sections = cache.get(file)[1]; sections = cache.get(file)[1];
} else { } else {
const vfile = { value: mdx, sections }; let vfile = { value: mdx, sections };
processor.runSync(processor.parse(vfile), vfile); processor.runSync(processor.parse(vfile), vfile);
cache.set(file, [mdx, sections]); cache.set(file, [mdx, sections]);
} }
-2
View File
@@ -1,6 +1,4 @@
import type { ResponseConfig } from "@asteasolutions/zod-to-openapi"; import type { ResponseConfig } from "@asteasolutions/zod-to-openapi";
import { z } from "@hono/zod-openapi"; import { z } from "@hono/zod-openapi";
export const authError = { export const authError = {
+5
View File
@@ -0,0 +1,5 @@
import { insertCivitaiCheckpointSchema } from "@/db/schema";
export const addCivitaiCheckpointSchema = insertCivitaiCheckpointSchema.pick({
civitai_url: true,
});
+197 -206
View File
@@ -1,10 +1,11 @@
"use server"; "use server";
import { withServerPromise } from "./withServerPromise";
import { db } from "@/db/db"; import { db } from "@/db/db";
import type { import type {
MachineType, MachineType,
WorkflowRunOriginType, WorkflowRunOriginType,
WorkflowVersionType, WorkflowVersionType,
} from "@/db/schema"; } from "@/db/schema";
import { machinesTable, workflowRunsTable } from "@/db/schema"; import { machinesTable, workflowRunsTable } from "@/db/schema";
import type { APIKeyUserType } from "@/server/APIKeyBodyRequest"; import type { APIKeyUserType } from "@/server/APIKeyBodyRequest";
@@ -15,229 +16,219 @@ import { and, eq } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import "server-only"; import "server-only";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { withServerPromise } from "./withServerPromise";
export const createRun = withServerPromise( export const createRun = withServerPromise(
async ({ async ({
origin, origin,
workflow_version_id, workflow_version_id,
machine_id, machine_id,
inputs, inputs,
runOrigin, runOrigin,
apiUser, apiUser,
}: { }: {
origin: string; origin: string;
workflow_version_id: string | WorkflowVersionType; workflow_version_id: string | WorkflowVersionType;
machine_id: string | MachineType; machine_id: string | MachineType;
inputs?: Record<string, string | number>; inputs?: Record<string, string | number>;
runOrigin?: WorkflowRunOriginType; runOrigin?: WorkflowRunOriginType;
apiUser?: APIKeyUserType; apiUser?: APIKeyUserType;
}) => { }) => {
const machine = const machine =
typeof machine_id === "string" typeof machine_id === "string"
? await db.query.machinesTable.findFirst({ ? await db.query.machinesTable.findFirst({
where: and( where: and(
eq(machinesTable.id, machine_id), eq(machinesTable.id, machine_id),
eq(machinesTable.disabled, false), eq(machinesTable.disabled, false)
), ),
}) })
: machine_id; : machine_id;
if (!machine) { if (!machine) {
throw new Error("Machine not found"); throw new Error("Machine not found");
} }
const workflow_version_data = const workflow_version_data =
typeof workflow_version_id === "string" typeof workflow_version_id === "string"
? await db.query.workflowVersionTable.findFirst({ ? await db.query.workflowVersionTable.findFirst({
where: eq(workflowRunsTable.id, workflow_version_id), where: eq(workflowRunsTable.id, workflow_version_id),
with: { with: {
workflow: { workflow: {
columns: { columns: {
org_id: true, org_id: true,
user_id: true, user_id: true,
}, },
}, },
}, },
}) })
: workflow_version_id; : workflow_version_id;
if (!workflow_version_data) { if (!workflow_version_data) {
throw new Error("Workflow version not found"); throw new Error("Workflow version not found");
} }
if (apiUser) if (apiUser)
if (apiUser.org_id) { if (apiUser.org_id) {
// is org api call, check org only // is org api call, check org only
if (apiUser.org_id != workflow_version_data.workflow.org_id) { if (apiUser.org_id != workflow_version_data.workflow.org_id) {
throw new Error("Workflow not found"); throw new Error("Workflow not found");
} }
} else { } else {
// is user api call, check user only // is user api call, check user only
if ( if (
apiUser.user_id != workflow_version_data.workflow.user_id && apiUser.user_id != workflow_version_data.workflow.user_id &&
workflow_version_data.workflow.org_id == null workflow_version_data.workflow.org_id == null
) { ) {
throw new Error("Workflow not found"); throw new Error("Workflow not found");
} }
} }
const workflow_api = workflow_version_data.workflow_api; const workflow_api = workflow_version_data.workflow_api;
// Replace the inputs // Replace the inputs
if (inputs && workflow_api) { if (inputs && workflow_api) {
for (const key in inputs) { for (const key in inputs) {
Object.entries(workflow_api).forEach(([_, node]) => { Object.entries(workflow_api).forEach(([_, node]) => {
if (node.inputs["input_id"] === key) { if (node.inputs["input_id"] === key) {
node.inputs["input_id"] = inputs[key]; node.inputs["input_id"] = inputs[key];
} }
}); });
} }
} }
let prompt_id: string | undefined = undefined; let prompt_id: string | undefined = undefined;
const shareData = { const shareData = {
workflow_api: workflow_api, workflow_api: workflow_api,
status_endpoint: `${origin}/api/update-run`, status_endpoint: `${origin}/api/update-run`,
file_upload_endpoint: `${origin}/api/file-upload`, file_upload_endpoint: `${origin}/api/file-upload`,
}; };
prompt_id = v4(); prompt_id = v4();
// Add to our db // Add to our db
const workflow_run = await db const workflow_run = await db
.insert(workflowRunsTable) .insert(workflowRunsTable)
.values({ .values({
id: prompt_id, id: prompt_id,
workflow_id: workflow_version_data.workflow_id, workflow_id: workflow_version_data.workflow_id,
workflow_version_id: workflow_version_data.id, workflow_version_id: workflow_version_data.id,
workflow_inputs: inputs, workflow_inputs: inputs,
machine_id: machine.id, machine_id: machine.id,
origin: runOrigin, origin: runOrigin,
}) })
.returning(); .returning();
revalidatePath(`/${workflow_version_data.workflow_id}`); revalidatePath(`/${workflow_version_data.workflow_id}`);
try { try {
switch (machine.type) { switch (machine.type) {
case "comfy-deploy-serverless": case "comfy-deploy-serverless":
case "modal-serverless": case "modal-serverless":
const _data = { const _data = {
input: { input: {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}, },
}; };
const ___result = await fetch(`${machine.endpoint}/run`, { const ___result = await fetch(`${machine.endpoint}/run`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(_data), body: JSON.stringify(_data),
cache: "no-store", cache: "no-store",
}); });
console.log(___result); console.log(___result);
if (!___result.ok) if (!___result.ok)
throw new Error( throw new Error(
`Error creating run, ${ `Error creating run, ${
___result.statusText ___result.statusText
} ${await ___result.text()}`, } ${await ___result.text()}`
); );
console.log(_data, ___result); console.log(_data, ___result);
break; break;
case "runpod-serverless": case "runpod-serverless":
const data = { const data = {
input: { input: {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}, },
}; };
if ( if (
!machine.auth_token && !machine.auth_token &&
!machine.endpoint.includes("localhost") && !machine.endpoint.includes("localhost") &&
!machine.endpoint.includes("127.0.0.1") !machine.endpoint.includes("127.0.0.1")
) { ) {
throw new Error("Machine auth token not found"); throw new Error("Machine auth token not found");
} }
const __result = await fetch(`${machine.endpoint}/run`, { const __result = await fetch(`${machine.endpoint}/run`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${machine.auth_token}`, Authorization: `Bearer ${machine.auth_token}`,
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
cache: "no-store", cache: "no-store",
}); });
console.log(__result); console.log(__result);
if (!__result.ok) if (!__result.ok)
throw new Error( throw new Error(
`Error creating run, ${ `Error creating run, ${
__result.statusText __result.statusText
} ${await __result.text()}`, } ${await __result.text()}`
); );
console.log(data, __result); console.log(data, __result);
break; break;
case "classic": case "classic":
const body = { const body = {
...shareData, ...shareData,
prompt_id: prompt_id, prompt_id: prompt_id,
}; };
// console.log(body); // console.log(body);
const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`; const comfyui_endpoint = `${machine.endpoint}/comfyui-deploy/run`;
const _result = await fetch(comfyui_endpoint, { const _result = await fetch(comfyui_endpoint, {
method: "POST", method: "POST",
body: JSON.stringify(body), body: JSON.stringify(body),
cache: "no-store", cache: "no-store",
}); });
// console.log(_result); // console.log(_result);
if (!_result.ok) { if (!_result.ok) {
let message = `Error creating run, ${_result.statusText}`; let message = `Error creating run, ${_result.statusText}`;
try { try {
const result = await ComfyAPI_Run.parseAsync( const result = await ComfyAPI_Run.parseAsync(
await _result.json(), await _result.json()
); );
message += ` ${result.node_errors}`; message += ` ${result.node_errors}`;
} catch (error) {} } catch (error) {}
throw new Error(message); throw new Error(message);
} }
// prompt_id = result.prompt_id; // prompt_id = result.prompt_id;
break; break;
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
await db await db
.update(workflowRunsTable) .update(workflowRunsTable)
.set({ .set({
status: "failed", status: "failed",
}) })
.where(eq(workflowRunsTable.id, workflow_run[0].id)); .where(eq(workflowRunsTable.id, workflow_run[0].id));
throw e; throw e;
} }
// It successfully started, update the started_at time return {
workflow_run_id: workflow_run[0].id,
await db message: "Successful workflow run",
.update(workflowRunsTable) };
.set({ }
started_at: new Date(),
})
.where(eq(workflowRunsTable.id, workflow_run[0].id));
return {
workflow_run_id: workflow_run[0].id,
message: "Successful workflow run",
};
},
); );
export async function checkStatus(run_id: string) { export async function checkStatus(run_id: string) {
const { userId } = auth(); const { userId } = auth();
if (!userId) throw new Error("User not found"); if (!userId) throw new Error("User not found");
return await getRunsData(run_id); return await getRunsData(run_id);
} }
+224
View File
@@ -0,0 +1,224 @@
"use server";
import { auth } from "@clerk/nextjs";
import {
checkpointTable,
CheckpointType,
volumeTable,
CheckpointVolumeType,
} from "@/db/schema";
import { withServerPromise } from "./withServerPromise";
import { redirect } from "next/navigation";
import { db } from "@/db/db";
import type { z } from "zod";
import { headers } from "next/headers";
import { addCivitaiCheckpointSchema } from "./addCheckpointSchema";
import { and, eq, isNull } from "drizzle-orm";
import { CivitaiModelResponse } from "@/types/civitai";
export async function getCheckpoints() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const checkpoints = await db
.select()
.from(checkpointTable)
.where(
orgId
? eq(checkpointTable.org_id, orgId)
// make sure org_id is null
: and(
eq(checkpointTable.user_id, userId),
isNull(checkpointTable.org_id),
),
);
return checkpoints;
}
export async function getCheckpointById(id: string) {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const checkpoint = await db
.select()
.from(checkpointTable)
.where(
and(
orgId ? eq(checkpointTable.org_id, orgId) : and(
eq(checkpointTable.user_id, userId),
isNull(checkpointTable.org_id),
),
eq(checkpointTable.id, id),
),
);
return checkpoint[0];
}
export async function getCheckpointVolumes() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const checkpointVolume = await db
.select()
.from(volumeTable)
.where(
and(
orgId
? eq(volumeTable.org_id, orgId)
// make sure org_id is null
: and(
eq(volumeTable.user_id, userId),
isNull(volumeTable.org_id),
),
eq(volumeTable.disabled, false),
),
);
return checkpointVolume;
}
export async function addCheckpointVolume() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
// Insert the new volume into the checkpointVolumeTable
const insertedVolume = await db
.insert(volumeTable)
.values({
user_id: userId,
org_id: orgId,
volume_name: `checkpoints_${userId}`,
// created_at and updated_at will be set to current timestamp by default
disabled: false, // Default value
})
.returning(); // Returns the inserted row
return insertedVolume;
}
function getUrl(civitai_url: string) {
// expect to be a URL to be https://civitai.com/models/36520
// possiblity with slugged name and query-param modelVersionId
const baseUrl = "https://civitai.com/api/v1/models/";
const url = new URL(civitai_url);
const pathSegments = url.pathname.split("/");
const modelId = pathSegments[pathSegments.indexOf("models") + 1];
const modelVersionId = url.searchParams.get("modelVersionId");
return { url: baseUrl + modelId, modelVersionId };
}
export const addCivitaiCheckpoint = withServerPromise(
async (data: z.infer<typeof addCivitaiCheckpointSchema>) => {
const { userId, orgId } = auth();
if (!data.civitai_url) return { error: "no civitai_url" };
if (!userId) return { error: "No user id" };
const { url, modelVersionId } = getUrl(data?.civitai_url);
const civitaiModelRes = await fetch(url)
.then((x) => x.json())
.then((a) => {
return CivitaiModelResponse.parse(a);
});
if (civitaiModelRes?.modelVersions?.length === 0) {
return; // no versions to download
}
let selectedModelVersion;
let selectedModelVersionId: string | null = modelVersionId;
if (!selectedModelVersionId) {
selectedModelVersion = civitaiModelRes.modelVersions[0];
selectedModelVersionId = civitaiModelRes.modelVersions[0].id.toString();
} else {
selectedModelVersion = civitaiModelRes.modelVersions.find((version) =>
version.id.toString() === selectedModelVersionId
);
if (!selectedModelVersion) {
return; // version id is wrong
}
selectedModelVersionId = selectedModelVersion?.id.toString();
}
const checkpointVolumes = await getCheckpointVolumes();
let cVolume;
if (checkpointVolumes.length === 0) {
const volume = await addCheckpointVolume();
cVolume = volume[0];
} else {
cVolume = checkpointVolumes[0];
}
const a = await db
.insert(checkpointTable)
.values({
user_id: userId,
org_id: orgId,
upload_type: "civitai",
civitai_id: civitaiModelRes.id.toString(),
civitai_version_id: selectedModelVersionId,
civitai_url: data.civitai_url,
civitai_download_url: selectedModelVersion.downloadUrl,
civitai_model_response: civitaiModelRes,
checkpoint_volume_id: cVolume.id,
})
.returning();
const b = a[0];
await uploadCheckpoint(data, b, cVolume);
redirect(`/checkpoints/${b.id}`);
},
);
async function uploadCheckpoint(
data: z.infer<typeof addCivitaiCheckpointSchema>,
b: CheckpointType,
v: CheckpointVolumeType,
) {
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: data.civitai_url,
volume_name: v.volume_name,
volume_id: v.id,
callback_url: `${protocol}://${domain}/api/volume-updated`,
}),
},
);
if (!result.ok) {
const error_log = await result.text();
await db
.update(checkpointTable)
.set({
...data,
status: "error",
build_log: error_log,
})
.where(eq(checkpointTable.id, b.id));
throw new Error(`Error: ${result.statusText} ${error_log}`);
} else {
// setting the build machine id
const json = await result.json();
await db
.update(checkpointTable)
.set({
...data,
upload_machine_id: json.build_machine_instance_id,
})
.where(eq(checkpointTable.id, b.id));
}
}
+192 -224
View File
@@ -7,279 +7,247 @@ import { createNewWorkflow } from "@/server/createNewWorkflow";
import { addCustomMachine } from "@/server/curdMachine"; import { addCustomMachine } from "@/server/curdMachine";
import { withServerPromise } from "@/server/withServerPromise"; import { withServerPromise } from "@/server/withServerPromise";
import { auth } from "@clerk/nextjs"; import { auth } from "@clerk/nextjs";
import { clerkClient } from "@clerk/nextjs/server"; import { and, eq, isNull } from "drizzle-orm";
import slugify from "@sindresorhus/slugify";
import { and, eq, isNull, or } from "drizzle-orm";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import "server-only"; import "server-only";
import { validate as isValidUUID } from "uuid";
import type { z } from "zod"; import type { z } from "zod";
export async function createDeployments( export async function createDeployments(
workflow_id: string, workflow_id: string,
version_id: string, version_id: string,
machine_id: string, machine_id: string,
environment: DeploymentType["environment"], environment: DeploymentType["environment"]
) { ) {
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
if (!machine_id) { if (!machine_id) {
throw new Error("No machine id provided"); throw new Error("No machine id provided");
} }
// Same environment and same workflow // Same environment and same workflow
const existingDeployment = await db.query.deploymentsTable.findFirst({ const existingDeployment = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.workflow_id, workflow_id), eq(deploymentsTable.workflow_id, workflow_id),
eq(deploymentsTable.environment, environment), eq(deploymentsTable.environment, environment)
), ),
}); });
if (existingDeployment) { if (existingDeployment) {
await db await db
.update(deploymentsTable) .update(deploymentsTable)
.set({ .set({
workflow_id, workflow_id,
workflow_version_id: version_id, workflow_version_id: version_id,
machine_id, machine_id,
org_id: orgId, org_id: orgId,
}) })
.where(eq(deploymentsTable.id, existingDeployment.id)); .where(eq(deploymentsTable.id, existingDeployment.id));
} else { } else {
const workflow = await db.query.workflowTable.findFirst({ await db.insert(deploymentsTable).values({
where: eq(workflowTable.id, workflow_id), user_id: userId,
with: { workflow_id,
user: { workflow_version_id: version_id,
columns: { machine_id,
name: true, environment,
}, org_id: orgId,
}, });
}, }
}); revalidatePath(`/${workflow_id}`);
return {
if (!workflow) throw new Error("No workflow found"); message: `Successfully created deployment for ${environment}`,
};
const userName = workflow.org_id
? await clerkClient.organizations
.getOrganization({
organizationId: workflow.org_id,
})
.then((x) => x.name)
: workflow.user.name;
await db.insert(deploymentsTable).values({
user_id: userId,
workflow_id,
workflow_version_id: version_id,
machine_id,
environment,
org_id: orgId,
share_slug: slugify(`${userName} ${workflow.name}`),
});
}
revalidatePath(`/${workflow_id}`);
return {
message: `Successfully created deployment for ${environment}`,
};
} }
export async function findAllDeployments() { export async function findAllDeployments() {
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
const deployments = await db.query.workflowTable.findMany({ const deployments = await db.query.workflowTable.findMany({
where: and( where: and(
orgId orgId
? eq(workflowTable.org_id, orgId) ? eq(workflowTable.org_id, orgId)
: and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)), : and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id))
), ),
columns: { columns: {
name: true, name: true,
}, },
with: { with: {
deployments: { deployments: {
columns: { columns: {
environment: true, environment: true,
}, },
with: { with: {
version: { version: {
columns: { columns: {
id: true, id: true,
snapshot: true, snapshot: true,
}, },
}, },
}, },
}, },
}, },
}); });
return deployments; return deployments;
} }
export async function findSharedDeployment(workflow_id: string) { export async function findSharedDeployment(workflow_id: string) {
const deploymentData = await db.query.deploymentsTable.findFirst({ const deploymentData = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
isValidUUID(workflow_id) eq(deploymentsTable.id, workflow_id)
? eq(deploymentsTable.id, workflow_id) ),
: eq(deploymentsTable.share_slug, workflow_id), with: {
), user: true,
with: { machine: true,
user: true, workflow: {
machine: true, columns: {
workflow: { name: true,
columns: { org_id: true,
name: true, user_id: true,
org_id: true, },
user_id: true, },
}, version: true,
}, },
version: true, });
},
});
return deploymentData; return deploymentData;
} }
export const removePublicShareDeployment = withServerPromise( export const removePublicShareDeployment = withServerPromise(
async (deployment_id: string) => { async (deployment_id: string) => {
const [removed] = await db await db
.delete(deploymentsTable) .delete(deploymentsTable)
.where( .where(
and( and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, deployment_id), eq(deploymentsTable.id, deployment_id)
), )
).returning(); );
}
// revalidatePath(
// `/workflows/${removed.workflow_id}`
// )
},
); );
export const cloneWorkflow = withServerPromise( export const cloneWorkflow = withServerPromise(
async (deployment_id: string) => { async (deployment_id: string) => {
const deployment = await db.query.deploymentsTable.findFirst({ const deployment = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, deployment_id), eq(deploymentsTable.id, deployment_id)
), ),
with: { with: {
version: true, version: true,
workflow: true, workflow: true,
}, },
}); });
if (!deployment) throw new Error("No deployment found"); if (!deployment) throw new Error("No deployment found");
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
await createNewWorkflow({ await createNewWorkflow({
user_id: userId, user_id: userId,
org_id: orgId, org_id: orgId,
workflow_name: `${deployment.workflow.name} (Cloned)`, workflow_name: `${deployment.workflow.name} (Cloned)`,
workflowData: { workflowData: {
workflow: deployment.version.workflow, workflow: deployment.version.workflow,
workflow_api: deployment?.version.workflow_api, workflow_api: deployment?.version.workflow_api,
snapshot: deployment?.version.snapshot, snapshot: deployment?.version.snapshot,
}, },
}); });
redirect(`/workflows/${deployment.workflow.id}`); redirect(`/workflows/${deployment.workflow.id}`);
return { return {
message: "Successfully cloned workflow", message: "Successfully cloned workflow",
}; };
}, }
); );
export const cloneMachine = withServerPromise(async (deployment_id: string) => { export const cloneMachine = withServerPromise(async (deployment_id: string) => {
const deployment = await db.query.deploymentsTable.findFirst({ const deployment = await db.query.deploymentsTable.findFirst({
where: and( where: and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, deployment_id), eq(deploymentsTable.id, deployment_id)
), ),
with: { with: {
machine: true, machine: true,
}, },
}); });
if (!deployment) throw new Error("No deployment found"); if (!deployment) throw new Error("No deployment found");
if (deployment.machine.type !== "comfy-deploy-serverless") if (deployment.machine.type !== "comfy-deploy-serverless")
throw new Error("Can only clone comfy-deploy-serverlesss"); throw new Error("Can only clone comfy-deploy-serverlesss");
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
await addCustomMachine({ await addCustomMachine({
gpu: deployment.machine.gpu, gpu: deployment.machine.gpu,
models: deployment.machine.models, models: deployment.machine.models,
snapshot: deployment.machine.snapshot, snapshot: deployment.machine.snapshot,
name: `${deployment.machine.name} (Cloned)`, name: `${deployment.machine.name} (Cloned)`,
type: "comfy-deploy-serverless", type: "comfy-deploy-serverless",
}); });
return { return {
message: "Successfully cloned workflow", message: "Successfully cloned workflow",
}; };
}); });
export async function findUserShareDeployment(share_id: string) { export async function findUserShareDeployment(share_id: string) {
const { userId, orgId } = auth(); const { userId, orgId } = auth();
if (!userId) throw new Error("No user id"); if (!userId) throw new Error("No user id");
const [deployment] = await db const [deployment] = await db
.select() .select()
.from(deploymentsTable) .from(deploymentsTable)
.where( .where(
and( and(
isValidUUID(share_id) eq(deploymentsTable.id, share_id),
? eq(deploymentsTable.id, share_id) eq(deploymentsTable.environment, "public-share"),
: eq(deploymentsTable.share_slug, share_id), orgId
eq(deploymentsTable.environment, "public-share"), ? eq(deploymentsTable.org_id, orgId)
orgId : and(
? eq(deploymentsTable.org_id, orgId) eq(deploymentsTable.user_id, userId),
: and( isNull(deploymentsTable.org_id)
eq(deploymentsTable.user_id, userId), )
isNull(deploymentsTable.org_id), )
), );
),
);
if (!deployment) throw new Error("No deployment found"); if (!deployment) throw new Error("No deployment found");
return deployment; return deployment;
} }
export const updateSharePageInfo = withServerPromise( export const updateSharePageInfo = withServerPromise(
async ({ async ({
id, id,
...data ...data
}: z.infer<typeof publicShareDeployment> & { }: z.infer<typeof publicShareDeployment> & {
id: string; id: string;
}) => { }) => {
const { userId } = auth(); const { userId } = auth();
if (!userId) return { error: "No user id" }; if (!userId) return { error: "No user id" };
console.log(data); console.log(data);
const [deployment] = await db const [deployment] = await db
.update(deploymentsTable) .update(deploymentsTable)
.set(data) .set(data)
.where( .where(
and( and(
eq(deploymentsTable.environment, "public-share"), eq(deploymentsTable.environment, "public-share"),
eq(deploymentsTable.id, id), eq(deploymentsTable.id, id)
), )
) )
.returning(); .returning();
return { message: "Info Updated" }; return { message: "Info Updated" };
}, }
); );
+26 -36
View File
@@ -16,42 +16,32 @@ export async function findAllRuns({
offset = 0, offset = 0,
}: RunsSearchTypes) { }: RunsSearchTypes) {
return await db.query.workflowRunsTable.findMany({ return await db.query.workflowRunsTable.findMany({
where: eq(workflowRunsTable.workflow_id, workflow_id), where: eq(workflowRunsTable.workflow_id, workflow_id),
orderBy: desc(workflowRunsTable.created_at), orderBy: desc(workflowRunsTable.created_at),
offset: offset, offset: offset,
limit: limit, limit: limit,
extras: { extras: {
number: sql<number>`row_number() over (order by created_at)`.as( number: sql<number>`row_number() over (order by created_at)`.as("number"),
"number", total: sql<number>`count(*) over ()`.as("total"),
), duration:
total: sql<number>`count(*) over ()`.as("total"), sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
duration: "duration"
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as( ),
"duration", },
), with: {
cold_start_duration: machine: {
sql<number>`(extract(epoch from started_at) - extract(epoch from created_at))`.as( columns: {
"cold_start_duration", name: true,
), endpoint: true,
run_duration: },
sql<number>`(extract(epoch from ended_at) - extract(epoch from started_at))`.as( },
"run_duration", version: {
), columns: {
}, version: true,
with: { },
machine: { },
columns: { },
name: true, });
endpoint: true,
},
},
version: {
columns: {
version: true,
},
},
},
});
} }
export async function findAllRunsWithCounts(props: RunsSearchTypes) { export async function findAllRunsWithCounts(props: RunsSearchTypes) {
+39
View File
@@ -0,0 +1,39 @@
import { db } from "@/db/db";
import {
checkpointTable,
} from "@/db/schema";
import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm";
export async function getAllUserCheckpoints() {
const { userId, orgId } = await auth();
if (!userId) {
return null;
}
const checkpoints = await db.query.checkpointTable.findMany({
with: {
user: {
columns: {
name: true,
},
},
},
columns: {
id: true,
updated_at: true,
name: true,
civitai_url: true,
civitai_model_response: true,
is_public: true,
},
orderBy: desc(checkpointTable.updated_at),
where:
orgId != undefined
? eq(checkpointTable.org_id, orgId)
: and(eq(checkpointTable.user_id, userId), isNull(checkpointTable.org_id)),
});
return checkpoints;
}
-45
View File
@@ -1,45 +0,0 @@
"use server";
import { LemonSqueezy } from "@lemonsqueezy/lemonsqueezy.js";
import "server-only";
const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY || "");
export async function getPricing() {
const products = await ls.getProducts();
return products;
}
export async function getUsage() {
const usageRecord = await ls.getUsageRecords();
return usageRecord;
}
export async function setUsage(id: number, quantity: number) {
const setUsage = await ls.createUsageRecord({
subscriptionItemId: id,
quantity: quantity,
});
return setUsage;
}
export async function getSubscription() {
const subscription = await ls.getSubscriptions();
return subscription;
}
export async function getSubscriptionItem() {
const subscriptionItem = await ls.getSubscriptionItems();
return subscriptionItem;
}
export async function getUserData() {
const user = await ls.getUser();
return user;
}
+126
View File
@@ -0,0 +1,126 @@
import { z } from 'zod';
// from chatgpt https://chat.openai.com/share/4985d20b-30b1-4a28-87f6-6ebf84a1040e
export const creatorSchema = z.object({
username: z.string().optional(),
image: z.string().url().optional(),
});
export const fileMetadataSchema = z.object({
fp: z.string().optional(),
size: z.string().optional(),
format: z.string().optional(),
});
export const fileSchema = z.object({
id: z.number(),
sizeKB: z.number().optional(),
name: z.string(),
type: z.string().optional(),
metadata: fileMetadataSchema.optional(),
pickleScanResult: z.string().optional(),
pickleScanMessage: z.string().nullable(),
virusScanResult: z.string().optional(),
virusScanMessage: z.string().nullable(),
scannedAt: z.string().optional(),
hashes: z.record(z.string()).optional(),
downloadUrl: z.string().url(),
primary: z.boolean().optional().optional(),
});
export const imageMetadataSchema = z.object({
hash: z.string(),
width: z.number(),
height: z.number(),
});
export const imageMetaSchema = z.object({
ENSD: z.string().optional(),
Size: z.string().optional(),
seed: z.number().optional(),
Model: z.string().optional(),
steps: z.number().optional(),
hashes: z.record(z.string()).optional(),
prompt: z.string().optional(),
sampler: z.string().optional(),
cfgScale: z.number().optional(),
ClipSkip: z.number().optional(),
resources: z.array(
z.object({
hash: z.string().optional(),
name: z.string(),
type: z.string(),
weight: z.number().optional(),
})
).optional(),
ModelHash: z.string().optional(),
HiresSteps: z.string().optional(),
HiresUpscale: z.string().optional(),
HiresUpscaler: z.string().optional(),
negativePrompt: z.string(),
DenoisingStrength: z.number().optional(),
});
export const imageSchema = z.object({
url: z.string().url().optional(),
nsfw: z.enum(["None", "Soft", "Mature"]).optional(),
width: z.number().optional(),
height: z.number().optional(),
hash: z.string().optional(),
type: z.string().optional(),
metadata: imageMetadataSchema.optional(),
meta: imageMetaSchema.optional(),
});
export const modelVersionSchema = z.object({
id: z.number(),
modelId: z.number(),
name: z.string(),
createdAt: z.string().optional(),
updatedAt: z.string().optional(),
status: z.enum(["Published", "Unpublished"]).optional(),
publishedAt: z.string().optional(),
trainedWords: z.array(z.string()).nullable(),
trainingStatus: z.string().nullable(),
trainingDetails: z.string().nullable(),
baseModel: z.string().optional(),
baseModelType: z.string().optional(),
earlyAccessTimeFrame: z.number().optional(),
description: z.string().nullable(),
vaeId: z.string().nullable(),
stats: z.object({
downloadCount: z.number(),
ratingCount: z.number(),
rating: z.number(),
}).optional(),
files: z.array(fileSchema),
images: z.array(imageSchema),
downloadUrl: z.string().url(),
});
export const statsSchema = z.object({
downloadCount: z.number(),
favoriteCount: z.number(),
commentCount: z.number(),
ratingCount: z.number(),
rating: z.number(),
tippedAmountCount: z.number(),
});
export const CivitaiModelResponse = z.object({
id: z.number(),
name: z.string().optional(),
description: z.string().optional(),
type: z.enum(["Checkpoint", "Lora"]),
poi: z.boolean().optional(),
nsfw: z.boolean().optional(),
allowNoCredit: z.boolean().optional(),
allowCommercialUse: z.enum(["Rent"]).optional(),
allowDerivatives: z.boolean().optional(),
allowDifferentLicense: z.boolean().optional(),
stats: statsSchema.optional(),
creator: creatorSchema.optional(),
tags: z.array(z.string()).optional(),
modelVersions: z.array(modelVersionSchema),
});