Compare commits

...
Author SHA1 Message Date
Nicholas Koben Kao ed659ebdb8 schema 2024-01-22 19:05:24 -08:00
Nicholas Koben Kao f6a1b88dda rest 2024-01-22 19:05:18 -08:00
Nicholas Koben Kao fed7b380b6 work 2024-01-22 17:23:35 -08:00
Nicholas Koben Kao 90cec6b778 schema 2024-01-20 22:01:11 -08:00
Nicholas Koben Kao 7b61fea849 volume work 2024-01-20 19:54:35 -08:00
Nicholas Koben Kao 60471a8e01 model in voluems 2024-01-20 12:53:48 -08:00
BennyKok c98a16a2dd feat: add duration display 2024-01-20 17:46:15 +08:00
BennyKok 1a0d73ff8b fix: unable to open deployment code template 2024-01-20 14:27:01 +08:00
BennyKok 163e6f0426 feat: add share page settings dialog + add org id to deployment as well 2024-01-20 12:06:26 +08:00
60 changed files with 5940 additions and 405 deletions
+51 -1
View File
@@ -8,6 +8,7 @@ from enum import Enum
import json
import subprocess
import time
from uuid import uuid4
from contextlib import asynccontextmanager
import asyncio
import threading
@@ -19,6 +20,7 @@ from urllib.parse import parse_qs
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp, Scope, Receive, Send
from concurrent.futures import ThreadPoolExecutor
# executor = ThreadPoolExecutor(max_workers=5)
@@ -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))
+11 -6
View File
@@ -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():
+7 -1
View File
@@ -1 +1,7 @@
config = {"name": "my-app", "deploy_test": "True", "gpu": "T4"}
config = {
"name": "my-app",
"deploy_test": "True",
"gpu": "T4",
"public_checkpoint_volume": "model-store",
"private_checkpoint_volume": "private-model-store"
}
@@ -1,11 +1,30 @@
comfyui:
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,12 +45,12 @@ 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()
@@ -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"
}
}
BIN
View File
Binary file not shown.
@@ -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;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "comfyui_deploy"."deployments" ADD COLUMN "org_id" text;
+62
View File
@@ -0,0 +1,62 @@
DO $$ BEGIN
CREATE TYPE "model_upload_type" AS ENUM('civitai', 'huggingface', 'other');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
CREATE TYPE "resource_upload" AS ENUM('started', 'failed', 'succeded');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."checkpoints" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text,
"org_id" text,
"description" text,
"checkpoint_volume_id" uuid NOT NULL,
"model_name" text,
"civitai_id" text,
"civitai_version_id" text,
"civitai_url" text,
"civitai_download_url" text,
"civitai_model_response" jsonb,
"hf_url" text,
"s3_url" text,
"client_url" text,
"is_public" boolean DEFAULT false NOT NULL,
"status" "resource_upload" DEFAULT 'started' NOT NULL,
"upload_machine_id" text,
"upload_type" "model_upload_type" NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "comfyui_deploy"."checkpoint_volume" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" text,
"org_id" text,
"volume_name" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"disabled" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."checkpoints" ADD CONSTRAINT "checkpoints_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."checkpoints" ADD CONSTRAINT "checkpoints_checkpoint_volume_id_workflow_runs_id_fk" FOREIGN KEY ("checkpoint_volume_id") REFERENCES "comfyui_deploy"."workflow_runs"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "comfyui_deploy"."checkpoint_volume" ADD CONSTRAINT "checkpoint_volume_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
+2
View File
@@ -0,0 +1,2 @@
ALTER TYPE "resource_upload" ADD VALUE 'error';--> statement-breakpoint
ALTER TABLE "comfyui_deploy"."checkpoints" ADD COLUMN "build_log" text;
+750
View File
@@ -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": {}
}
}
+756
View File
@@ -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
+28
View File
@@ -204,6 +204,34 @@
"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
View File
@@ -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 } }
// );
+1
View File
@@ -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",
@@ -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} />;
}
+7
View File
@@ -0,0 +1,7 @@
import type { FC } from "react";
const Default: FC = () => {
return null;
};
export default Default;
@@ -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,
}
);
}
+5 -3
View File
@@ -1,15 +1,14 @@
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";
import { PHProvider } from "./providers";
import dynamic from "next/dynamic";
const PostHogPageView = dynamic(() => import("./PostHogPageView"), {
ssr: false,
@@ -34,8 +33,10 @@ export const metadata: Metadata = {
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html lang="en">
@@ -60,6 +61,7 @@ export default function RootLayout({
{children}
</div>
<Toaster richColors />
{modal}
</main>
</body>
</PHProvider>
+7 -4
View File
@@ -1,8 +1,6 @@
import { ButtonActionMenu } from "@/components/ButtonActionLoader";
import {
PublicRunOutputs,
} 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} />;
}
+9
View File
@@ -0,0 +1,9 @@
"use client";
import { LoadingPageWrapper } from "@/components/LoadingWrapper";
import { usePathname } from "next/navigation";
export default function Loading() {
const pathName = usePathname();
return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
}
+35
View File
@@ -0,0 +1,35 @@
import { setInitialUserData } from "../../../lib/setInitialUserData";
import { auth } from "@clerk/nextjs";
import { clerkClient } from "@clerk/nextjs/server";
import { 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>
);
}
@@ -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>
+10
View File
@@ -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);
+315
View File
@@ -0,0 +1,315 @@
"use client";
import { getRelativeTime } from "../lib/getRelativeTime";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { InsertModal, UpdateModal } from "./InsertModal";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import type { getAllUserCheckpoints } from "@/server/getAllUserCheckpoints";
import type {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
} from "@tanstack/react-table";
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
import * as React from "react";
import { insertCivitaiCheckpointSchema } from "@/db/schema";
import { addCivitaiCheckpoint } from "@/server/curdCheckpoint";
import { addCivitaiCheckpointSchema } from "@/server/addCheckpointSchema";
export type CheckpointItemList = NonNullable<
Awaited<ReturnType<typeof getAllUserCheckpoints>>
>[0];
export const columns: ColumnDef<CheckpointItemList>[] = [
{
accessorKey: "id",
id: "select",
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "name",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
const checkpoint = row.original;
return (
<a
className="hover:underline flex gap-2"
href={`/storage/${checkpoint.id}`} // TODO
>
<span className="truncate max-w-[200px]">{row.original.model_name}</span>
<Badge variant="default">{}</Badge>
{checkpoint.is_public
? <Badge variant="success">Public</Badge>
: <Badge variant="teal">Private</Badge>}
</a>
);
},
},
{
accessorKey: "creator",
header: ({ column }) => {
return (
<button
className="flex items-center hover:underline"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Creator
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => {
// return <Badge variant="cyan">{row?.original?.user?.name ? row.original.user.name : "Public"}</Badge>;
},
},
{
accessorKey: "date",
sortingFn: "datetime",
enableSorting: true,
header: ({ column }) => {
return (
<button
className="w-full flex items-center justify-end hover:underline truncate"
// variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Update Date
<ArrowUpDown className="ml-2 h-4 w-4" />
</button>
);
},
cell: ({ row }) => (
<div className="w-full capitalize text-right truncate">
{getRelativeTime(row.original.updated_at)}
</div>
),
},
// {
// id: "actions",
// enableHiding: false,
// cell: ({ row }) => {
// const checkpoint = row.original;
//
// return (
// <DropdownMenu>
// <DropdownMenuTrigger asChild>
// <Button variant="ghost" className="h-8 w-8 p-0">
// <span className="sr-only">Open menu</span>
// <MoreHorizontal className="h-4 w-4" />
// </Button>
// </DropdownMenuTrigger>
// <DropdownMenuContent align="end">
// <DropdownMenuLabel>Actions</DropdownMenuLabel>
// <DropdownMenuItem
// className="text-destructive"
// onClick={() => {
// deleteWorkflow(checkpoint.id);
// }}
// >
// Delete Workflow
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// );
// },
// },
];
export function CheckpointList({ data }: { data: CheckpointItemList[] }) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] = React.useState<
VisibilityState
>({});
const [rowSelection, setRowSelection] = React.useState({});
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
});
return (
<div className="grid grid-rows-[auto,1fr,auto] h-full">
<div className="flex flex-row w-full items-center py-4">
<Input
placeholder="Filter workflows..."
value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("name")?.setFilterValue(event.target.value)}
className="max-w-sm"
/>
<div className="ml-auto flex gap-2">
<InsertModal
dialogClassName="sm:max-w-[600px]"
disabled={
false
// TODO: limitations based on plan
}
tooltip={"Add models using their civitai url!"}
title="Civitai Checkpoint"
description="Pick a model from civitai"
serverAction={addCivitaiCheckpoint}
formSchema={addCivitaiCheckpointSchema}
fieldConfig={{
civitai_url: {
fieldType: "fallback",
// fieldType: "fallback",
inputProps: { required: true },
description: (
<>
Pick a checkpoint from{" "}
<a
href="https://www.civitai.com/models"
target="_blank"
className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"
>
civitai.com
</a>{" "}
and place it's url here
</>
),
},
}}
/>
</div>
</div>
<ScrollArea className="h-full w-full rounded-md border">
<Table>
<TableHeader className="bg-background top-0 sticky">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length
? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
)
: (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</ScrollArea>
<div className="flex flex-row items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
);
}
+11 -53
View File
@@ -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 \
@@ -90,32 +84,22 @@ 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">
@@ -126,7 +110,6 @@ 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 text-sm">
<TabsList className="grid w-fit grid-cols-3 mb-2">
<TabsTrigger value="client">Server Client</TabsTrigger>
@@ -181,12 +164,7 @@ export function DeploymentDisplay({
Trigger the workflow
<CodeBlock
lang="js"
code={formatCode(
jsTemplate,
deployment,
domain,
workflowInput
)}
code={formatCode(jsTemplate, deployment, domain, workflowInput)}
/>
Check the status of the run, and retrieve the outputs
<CodeBlock
@@ -201,30 +179,10 @@ export function DeploymentDisplay({
/>
<CodeBlock
lang="bash"
code={formatCode(
curlTemplate_checkStatus,
deployment,
domain
)}
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)}
>
Remove
</ButtonAction>
</Button>
<Button asChild className="gap-2">
<Link href={`/share/${deployment.id}`} target="_blank">
View Share Page <ExternalLink size={14} />
</Link>
</Button>
</div>
)}
</ScrollArea>
</DialogContent>
</Dialog>
+60
View File
@@ -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>
</>
);
}
+28 -11
View File
@@ -11,6 +11,7 @@ import {
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Tooltip,
@@ -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>
+1 -1
View File
@@ -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>
+6 -2
View File
@@ -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}
+52
View File
@@ -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>
);
}
+27 -3
View File
@@ -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>
);
}
+7 -2
View File
@@ -11,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";
@@ -33,7 +33,12 @@ export async function RunDisplay({
</TableCell>
<TableCell>{run.version?.version}</TableCell>
<TableCell>
<Badge variant="outline">{run.origin}</Badge>
<Badge variant="outline" className="truncate">
{run.origin}
</Badge>
</TableCell>
<TableCell className="truncate">
{getDuration(run.duration)}
</TableCell>
<LiveStatus run={run} />
</TableRow>
-3
View File
@@ -40,13 +40,10 @@ export function RunWorkflowInline({
} = publicRunStore();
const runWorkflow = async () => {
console.log();
if (!user.isSignedIn) {
clerk.openSignIn({
redirectUrl: window.location.href,
});
console.log("hi");
return;
}
console.log(values);
+13 -5
View File
@@ -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,11 +41,12 @@ 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>
</TableRow>
@@ -69,6 +71,12 @@ 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">
<Table className="">
@@ -83,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>
+79 -75
View File
@@ -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>
+90
View File
@@ -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",
},
}}
/>
</>
);
}
+13 -2
View File
@@ -39,7 +39,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import type { workflowAPINodeType } from "@/db/schema";
import type { showcaseMediaNullable, workflowAPINodeType } from "@/db/schema";
import { checkStatus, createRun } from "@/server/createRun";
import { createDeployments } from "@/server/curdDeploments";
import type { getMachines } from "@/server/curdMachine";
@@ -154,7 +154,9 @@ export 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();
@@ -176,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"
@@ -42,9 +42,7 @@ const Model = z.object({
url: z.string(),
});
export const CivitalModelSchema = z.object({
items: z.array(
z.object({
export const CivitaiModel = z.object({
id: z.number(),
name: z.string(),
description: z.string(),
@@ -117,7 +115,7 @@ export const CivitalModelSchema = z.object({
// }),
downloadUrl: z.string(),
// primary: z.boolean().default(false),
})
}),
),
images: z.array(
z.object({
@@ -134,13 +132,15 @@ export const CivitalModelSchema = z.object({
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>
);
}
+18 -8
View File
@@ -10,12 +10,11 @@ export default function ErrorPage({
error,
reset,
}: {
error?: Error & { digest?: string };
reset?: () => void;
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to an error reporting service
console.log(error?.message);
console.log(error.message);
}, [error]);
return (
@@ -26,9 +25,14 @@ export default function ErrorPage({
<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="text-sm">Error: {error.message}</div>
<div className="flex w-full justify-end">
<Button className="w-fit" onClick={() => reset?.()}>
<Button
className="w-fit"
onClick={() => {
window.location.reload();
}}
>
Refresh Page
</Button>
</div>
@@ -39,14 +43,20 @@ export default function ErrorPage({
);
}
export function ErrorFullPage() {
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 />
<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,
};
/**
+12 -11
View File
@@ -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,8 +128,9 @@ 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;
+262
View File
@@ -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,
};
}
+148 -13
View File
@@ -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";
@@ -87,7 +88,7 @@ export const workflowVersionRelations = relations(
fields: [workflowVersionTable.workflow_id],
references: [workflowTable.id],
}),
})
}),
);
export const workflowRunStatus = pgEnum("workflow_run_status", [
@@ -136,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, {
@@ -171,7 +173,7 @@ export const workflowRunRelations = relations(
fields: [workflowRunsTable.workflow_id],
references: [workflowTable.id],
}),
})
}),
);
// We still want to keep the workflow run record.
@@ -195,7 +197,7 @@ export const workflowOutputRelations = relations(
fields: [workflowRunOutputs.run_id],
references: [workflowRunsTable.id],
}),
})
}),
);
// when user delete, also delete all the workflow versions
@@ -228,7 +230,7 @@ export const snapshotType = z.object({
z.object({
hash: z.string(),
disabled: z.boolean(),
})
}),
),
file_custom_nodes: z.array(z.any()),
});
@@ -239,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")
@@ -246,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),
@@ -257,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],
@@ -296,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
>;
+16 -2
View File
@@ -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)}`;
}
+5
View File
@@ -0,0 +1,5 @@
import { insertCivitaiCheckpointSchema } from "@/db/schema";
export const addCivitaiCheckpointSchema = insertCivitaiCheckpointSchema.pick({
civitai_url: true,
});
+224
View File
@@ -0,0 +1,224 @@
"use server";
import { auth } from "@clerk/nextjs";
import {
checkpointTable,
CheckpointType,
volumeTable,
CheckpointVolumeType,
} from "@/db/schema";
import { withServerPromise } from "./withServerPromise";
import { redirect } from "next/navigation";
import { db } from "@/db/db";
import type { z } from "zod";
import { headers } from "next/headers";
import { addCivitaiCheckpointSchema } from "./addCheckpointSchema";
import { and, eq, isNull } from "drizzle-orm";
import { CivitaiModelResponse } from "@/types/civitai";
export async function getCheckpoints() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const checkpoints = await db
.select()
.from(checkpointTable)
.where(
orgId
? eq(checkpointTable.org_id, orgId)
// make sure org_id is null
: and(
eq(checkpointTable.user_id, userId),
isNull(checkpointTable.org_id),
),
);
return checkpoints;
}
export async function getCheckpointById(id: string) {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const checkpoint = await db
.select()
.from(checkpointTable)
.where(
and(
orgId ? eq(checkpointTable.org_id, orgId) : and(
eq(checkpointTable.user_id, userId),
isNull(checkpointTable.org_id),
),
eq(checkpointTable.id, id),
),
);
return checkpoint[0];
}
export async function getCheckpointVolumes() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
const checkpointVolume = await db
.select()
.from(volumeTable)
.where(
and(
orgId
? eq(volumeTable.org_id, orgId)
// make sure org_id is null
: and(
eq(volumeTable.user_id, userId),
isNull(volumeTable.org_id),
),
eq(volumeTable.disabled, false),
),
);
return checkpointVolume;
}
export async function addCheckpointVolume() {
const { userId, orgId } = auth();
if (!userId) throw new Error("No user id");
// Insert the new volume into the checkpointVolumeTable
const insertedVolume = await db
.insert(volumeTable)
.values({
user_id: userId,
org_id: orgId,
volume_name: `checkpoints_${userId}`,
// created_at and updated_at will be set to current timestamp by default
disabled: false, // Default value
})
.returning(); // Returns the inserted row
return insertedVolume;
}
function getUrl(civitai_url: string) {
// expect to be a URL to be https://civitai.com/models/36520
// possiblity with slugged name and query-param modelVersionId
const baseUrl = "https://civitai.com/api/v1/models/";
const url = new URL(civitai_url);
const pathSegments = url.pathname.split("/");
const modelId = pathSegments[pathSegments.indexOf("models") + 1];
const modelVersionId = url.searchParams.get("modelVersionId");
return { url: baseUrl + modelId, modelVersionId };
}
export const addCivitaiCheckpoint = withServerPromise(
async (data: z.infer<typeof addCivitaiCheckpointSchema>) => {
const { userId, orgId } = auth();
if (!data.civitai_url) return { error: "no civitai_url" };
if (!userId) return { error: "No user id" };
const { url, modelVersionId } = getUrl(data?.civitai_url);
const civitaiModelRes = await fetch(url)
.then((x) => x.json())
.then((a) => {
return CivitaiModelResponse.parse(a);
});
if (civitaiModelRes?.modelVersions?.length === 0) {
return; // no versions to download
}
let selectedModelVersion;
let selectedModelVersionId: string | null = modelVersionId;
if (!selectedModelVersionId) {
selectedModelVersion = civitaiModelRes.modelVersions[0];
selectedModelVersionId = civitaiModelRes.modelVersions[0].id.toString();
} else {
selectedModelVersion = civitaiModelRes.modelVersions.find((version) =>
version.id.toString() === selectedModelVersionId
);
if (!selectedModelVersion) {
return; // version id is wrong
}
selectedModelVersionId = selectedModelVersion?.id.toString();
}
const checkpointVolumes = await getCheckpointVolumes();
let cVolume;
if (checkpointVolumes.length === 0) {
const volume = await addCheckpointVolume();
cVolume = volume[0];
} else {
cVolume = checkpointVolumes[0];
}
const a = await db
.insert(checkpointTable)
.values({
user_id: userId,
org_id: orgId,
upload_type: "civitai",
civitai_id: civitaiModelRes.id.toString(),
civitai_version_id: selectedModelVersionId,
civitai_url: data.civitai_url,
civitai_download_url: selectedModelVersion.downloadUrl,
civitai_model_response: civitaiModelRes,
checkpoint_volume_id: cVolume.id,
})
.returning();
const b = a[0];
await uploadCheckpoint(data, b, cVolume);
redirect(`/checkpoints/${b.id}`);
},
);
async function uploadCheckpoint(
data: z.infer<typeof addCivitaiCheckpointSchema>,
b: CheckpointType,
v: CheckpointVolumeType,
) {
const headersList = headers();
const domain = headersList.get("x-forwarded-host") || "";
const protocol = headersList.get("x-forwarded-proto") || "";
if (domain === "") {
throw new Error("No domain");
}
// Call remote builder
const result = await fetch(
`${process.env.MODAL_BUILDER_URL!}/upload_volume`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
download_url: data.civitai_url,
volume_name: v.volume_name,
volume_id: v.id,
callback_url: `${protocol}://${domain}/api/volume-updated`,
}),
},
);
if (!result.ok) {
const error_log = await result.text();
await db
.update(checkpointTable)
.set({
...data,
status: "error",
build_log: error_log,
})
.where(eq(checkpointTable.id, b.id));
throw new Error(`Error: ${result.statusText} ${error_log}`);
} else {
// setting the build machine id
const json = await result.json();
await db
.update(checkpointTable)
.set({
...data,
upload_machine_id: json.build_machine_instance_id,
})
.where(eq(checkpointTable.id, b.id));
}
}
+58 -2
View File
@@ -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" };
}
);
+4
View File
@@ -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: {
+39
View File
@@ -0,0 +1,39 @@
import { db } from "@/db/db";
import {
checkpointTable,
} from "@/db/schema";
import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm";
export async function getAllUserCheckpoints() {
const { userId, orgId } = await auth();
if (!userId) {
return null;
}
const checkpoints = await db.query.checkpointTable.findMany({
with: {
user: {
columns: {
name: true,
},
},
},
columns: {
id: true,
updated_at: true,
name: true,
civitai_url: true,
civitai_model_response: true,
is_public: true,
},
orderBy: desc(checkpointTable.updated_at),
where:
orgId != undefined
? eq(checkpointTable.org_id, orgId)
: and(eq(checkpointTable.user_id, userId), isNull(checkpointTable.org_id)),
});
return checkpoints;
}
+126
View File
@@ -0,0 +1,126 @@
import { z } from 'zod';
// from chatgpt https://chat.openai.com/share/4985d20b-30b1-4a28-87f6-6ebf84a1040e
export const creatorSchema = z.object({
username: z.string().optional(),
image: z.string().url().optional(),
});
export const fileMetadataSchema = z.object({
fp: z.string().optional(),
size: z.string().optional(),
format: z.string().optional(),
});
export const fileSchema = z.object({
id: z.number(),
sizeKB: z.number().optional(),
name: z.string(),
type: z.string().optional(),
metadata: fileMetadataSchema.optional(),
pickleScanResult: z.string().optional(),
pickleScanMessage: z.string().nullable(),
virusScanResult: z.string().optional(),
virusScanMessage: z.string().nullable(),
scannedAt: z.string().optional(),
hashes: z.record(z.string()).optional(),
downloadUrl: z.string().url(),
primary: z.boolean().optional().optional(),
});
export const imageMetadataSchema = z.object({
hash: z.string(),
width: z.number(),
height: z.number(),
});
export const imageMetaSchema = z.object({
ENSD: z.string().optional(),
Size: z.string().optional(),
seed: z.number().optional(),
Model: z.string().optional(),
steps: z.number().optional(),
hashes: z.record(z.string()).optional(),
prompt: z.string().optional(),
sampler: z.string().optional(),
cfgScale: z.number().optional(),
ClipSkip: z.number().optional(),
resources: z.array(
z.object({
hash: z.string().optional(),
name: z.string(),
type: z.string(),
weight: z.number().optional(),
})
).optional(),
ModelHash: z.string().optional(),
HiresSteps: z.string().optional(),
HiresUpscale: z.string().optional(),
HiresUpscaler: z.string().optional(),
negativePrompt: z.string(),
DenoisingStrength: z.number().optional(),
});
export const imageSchema = z.object({
url: z.string().url().optional(),
nsfw: z.enum(["None", "Soft", "Mature"]).optional(),
width: z.number().optional(),
height: z.number().optional(),
hash: z.string().optional(),
type: z.string().optional(),
metadata: imageMetadataSchema.optional(),
meta: imageMetaSchema.optional(),
});
export const modelVersionSchema = z.object({
id: z.number(),
modelId: z.number(),
name: z.string(),
createdAt: z.string().optional(),
updatedAt: z.string().optional(),
status: z.enum(["Published", "Unpublished"]).optional(),
publishedAt: z.string().optional(),
trainedWords: z.array(z.string()).nullable(),
trainingStatus: z.string().nullable(),
trainingDetails: z.string().nullable(),
baseModel: z.string().optional(),
baseModelType: z.string().optional(),
earlyAccessTimeFrame: z.number().optional(),
description: z.string().nullable(),
vaeId: z.string().nullable(),
stats: z.object({
downloadCount: z.number(),
ratingCount: z.number(),
rating: z.number(),
}).optional(),
files: z.array(fileSchema),
images: z.array(imageSchema),
downloadUrl: z.string().url(),
});
export const statsSchema = z.object({
downloadCount: z.number(),
favoriteCount: z.number(),
commentCount: z.number(),
ratingCount: z.number(),
rating: z.number(),
tippedAmountCount: z.number(),
});
export const CivitaiModelResponse = z.object({
id: z.number(),
name: z.string().optional(),
description: z.string().optional(),
type: z.enum(["Checkpoint", "Lora"]),
poi: z.boolean().optional(),
nsfw: z.boolean().optional(),
allowNoCredit: z.boolean().optional(),
allowCommercialUse: z.enum(["Rent"]).optional(),
allowDerivatives: z.boolean().optional(),
allowDifferentLicense: z.boolean().optional(),
stats: statsSchema.optional(),
creator: creatorSchema.optional(),
tags: z.array(z.string()).optional(),
modelVersions: z.array(modelVersionSchema),
});