Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed659ebdb8 | ||
|
|
f6a1b88dda | ||
|
|
fed7b380b6 | ||
|
|
90cec6b778 | ||
|
|
7b61fea849 | ||
|
|
60471a8e01 | ||
|
|
c98a16a2dd | ||
|
|
1a0d73ff8b | ||
|
|
163e6f0426 | ||
|
|
a2d0e93172 | ||
|
|
b2690ab2c5 | ||
|
|
c7772ca91e | ||
|
|
ffb117d85d | ||
|
|
45c7be3d50 | ||
|
|
544de57628 | ||
|
|
85618c8470 | ||
|
|
e7e91f4c50 | ||
|
|
e220921440 | ||
|
|
81cb44c5cf | ||
|
|
8302a934cc |
@@ -4,6 +4,8 @@ Open source comfyui deployment platform, a `vercel` for generative workflow infr
|
||||
|
||||
Join [Discord](https://discord.gg/EEYcQmdYZw) to chat more or visit [Comfy Deploy](https://comfydeploy.com/) to get started!
|
||||
|
||||
Check out our latest [nextjs starter kit](https://github.com/BennyKok/comfyui-deploy-next-example) with Comfy Deploy
|
||||
|
||||
\# How it works
|
||||
|
||||
1. Comfy Deploy Dashboard (https://comfydeploy.com) or self-hosted version
|
||||
@@ -132,4 +134,4 @@ SPACES_CDN_FORCE_PATH_STYLE="false"
|
||||
SPACES_REGION="nyc3"
|
||||
SPACES_CDN_DONT_INCLUDE_BUCKET="false"
|
||||
SPACES_CDN_FORCE_PATH_STYLE="true"
|
||||
```
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ from enum import Enum
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from uuid import uuid4
|
||||
from contextlib import asynccontextmanager
|
||||
import asyncio
|
||||
import threading
|
||||
@@ -19,6 +20,7 @@ from urllib.parse import parse_qs
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.types import ASGIApp, Scope, Receive, Send
|
||||
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
# executor = ThreadPoolExecutor(max_workers=5)
|
||||
@@ -224,6 +226,52 @@ async def websocket_endpoint(websocket: WebSocket, machine_id: str):
|
||||
# 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")
|
||||
async def create_machine(item: Item):
|
||||
global last_activity_time
|
||||
@@ -312,7 +360,9 @@ async def build_logic(item: Item):
|
||||
config = {
|
||||
"name": item.name,
|
||||
"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:
|
||||
f.write("config = " + json.dumps(config))
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
from config import config
|
||||
import modal
|
||||
from modal import Image, Mount, web_endpoint, Stub, asgi_app
|
||||
from modal import Image, Mount, web_endpoint, Stub, asgi_app, Volume
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
from pydantic import BaseModel
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from volume import volumes
|
||||
|
||||
# deploy_test = False
|
||||
|
||||
@@ -28,7 +29,6 @@ web_app = FastAPI()
|
||||
print(config)
|
||||
print("deploy_test ", deploy_test)
|
||||
stub = Stub(name=config["name"])
|
||||
# print(stub.app_id)
|
||||
|
||||
if not deploy_test:
|
||||
# dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data"))
|
||||
@@ -56,7 +56,7 @@ if not deploy_test:
|
||||
# # Install comfy deploy
|
||||
# "cd /comfyui/custom_nodes && git clone https://github.com/BennyKok/comfyui-deploy.git",
|
||||
# )
|
||||
# .copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui")
|
||||
.copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui")
|
||||
|
||||
.copy_local_file(f"{current_directory}/data/start.sh", "/start.sh")
|
||||
.run_commands("chmod +x /start.sh")
|
||||
@@ -153,8 +153,9 @@ image = Image.debian_slim()
|
||||
|
||||
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):
|
||||
import subprocess
|
||||
import time
|
||||
@@ -163,6 +164,7 @@ def run(input: Input):
|
||||
|
||||
command = ["python", "main.py",
|
||||
"--disable-auto-launch", "--disable-metadata"]
|
||||
|
||||
server_process = subprocess.Popen(command, cwd="/comfyui")
|
||||
|
||||
check_server(
|
||||
@@ -235,7 +237,9 @@ async def bar(request_input: RequestInput):
|
||||
# pass
|
||||
|
||||
|
||||
@stub.function(image=image)
|
||||
@stub.function(image=image
|
||||
,volumes=volumes
|
||||
)
|
||||
@asgi_app()
|
||||
def comfyui_api():
|
||||
return web_app
|
||||
@@ -285,6 +289,7 @@ def spawn_comfyui_in_background():
|
||||
# to be on a single container.
|
||||
concurrency_limit=1,
|
||||
timeout=10 * 60,
|
||||
volumes=volumes,
|
||||
)
|
||||
@asgi_app()
|
||||
def comfyui_app():
|
||||
@@ -303,4 +308,4 @@ def comfyui_app():
|
||||
},
|
||||
)()
|
||||
|
||||
return make_simple_proxy_app(ProxyContext(config))
|
||||
return make_simple_proxy_app(ProxyContext(config))
|
||||
|
||||
@@ -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:
|
||||
base_path: /runpod-volume/ComfyUI/
|
||||
checkpoints: models/checkpoints/
|
||||
clip: models/clip/
|
||||
clip_vision: models/clip_vision/
|
||||
configs: models/configs/
|
||||
controlnet: models/controlnet/
|
||||
embeddings: models/embeddings/
|
||||
loras: models/loras/
|
||||
upscale_models: models/upscale_models/
|
||||
vae: models/vae/
|
||||
base_path: /extra_models/
|
||||
checkpoints: |
|
||||
checkpoints
|
||||
private_checkpoints
|
||||
clip: |
|
||||
clip
|
||||
private_clip
|
||||
clip_vision: |
|
||||
clip_vision
|
||||
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)
|
||||
print(response.text)
|
||||
|
||||
with open('models.json') as f:
|
||||
models = json.load(f)
|
||||
|
||||
for model in models:
|
||||
response = requests.request("POST", f"{root_url}/model/install", json=model, headers=headers)
|
||||
print(response.text)
|
||||
# with open('models.json') as f:
|
||||
# models = json.load(f)
|
||||
#
|
||||
# for model in models:
|
||||
# response = requests.request("POST", f"{root_url}/model/install", json=model, headers=headers)
|
||||
# print(response.text)
|
||||
|
||||
# Close the server
|
||||
server_process.terminate()
|
||||
print("Finished installing dependencies.")
|
||||
print("Finished installing dependencies.")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -16,4 +16,7 @@ SPACES_CDN_FORCE_PATH_STYLE="true"
|
||||
MODAL_BUILDER_URL=
|
||||
|
||||
JWT_SECRET="openssl rand -hex 32"
|
||||
PLAUSIBLE_DOMAIN=
|
||||
PLAUSIBLE_DOMAIN=
|
||||
|
||||
NEXT_PUBLIC_POSTHOG_KEY="your-api-key"
|
||||
NEXT_PUBLIC_POSTHOG_HOST="your-ph-address"
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
ALTER TYPE "workflow_run_origin" ADD VALUE 'public-share';
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "description" text;--> statement-breakpoint
|
||||
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "showcase_media" jsonb;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "org_id" text;
|
||||
@@ -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 $$;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TYPE "resource_upload" ADD VALUE 'error';--> statement-breakpoint
|
||||
ALTER TABLE "comfyui_deploy"."checkpoints" ADD COLUMN "build_log" text;
|
||||
@@ -0,0 +1,738 @@
|
||||
{
|
||||
"id": "7bdeb193-ee27-40cc-8252-59ddaf505ab8",
|
||||
"prevId": "dddfedc3-5b2f-4d54-a996-821945b9ff80",
|
||||
"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
|
||||
},
|
||||
"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
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
|
||||
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "workflow_versions",
|
||||
"columnsFrom": [
|
||||
"workflow_version_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"workflow_runs_workflow_id_workflows_id_fk": {
|
||||
"name": "workflow_runs_workflow_id_workflows_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "workflows",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"workflow_runs_machine_id_machines_id_fk": {
|
||||
"name": "workflow_runs_machine_id_machines_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "machines",
|
||||
"columnsFrom": [
|
||||
"machine_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflows": {
|
||||
"name": "workflows",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"org_id": {
|
||||
"name": "org_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflows_user_id_users_id_fk": {
|
||||
"name": "workflows_user_id_users_id_fk",
|
||||
"tableFrom": "workflows",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflow_versions": {
|
||||
"name": "workflow_versions",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"workflow_id": {
|
||||
"name": "workflow_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"workflow": {
|
||||
"name": "workflow",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"workflow_api": {
|
||||
"name": "workflow_api",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"snapshot": {
|
||||
"name": "snapshot",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflow_versions_workflow_id_workflows_id_fk": {
|
||||
"name": "workflow_versions_workflow_id_workflows_id_fk",
|
||||
"tableFrom": "workflow_versions",
|
||||
"tableTo": "workflows",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"deployment_environment": {
|
||||
"name": "deployment_environment",
|
||||
"values": {
|
||||
"staging": "staging",
|
||||
"production": "production",
|
||||
"public-share": "public-share"
|
||||
}
|
||||
},
|
||||
"machine_gpu": {
|
||||
"name": "machine_gpu",
|
||||
"values": {
|
||||
"T4": "T4",
|
||||
"A10G": "A10G",
|
||||
"A100": "A100"
|
||||
}
|
||||
},
|
||||
"machine_status": {
|
||||
"name": "machine_status",
|
||||
"values": {
|
||||
"ready": "ready",
|
||||
"building": "building",
|
||||
"error": "error"
|
||||
}
|
||||
},
|
||||
"machine_type": {
|
||||
"name": "machine_type",
|
||||
"values": {
|
||||
"classic": "classic",
|
||||
"runpod-serverless": "runpod-serverless",
|
||||
"modal-serverless": "modal-serverless",
|
||||
"comfy-deploy-serverless": "comfy-deploy-serverless"
|
||||
}
|
||||
},
|
||||
"workflow_run_origin": {
|
||||
"name": "workflow_run_origin",
|
||||
"values": {
|
||||
"manual": "manual",
|
||||
"api": "api",
|
||||
"public-share": "public-share"
|
||||
}
|
||||
},
|
||||
"workflow_run_status": {
|
||||
"name": "workflow_run_status",
|
||||
"values": {
|
||||
"not-started": "not-started",
|
||||
"running": "running",
|
||||
"uploading": "uploading",
|
||||
"success": "success",
|
||||
"failed": "failed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"comfyui_deploy": "comfyui_deploy"
|
||||
},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,750 @@
|
||||
{
|
||||
"id": "a7d6a8dd-0e15-4165-98a2-de2a334455dc",
|
||||
"prevId": "7bdeb193-ee27-40cc-8252-59ddaf505ab8",
|
||||
"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
|
||||
},
|
||||
"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
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflow_runs_workflow_version_id_workflow_versions_id_fk": {
|
||||
"name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "workflow_versions",
|
||||
"columnsFrom": [
|
||||
"workflow_version_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"workflow_runs_workflow_id_workflows_id_fk": {
|
||||
"name": "workflow_runs_workflow_id_workflows_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "workflows",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"workflow_runs_machine_id_machines_id_fk": {
|
||||
"name": "workflow_runs_machine_id_machines_id_fk",
|
||||
"tableFrom": "workflow_runs",
|
||||
"tableTo": "machines",
|
||||
"columnsFrom": [
|
||||
"machine_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflows": {
|
||||
"name": "workflows",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"org_id": {
|
||||
"name": "org_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflows_user_id_users_id_fk": {
|
||||
"name": "workflows_user_id_users_id_fk",
|
||||
"tableFrom": "workflows",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"workflow_versions": {
|
||||
"name": "workflow_versions",
|
||||
"schema": "comfyui_deploy",
|
||||
"columns": {
|
||||
"workflow_id": {
|
||||
"name": "workflow_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"workflow": {
|
||||
"name": "workflow",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"workflow_api": {
|
||||
"name": "workflow_api",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"snapshot": {
|
||||
"name": "snapshot",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"workflow_versions_workflow_id_workflows_id_fk": {
|
||||
"name": "workflow_versions_workflow_id_workflows_id_fk",
|
||||
"tableFrom": "workflow_versions",
|
||||
"tableTo": "workflows",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"deployment_environment": {
|
||||
"name": "deployment_environment",
|
||||
"values": {
|
||||
"staging": "staging",
|
||||
"production": "production",
|
||||
"public-share": "public-share"
|
||||
}
|
||||
},
|
||||
"machine_gpu": {
|
||||
"name": "machine_gpu",
|
||||
"values": {
|
||||
"T4": "T4",
|
||||
"A10G": "A10G",
|
||||
"A100": "A100"
|
||||
}
|
||||
},
|
||||
"machine_status": {
|
||||
"name": "machine_status",
|
||||
"values": {
|
||||
"ready": "ready",
|
||||
"building": "building",
|
||||
"error": "error"
|
||||
}
|
||||
},
|
||||
"machine_type": {
|
||||
"name": "machine_type",
|
||||
"values": {
|
||||
"classic": "classic",
|
||||
"runpod-serverless": "runpod-serverless",
|
||||
"modal-serverless": "modal-serverless",
|
||||
"comfy-deploy-serverless": "comfy-deploy-serverless"
|
||||
}
|
||||
},
|
||||
"workflow_run_origin": {
|
||||
"name": "workflow_run_origin",
|
||||
"values": {
|
||||
"manual": "manual",
|
||||
"api": "api",
|
||||
"public-share": "public-share"
|
||||
}
|
||||
},
|
||||
"workflow_run_status": {
|
||||
"name": "workflow_run_status",
|
||||
"values": {
|
||||
"not-started": "not-started",
|
||||
"running": "running",
|
||||
"uploading": "uploading",
|
||||
"success": "success",
|
||||
"failed": "failed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"comfyui_deploy": "comfyui_deploy"
|
||||
},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,756 @@
|
||||
{
|
||||
"id": "db06ea66-92c2-4ebe-93c1-6cb8a90ccd8b",
|
||||
"prevId": "a7d6a8dd-0e15-4165-98a2-de2a334455dc",
|
||||
"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
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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": {}
|
||||
},
|
||||
"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()"
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -197,6 +197,41 @@
|
||||
"when": 1705228261543,
|
||||
"tag": "0027_eminent_lilith",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 28,
|
||||
"version": "5",
|
||||
"when": 1705642345817,
|
||||
"tag": "0028_futuristic_lady_deathstrike",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 29,
|
||||
"version": "5",
|
||||
"when": 1705662714161,
|
||||
"tag": "0029_large_frightful_four",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 30,
|
||||
"version": "5",
|
||||
"when": 1705716303820,
|
||||
"tag": "0030_kind_doorman",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 31,
|
||||
"version": "5",
|
||||
"when": 1705975916818,
|
||||
"tag": "0031_safe_multiple_man",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 32,
|
||||
"version": "5",
|
||||
"when": 1705979098372,
|
||||
"tag": "0032_material_wallflower",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+5
-4
@@ -1,4 +1,3 @@
|
||||
import million from 'million/compiler';
|
||||
import { recmaPlugins } from "./src/mdx/recma.mjs";
|
||||
import { rehypePlugins } from "./src/mdx/rehype.mjs";
|
||||
import { remarkPlugins } from "./src/mdx/remark.mjs";
|
||||
@@ -21,6 +20,8 @@ const nextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export default million.next(
|
||||
withSearch(withMDX(nextConfig)), { auto: { rsc: true } }
|
||||
);
|
||||
export default withSearch(withMDX(nextConfig));
|
||||
|
||||
// export default million.next(
|
||||
// withSearch(withMDX(nextConfig)), { auto: { rsc: true } }
|
||||
// );
|
||||
|
||||
+4
-1
@@ -60,6 +60,7 @@
|
||||
"dayjs": "^1.11.10",
|
||||
"drizzle-orm": "^0.29.1",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"embla-carousel-react": "^8.0.0-rc19",
|
||||
"fast-glob": "^3.3.2",
|
||||
"flexsearch": "^0.7.31",
|
||||
"framer-motion": "^10.16.16",
|
||||
@@ -71,11 +72,13 @@
|
||||
"million": "latest",
|
||||
"mitata": "^0.1.6",
|
||||
"nanoid": "^5.0.4",
|
||||
"next": "14.0.4",
|
||||
"next": "14.1",
|
||||
"next-plausible": "^3.12.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"next-usequerystate": "^1.13.2",
|
||||
"pg": "^8.11.3",
|
||||
"posthog-js": "^1.100.0",
|
||||
"posthog-node": "^3.5.0",
|
||||
"react": "^18",
|
||||
"react-day-picker": "^8.9.1",
|
||||
"react-dom": "^18",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SharePageSettings } from "@/components/SharePageSettings";
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: { share_id: string };
|
||||
}) {
|
||||
return <SharePageSettings deployment_id={params.share_id} />;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { FC } from "react";
|
||||
|
||||
const Default: FC = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Default;
|
||||
@@ -0,0 +1,29 @@
|
||||
// app/PostHogPageView.tsx
|
||||
'use client'
|
||||
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { usePostHog } from 'posthog-js/react';
|
||||
|
||||
export default function PostHogPageView() {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const posthog = usePostHog();
|
||||
// Track pageviews
|
||||
useEffect(() => {
|
||||
if (pathname && posthog) {
|
||||
let url = window.origin + pathname
|
||||
if (searchParams.toString()) {
|
||||
url = url + `?${searchParams.toString()}`
|
||||
}
|
||||
posthog.capture(
|
||||
'$pageview',
|
||||
{
|
||||
'$current_url': url,
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [pathname, searchParams, posthog])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -4,11 +4,12 @@ import { db } from "@/db/db";
|
||||
import {
|
||||
snapshotType,
|
||||
workflowAPIType,
|
||||
workflowTable,
|
||||
workflowType,
|
||||
workflowVersionTable,
|
||||
} from "@/db/schema";
|
||||
import { parseDataSafe } from "@/lib/parseDataSafe";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -132,6 +133,14 @@ export async function POST(request: Request) {
|
||||
})
|
||||
.returning();
|
||||
version = data[0].version;
|
||||
|
||||
await db
|
||||
.update(workflowTable)
|
||||
.set({
|
||||
updated_at: new Date(),
|
||||
})
|
||||
.where(eq(workflowTable.id, workflow_id))
|
||||
.returning();
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { ErrorFullPage } from "@/components/docs/ErrorPage";
|
||||
|
||||
export default ErrorFullPage;
|
||||
@@ -1,13 +1,19 @@
|
||||
import { Navbar } from "../../components/Navbar";
|
||||
import "./globals.css";
|
||||
import { PHProvider } from "./providers";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { ClerkProvider } from "@clerk/nextjs";
|
||||
import type { Metadata } from "next";
|
||||
import meta from "next-gen/config";
|
||||
import PlausibleProvider from "next-plausible";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Inter } from "next/font/google";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
const PostHogPageView = dynamic(() => import("./PostHogPageView"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -27,8 +33,10 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
modal,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
modal: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
@@ -39,20 +47,24 @@ export default function RootLayout({
|
||||
<PlausibleProvider domain={process.env.PLAUSIBLE_DOMAIN} />
|
||||
</head>
|
||||
)}
|
||||
<body className={inter.className}>
|
||||
<main className="w-full flex min-h-[100dvh] flex-col items-center justify-start">
|
||||
<div className="z-[-1] fixed h-full w-full bg-white">
|
||||
<div className="absolute h-full w-full bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
|
||||
</div>
|
||||
<div className="sticky w-full h-18 flex items-center justify-between gap-4 p-4 border-b border-gray-200">
|
||||
<Navbar />
|
||||
</div>
|
||||
<div className="md:px-10 px-6 w-full h-[calc(100dvh-73px)]">
|
||||
{children}
|
||||
</div>
|
||||
<Toaster richColors />
|
||||
</main>
|
||||
</body>
|
||||
<PHProvider>
|
||||
<body className={inter.className}>
|
||||
<PostHogPageView />
|
||||
<main className="w-full flex min-h-[100dvh] flex-col items-center justify-start">
|
||||
<div className="z-[-1] fixed h-full w-full bg-white">
|
||||
<div className="absolute h-full w-full bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
|
||||
</div>
|
||||
<div className="sticky w-full h-18 flex items-center justify-between gap-4 p-4 border-b border-gray-200">
|
||||
<Navbar />
|
||||
</div>
|
||||
<div className="md:px-10 px-6 w-full h-[calc(100dvh-73px)]">
|
||||
{children}
|
||||
</div>
|
||||
<Toaster richColors />
|
||||
{modal}
|
||||
</main>
|
||||
</body>
|
||||
</PHProvider>
|
||||
</TooltipProvider>
|
||||
</ClerkProvider>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// app/providers.tsx
|
||||
'use client'
|
||||
import posthog from 'posthog-js'
|
||||
import { PostHogProvider } from 'posthog-js/react'
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
|
||||
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
capture_pageview: false // Disable automatic pageview capture, as we capture manually
|
||||
})
|
||||
}
|
||||
|
||||
export function PHProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import { ButtonActionMenu } from "@/components/ButtonActionLoader";
|
||||
import {
|
||||
PublicRunOutputs,
|
||||
RunWorkflowInline,
|
||||
} from "@/components/VersionSelect";
|
||||
import { RunWorkflowInline } from "@/components/RunWorkflowInline";
|
||||
import { PublicRunOutputs } from "@/components/VersionSelect";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -89,6 +87,11 @@ export default async function Page({
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<div>
|
||||
{sharedDeployment?.description && (
|
||||
<>{sharedDeployment?.description}</>
|
||||
)}
|
||||
</div>
|
||||
<RunWorkflowInline
|
||||
inputs={inputs}
|
||||
machine_id={sharedDeployment.machine_id}
|
||||
@@ -102,7 +105,7 @@ export default async function Page({
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<PublicRunOutputs />
|
||||
<PublicRunOutputs preview={sharedDeployment.showcase_media} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SharePageSettings } from "@/components/SharePageSettings";
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: { share_id: string };
|
||||
}) {
|
||||
return <SharePageSettings deployment_id={params.share_id} />;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export default function Loading() {
|
||||
const pathName = usePathname();
|
||||
return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { setInitialUserData } from "../../../lib/setInitialUserData";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { clerkClient } from "@clerk/nextjs/server";
|
||||
import { 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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import ErrorPage from "@/components/docs/ErrorPage";
|
||||
|
||||
export default ErrorPage;
|
||||
@@ -1,51 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
// Error components must be Client Components
|
||||
import { useEffect } from "react";
|
||||
import ErrorPage from "@/components/docs/ErrorPage";
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error?: Error & { digest?: string };
|
||||
reset?: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.log(error?.message);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="border rounded-2xl flex flex-col w-full gap-20 justify-center items-center">
|
||||
<Card className="max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<div className="text-2xl">Something went wrong!</div>
|
||||
</CardTitle>
|
||||
<CardDescription
|
||||
suppressHydrationWarning={true}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<div className="text-lg">{error?.message}</div>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={
|
||||
// Attempt to recover by trying to re-render the segment
|
||||
() => reset?.()
|
||||
}
|
||||
>
|
||||
Try again
|
||||
</Button>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default ErrorPage;
|
||||
|
||||
@@ -17,7 +17,7 @@ export default async function Page({
|
||||
<CardHeader className="relative">
|
||||
<CardTitle>Run</CardTitle>
|
||||
<div className="absolute right-6 top-6">
|
||||
<RouteRefresher interval={5000} />
|
||||
<RouteRefresher interval={5000} autoRefresh={false} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import ErrorPage from "@/components/docs/ErrorPage";
|
||||
|
||||
export default ErrorPage;
|
||||
@@ -1,6 +1,3 @@
|
||||
import Error from "@/app/(app)/workflows/[workflow_id]/@runs/error";
|
||||
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
deployment,
|
||||
@@ -13,15 +10,13 @@ export default async function Layout({
|
||||
workflow: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<ErrorBoundary fallback={<Error />}>
|
||||
<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="w-full flex gap-4 flex-col min-w-0">
|
||||
{workflow}
|
||||
{deployment}
|
||||
</div>
|
||||
{runs}
|
||||
{children}
|
||||
<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="w-full flex gap-4 flex-col min-w-0">
|
||||
{workflow}
|
||||
{deployment}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
{runs}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { setInitialUserData } from "../../../lib/setInitialUserData";
|
||||
import { getAllUserWorkflow } from "../../../server/getAllUserWorkflow";
|
||||
import { WorkflowList } from "@/components/WorkflowList";
|
||||
import { db } from "@/db/db";
|
||||
import { usersTable, workflowTable, workflowVersionTable } from "@/db/schema";
|
||||
import { usersTable } from "@/db/schema";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { and, desc, eq, isNull } from "drizzle-orm";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export default function Home() {
|
||||
return <WorkflowServer />;
|
||||
}
|
||||
|
||||
async function WorkflowServer() {
|
||||
const { userId, orgId } = await auth();
|
||||
const { userId } = await auth();
|
||||
|
||||
if (!userId) {
|
||||
return <div>No auth</div>;
|
||||
@@ -24,35 +25,11 @@ async function WorkflowServer() {
|
||||
await setInitialUserData(userId);
|
||||
}
|
||||
|
||||
const workflow = await db.query.workflowTable.findMany({
|
||||
// extras: {
|
||||
// count: sql<number>`(select count(*) from ${workflowVersionTable})`.as(
|
||||
// "count",
|
||||
// ),
|
||||
// },
|
||||
with: {
|
||||
versions: {
|
||||
limit: 1,
|
||||
orderBy: desc(workflowVersionTable.version),
|
||||
},
|
||||
},
|
||||
orderBy: desc(workflowTable.updated_at),
|
||||
where:
|
||||
orgId != undefined
|
||||
? eq(workflowTable.org_id, orgId)
|
||||
: and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)),
|
||||
});
|
||||
const workflow = await getAllUserWorkflow();
|
||||
|
||||
return (
|
||||
<WorkflowList
|
||||
data={workflow.map((x) => {
|
||||
return {
|
||||
id: x.id,
|
||||
email: x.name,
|
||||
amount: x.versions[0]?.version ?? 0,
|
||||
date: x.updated_at,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
);
|
||||
if (!workflow) {
|
||||
return <div>No workflow found</div>;
|
||||
}
|
||||
|
||||
return <WorkflowList data={workflow} />;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useAuth, useClerk } from "@clerk/nextjs";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
@@ -49,7 +50,9 @@ export function ButtonActionMenu(props: {
|
||||
action: () => Promise<any>;
|
||||
}[];
|
||||
}) {
|
||||
const user = useAuth();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -64,6 +67,13 @@ export function ButtonActionMenu(props: {
|
||||
<DropdownMenuItem
|
||||
key={action.title}
|
||||
onClick={async () => {
|
||||
if (!user.isSignedIn) {
|
||||
clerk.openSignIn({
|
||||
redirectUrl: window.location.href,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
await callServerPromise(action.action());
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ButtonAction } from "@/components/ButtonActionLoader";
|
||||
import { DeploymentRow, SharePageDeploymentRow } from "./DeploymentRow";
|
||||
import { CodeBlock } from "@/components/CodeBlock";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -10,15 +9,10 @@ import {
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import { TableRow } from "@/components/ui/table";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
|
||||
import { getRelativeTime } from "@/lib/getRelativeTime";
|
||||
import { removePublicShareDeployment } from "@/server/curdDeploments";
|
||||
import type { findAllDeployments } from "@/server/findAllRuns";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { headers } from "next/headers";
|
||||
import Link from "next/link";
|
||||
|
||||
const curlTemplate = `
|
||||
curl --request POST \
|
||||
@@ -61,64 +55,51 @@ const output = fetch("<URL>?run_id=" + run_id, {
|
||||
}).then(response => response.json())
|
||||
`;
|
||||
|
||||
const clientTemplate = `
|
||||
const jsClientSetupTemplate = `
|
||||
const client = new ComfyDeployClient({
|
||||
apiBase: "<URLONLY>",
|
||||
apiToken: process.env.COMFY_DEPLOY_API_KEY!,
|
||||
});
|
||||
`;
|
||||
|
||||
export async function generateTextures(scrImageId: string) {
|
||||
const result = await client.run("<ID>", {
|
||||
input_image: "",
|
||||
});
|
||||
if (!result || !result.run_id) return { error: "run id not found" };
|
||||
return { id: result.run_id };
|
||||
}
|
||||
const jsClientSetupTemplateHostedVersion = `
|
||||
const client = new ComfyDeployClient({
|
||||
apiToken: process.env.COMFY_DEPLOY_API_KEY!,
|
||||
});
|
||||
`;
|
||||
|
||||
const jsClientCreateRunTemplate = `
|
||||
const { run_id } = await client.run("<ID>", {
|
||||
inputs: {}
|
||||
});
|
||||
`;
|
||||
|
||||
const jsClientCreateRunNoInputsTemplate = `
|
||||
const { run_id } = await client.run("<ID>");
|
||||
`;
|
||||
|
||||
const clientTemplate_checkStatus = `
|
||||
const run_id = "<RUN_ID>";
|
||||
|
||||
while (true) {
|
||||
const run = await client.getRun(run_id);
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
if (!run) continue;
|
||||
run.outputs.map((val) => {
|
||||
if (!val.data.image) return;
|
||||
});
|
||||
}
|
||||
|
||||
const run = await client.getRun(run_id);
|
||||
`;
|
||||
|
||||
export function DeploymentDisplay({
|
||||
deployment,
|
||||
domain,
|
||||
}: {
|
||||
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
|
||||
domain: string;
|
||||
}) {
|
||||
const headersList = headers();
|
||||
const host = headersList.get("host") || "";
|
||||
const protocol = headersList.get("x-forwarded-proto") || "";
|
||||
const domain = `${protocol}://${host}`;
|
||||
|
||||
const workflowInput = getInputsFromWorkflow(deployment.version);
|
||||
|
||||
if (deployment.environment == "public-share") {
|
||||
return <SharePageDeploymentRow deployment={deployment} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
||||
<TableRow>
|
||||
<TableCell className="capitalize truncate">
|
||||
{deployment.environment}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{deployment.version?.version}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{deployment.machine?.name}
|
||||
</TableCell>
|
||||
<TableCell className="text-right truncate">
|
||||
{getRelativeTime(deployment.updated_at)}
|
||||
</TableCell>
|
||||
<DeploymentRow deployment={deployment} />
|
||||
</TableRow>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-3xl">
|
||||
@@ -129,94 +110,79 @@ export function DeploymentDisplay({
|
||||
<DialogDescription>Code for your deployment client</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="max-h-[600px] pr-4">
|
||||
{deployment.environment !== "public-share" ? (
|
||||
<Tabs defaultValue="client" className="w-full gap-2">
|
||||
<TabsList className="grid w-fit grid-cols-3">
|
||||
<TabsTrigger value="client">client</TabsTrigger>
|
||||
<TabsTrigger value="js">js</TabsTrigger>
|
||||
<TabsTrigger value="curl">curl</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent className="flex flex-col gap-2 !mt-0" value="client">
|
||||
<div>
|
||||
Trigger the workflow with
|
||||
<a
|
||||
href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts"
|
||||
className="text-blue-500 hover:underline"
|
||||
target="_blank"
|
||||
>
|
||||
comfy deploy wrapper
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
Copy the wrapper to your project, and import the function
|
||||
</div>
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(
|
||||
clientTemplate,
|
||||
deployment,
|
||||
domain,
|
||||
workflowInput
|
||||
)}
|
||||
/>
|
||||
Check the status of the run, and retrieve the outputs
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(
|
||||
clientTemplate_checkStatus,
|
||||
deployment,
|
||||
domain
|
||||
)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent className="flex flex-col gap-2 !mt-0" value="js">
|
||||
Trigger the workflow
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(
|
||||
jsTemplate,
|
||||
deployment,
|
||||
domain,
|
||||
workflowInput
|
||||
)}
|
||||
/>
|
||||
Check the status of the run, and retrieve the outputs
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(jsTemplate_checkStatus, deployment, domain)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent className="flex flex-col gap-2 !mt-2" value="curl">
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={formatCode(curlTemplate, deployment, domain)}
|
||||
/>
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={formatCode(
|
||||
curlTemplate_checkStatus,
|
||||
deployment,
|
||||
domain
|
||||
)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
) : (
|
||||
<div className="w-full justify-end flex gap-2 py-1">
|
||||
<Button asChild className="gap-2" variant="outline" type="submit">
|
||||
<ButtonAction
|
||||
action={removePublicShareDeployment.bind(null, deployment.id)}
|
||||
<Tabs defaultValue="client" className="w-full gap-2 text-sm">
|
||||
<TabsList className="grid w-fit grid-cols-3 mb-2">
|
||||
<TabsTrigger value="client">Server Client</TabsTrigger>
|
||||
<TabsTrigger value="js">NodeJS Fetch</TabsTrigger>
|
||||
<TabsTrigger value="curl">CURL</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent className="flex flex-col gap-2 !mt-0" value="client">
|
||||
<div>
|
||||
Copy and paste the ComfyDeployClient form
|
||||
<a
|
||||
href="https://github.com/BennyKok/comfyui-deploy-next-example/blob/main/src/lib/comfy-deploy.ts"
|
||||
className="text-blue-500 hover:underline"
|
||||
target="_blank"
|
||||
>
|
||||
Remove
|
||||
</ButtonAction>
|
||||
</Button>
|
||||
<Button asChild className="gap-2">
|
||||
<Link href={`/share/${deployment.id}`} target="_blank">
|
||||
View Share Page <ExternalLink size={14} />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
here
|
||||
</a>
|
||||
</div>
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(
|
||||
domain == "https://www.comfydeploy.com"
|
||||
? jsClientSetupTemplateHostedVersion
|
||||
: jsClientSetupTemplate,
|
||||
deployment,
|
||||
domain,
|
||||
workflowInput
|
||||
)}
|
||||
/>
|
||||
Create a run via deployment id
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(
|
||||
workflowInput && workflowInput.length > 0
|
||||
? jsClientCreateRunTemplate
|
||||
: jsClientCreateRunNoInputsTemplate,
|
||||
deployment,
|
||||
domain,
|
||||
workflowInput
|
||||
)}
|
||||
/>
|
||||
Check the status of the run, and retrieve the outputs
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(
|
||||
clientTemplate_checkStatus,
|
||||
deployment,
|
||||
domain
|
||||
)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent className="flex flex-col gap-2 !mt-0" value="js">
|
||||
Trigger the workflow
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(jsTemplate, deployment, domain, workflowInput)}
|
||||
/>
|
||||
Check the status of the run, and retrieve the outputs
|
||||
<CodeBlock
|
||||
lang="js"
|
||||
code={formatCode(jsTemplate_checkStatus, deployment, domain)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent className="flex flex-col gap-2 !mt-2" value="curl">
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={formatCode(curlTemplate, deployment, domain)}
|
||||
/>
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={formatCode(curlTemplate_checkStatus, deployment, domain)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -227,7 +193,8 @@ function formatCode(
|
||||
codeTemplate: string,
|
||||
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0],
|
||||
domain: string,
|
||||
inputs?: ReturnType<typeof getInputsFromWorkflow>
|
||||
inputs?: ReturnType<typeof getInputsFromWorkflow>,
|
||||
inputsTabs?: number
|
||||
) {
|
||||
if (inputs && inputs.length > 0) {
|
||||
codeTemplate = codeTemplate.replace(
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import { getRelativeTime } from "@/lib/getRelativeTime";
|
||||
import type { findAllDeployments } from "@/server/findAllRuns";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export function SharePageDeploymentRow({
|
||||
deployment,
|
||||
}: {
|
||||
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<TableRow
|
||||
className="appearance-none hover:cursor-pointer"
|
||||
onClick={() => {
|
||||
if (deployment.environment == "public-share") {
|
||||
router.push(`/share/${deployment.id}/settings`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TableCell className="capitalize truncate">
|
||||
{deployment.environment}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{deployment.version?.version}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{deployment.machine?.name}
|
||||
</TableCell>
|
||||
<TableCell className="text-right truncate">
|
||||
{getRelativeTime(deployment.updated_at)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export function DeploymentRow({
|
||||
deployment,
|
||||
}: {
|
||||
deployment: Awaited<ReturnType<typeof findAllDeployments>>[0];
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<TableCell className="capitalize truncate">
|
||||
{deployment.environment}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{deployment.version?.version}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium truncate">
|
||||
{deployment.machine?.name}
|
||||
</TableCell>
|
||||
<TableCell className="text-right truncate">
|
||||
{getRelativeTime(deployment.updated_at)}
|
||||
</TableCell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -91,7 +92,7 @@ export function InsertModal<
|
||||
<div className="flex justify-end">
|
||||
<AutoFormSubmit>
|
||||
Save Changes
|
||||
<span className="ml-2">{isLoading && <LoadingIcon />}</span>
|
||||
{isLoading && <LoadingIcon />}
|
||||
</AutoFormSubmit>
|
||||
</div>
|
||||
</AutoForm>
|
||||
@@ -106,12 +107,14 @@ export function UpdateModal<
|
||||
Y extends UnknownKeysParam,
|
||||
Z extends ZodObject<K, Y>
|
||||
>(props: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
open?: boolean;
|
||||
setOpen?: (open: boolean) => void;
|
||||
title: string;
|
||||
description: string;
|
||||
dialogClassName?: string;
|
||||
data: z.infer<Z>;
|
||||
data: z.infer<Z> & {
|
||||
id: string;
|
||||
};
|
||||
serverAction: (
|
||||
data: z.infer<Z> & {
|
||||
id: string;
|
||||
@@ -119,8 +122,13 @@ export function UpdateModal<
|
||||
) => Promise<unknown>;
|
||||
formSchema: Z;
|
||||
fieldConfig?: FieldConfig<z.infer<Z>>;
|
||||
trigger?: React.ReactNode;
|
||||
extraButtons?: React.ReactNode;
|
||||
}) {
|
||||
// const [open, setOpen] = React.useState(false);
|
||||
const [_open, _setOpen] = React.useState(false);
|
||||
const open = props.open ?? _open;
|
||||
const setOpen = props.setOpen ?? _setOpen;
|
||||
|
||||
const [values, setValues] = useState<Partial<z.infer<Z>>>({});
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
|
||||
@@ -129,10 +137,18 @@ export function UpdateModal<
|
||||
}, [props.data]);
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onOpenChange={props.setOpen}>
|
||||
{/* <DialogTrigger asChild>
|
||||
<DropdownMenuItem>{props.title}</DropdownMenuItem>
|
||||
</DialogTrigger> */}
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
{props.trigger ?? (
|
||||
<DialogTrigger
|
||||
className="appearance-none hover:cursor-pointer"
|
||||
asChild
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{props.trigger}
|
||||
</DialogTrigger>
|
||||
)}
|
||||
<DialogContent className={cn("sm:max-w-[425px]", props.dialogClassName)}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{props.title}</DialogTitle>
|
||||
@@ -152,13 +168,14 @@ export function UpdateModal<
|
||||
})
|
||||
);
|
||||
setIsLoading(false);
|
||||
props.setOpen(false);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-end">
|
||||
<div className="flex justify-end flex-wrap gap-2">
|
||||
{props.extraButtons}
|
||||
<AutoFormSubmit>
|
||||
Save Changes
|
||||
<span className="ml-2">{isLoading && <LoadingIcon />}</span>
|
||||
{isLoading && <LoadingIcon />}
|
||||
</AutoFormSubmit>
|
||||
</div>
|
||||
</AutoForm>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { getRelativeTime } from "../lib/getRelativeTime";
|
||||
import { InsertModal, UpdateModal } from "./InsertModal";
|
||||
import { callServerPromise } from "./callServerPromise";
|
||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
@@ -107,8 +108,20 @@ export const columns: ColumnDef<Machine>[] = [
|
||||
{row.original.disabled && (
|
||||
<Badge variant="destructive">Disabled</Badge>
|
||||
)}
|
||||
{row.original.status == "building" && (
|
||||
<Badge variant="amber" className="capitalize">
|
||||
{row.original.status} <LoadingIcon />
|
||||
</Badge>
|
||||
)}
|
||||
{!row.original.disabled && row.original.status && (
|
||||
<Badge variant="outline">{row.original.status}</Badge>
|
||||
<Badge
|
||||
variant={
|
||||
row.original.status == "ready" ? "success" : "destructive"
|
||||
}
|
||||
className="capitalize"
|
||||
>
|
||||
{row.original.status}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
// </a>
|
||||
|
||||
@@ -69,7 +69,7 @@ export default async function Main() {
|
||||
</Section.Announcement>
|
||||
|
||||
<Section.Title className="text-left">
|
||||
<span className="text-6xl md:text-7xl pb-2 inline-flex animate-background-shine bg-[linear-gradient(110deg,#1e293b,45%,#939393,55%,#1e293b)] bg-[length:250%_100%] bg-clip-text text-transparent">
|
||||
<span className="text-5xl sm:text-6xl md:text-7xl pb-2 inline-flex animate-background-shine bg-[linear-gradient(110deg,#1e293b,45%,#939393,55%,#1e293b)] bg-[length:250%_100%] bg-clip-text text-transparent">
|
||||
{meta.tagline}
|
||||
</span>
|
||||
</Section.Title>
|
||||
|
||||
@@ -38,9 +38,9 @@ export function Navbar() {
|
||||
{!isDesktop && (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" className="w-8 h-8 p-2">
|
||||
<button className="flex items-center justify-center w-8 h-8 p-2">
|
||||
<Menu />
|
||||
</Button>
|
||||
</button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" className="flex flex-col gap-4">
|
||||
<SheetHeader>
|
||||
|
||||
@@ -34,6 +34,10 @@ export function NavbarMenu({ className }: { className?: string }) {
|
||||
name: "API Keys",
|
||||
path: "/api-keys",
|
||||
},
|
||||
{
|
||||
name: "Storage",
|
||||
path: "/storage",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -42,9 +46,9 @@ export function NavbarMenu({ className }: { className?: string }) {
|
||||
{isDesktop && (
|
||||
<Tabs
|
||||
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) => (
|
||||
<TabsTrigger
|
||||
key={page.name}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
type CarouselApi,
|
||||
} from "@/components/ui/carousel";
|
||||
import * as React from "react";
|
||||
|
||||
export function OutputPreview() {
|
||||
const [api, setApi] = React.useState<CarouselApi>();
|
||||
const [current, setCurrent] = React.useState(0);
|
||||
const [count, setCount] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCount(api.scrollSnapList().length);
|
||||
setCurrent(api.selectedScrollSnap() + 1);
|
||||
|
||||
api.on("select", () => {
|
||||
setCurrent(api.selectedScrollSnap() + 1);
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Carousel setApi={setApi} className="w-full max-w-xs">
|
||||
<CarouselContent>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<CarouselItem key={index}>
|
||||
<Card>
|
||||
<CardContent className="flex aspect-square items-center justify-center p-6">
|
||||
<span className="text-4xl font-semibold">{index + 1}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<CarouselPrevious />
|
||||
<CarouselNext />
|
||||
</Carousel>
|
||||
<div className="py-2 text-center text-sm text-muted-foreground">
|
||||
Slide {current} of {count}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RefreshCcw } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useTransition } from "react";
|
||||
|
||||
export function RouteRefresher(props: { interval: number }) {
|
||||
export function RouteRefresher(props: {
|
||||
interval: number;
|
||||
autoRefresh: boolean;
|
||||
}) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.autoRefresh) return;
|
||||
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
const refresh = () => {
|
||||
@@ -35,7 +42,24 @@ export function RouteRefresher(props: { interval: number }) {
|
||||
clearTimeout(timeout);
|
||||
window.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
};
|
||||
}, [props.interval, router]);
|
||||
}, [props.interval, router, props.autoRefresh]);
|
||||
|
||||
return <div>{isPending && <LoadingIcon />}</div>;
|
||||
return (
|
||||
<div>
|
||||
{isPending && <LoadingIcon />}
|
||||
{!isPending && !props.autoRefresh && (
|
||||
<Button
|
||||
className="p-0 h-min"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
startTransition(() => {
|
||||
router.refresh();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<RefreshCcw size={14} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LiveStatus } from "./LiveStatus";
|
||||
import { RunInputs } from "@/components/RunInputs";
|
||||
import { RunOutputs } from "@/components/RunOutputs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import { getRelativeTime } from "@/lib/getRelativeTime";
|
||||
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
|
||||
import { type findAllRuns } from "@/server/findAllRuns";
|
||||
import { Suspense } from "react";
|
||||
|
||||
@@ -31,6 +32,14 @@ export async function RunDisplay({
|
||||
{getRelativeTime(run.created_at)}
|
||||
</TableCell>
|
||||
<TableCell>{run.version?.version}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className="truncate">
|
||||
{run.origin}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="truncate">
|
||||
{getDuration(run.duration)}
|
||||
</TableCell>
|
||||
<LiveStatus run={run} />
|
||||
</TableRow>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { plainInputsToZod } from "../lib/workflowVersionInputsToZod";
|
||||
import { publicRunStore } from "./VersionSelect";
|
||||
import { callServerPromise } from "./callServerPromise";
|
||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||
import AutoForm, { AutoFormSubmit } from "@/components/ui/auto-form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
|
||||
import { createRun } from "@/server/createRun";
|
||||
import { useAuth, useClerk } from "@clerk/nextjs";
|
||||
import { Play } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
// For share page
|
||||
export function RunWorkflowInline({
|
||||
inputs,
|
||||
workflow_version_id,
|
||||
machine_id,
|
||||
}: {
|
||||
inputs: ReturnType<typeof getInputsFromWorkflow>;
|
||||
workflow_version_id: string;
|
||||
machine_id: string;
|
||||
}) {
|
||||
const [values, setValues] = useState<Record<string, string>>({});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const user = useAuth();
|
||||
const clerk = useClerk();
|
||||
|
||||
const schema = useMemo(() => {
|
||||
return plainInputsToZod(inputs);
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
setRunId,
|
||||
loading,
|
||||
setLoading: setLoading2,
|
||||
setStatus,
|
||||
} = publicRunStore();
|
||||
|
||||
const runWorkflow = async () => {
|
||||
if (!user.isSignedIn) {
|
||||
clerk.openSignIn({
|
||||
redirectUrl: window.location.href,
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(values);
|
||||
|
||||
const val = Object.keys(values).length > 0 ? values : undefined;
|
||||
setLoading2(true);
|
||||
setIsLoading(true);
|
||||
setStatus("preparing");
|
||||
try {
|
||||
const origin = window.location.origin;
|
||||
const a = await callServerPromise(
|
||||
createRun({
|
||||
origin,
|
||||
workflow_version_id: workflow_version_id,
|
||||
machine_id: machine_id,
|
||||
inputs: val,
|
||||
runOrigin: "public-share",
|
||||
})
|
||||
);
|
||||
if (a && !("error" in a)) {
|
||||
setRunId(a.workflow_run_id);
|
||||
} else {
|
||||
setLoading2(false);
|
||||
}
|
||||
console.log(a);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
setLoading2(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{schema && (
|
||||
<AutoForm
|
||||
formSchema={schema}
|
||||
values={values}
|
||||
onValuesChange={setValues}
|
||||
onSubmit={runWorkflow}
|
||||
className="px-1"
|
||||
>
|
||||
<div className="flex justify-end">
|
||||
<AutoFormSubmit disabled={isLoading || loading}>
|
||||
Run
|
||||
{isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
|
||||
</AutoFormSubmit>
|
||||
</div>
|
||||
</AutoForm>
|
||||
)}
|
||||
{!schema && (
|
||||
<Button
|
||||
className="gap-2"
|
||||
disabled={isLoading || loading}
|
||||
onClick={runWorkflow}
|
||||
>
|
||||
Confirm {isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { parseAsInteger } from "next-usequerystate";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
const itemPerPage = 6;
|
||||
const pageParser = parseAsInteger.withDefault(1);
|
||||
@@ -40,12 +41,14 @@ export async function RunsTable(props: {
|
||||
)}
|
||||
<TableHeader className="bg-background top-0 sticky">
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Number</TableHead>
|
||||
<TableHead className="">Machine</TableHead>
|
||||
<TableHead className="">Time</TableHead>
|
||||
<TableHead className="w-[100px]">Version</TableHead>
|
||||
<TableHead className="truncate">Number</TableHead>
|
||||
<TableHead className="truncate">Machine</TableHead>
|
||||
<TableHead className="truncate">Time</TableHead>
|
||||
<TableHead className="truncate">Version</TableHead>
|
||||
<TableHead className="truncate">Origin</TableHead>
|
||||
<TableHead className="truncate">Duration</TableHead>
|
||||
<TableHead className="truncate">Live Status</TableHead>
|
||||
<TableHead className=" text-right">Status</TableHead>
|
||||
<TableHead className="text-right">Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -68,8 +71,14 @@ export async function RunsTable(props: {
|
||||
|
||||
export async function DeploymentsTable(props: { workflow_id: string }) {
|
||||
const allRuns = await findAllDeployments(props.workflow_id);
|
||||
|
||||
const headersList = headers();
|
||||
const host = headersList.get("host") || "";
|
||||
const protocol = headersList.get("x-forwarded-proto") || "";
|
||||
const domain = `${protocol}://${host}`;
|
||||
|
||||
return (
|
||||
<div className="overflow-auto h-fit w-full">
|
||||
<div className="overflow-auto h-fit w-full">
|
||||
<Table className="">
|
||||
<TableCaption>A list of your deployments</TableCaption>
|
||||
<TableHeader className="bg-background top-0 sticky">
|
||||
@@ -82,7 +91,7 @@ export async function DeploymentsTable(props: { workflow_id: string }) {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{allRuns.map((run) => (
|
||||
<DeploymentDisplay deployment={run} key={run.id} />
|
||||
<DeploymentDisplay deployment={run} key={run.id} domain={domain} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import { Button, buttonVariants } from '@/components/ui/button';
|
||||
type ButtonProps = React.ComponentProps<typeof Button>;
|
||||
type LinkProps = React.ComponentProps<typeof Link>;
|
||||
import { Card as BaseCard } from '@/components/ui/card';
|
||||
type CardProps = React.ComponentProps<typeof BaseCard>;
|
||||
import { Tabs, TabsTrigger as Tab, TabsList } from '@/components/ui/tabs';
|
||||
type TabsProps = React.ComponentProps<typeof Tabs>;
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionContent,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion';
|
||||
type AccordionProps = React.ComponentProps<typeof Accordion>;
|
||||
import { Badge as Chip } from '@/components/ui/badge';
|
||||
type ChipProps = React.ComponentProps<typeof Chip>;
|
||||
|
||||
import Link from 'next/link';
|
||||
} from "@/components/ui/accordion";
|
||||
import { Badge as Chip } from "@/components/ui/badge";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { Card as BaseCard } from "@/components/ui/card";
|
||||
import { Tabs, TabsTrigger as Tab, TabsList } from "@/components/ui/tabs";
|
||||
// import { PiCheckCircleDuotone } from 'react-icons/pi';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChevronRight as MdChevronRight } from "lucide-react";
|
||||
import { CheckCircle as PiCheckCircleDuotone } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import type {
|
||||
HTMLAttributeAnchorTarget,
|
||||
HTMLAttributes,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { ChevronRight as MdChevronRight} from 'lucide-react'
|
||||
} from "react";
|
||||
// import { MdChevronRight } from 'react-icons/md';
|
||||
import React from 'react';
|
||||
import { CheckCircle as PiCheckCircleDuotone } from 'lucide-react'
|
||||
// import { PiCheckCircleDuotone } from 'react-icons/pi';
|
||||
import { cn } from '@/lib/utils';
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type ButtonProps = React.ComponentProps<typeof Button>;
|
||||
type LinkProps = React.ComponentProps<typeof Link>;
|
||||
|
||||
type CardProps = React.ComponentProps<typeof BaseCard>;
|
||||
|
||||
type TabsProps = React.ComponentProps<typeof Tabs>;
|
||||
|
||||
type AccordionProps = React.ComponentProps<typeof Accordion>;
|
||||
|
||||
type ChipProps = React.ComponentProps<typeof Chip>;
|
||||
|
||||
function Section({
|
||||
className,
|
||||
@@ -41,8 +45,8 @@ function Section({
|
||||
return (
|
||||
<section
|
||||
className={twMerge(
|
||||
'flex min-h-[400px] w-full max-w-6xl flex-col justify-center gap-2 rounded-lg px-10 py-10 md:px-20',
|
||||
className,
|
||||
"flex min-h-[400px] w-full max-w-6xl flex-col justify-center gap-2 rounded-lg px-2 sm:px-10 py-10 md:px-20",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -64,12 +68,12 @@ function Title({
|
||||
<h1
|
||||
{...props}
|
||||
className={twMerge(
|
||||
'text-center text-4xl font-bold md:text-6xl',
|
||||
className,
|
||||
"text-center text-4xl font-bold md:text-6xl",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
// @ts-ignore
|
||||
textWrap: 'balance',
|
||||
textWrap: "balance",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -86,12 +90,12 @@ function Subtitle({
|
||||
<h2
|
||||
{...props}
|
||||
className={twMerge(
|
||||
'text text-center overflow-hidden text-ellipsis text-xl',
|
||||
className,
|
||||
"text text-center overflow-hidden text-ellipsis text-xl",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
// @ts-ignore
|
||||
textWrap: 'balance',
|
||||
textWrap: "balance",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -103,7 +107,7 @@ function Announcement({
|
||||
className,
|
||||
children,
|
||||
href,
|
||||
target = '_blank',
|
||||
target = "_blank",
|
||||
...props
|
||||
}: ChipProps & {
|
||||
href?: string; //string | UrlObject;
|
||||
@@ -112,8 +116,8 @@ function Announcement({
|
||||
return (
|
||||
<Chip
|
||||
className={twMerge(
|
||||
'w-fit group bg-foreground-50 text-center transition-colors hover:bg-gray-200',
|
||||
className,
|
||||
"w-fit group bg-foreground-50 text-center transition-colors hover:bg-gray-200",
|
||||
className
|
||||
)}
|
||||
variant="outline"
|
||||
// href={href}
|
||||
@@ -127,13 +131,13 @@ function Announcement({
|
||||
// }
|
||||
style={{
|
||||
// @ts-ignore
|
||||
textWrap: 'balance',
|
||||
textWrap: "balance",
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<a href={href} target={target}>
|
||||
{children}
|
||||
</a>{' '}
|
||||
</a>{" "}
|
||||
<MdChevronRight
|
||||
size={20}
|
||||
className="pr-1 transition-transform group-hover:translate-x-[2px]"
|
||||
@@ -143,14 +147,14 @@ function Announcement({
|
||||
}
|
||||
|
||||
type ActionProps = ButtonProps & {
|
||||
be: 'button';
|
||||
be: "button";
|
||||
hideArrow?: boolean;
|
||||
};
|
||||
|
||||
type ActionLinkProps = LinkProps & {
|
||||
be?: 'a';
|
||||
be?: "a";
|
||||
hideArrow?: boolean;
|
||||
variant?: ButtonProps['variant'];
|
||||
variant?: ButtonProps["variant"];
|
||||
};
|
||||
|
||||
function PrimaryAction({
|
||||
@@ -160,15 +164,15 @@ function PrimaryAction({
|
||||
hideArrow,
|
||||
...props
|
||||
}: ActionLinkProps | ActionProps) {
|
||||
if (props.be === 'button') {
|
||||
if (props.be === "button") {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: variant,
|
||||
}),
|
||||
'group',
|
||||
className,
|
||||
"group",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -186,8 +190,8 @@ function PrimaryAction({
|
||||
buttonVariants({
|
||||
variant: variant,
|
||||
}),
|
||||
'group',
|
||||
className,
|
||||
"group",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -206,17 +210,17 @@ function SecondaryAction({
|
||||
hideArrow,
|
||||
...props
|
||||
}: ActionLinkProps | ActionProps) {
|
||||
if (props.be === 'button') {
|
||||
if (props.be === "button") {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: variant,
|
||||
}),
|
||||
'group',
|
||||
className,
|
||||
"group",
|
||||
className
|
||||
)}
|
||||
variant={'ghost'}
|
||||
variant="ghost"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -231,10 +235,10 @@ function SecondaryAction({
|
||||
<Link
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: 'ghost',
|
||||
variant: "ghost",
|
||||
}),
|
||||
'group',
|
||||
className,
|
||||
"group",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -249,31 +253,31 @@ function PricingCard({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: Omit<CardProps, 'children'> & {
|
||||
}: Omit<CardProps, "children"> & {
|
||||
children:
|
||||
| ReactNode
|
||||
| ReactNode[]
|
||||
| ((pricingType: PricingType) => ReactNode | ReactNode[]);
|
||||
}) {
|
||||
// const { pricingType } = usePricingContext();
|
||||
if (typeof children === 'function')
|
||||
children = (children('month') as React.ReactElement).props.children as
|
||||
if (typeof children === "function")
|
||||
children = (children("month") as React.ReactElement).props.children as
|
||||
| ReactNode
|
||||
| ReactNode[];
|
||||
|
||||
// extract the title and subtitle from the children
|
||||
// const cardTitleStyles =
|
||||
const title = getChildComponent(children, Title, {
|
||||
className: 'text-2xl md:text-2xl text-start font-bold',
|
||||
className: "text-2xl md:text-2xl text-start font-bold",
|
||||
});
|
||||
const subTitle = getChildComponent(children, Subtitle, {
|
||||
className: 'text-md text-start text-foreground-500 mt-4',
|
||||
className: "text-md text-start text-foreground-500 mt-4",
|
||||
});
|
||||
const priceTags = getChildComponents(children, PriceTag, {
|
||||
className: 'text-4xl font-bold',
|
||||
className: "text-4xl font-bold",
|
||||
});
|
||||
const primaryAction = getChildComponent(children, PrimaryAction, {
|
||||
className: 'w-full',
|
||||
className: "w-full",
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -281,8 +285,8 @@ function PricingCard({
|
||||
// shadow="sm"
|
||||
{...props}
|
||||
className={twMerge(
|
||||
'flex flex-col min-h-[400px] w-full max-w-full items-start justify-between gap-2 p-8 text-sm',
|
||||
className,
|
||||
"flex flex-col min-h-[400px] w-full max-w-full items-start justify-between gap-2 p-8 text-sm",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
@@ -320,7 +324,7 @@ function PricingCard({
|
||||
// setPricingType: (pricingType: PricingType) => {},
|
||||
// });
|
||||
|
||||
const PricingTypeValue = ['month', 'year'] as const;
|
||||
const PricingTypeValue = ["month", "year"] as const;
|
||||
export type PricingType = (typeof PricingTypeValue)[number];
|
||||
|
||||
// // an helper function to useContext
|
||||
@@ -350,7 +354,7 @@ function PricingOption({ className, ...props }: TabsProps) {
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
className={twMerge('w-fit', className)}
|
||||
className={twMerge("w-fit", className)}
|
||||
defaultValue="month"
|
||||
aria-label="Pricing Options"
|
||||
{...props}
|
||||
@@ -384,10 +388,10 @@ function PriceTag({
|
||||
pricingType,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLHeadingElement> & {
|
||||
pricingType?: 'month' | 'year' | string;
|
||||
pricingType?: "month" | "year" | string;
|
||||
}) {
|
||||
// const { pricingType: currentPricingType } = usePricingContext();
|
||||
let currentPricingType = 'month';
|
||||
const currentPricingType = "month";
|
||||
|
||||
if (pricingType != undefined && currentPricingType !== pricingType)
|
||||
return <></>;
|
||||
@@ -399,10 +403,10 @@ function Card({ className, children, ...props }: CardProps) {
|
||||
// extract the title and subtitle from the children
|
||||
// const cardTitleStyles =
|
||||
const title = getChildComponent(children, Title, {
|
||||
className: 'text-2xl md:text-2xl font-normal text-center',
|
||||
className: "text-2xl md:text-2xl font-normal text-center",
|
||||
});
|
||||
const subTitle = getChildComponent(children, Subtitle, {
|
||||
className: 'text-md text-center',
|
||||
className: "text-md text-center",
|
||||
});
|
||||
const image = getChildComponent(children, ImageArea);
|
||||
|
||||
@@ -411,8 +415,8 @@ function Card({ className, children, ...props }: CardProps) {
|
||||
// shadow="sm"
|
||||
{...props}
|
||||
className={twMerge(
|
||||
'flex min-h-[280px] w-full max-w-full items-center justify-center gap-2 p-4 text-sm flex-col',
|
||||
className,
|
||||
"flex min-h-[280px] w-full max-w-full items-center justify-center gap-2 p-4 text-sm flex-col",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{image}
|
||||
@@ -431,7 +435,7 @@ function ImageArea({
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={twMerge('aspect-square w-14 bg-foreground-300', className)}
|
||||
className={twMerge("aspect-square w-14 bg-foreground-300", className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
@@ -442,11 +446,11 @@ function ImageArea({
|
||||
function getChildComponent<T extends (...args: any[]) => React.JSX.Element>(
|
||||
children: React.ReactNode | React.ReactNode[],
|
||||
type: T,
|
||||
propsOverride?: Partial<Parameters<T>[0]>,
|
||||
propsOverride?: Partial<Parameters<T>[0]>
|
||||
) {
|
||||
const childrenArr = React.Children.toArray(children);
|
||||
let child = childrenArr.find(
|
||||
(child) => React.isValidElement(child) && child.type === type,
|
||||
(child) => React.isValidElement(child) && child.type === type
|
||||
) as React.ReactElement<
|
||||
Parameters<T>[0],
|
||||
string | React.JSXElementConstructor<any>
|
||||
@@ -466,12 +470,12 @@ function getChildComponent<T extends (...args: any[]) => React.JSX.Element>(
|
||||
function getChildComponents<T extends (...args: any[]) => React.JSX.Element>(
|
||||
children: React.ReactNode | React.ReactNode[],
|
||||
type: T,
|
||||
propsOverride?: Partial<Parameters<T>[0]>,
|
||||
propsOverride?: Partial<Parameters<T>[0]>
|
||||
) {
|
||||
const childrenArr = React.Children.toArray(children);
|
||||
let child = (
|
||||
const child = (
|
||||
childrenArr.filter(
|
||||
(child) => React.isValidElement(child) && child.type === type,
|
||||
(child) => React.isValidElement(child) && child.type === type
|
||||
) as React.ReactElement<
|
||||
Parameters<T>[0],
|
||||
string | React.JSXElementConstructor<any>
|
||||
@@ -492,10 +496,10 @@ function getChildComponents<T extends (...args: any[]) => React.JSX.Element>(
|
||||
|
||||
function removeFromChildren(
|
||||
children: React.ReactNode | React.ReactNode[],
|
||||
types: any[],
|
||||
types: any[]
|
||||
): React.ReactNode[] {
|
||||
return React.Children.toArray(children).filter(
|
||||
(child) => React.isValidElement(child) && !types.includes(child.type),
|
||||
(child) => React.isValidElement(child) && !types.includes(child.type)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -508,11 +512,11 @@ function FAQItem({
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode | React.ReactNode[];
|
||||
'aria-label': string;
|
||||
"aria-label": string;
|
||||
title: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<AccordionItem value={props['aria-label']}>
|
||||
<AccordionItem value={props["aria-label"]}>
|
||||
<AccordionTrigger>{props.title}</AccordionTrigger>
|
||||
<AccordionContent>{children}</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { useServerActionData } from "./useServerActionData";
|
||||
import { ButtonAction } from "@/components/ButtonActionLoader";
|
||||
import { UpdateModal } from "@/components/InsertModal";
|
||||
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { publicShareDeployment } from "@/db/schema";
|
||||
import {
|
||||
findUserShareDeployment,
|
||||
removePublicShareDeployment,
|
||||
updateSharePageInfo,
|
||||
} from "@/server/curdDeploments";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
export function SharePageSettings({
|
||||
deployment_id,
|
||||
}: {
|
||||
deployment_id: string;
|
||||
}) {
|
||||
const {
|
||||
data: deployment,
|
||||
pending,
|
||||
started,
|
||||
} = useServerActionData(findUserShareDeployment, deployment_id);
|
||||
|
||||
const [_open, _setOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
if (pending) return <LoadingPageWrapper className="h-full" tag="settings" />;
|
||||
|
||||
if (!deployment && started && !pending)
|
||||
return (
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<p>Settings page not found.</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!deployment) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<UpdateModal
|
||||
dialogClassName="sm:max-w-[600px]"
|
||||
open={true}
|
||||
setOpen={() => {
|
||||
router.back();
|
||||
}}
|
||||
extraButtons={
|
||||
<>
|
||||
<Button
|
||||
asChild
|
||||
className="gap-2 truncate"
|
||||
variant="outline"
|
||||
type="button"
|
||||
>
|
||||
<ButtonAction
|
||||
action={removePublicShareDeployment.bind(null, deployment.id)}
|
||||
>
|
||||
Remove
|
||||
</ButtonAction>
|
||||
</Button>
|
||||
<Button asChild className="gap-2 truncate" type="button">
|
||||
<Link href={`/share/${deployment.id}`} target="_blank">
|
||||
View Share Page <ExternalLink size={14} />
|
||||
</Link>
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
data={{
|
||||
id: deployment.id,
|
||||
description: deployment.description,
|
||||
showcase_media: deployment.showcase_media ?? [],
|
||||
}}
|
||||
title="Share Page"
|
||||
description="Edit share page details."
|
||||
serverAction={updateSharePageInfo}
|
||||
formSchema={publicShareDeployment}
|
||||
fieldConfig={{
|
||||
description: {
|
||||
fieldType: "textarea",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
plainInputsToZod,
|
||||
workflowVersionInputsToZod,
|
||||
} from "../lib/workflowVersionInputsToZod";
|
||||
import { workflowVersionInputsToZod } from "../lib/workflowVersionInputsToZod";
|
||||
import { callServerPromise } from "./callServerPromise";
|
||||
import fetcher from "./fetcher";
|
||||
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||
@@ -42,13 +39,11 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import type { workflowAPINodeType } from "@/db/schema";
|
||||
import type { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
|
||||
import type { showcaseMediaNullable, workflowAPINodeType } from "@/db/schema";
|
||||
import { checkStatus, createRun } from "@/server/createRun";
|
||||
import { createDeployments } from "@/server/curdDeploments";
|
||||
import type { getMachines } from "@/server/curdMachine";
|
||||
import type { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
|
||||
import { useAuth, useClerk } from "@clerk/nextjs";
|
||||
import {
|
||||
Copy,
|
||||
ExternalLink,
|
||||
@@ -147,7 +142,7 @@ type PublicRunStore = {
|
||||
setStatus: (status: string) => void;
|
||||
};
|
||||
|
||||
const publicRunStore = create<PublicRunStore>((set) => ({
|
||||
export const publicRunStore = create<PublicRunStore>((set) => ({
|
||||
image: "",
|
||||
loading: false,
|
||||
runId: "",
|
||||
@@ -159,7 +154,9 @@ const publicRunStore = create<PublicRunStore>((set) => ({
|
||||
setStatus: (status) => set({ status }),
|
||||
}));
|
||||
|
||||
export function PublicRunOutputs() {
|
||||
export function PublicRunOutputs(props: {
|
||||
preview: z.infer<typeof showcaseMediaNullable>;
|
||||
}) {
|
||||
const { image, loading, runId, status, setStatus, setImage, setLoading } =
|
||||
publicRunStore();
|
||||
|
||||
@@ -181,6 +178,15 @@ export function PublicRunOutputs() {
|
||||
|
||||
return (
|
||||
<div className="border border-gray-200 w-full square h-[400px] rounded-lg relative">
|
||||
{!loading && !image && props.preview && props.preview.length > 0 && (
|
||||
<>
|
||||
<img
|
||||
className="w-full h-full object-contain"
|
||||
src={props.preview[0]?.url}
|
||||
alt="Generated image"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!loading && image && (
|
||||
<img
|
||||
className="w-full h-full object-contain"
|
||||
@@ -198,105 +204,6 @@ export function PublicRunOutputs() {
|
||||
);
|
||||
}
|
||||
|
||||
export function RunWorkflowInline({
|
||||
inputs,
|
||||
workflow_version_id,
|
||||
machine_id,
|
||||
}: {
|
||||
inputs: ReturnType<typeof getInputsFromWorkflow>;
|
||||
workflow_version_id: string;
|
||||
machine_id: string;
|
||||
}) {
|
||||
const [values, setValues] = useState<Record<string, string>>({});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const user = useAuth();
|
||||
const clerk = useClerk();
|
||||
|
||||
const schema = useMemo(() => {
|
||||
return plainInputsToZod(inputs);
|
||||
}, [inputs]);
|
||||
|
||||
const {
|
||||
setRunId,
|
||||
loading,
|
||||
setLoading: setLoading2,
|
||||
setStatus,
|
||||
} = publicRunStore();
|
||||
|
||||
const runWorkflow = async () => {
|
||||
console.log();
|
||||
|
||||
if (!user.isSignedIn) {
|
||||
clerk.openSignIn({
|
||||
redirectUrl: window.location.href,
|
||||
});
|
||||
console.log("hi");
|
||||
return;
|
||||
}
|
||||
console.log(values);
|
||||
|
||||
const val = Object.keys(values).length > 0 ? values : undefined;
|
||||
setLoading2(true);
|
||||
setIsLoading(true);
|
||||
setStatus("preparing");
|
||||
try {
|
||||
const origin = window.location.origin;
|
||||
const a = await callServerPromise(
|
||||
createRun({
|
||||
origin,
|
||||
workflow_version_id: workflow_version_id,
|
||||
machine_id: machine_id,
|
||||
inputs: val,
|
||||
isManualRun: true,
|
||||
})
|
||||
);
|
||||
if (a && !("error" in a)) {
|
||||
setRunId(a.workflow_run_id);
|
||||
} else {
|
||||
setLoading2(false);
|
||||
}
|
||||
console.log(a);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
setLoading2(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{schema && (
|
||||
<AutoForm
|
||||
formSchema={schema}
|
||||
values={values}
|
||||
onValuesChange={setValues}
|
||||
onSubmit={runWorkflow}
|
||||
className="px-1"
|
||||
>
|
||||
<div className="flex justify-end">
|
||||
<AutoFormSubmit disabled={isLoading || loading}>
|
||||
Run
|
||||
<span className="ml-2">
|
||||
{isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
|
||||
</span>
|
||||
</AutoFormSubmit>
|
||||
</div>
|
||||
</AutoForm>
|
||||
)}
|
||||
{!schema && (
|
||||
<Button
|
||||
className="gap-2"
|
||||
disabled={isLoading || loading}
|
||||
onClick={runWorkflow}
|
||||
>
|
||||
Confirm {isLoading || loading ? <LoadingIcon /> : <Play size={14} />}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function RunWorkflowButton({
|
||||
workflow,
|
||||
machines,
|
||||
@@ -345,7 +252,7 @@ export function RunWorkflowButton({
|
||||
workflow_version_id,
|
||||
machine_id: machine,
|
||||
inputs: val,
|
||||
isManualRun: true,
|
||||
runOrigin: "manual",
|
||||
})
|
||||
);
|
||||
// console.log(res.json());
|
||||
@@ -385,9 +292,7 @@ export function RunWorkflowButton({
|
||||
<div className="flex justify-end">
|
||||
<AutoFormSubmit>
|
||||
Run
|
||||
<span className="ml-2">
|
||||
{isLoading ? <LoadingIcon /> : <Play size={14} />}
|
||||
</span>
|
||||
{isLoading ? <LoadingIcon /> : <Play size={14} />}
|
||||
</AutoFormSubmit>
|
||||
</div>
|
||||
</AutoForm>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"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 {
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { deleteWorkflow } from "@/server/deleteWorkflow";
|
||||
import type { getAllUserWorkflow } from "@/server/getAllUserWorkflow";
|
||||
import type {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
@@ -38,14 +40,11 @@ import {
|
||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
export type Payment = {
|
||||
id: string;
|
||||
amount: number;
|
||||
date: Date;
|
||||
email: string;
|
||||
};
|
||||
export type WorkflowItemList = NonNullable<
|
||||
Awaited<ReturnType<typeof getAllUserWorkflow>>
|
||||
>[0];
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
export const columns: ColumnDef<WorkflowItemList>[] = [
|
||||
{
|
||||
accessorKey: "id",
|
||||
id: "select",
|
||||
@@ -70,7 +69,7 @@ export const columns: ColumnDef<Payment>[] = [
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "email",
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<button
|
||||
@@ -83,27 +82,35 @@ export const columns: ColumnDef<Payment>[] = [
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const workflow = row.original;
|
||||
return (
|
||||
<a className="hover:underline" href={`/workflows/${row.original.id}`}>
|
||||
{row.getValue("email")}
|
||||
<a
|
||||
className="hover:underline flex gap-2"
|
||||
href={`/workflows/${workflow.id}`}
|
||||
>
|
||||
<span className="truncate max-w-[200px]">{row.original.name}</span>
|
||||
|
||||
<Badge variant="default">v{row.original.versions[0]?.version}</Badge>
|
||||
{workflow.deployments?.[0] && <Badge variant="success">Public</Badge>}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "amount",
|
||||
header: () => <div className="text-left">Version</div>,
|
||||
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 }) => {
|
||||
const amount = parseFloat(row.getValue("amount"));
|
||||
|
||||
// Format the amount as a dollar amount
|
||||
// const formatted = new Intl.NumberFormat("en-US", {
|
||||
// style: "currency",
|
||||
// currency: "USD",
|
||||
// }).format(amount);
|
||||
|
||||
return <div className="text-left font-medium">{amount}</div>;
|
||||
return <Badge variant="cyan">{row.original.user.name}</Badge>;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -113,7 +120,7 @@ export const columns: ColumnDef<Payment>[] = [
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<button
|
||||
className="w-full flex items-center justify-end hover:underline"
|
||||
className="w-full flex items-center justify-end hover:underline truncate"
|
||||
// variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
>
|
||||
@@ -123,8 +130,8 @@ export const columns: ColumnDef<Payment>[] = [
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="w-full capitalize text-right">
|
||||
{getRelativeTime(row.original.date)}
|
||||
<div className="w-full capitalize text-right truncate">
|
||||
{getRelativeTime(row.original.updated_at)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -148,14 +155,10 @@ export const columns: ColumnDef<Payment>[] = [
|
||||
className="text-destructive"
|
||||
onClick={() => {
|
||||
deleteWorkflow(workflow.id);
|
||||
// navigator.clipboard.writeText(payment.id)
|
||||
}}
|
||||
>
|
||||
Delete Workflow
|
||||
</DropdownMenuItem>
|
||||
{/* <DropdownMenuSeparator />
|
||||
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||
<DropdownMenuItem>View payment details</DropdownMenuItem> */}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
@@ -163,7 +166,7 @@ export const columns: ColumnDef<Payment>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export function WorkflowList({ data }: { data: Payment[] }) {
|
||||
export function WorkflowList({ data }: { data: WorkflowItemList[] }) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
@@ -196,38 +199,12 @@ export function WorkflowList({ data }: { data: Payment[] }) {
|
||||
<div className="flex flex-row w-full items-center py-4">
|
||||
<Input
|
||||
placeholder="Filter workflows..."
|
||||
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
|
||||
value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
|
||||
onChange={(event) =>
|
||||
table.getColumn("email")?.setFilterValue(event.target.value)
|
||||
table.getColumn("name")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
{/* <DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto">
|
||||
Columns <ChevronDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{table
|
||||
.getAllColumns()
|
||||
.filter((column) => column.getCanHide())
|
||||
.map((column) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) =>
|
||||
column.toggleVisibility(!!value)
|
||||
}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu> */}
|
||||
</div>
|
||||
<ScrollArea className="h-full w-full rounded-md border">
|
||||
<Table>
|
||||
|
||||
@@ -42,105 +42,105 @@ const Model = z.object({
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export const CivitalModelSchema = z.object({
|
||||
items: z.array(
|
||||
export const CivitaiModel = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
type: z.string(),
|
||||
// poi: z.boolean(),
|
||||
// nsfw: z.boolean(),
|
||||
// allowNoCredit: z.boolean(),
|
||||
// allowCommercialUse: z.string(),
|
||||
// allowDerivatives: z.boolean(),
|
||||
// allowDifferentLicense: z.boolean(),
|
||||
// stats: z.object({
|
||||
// downloadCount: z.number(),
|
||||
// favoriteCount: z.number(),
|
||||
// commentCount: z.number(),
|
||||
// ratingCount: z.number(),
|
||||
// rating: z.number(),
|
||||
// tippedAmountCount: z.number(),
|
||||
// }),
|
||||
creator: z
|
||||
.object({
|
||||
username: z.string().nullable(),
|
||||
image: z.string().nullable().default(null),
|
||||
})
|
||||
.nullable(),
|
||||
tags: z.array(z.string()),
|
||||
modelVersions: z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
modelId: z.number(),
|
||||
name: z.string(),
|
||||
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(
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
status: z.string(),
|
||||
publishedAt: z.string(),
|
||||
trainedWords: z.array(z.unknown()),
|
||||
trainingStatus: z.string().nullable(),
|
||||
trainingDetails: z.string().nullable(),
|
||||
baseModel: z.string(),
|
||||
baseModelType: z.string().nullable(),
|
||||
earlyAccessTimeFrame: z.number(),
|
||||
description: z.string().nullable(),
|
||||
vaeId: z.number().nullable(),
|
||||
stats: z.object({
|
||||
downloadCount: z.number(),
|
||||
ratingCount: z.number(),
|
||||
rating: z.number(),
|
||||
}),
|
||||
files: z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
modelId: z.number(),
|
||||
sizeKB: z.number(),
|
||||
name: z.string(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
status: z.string(),
|
||||
publishedAt: z.string(),
|
||||
trainedWords: z.array(z.unknown()),
|
||||
trainingStatus: z.string().nullable(),
|
||||
trainingDetails: z.string().nullable(),
|
||||
baseModel: z.string(),
|
||||
baseModelType: z.string().nullable(),
|
||||
earlyAccessTimeFrame: z.number(),
|
||||
description: z.string().nullable(),
|
||||
vaeId: z.number().nullable(),
|
||||
stats: z.object({
|
||||
downloadCount: z.number(),
|
||||
ratingCount: z.number(),
|
||||
rating: z.number(),
|
||||
}),
|
||||
files: z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
sizeKB: z.number(),
|
||||
name: z.string(),
|
||||
type: z.string(),
|
||||
// metadata: z.object({
|
||||
// fp: z.string().nullable().optional(),
|
||||
// size: z.string().nullable().optional(),
|
||||
// format: z.string().nullable().optional(),
|
||||
// }),
|
||||
// pickleScanResult: z.string(),
|
||||
// pickleScanMessage: z.string(),
|
||||
// virusScanResult: z.string(),
|
||||
// virusScanMessage: z.string().nullable(),
|
||||
// scannedAt: z.string(),
|
||||
// hashes: z.object({
|
||||
// AutoV1: z.string().nullable().optional(),
|
||||
// AutoV2: z.string().nullable().optional(),
|
||||
// SHA256: z.string().nullable().optional(),
|
||||
// CRC32: z.string().nullable().optional(),
|
||||
// BLAKE3: z.string().nullable().optional(),
|
||||
// }),
|
||||
downloadUrl: z.string(),
|
||||
// primary: z.boolean().default(false),
|
||||
})
|
||||
),
|
||||
images: z.array(
|
||||
z.object({
|
||||
id: z.number(),
|
||||
url: z.string(),
|
||||
nsfw: z.string(),
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
hash: z.string(),
|
||||
type: z.string(),
|
||||
metadata: z.object({
|
||||
hash: z.string(),
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
}),
|
||||
meta: z.any(),
|
||||
})
|
||||
),
|
||||
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(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export const CivitalModelSchema = z.object({
|
||||
items: z.array(CivitaiModel),
|
||||
metadata: z.object({
|
||||
totalItems: z.number(),
|
||||
currentPage: z.number(),
|
||||
@@ -197,7 +197,7 @@ function mapType(type: string) {
|
||||
}
|
||||
|
||||
function mapModelsList(
|
||||
models: z.infer<typeof CivitalModelSchema>
|
||||
models: z.infer<typeof CivitalModelSchema>,
|
||||
): z.infer<typeof ModelListWrapper> {
|
||||
return {
|
||||
models: models.items.flatMap((item) => {
|
||||
@@ -241,8 +241,9 @@ function getUrl(search?: string) {
|
||||
export function CivitaiModelRegistry({
|
||||
field,
|
||||
}: Pick<AutoFormInputComponentProps, "field">) {
|
||||
const [modelList, setModelList] =
|
||||
React.useState<z.infer<typeof ModelListWrapper>>();
|
||||
const [modelList, setModelList] = React.useState<
|
||||
z.infer<typeof ModelListWrapper>
|
||||
>();
|
||||
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
@@ -301,8 +302,9 @@ export function CivitaiModelRegistry({
|
||||
export function ComfyUIManagerModelRegistry({
|
||||
field,
|
||||
}: Pick<AutoFormInputComponentProps, "field">) {
|
||||
const [modelList, setModelList] =
|
||||
React.useState<z.infer<typeof ModelListWrapper>>();
|
||||
const [modelList, setModelList] = React.useState<
|
||||
z.infer<typeof ModelListWrapper>
|
||||
>();
|
||||
|
||||
React.useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
@@ -310,7 +312,7 @@ export function ComfyUIManagerModelRegistry({
|
||||
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
|
||||
{
|
||||
signal: controller.signal,
|
||||
}
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.then((a) => {
|
||||
@@ -353,14 +355,14 @@ export function ModelSelector({
|
||||
if (
|
||||
prevSelectedModels.some(
|
||||
(selectedModel) =>
|
||||
selectedModel.url + selectedModel.name === model.url + model.name
|
||||
selectedModel.url + selectedModel.name === model.url + model.name,
|
||||
)
|
||||
) {
|
||||
field.onChange(
|
||||
prevSelectedModels.filter(
|
||||
(selectedModel) =>
|
||||
selectedModel.url + selectedModel.name !== model.url + model.name
|
||||
)
|
||||
selectedModel.url + selectedModel.name !== model.url + model.name,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
field.onChange([...prevSelectedModels, model]);
|
||||
@@ -408,10 +410,10 @@ export function ModelSelector({
|
||||
className={cn(
|
||||
"ml-auto h-4 w-4",
|
||||
value.some(
|
||||
(selectedModel) => selectedModel.url === model.url
|
||||
)
|
||||
(selectedModel) => selectedModel.url === model.url,
|
||||
)
|
||||
? "opacity-100"
|
||||
: "opacity-0"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
// Error components must be Client Components
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function ErrorPage({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
console.log(error.message);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="border rounded-2xl flex flex-col w-full h-full gap-20 justify-center items-center text-xs">
|
||||
<div className="max-w-xl w-full">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<div className="text-xl">Unexpected error.</div>
|
||||
</CardTitle>
|
||||
<CardDescription className="flex flex-col gap-4">
|
||||
<div className="text-sm">Error: {error.message}</div>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
className="w-fit"
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
Refresh Page
|
||||
</Button>
|
||||
</div>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorFullPage({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-full py-4 flex justify-center items-center gap-2 text-sm h-full"
|
||||
)}
|
||||
>
|
||||
<ErrorPage error={error} reset={reset} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import AutoFormSwitch from "./fields/switch";
|
||||
import AutoFormTextarea from "./fields/textarea";
|
||||
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
|
||||
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
|
||||
import AutoFormCheckpointInput from "@/components/custom-form/checkpoint-input";
|
||||
|
||||
export const INPUT_COMPONENTS = {
|
||||
checkbox: AutoFormCheckbox,
|
||||
@@ -22,6 +23,7 @@ export const INPUT_COMPONENTS = {
|
||||
// Customs
|
||||
snapshot: AutoFormSnapshotPicker,
|
||||
models: AutoFormModelsPicker,
|
||||
checkpoints: AutoFormCheckpointInput,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,7 +21,7 @@ export function AutoFormSubmit({
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Button type="submit" disabled={disabled}>
|
||||
<Button type="submit" disabled={disabled} className="flex gap-2">
|
||||
{children ?? "Submit"}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DefaultValues } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import type { DefaultValues } from "react-hook-form";
|
||||
import type { z } from "zod";
|
||||
|
||||
// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.
|
||||
export type ZodObjectOrWrapped =
|
||||
@@ -21,7 +21,7 @@ export function beautifyObjectName(string: string) {
|
||||
* This will unpack optionals, refinements, etc.
|
||||
*/
|
||||
export function getBaseSchema<
|
||||
ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny,
|
||||
ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny
|
||||
>(schema: ChildType | z.ZodEffects<ChildType>): ChildType {
|
||||
if ("innerType" in schema._def) {
|
||||
return getBaseSchema(schema._def.innerType as ChildType);
|
||||
@@ -54,12 +54,12 @@ export function getDefaultValueInZodStack(schema: z.ZodAny): any {
|
||||
|
||||
if ("innerType" in typedSchema._def) {
|
||||
return getDefaultValueInZodStack(
|
||||
typedSchema._def.innerType as unknown as z.ZodAny,
|
||||
typedSchema._def.innerType as unknown as z.ZodAny
|
||||
);
|
||||
}
|
||||
if ("schema" in typedSchema._def) {
|
||||
return getDefaultValueInZodStack(
|
||||
(typedSchema._def as any).schema as z.ZodAny,
|
||||
(typedSchema._def as any).schema as z.ZodAny
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
@@ -69,7 +69,7 @@ export function getDefaultValueInZodStack(schema: z.ZodAny): any {
|
||||
* Get all default values from a Zod schema.
|
||||
*/
|
||||
export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
|
||||
schema: Schema,
|
||||
schema: Schema
|
||||
) {
|
||||
const { shape } = schema;
|
||||
type DefaultValuesType = DefaultValues<Partial<z.infer<Schema>>>;
|
||||
@@ -80,7 +80,7 @@ export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
|
||||
|
||||
if (getBaseType(item) === "ZodObject") {
|
||||
const defaultItems = getDefaultValues(
|
||||
getBaseSchema(item) as unknown as z.ZodObject<any, any>,
|
||||
getBaseSchema(item) as unknown as z.ZodObject<any, any>
|
||||
);
|
||||
for (const defaultItemKey of Object.keys(defaultItems)) {
|
||||
const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType;
|
||||
@@ -98,7 +98,7 @@ export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
|
||||
}
|
||||
|
||||
export function getObjectFormSchema(
|
||||
schema: ZodObjectOrWrapped,
|
||||
schema: ZodObjectOrWrapped
|
||||
): z.ZodObject<any, any> {
|
||||
if (schema._def.typeName === "ZodEffects") {
|
||||
const typedSchema = schema as z.ZodEffects<z.ZodObject<any, any>>;
|
||||
@@ -116,7 +116,7 @@ export function zodToHtmlInputProps(
|
||||
| z.ZodNumber
|
||||
| z.ZodString
|
||||
| z.ZodOptional<z.ZodNumber | z.ZodString>
|
||||
| any,
|
||||
| any
|
||||
): React.InputHTMLAttributes<HTMLInputElement> {
|
||||
if (["ZodOptional", "ZodNullable"].includes(schema._def.typeName)) {
|
||||
const typedSchema = schema as z.ZodOptional<z.ZodNumber | z.ZodString>;
|
||||
@@ -128,9 +128,10 @@ export function zodToHtmlInputProps(
|
||||
|
||||
const typedSchema = schema as z.ZodNumber | z.ZodString;
|
||||
|
||||
if (!("checks" in typedSchema._def)) return {
|
||||
required: true
|
||||
};
|
||||
if (!("checks" in typedSchema._def))
|
||||
return {
|
||||
required: true,
|
||||
};
|
||||
|
||||
const { checks } = typedSchema._def;
|
||||
const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {
|
||||
|
||||
@@ -2,20 +2,47 @@ import { cn } from "@/lib/utils";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
const colors = {
|
||||
red: "bg-red-500/15 text-red-700 group-data-[hover]:bg-red-500/25 dark:bg-red-500/10 dark:text-red-400 dark:group-data-[hover]:bg-red-500/20",
|
||||
orange:
|
||||
"bg-orange-500/15 text-orange-700 group-data-[hover]:bg-orange-500/25 dark:bg-orange-500/10 dark:text-orange-400 dark:group-data-[hover]:bg-orange-500/20",
|
||||
amber:
|
||||
"bg-amber-400/20 text-amber-700 group-data-[hover]:bg-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400 dark:group-data-[hover]:bg-amber-400/15",
|
||||
yellow:
|
||||
"bg-yellow-400/20 text-yellow-700 group-data-[hover]:bg-yellow-400/30 dark:bg-yellow-400/10 dark:text-yellow-300 dark:group-data-[hover]:bg-yellow-400/15",
|
||||
lime: "bg-lime-400/20 text-lime-700 group-data-[hover]:bg-lime-400/30 dark:bg-lime-400/10 dark:text-lime-300 dark:group-data-[hover]:bg-lime-400/15",
|
||||
green:
|
||||
"bg-green-500/15 text-green-700 group-data-[hover]:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:group-data-[hover]:bg-green-500/20",
|
||||
emerald:
|
||||
"bg-emerald-500/15 text-emerald-700 group-data-[hover]:bg-emerald-500/25 dark:bg-emerald-500/10 dark:text-emerald-400 dark:group-data-[hover]:bg-emerald-500/20",
|
||||
teal: "bg-teal-500/15 text-teal-700 group-data-[hover]:bg-teal-500/25 dark:bg-teal-500/10 dark:text-teal-300 dark:group-data-[hover]:bg-teal-500/20",
|
||||
cyan: "bg-cyan-400/20 text-cyan-700 group-data-[hover]:bg-cyan-400/30 dark:bg-cyan-400/10 dark:text-cyan-300 dark:group-data-[hover]:bg-cyan-400/15",
|
||||
sky: "bg-sky-500/15 text-sky-700 group-data-[hover]:bg-sky-500/25 dark:bg-sky-500/10 dark:text-sky-300 dark:group-data-[hover]:bg-sky-500/20",
|
||||
blue: "bg-blue-500/15 text-blue-700 group-data-[hover]:bg-blue-500/25 dark:text-blue-400 dark:group-data-[hover]:bg-blue-500/25",
|
||||
indigo:
|
||||
"bg-indigo-500/15 text-indigo-700 group-data-[hover]:bg-indigo-500/25 dark:text-indigo-400 dark:group-data-[hover]:bg-indigo-500/20",
|
||||
violet:
|
||||
"bg-violet-500/15 text-violet-700 group-data-[hover]:bg-violet-500/25 dark:text-violet-400 dark:group-data-[hover]:bg-violet-500/20",
|
||||
purple:
|
||||
"bg-purple-500/15 text-purple-700 group-data-[hover]:bg-purple-500/25 dark:text-purple-400 dark:group-data-[hover]:bg-purple-500/20",
|
||||
fuchsia:
|
||||
"bg-fuchsia-400/15 text-fuchsia-700 group-data-[hover]:bg-fuchsia-400/25 dark:bg-fuchsia-400/10 dark:text-fuchsia-400 dark:group-data-[hover]:bg-fuchsia-400/20",
|
||||
pink: "bg-pink-400/15 text-pink-700 group-data-[hover]:bg-pink-400/25 dark:bg-pink-400/10 dark:text-pink-400 dark:group-data-[hover]:bg-pink-400/20",
|
||||
rose: "bg-rose-400/15 text-rose-700 group-data-[hover]:bg-rose-400/25 dark:bg-rose-400/10 dark:text-rose-400 dark:group-data-[hover]:bg-rose-400/20",
|
||||
zinc: "bg-zinc-600/10 text-zinc-700 group-data-[hover]:bg-zinc-600/20 dark:bg-white/5 dark:text-zinc-400 dark:group-data-[hover]:bg-white/10",
|
||||
};
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
"inline-flex items-center gap-x-1.5 rounded-md px-2 py-0.5 text-sm/5 font-medium sm:text-xs/5 forced-colors:outline",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
success:
|
||||
"border-transparent bg-green-200 text-secondary-foreground hover:bg-green-200/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
default: colors["zinc"],
|
||||
secondary: colors["zinc"],
|
||||
success: colors["green"],
|
||||
destructive: colors["red"],
|
||||
outline: "text-foreground border",
|
||||
...colors,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -3,34 +3,201 @@ import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
const styles = {
|
||||
base: [
|
||||
// Base
|
||||
"relative isolate inline-flex items-center justify-center gap-x-2 rounded-lg border text-base/6 font-semibold",
|
||||
|
||||
// Sizing
|
||||
"px-[calc(theme(spacing.3)-1px)] py-[calc(theme(spacing[1.5])-1px)] text-sm/6",
|
||||
|
||||
// Focus
|
||||
"focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500",
|
||||
|
||||
// Disabled
|
||||
"data-[disabled]:opacity-50",
|
||||
|
||||
// Icon
|
||||
"[&>[data-slot=icon]]:-mx-0.5 [&>[data-slot=icon]]:my-0.5 [&>[data-slot=icon]]:size-5 [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-[--btn-icon] [&>[data-slot=icon]]:sm:my-1 [&>[data-slot=icon]]:sm:size-4 forced-colors:[--btn-icon:ButtonText] forced-colors:data-[hover]:[--btn-icon:ButtonText]",
|
||||
],
|
||||
solid: [
|
||||
// Optical border, implemented as the button background to avoid corner artifacts
|
||||
"border-transparent bg-[--btn-border]",
|
||||
|
||||
// Dark mode: border is rendered on `after` so background is set to button background
|
||||
"dark:bg-[--btn-bg]",
|
||||
|
||||
// Button background, implemented as foreground layer to stack on top of pseudo-border layer
|
||||
"before:absolute before:inset-0 before:-z-10 before:rounded-[calc(theme(borderRadius.lg)-1px)] before:bg-[--btn-bg]",
|
||||
|
||||
// Drop shadow, applied to the inset `before` layer so it blends with the border
|
||||
"before:shadow",
|
||||
|
||||
// Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
|
||||
"dark:before:hidden",
|
||||
|
||||
// Dark mode: Subtle white outline is applied using a border
|
||||
"dark:border-white/5",
|
||||
|
||||
// Shim/overlay, inset to match button foreground and used for hover state + highlight shadow
|
||||
"after:absolute after:inset-0 after:-z-10 after:rounded-[calc(theme(borderRadius.lg)-1px)]",
|
||||
|
||||
// Inner highlight shadow
|
||||
"after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)]",
|
||||
|
||||
// White overlay on hover
|
||||
"after:data-[active]:bg-[--btn-hover-overlay] after:data-[hover]:bg-[--btn-hover-overlay]",
|
||||
|
||||
// Dark mode: `after` layer expands to cover entire button
|
||||
"dark:after:-inset-px dark:after:rounded-lg",
|
||||
|
||||
// Disabled
|
||||
"before:data-[disabled]:shadow-none after:data-[disabled]:shadow-none",
|
||||
],
|
||||
outline: [
|
||||
// Base
|
||||
"border-zinc-950/10 text-zinc-950 data-[active]:bg-zinc-950/[2.5%] data-[hover]:bg-zinc-950/[2.5%]",
|
||||
|
||||
// Dark mode
|
||||
"dark:border-white/15 dark:text-white dark:[--btn-bg:transparent] dark:data-[active]:bg-white/5 dark:data-[hover]:bg-white/5",
|
||||
|
||||
// Icon
|
||||
"[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
|
||||
],
|
||||
plain: [
|
||||
// Base
|
||||
"border-transparent text-zinc-950 data-[active]:bg-zinc-950/5 data-[hover]:bg-zinc-950/5",
|
||||
|
||||
// Dark mode
|
||||
"dark:text-white dark:data-[active]:bg-white/10 dark:data-[hover]:bg-white/10",
|
||||
|
||||
// Icon
|
||||
"[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
|
||||
],
|
||||
colors: {
|
||||
"dark/zinc": [
|
||||
"text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]",
|
||||
"dark:text-white dark:[--btn-bg:theme(colors.zinc.600)] dark:[--btn-hover-overlay:theme(colors.white/5%)]",
|
||||
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]",
|
||||
],
|
||||
light: [
|
||||
"text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]",
|
||||
"dark:text-white dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]",
|
||||
"[--btn-icon:theme(colors.zinc.500)] data-[active]:[--btn-icon:theme(colors.zinc.700)] data-[hover]:[--btn-icon:theme(colors.zinc.700)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
|
||||
],
|
||||
"dark/white": [
|
||||
"text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]",
|
||||
"dark:text-zinc-950 dark:[--btn-bg:white] dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]",
|
||||
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)] dark:[--btn-icon:theme(colors.zinc.500)] dark:data-[active]:[--btn-icon:theme(colors.zinc.400)] dark:data-[hover]:[--btn-icon:theme(colors.zinc.400)]",
|
||||
],
|
||||
dark: [
|
||||
"text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)]",
|
||||
"dark:[--btn-hover-overlay:theme(colors.white/5%)] dark:[--btn-bg:theme(colors.zinc.800)]",
|
||||
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]",
|
||||
],
|
||||
white: [
|
||||
"text-zinc-950 [--btn-bg:white] [--btn-border:theme(colors.zinc.950/10%)] [--btn-hover-overlay:theme(colors.zinc.950/2.5%)] data-[active]:[--btn-border:theme(colors.zinc.950/15%)] data-[hover]:[--btn-border:theme(colors.zinc.950/15%)]",
|
||||
"dark:[--btn-hover-overlay:theme(colors.zinc.950/5%)]",
|
||||
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.500)] data-[hover]:[--btn-icon:theme(colors.zinc.500)]",
|
||||
],
|
||||
zinc: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.zinc.600)] [--btn-border:theme(colors.zinc.700/90%)]",
|
||||
"dark:[--btn-hover-overlay:theme(colors.white/5%)]",
|
||||
"[--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)]",
|
||||
],
|
||||
indigo: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.indigo.500)] [--btn-border:theme(colors.indigo.600/90%)]",
|
||||
"[--btn-icon:theme(colors.indigo.300)] data-[active]:[--btn-icon:theme(colors.indigo.200)] data-[hover]:[--btn-icon:theme(colors.indigo.200)]",
|
||||
],
|
||||
cyan: [
|
||||
"text-cyan-950 [--btn-bg:theme(colors.cyan.300)] [--btn-border:theme(colors.cyan.400/80%)] [--btn-hover-overlay:theme(colors.white/25%)]",
|
||||
"[--btn-icon:theme(colors.cyan.500)]",
|
||||
],
|
||||
red: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.red.600)] [--btn-border:theme(colors.red.700/90%)]",
|
||||
"[--btn-icon:theme(colors.red.300)] data-[active]:[--btn-icon:theme(colors.red.200)] data-[hover]:[--btn-icon:theme(colors.red.200)]",
|
||||
],
|
||||
orange: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.orange.500)] [--btn-border:theme(colors.orange.600/90%)]",
|
||||
"[--btn-icon:theme(colors.orange.300)] data-[active]:[--btn-icon:theme(colors.orange.200)] data-[hover]:[--btn-icon:theme(colors.orange.200)]",
|
||||
],
|
||||
amber: [
|
||||
"text-amber-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.amber.400)] [--btn-border:theme(colors.amber.500/80%)]",
|
||||
"[--btn-icon:theme(colors.amber.600)]",
|
||||
],
|
||||
yellow: [
|
||||
"text-yellow-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.yellow.300)] [--btn-border:theme(colors.yellow.400/80%)]",
|
||||
"[--btn-icon:theme(colors.yellow.600)] data-[active]:[--btn-icon:theme(colors.yellow.700)] data-[hover]:[--btn-icon:theme(colors.yellow.700)]",
|
||||
],
|
||||
lime: [
|
||||
"text-lime-950 [--btn-hover-overlay:theme(colors.white/25%)] [--btn-bg:theme(colors.lime.300)] [--btn-border:theme(colors.lime.400/80%)]",
|
||||
"[--btn-icon:theme(colors.lime.600)] data-[active]:[--btn-icon:theme(colors.lime.700)] data-[hover]:[--btn-icon:theme(colors.lime.700)]",
|
||||
],
|
||||
green: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.green.600)] [--btn-border:theme(colors.green.700/90%)]",
|
||||
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
|
||||
],
|
||||
emerald: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.emerald.600)] [--btn-border:theme(colors.emerald.700/90%)]",
|
||||
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
|
||||
],
|
||||
teal: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.teal.600)] [--btn-border:theme(colors.teal.700/90%)]",
|
||||
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
|
||||
],
|
||||
sky: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.sky.500)] [--btn-border:theme(colors.sky.600/80%)]",
|
||||
"[--btn-icon:theme(colors.white/60%)] data-[active]:[--btn-icon:theme(colors.white/80%)] data-[hover]:[--btn-icon:theme(colors.white/80%)]",
|
||||
],
|
||||
blue: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.blue.600)] [--btn-border:theme(colors.blue.700/90%)]",
|
||||
"[--btn-icon:theme(colors.blue.400)] data-[active]:[--btn-icon:theme(colors.blue.300)] data-[hover]:[--btn-icon:theme(colors.blue.300)]",
|
||||
],
|
||||
violet: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.violet.500)] [--btn-border:theme(colors.violet.600/90%)]",
|
||||
"[--btn-icon:theme(colors.violet.300)] data-[active]:[--btn-icon:theme(colors.violet.200)] data-[hover]:[--btn-icon:theme(colors.violet.200)]",
|
||||
],
|
||||
purple: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.purple.500)] [--btn-border:theme(colors.purple.600/90%)]",
|
||||
"[--btn-icon:theme(colors.purple.300)] data-[active]:[--btn-icon:theme(colors.purple.200)] data-[hover]:[--btn-icon:theme(colors.purple.200)]",
|
||||
],
|
||||
fuchsia: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.fuchsia.500)] [--btn-border:theme(colors.fuchsia.600/90%)]",
|
||||
"[--btn-icon:theme(colors.fuchsia.300)] data-[active]:[--btn-icon:theme(colors.fuchsia.200)] data-[hover]:[--btn-icon:theme(colors.fuchsia.200)]",
|
||||
],
|
||||
pink: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.pink.500)] [--btn-border:theme(colors.pink.600/90%)]",
|
||||
"[--btn-icon:theme(colors.pink.300)] data-[active]:[--btn-icon:theme(colors.pink.200)] data-[hover]:[--btn-icon:theme(colors.pink.200)]",
|
||||
],
|
||||
rose: [
|
||||
"text-white [--btn-hover-overlay:theme(colors.white/10%)] [--btn-bg:theme(colors.rose.500)] [--btn-border:theme(colors.rose.600/90%)]",
|
||||
"[--btn-icon:theme(colors.rose.300)] data-[active]:[--btn-icon:theme(colors.rose.200)] data-[hover]:[--btn-icon:theme(colors.rose.200)]",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const buttonVariants = cva(styles.base, {
|
||||
variants: {
|
||||
variant: {
|
||||
default: [...styles.solid, ...styles.colors.zinc],
|
||||
destructive: [...styles.solid, ...styles.colors.red],
|
||||
outline: styles.outline,
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: styles.plain,
|
||||
link: "border-none text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import useEmblaCarousel, {
|
||||
type UseEmblaCarouselType,
|
||||
} from "embla-carousel-react"
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
type CarouselApi = UseEmblaCarouselType[1]
|
||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||
type CarouselOptions = UseCarouselParameters[0]
|
||||
type CarouselPlugin = UseCarouselParameters[1]
|
||||
|
||||
type CarouselProps = {
|
||||
opts?: CarouselOptions
|
||||
plugins?: CarouselPlugin
|
||||
orientation?: "horizontal" | "vertical"
|
||||
setApi?: (api: CarouselApi) => void
|
||||
}
|
||||
|
||||
type CarouselContextProps = {
|
||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
||||
api: ReturnType<typeof useEmblaCarousel>[1]
|
||||
scrollPrev: () => void
|
||||
scrollNext: () => void
|
||||
canScrollPrev: boolean
|
||||
canScrollNext: boolean
|
||||
} & CarouselProps
|
||||
|
||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
||||
|
||||
function useCarousel() {
|
||||
const context = React.useContext(CarouselContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useCarousel must be used within a <Carousel />")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
const Carousel = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
orientation = "horizontal",
|
||||
opts,
|
||||
setApi,
|
||||
plugins,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [carouselRef, api] = useEmblaCarousel(
|
||||
{
|
||||
...opts,
|
||||
axis: orientation === "horizontal" ? "x" : "y",
|
||||
},
|
||||
plugins
|
||||
)
|
||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
||||
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
||||
|
||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||
if (!api) {
|
||||
return
|
||||
}
|
||||
|
||||
setCanScrollPrev(api.canScrollPrev())
|
||||
setCanScrollNext(api.canScrollNext())
|
||||
}, [])
|
||||
|
||||
const scrollPrev = React.useCallback(() => {
|
||||
api?.scrollPrev()
|
||||
}, [api])
|
||||
|
||||
const scrollNext = React.useCallback(() => {
|
||||
api?.scrollNext()
|
||||
}, [api])
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === "ArrowLeft") {
|
||||
event.preventDefault()
|
||||
scrollPrev()
|
||||
} else if (event.key === "ArrowRight") {
|
||||
event.preventDefault()
|
||||
scrollNext()
|
||||
}
|
||||
},
|
||||
[scrollPrev, scrollNext]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api || !setApi) {
|
||||
return
|
||||
}
|
||||
|
||||
setApi(api)
|
||||
}, [api, setApi])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return
|
||||
}
|
||||
|
||||
onSelect(api)
|
||||
api.on("reInit", onSelect)
|
||||
api.on("select", onSelect)
|
||||
|
||||
return () => {
|
||||
api?.off("select", onSelect)
|
||||
}
|
||||
}, [api, onSelect])
|
||||
|
||||
return (
|
||||
<CarouselContext.Provider
|
||||
value={{
|
||||
carouselRef,
|
||||
api: api,
|
||||
opts,
|
||||
orientation:
|
||||
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||
scrollPrev,
|
||||
scrollNext,
|
||||
canScrollPrev,
|
||||
canScrollNext,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
className={cn("relative", className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</CarouselContext.Provider>
|
||||
)
|
||||
}
|
||||
)
|
||||
Carousel.displayName = "Carousel"
|
||||
|
||||
const CarouselContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { carouselRef, orientation } = useCarousel()
|
||||
|
||||
return (
|
||||
<div ref={carouselRef} className="overflow-hidden">
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex",
|
||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
CarouselContent.displayName = "CarouselContent"
|
||||
|
||||
const CarouselItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { orientation } = useCarousel()
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
className={cn(
|
||||
"min-w-0 shrink-0 grow-0 basis-full",
|
||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
CarouselItem.displayName = "CarouselItem"
|
||||
|
||||
const CarouselPrevious = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "-left-12 top-1/2 -translate-y-1/2"
|
||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!canScrollPrev}
|
||||
onClick={scrollPrev}
|
||||
{...props}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
CarouselPrevious.displayName = "CarouselPrevious"
|
||||
|
||||
const CarouselNext = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "-right-12 top-1/2 -translate-y-1/2"
|
||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!canScrollNext}
|
||||
onClick={scrollNext}
|
||||
{...props}
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
<span className="sr-only">Next slide</span>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
CarouselNext.displayName = "CarouselNext"
|
||||
|
||||
export {
|
||||
type CarouselApi,
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselPrevious,
|
||||
CarouselNext,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { callServerPromise } from "@/components/callServerPromise";
|
||||
import { useEffect, useState, useTransition } from "react";
|
||||
|
||||
export function useServerActionData<I, O>(
|
||||
action: (data: I) => Promise<O>,
|
||||
input: I
|
||||
) {
|
||||
const [data, setData] = useState<O | null>(null);
|
||||
const [pending, startTransition] = useTransition();
|
||||
const [started, setStarted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
startTransition(() => {
|
||||
setStarted(true);
|
||||
callServerPromise(action(input)).then(setData);
|
||||
});
|
||||
}, [action, input]);
|
||||
|
||||
return {
|
||||
started,
|
||||
data,
|
||||
pending,
|
||||
};
|
||||
}
|
||||
+157
-14
@@ -1,13 +1,14 @@
|
||||
import { relations, type InferSelectModel } from "drizzle-orm";
|
||||
import { CivitaiModelResponse } from "@/types/civitai";
|
||||
import { type InferSelectModel, relations } from "drizzle-orm";
|
||||
import {
|
||||
text,
|
||||
pgSchema,
|
||||
uuid,
|
||||
boolean,
|
||||
integer,
|
||||
timestamp,
|
||||
jsonb,
|
||||
pgEnum,
|
||||
boolean,
|
||||
pgSchema,
|
||||
text,
|
||||
timestamp,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
@@ -35,7 +36,11 @@ export const workflowTable = dbSchema.table("workflows", {
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const workflowRelations = relations(workflowTable, ({ many }) => ({
|
||||
export const workflowRelations = relations(workflowTable, ({ many, one }) => ({
|
||||
user: one(usersTable, {
|
||||
fields: [workflowTable.user_id],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
versions: many(workflowVersionTable),
|
||||
deployments: many(deploymentsTable),
|
||||
}));
|
||||
@@ -83,7 +88,7 @@ export const workflowVersionRelations = relations(
|
||||
fields: [workflowVersionTable.workflow_id],
|
||||
references: [workflowTable.id],
|
||||
}),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
export const workflowRunStatus = pgEnum("workflow_run_status", [
|
||||
@@ -103,8 +108,12 @@ export const deploymentEnvironment = pgEnum("deployment_environment", [
|
||||
export const workflowRunOrigin = pgEnum("workflow_run_origin", [
|
||||
"manual",
|
||||
"api",
|
||||
"public-share",
|
||||
]);
|
||||
|
||||
export const WorkflowRunOriginSchema = z.enum(workflowRunOrigin.enumValues);
|
||||
export type WorkflowRunOriginType = z.infer<typeof WorkflowRunOriginSchema>;
|
||||
|
||||
export const machineGPUOptions = pgEnum("machine_gpu", ["T4", "A10G", "A100"]);
|
||||
|
||||
export const machinesType = pgEnum("machine_type", [
|
||||
@@ -128,10 +137,11 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
|
||||
() => workflowVersionTable.id,
|
||||
{
|
||||
onDelete: "set null",
|
||||
}
|
||||
},
|
||||
),
|
||||
workflow_inputs:
|
||||
jsonb("workflow_inputs").$type<Record<string, string | number>>(),
|
||||
workflow_inputs: jsonb("workflow_inputs").$type<
|
||||
Record<string, string | number>
|
||||
>(),
|
||||
workflow_id: uuid("workflow_id")
|
||||
.notNull()
|
||||
.references(() => workflowTable.id, {
|
||||
@@ -163,7 +173,7 @@ export const workflowRunRelations = relations(
|
||||
fields: [workflowRunsTable.workflow_id],
|
||||
references: [workflowTable.id],
|
||||
}),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// We still want to keep the workflow run record.
|
||||
@@ -187,7 +197,7 @@ export const workflowOutputRelations = relations(
|
||||
fields: [workflowRunOutputs.run_id],
|
||||
references: [workflowRunsTable.id],
|
||||
}),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// when user delete, also delete all the workflow versions
|
||||
@@ -220,7 +230,7 @@ export const snapshotType = z.object({
|
||||
z.object({
|
||||
hash: z.string(),
|
||||
disabled: z.boolean(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
file_custom_nodes: z.array(z.any()),
|
||||
});
|
||||
@@ -231,6 +241,22 @@ export const insertMachineSchema = createInsertSchema(machinesTable, {
|
||||
type: (schema) => schema.type.default("classic"),
|
||||
});
|
||||
|
||||
export const showcaseMedia = z.array(
|
||||
z.object({
|
||||
url: z.string(),
|
||||
isCover: z.boolean().default(false),
|
||||
}),
|
||||
);
|
||||
|
||||
export const showcaseMediaNullable = z
|
||||
.array(
|
||||
z.object({
|
||||
url: z.string(),
|
||||
isCover: z.boolean().default(false),
|
||||
}),
|
||||
)
|
||||
.nullable();
|
||||
|
||||
export const deploymentsTable = dbSchema.table("deployments", {
|
||||
id: uuid("id").primaryKey().defaultRandom().notNull(),
|
||||
user_id: text("user_id")
|
||||
@@ -238,6 +264,7 @@ export const deploymentsTable = dbSchema.table("deployments", {
|
||||
onDelete: "cascade",
|
||||
})
|
||||
.notNull(),
|
||||
org_id: text("org_id"),
|
||||
workflow_version_id: uuid("workflow_version_id")
|
||||
.notNull()
|
||||
.references(() => workflowVersionTable.id),
|
||||
@@ -249,11 +276,28 @@ export const deploymentsTable = dbSchema.table("deployments", {
|
||||
machine_id: uuid("machine_id")
|
||||
.notNull()
|
||||
.references(() => machinesTable.id),
|
||||
description: text("description"),
|
||||
showcase_media: jsonb("showcase_media").$type<
|
||||
z.infer<typeof showcaseMedia>
|
||||
>(),
|
||||
environment: deploymentEnvironment("environment").notNull(),
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const publicShareDeployment = z.object({
|
||||
description: z.string().nullable(),
|
||||
showcase_media: showcaseMedia,
|
||||
});
|
||||
|
||||
// createInsertSchema(deploymentsTable, {
|
||||
// description: (schema) => schema.description.default(""),
|
||||
// showcase_media: () => showcaseMedia.default([]),
|
||||
// }).pick({
|
||||
// description: true,
|
||||
// showcase_media: true,
|
||||
// });
|
||||
|
||||
export const deploymentsRelations = relations(deploymentsTable, ({ one }) => ({
|
||||
machine: one(machinesTable, {
|
||||
fields: [deploymentsTable.machine_id],
|
||||
@@ -288,8 +332,107 @@ export const apiKeyTable = dbSchema.table("api_keys", {
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
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(),
|
||||
user_id: text("user_id")
|
||||
.references(() => usersTable.id, {}), // perhaps a "special" user_id for global checkpoints
|
||||
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")
|
||||
.references(() => usersTable.id, {
|
||||
// onDelete: "cascade",
|
||||
}),
|
||||
org_id: text("org_id"),
|
||||
volume_name: text("volume_name").notNull(),
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
disabled: boolean("disabled").default(false).notNull(),
|
||||
});
|
||||
|
||||
export const 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 WorkflowType = InferSelectModel<typeof workflowTable>;
|
||||
export type MachineType = InferSelectModel<typeof machinesTable>;
|
||||
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
|
||||
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
|
||||
export type CheckpointType = InferSelectModel<typeof checkpointTable>;
|
||||
export type CheckpointVolumeType = InferSelectModel<
|
||||
typeof checkpointVolumeTable
|
||||
>;
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import React from "react";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
dayjs.extend(duration);
|
||||
export function getRelativeTime(time: string | Date | null | undefined) {
|
||||
if (typeof time === "string" || time instanceof Date) {
|
||||
return dayjs().to(time);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
if (minutes > 0) {
|
||||
return `${minutes}.${remainingSeconds} mins`;
|
||||
} else {
|
||||
return `${remainingSeconds.toFixed(1)} secs`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDuration(durationInSecs: number) {
|
||||
return `${formatDuration(durationInSecs)}`;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export const registerCreateRunRoute = (app: App) => {
|
||||
workflow_version_id: deploymentData.version,
|
||||
machine_id: deploymentData.machine,
|
||||
inputs,
|
||||
isManualRun: false,
|
||||
runOrigin: "api",
|
||||
apiUser: apiKeyTokenData,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { insertCivitaiCheckpointSchema } from "@/db/schema";
|
||||
|
||||
export const addCivitaiCheckpointSchema = insertCivitaiCheckpointSchema.pick({
|
||||
civitai_url: true,
|
||||
});
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
import { withServerPromise } from "./withServerPromise";
|
||||
import { db } from "@/db/db";
|
||||
import type { MachineType, WorkflowVersionType } from "@/db/schema";
|
||||
import type {
|
||||
MachineType,
|
||||
WorkflowRunOriginType,
|
||||
WorkflowVersionType,
|
||||
} from "@/db/schema";
|
||||
import { machinesTable, workflowRunsTable } from "@/db/schema";
|
||||
import type { APIKeyUserType } from "@/server/APIKeyBodyRequest";
|
||||
import { getRunsData } from "@/server/getRunsData";
|
||||
@@ -19,14 +23,14 @@ export const createRun = withServerPromise(
|
||||
workflow_version_id,
|
||||
machine_id,
|
||||
inputs,
|
||||
isManualRun,
|
||||
runOrigin,
|
||||
apiUser,
|
||||
}: {
|
||||
origin: string;
|
||||
workflow_version_id: string | WorkflowVersionType;
|
||||
machine_id: string | MachineType;
|
||||
inputs?: Record<string, string | number>;
|
||||
isManualRun?: boolean;
|
||||
runOrigin?: WorkflowRunOriginType;
|
||||
apiUser?: APIKeyUserType;
|
||||
}) => {
|
||||
const machine =
|
||||
@@ -109,7 +113,7 @@ export const createRun = withServerPromise(
|
||||
workflow_version_id: workflow_version_data.id,
|
||||
workflow_inputs: inputs,
|
||||
machine_id: machine.id,
|
||||
origin: isManualRun ? "manual" : "api",
|
||||
origin: runOrigin,
|
||||
})
|
||||
.returning();
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { db } from "@/db/db";
|
||||
import type { DeploymentType } from "@/db/schema";
|
||||
import type { DeploymentType, publicShareDeployment } from "@/db/schema";
|
||||
import { deploymentsTable, workflowTable } from "@/db/schema";
|
||||
import { createNewWorkflow } from "@/server/createNewWorkflow";
|
||||
import { addCustomMachine } from "@/server/curdMachine";
|
||||
@@ -11,6 +11,7 @@ import { and, eq, isNull } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import "server-only";
|
||||
import type { z } from "zod";
|
||||
|
||||
export async function createDeployments(
|
||||
workflow_id: string,
|
||||
@@ -18,7 +19,7 @@ export async function createDeployments(
|
||||
machine_id: string,
|
||||
environment: DeploymentType["environment"]
|
||||
) {
|
||||
const { userId } = auth();
|
||||
const { userId, orgId } = auth();
|
||||
if (!userId) throw new Error("No user id");
|
||||
|
||||
if (!machine_id) {
|
||||
@@ -40,6 +41,7 @@ export async function createDeployments(
|
||||
workflow_id,
|
||||
workflow_version_id: version_id,
|
||||
machine_id,
|
||||
org_id: orgId,
|
||||
})
|
||||
.where(eq(deploymentsTable.id, existingDeployment.id));
|
||||
} else {
|
||||
@@ -49,6 +51,7 @@ export async function createDeployments(
|
||||
workflow_version_id: version_id,
|
||||
machine_id,
|
||||
environment,
|
||||
org_id: orgId,
|
||||
});
|
||||
}
|
||||
revalidatePath(`/${workflow_id}`);
|
||||
@@ -195,3 +198,56 @@ export const cloneMachine = withServerPromise(async (deployment_id: string) => {
|
||||
message: "Successfully cloned workflow",
|
||||
};
|
||||
});
|
||||
|
||||
export async function findUserShareDeployment(share_id: string) {
|
||||
const { userId, orgId } = auth();
|
||||
|
||||
if (!userId) throw new Error("No user id");
|
||||
|
||||
const [deployment] = await db
|
||||
.select()
|
||||
.from(deploymentsTable)
|
||||
.where(
|
||||
and(
|
||||
eq(deploymentsTable.id, share_id),
|
||||
eq(deploymentsTable.environment, "public-share"),
|
||||
orgId
|
||||
? eq(deploymentsTable.org_id, orgId)
|
||||
: and(
|
||||
eq(deploymentsTable.user_id, userId),
|
||||
isNull(deploymentsTable.org_id)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (!deployment) throw new Error("No deployment found");
|
||||
|
||||
return deployment;
|
||||
}
|
||||
|
||||
export const updateSharePageInfo = withServerPromise(
|
||||
async ({
|
||||
id,
|
||||
...data
|
||||
}: z.infer<typeof publicShareDeployment> & {
|
||||
id: string;
|
||||
}) => {
|
||||
const { userId } = auth();
|
||||
if (!userId) return { error: "No user id" };
|
||||
|
||||
console.log(data);
|
||||
|
||||
const [deployment] = await db
|
||||
.update(deploymentsTable)
|
||||
.set(data)
|
||||
.where(
|
||||
and(
|
||||
eq(deploymentsTable.environment, "public-share"),
|
||||
eq(deploymentsTable.id, id)
|
||||
)
|
||||
)
|
||||
.returning();
|
||||
|
||||
return { message: "Info Updated" };
|
||||
}
|
||||
);
|
||||
|
||||
@@ -23,6 +23,10 @@ export async function findAllRuns({
|
||||
extras: {
|
||||
number: sql<number>`row_number() over (order by created_at)`.as("number"),
|
||||
total: sql<number>`count(*) over ()`.as("total"),
|
||||
duration:
|
||||
sql<number>`(extract(epoch from ended_at) - extract(epoch from created_at))`.as(
|
||||
"duration"
|
||||
),
|
||||
},
|
||||
with: {
|
||||
machine: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { db } from "@/db/db";
|
||||
import {
|
||||
deploymentsTable,
|
||||
workflowTable,
|
||||
workflowVersionTable,
|
||||
} from "@/db/schema";
|
||||
import { auth } from "@clerk/nextjs";
|
||||
import { and, desc, eq, isNull } from "drizzle-orm";
|
||||
|
||||
export async function getAllUserWorkflow() {
|
||||
const { userId, orgId } = await auth();
|
||||
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const workflow = await db.query.workflowTable.findMany({
|
||||
with: {
|
||||
user: {
|
||||
columns: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
versions: {
|
||||
limit: 1,
|
||||
orderBy: desc(workflowVersionTable.version),
|
||||
columns: {
|
||||
id: true,
|
||||
version: true,
|
||||
},
|
||||
},
|
||||
deployments: {
|
||||
limit: 1,
|
||||
where: eq(deploymentsTable.environment, "public-share"),
|
||||
columns: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: true,
|
||||
updated_at: true,
|
||||
name: true,
|
||||
},
|
||||
orderBy: desc(workflowTable.updated_at),
|
||||
where:
|
||||
orgId != undefined
|
||||
? eq(workflowTable.org_id, orgId)
|
||||
: and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id)),
|
||||
});
|
||||
|
||||
return workflow;
|
||||
}
|
||||
@@ -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),
|
||||
});
|
||||
Reference in New Issue
Block a user