feat: add millon js, add models picker dialog, update builder
This commit is contained in:
parent
3c4bce630e
commit
01a9c1a1d6
@ -1,9 +1,10 @@
|
|||||||
from typing import Union, Optional, Dict
|
from typing import Union, Optional, Dict, List
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field, field_validator
|
||||||
from fastapi import FastAPI, HTTPException, WebSocket, BackgroundTasks, WebSocketDisconnect
|
from fastapi import FastAPI, HTTPException, WebSocket, BackgroundTasks, WebSocketDisconnect
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.logger import logger as fastapi_logger
|
from fastapi.logger import logger as fastapi_logger
|
||||||
import os
|
import os
|
||||||
|
from enum import Enum
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
@ -104,17 +105,49 @@ class Snapshot(BaseModel):
|
|||||||
comfyui: str
|
comfyui: str
|
||||||
git_custom_nodes: Dict[str, GitCustomNodes]
|
git_custom_nodes: Dict[str, GitCustomNodes]
|
||||||
|
|
||||||
|
class Model(BaseModel):
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
base: str
|
||||||
|
save_path: str
|
||||||
|
description: str
|
||||||
|
reference: str
|
||||||
|
filename: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
class GPUType(str, Enum):
|
||||||
|
T4 = "T4"
|
||||||
|
A10G = "A10G"
|
||||||
|
A100 = "A100"
|
||||||
|
L4 = "L4"
|
||||||
|
|
||||||
class Item(BaseModel):
|
class Item(BaseModel):
|
||||||
machine_id: str
|
machine_id: str
|
||||||
name: str
|
name: str
|
||||||
snapshot: Snapshot
|
snapshot: Snapshot
|
||||||
|
models: List[Model]
|
||||||
callback_url: str
|
callback_url: str
|
||||||
|
gpu: GPUType = Field(default=GPUType.T4)
|
||||||
|
|
||||||
|
@field_validator('gpu')
|
||||||
|
@classmethod
|
||||||
|
def check_gpu(cls, value):
|
||||||
|
if value not in GPUType.__members__:
|
||||||
|
raise ValueError(f"Invalid GPU option. Choose from: {', '.join(GPUType.__members__.keys())}")
|
||||||
|
return GPUType(value)
|
||||||
|
|
||||||
|
|
||||||
@app.websocket("/ws/{machine_id}")
|
@app.websocket("/ws/{machine_id}")
|
||||||
async def websocket_endpoint(websocket: WebSocket, machine_id: str):
|
async def websocket_endpoint(websocket: WebSocket, machine_id: str):
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
machine_id_websocket_dict[machine_id] = websocket
|
machine_id_websocket_dict[machine_id] = websocket
|
||||||
|
# Send existing logs
|
||||||
|
if machine_id in machine_logs_cache:
|
||||||
|
await websocket.send_text(json.dumps({"event": "LOGS", "data": {
|
||||||
|
"machine_id": machine_id,
|
||||||
|
"logs": json.dumps(machine_logs_cache[machine_id]) ,
|
||||||
|
"timestamp": time.time()
|
||||||
|
}}))
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
data = await websocket.receive_text()
|
data = await websocket.receive_text()
|
||||||
@ -156,6 +189,9 @@ async def create_item(item: Item):
|
|||||||
return JSONResponse(status_code=200, content={"message": "Build Queued"})
|
return JSONResponse(status_code=200, content={"message": "Build Queued"})
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize the logs cache
|
||||||
|
machine_logs_cache = {}
|
||||||
|
|
||||||
async def build_logic(item: Item):
|
async def build_logic(item: Item):
|
||||||
# Deploy to modal
|
# Deploy to modal
|
||||||
folder_path = f"/app/builds/{item.machine_id}"
|
folder_path = f"/app/builds/{item.machine_id}"
|
||||||
@ -175,7 +211,8 @@ async def build_logic(item: Item):
|
|||||||
# Write the config file
|
# Write the config file
|
||||||
config = {
|
config = {
|
||||||
"name": item.name,
|
"name": item.name,
|
||||||
"deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False")
|
"deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False"),
|
||||||
|
"gpu": item.gpu
|
||||||
}
|
}
|
||||||
with open(f"{folder_path}/config.py", "w") as f:
|
with open(f"{folder_path}/config.py", "w") as f:
|
||||||
f.write("config = " + json.dumps(config))
|
f.write("config = " + json.dumps(config))
|
||||||
@ -183,33 +220,40 @@ async def build_logic(item: Item):
|
|||||||
with open(f"{folder_path}/data/snapshot.json", "w") as f:
|
with open(f"{folder_path}/data/snapshot.json", "w") as f:
|
||||||
f.write(item.snapshot.json())
|
f.write(item.snapshot.json())
|
||||||
|
|
||||||
|
with open(f"{folder_path}/data/models.json", "w") as f:
|
||||||
|
models_json_list = [model.dict() for model in item.models]
|
||||||
|
models_json_string = json.dumps(models_json_list)
|
||||||
|
f.write(models_json_string)
|
||||||
|
|
||||||
# os.chdir(folder_path)
|
# os.chdir(folder_path)
|
||||||
# process = subprocess.Popen(f"modal deploy {folder_path}/app.py", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
|
# process = subprocess.Popen(f"modal deploy {folder_path}/app.py", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
|
||||||
process = await asyncio.subprocess.create_subprocess_shell(
|
process = await asyncio.subprocess.create_subprocess_shell(
|
||||||
f"modal deploy app.py",
|
f"modal deploy app.py",
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.PIPE,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
cwd=folder_path
|
cwd=folder_path,
|
||||||
|
# env={**os.environ, "PYTHONUNBUFFERED": "1"}
|
||||||
)
|
)
|
||||||
|
|
||||||
url = None
|
url = None
|
||||||
|
|
||||||
# Initialize the logs cache
|
if item.machine_id not in machine_logs_cache:
|
||||||
machine_logs_cache = []
|
machine_logs_cache[item.machine_id] = []
|
||||||
|
|
||||||
# Stream the output
|
machine_logs = machine_logs_cache[item.machine_id]
|
||||||
# Read output
|
|
||||||
|
async def read_stream(stream, isStderr):
|
||||||
while True:
|
while True:
|
||||||
line = await process.stdout.readline()
|
line = await stream.readline()
|
||||||
error = await process.stderr.readline()
|
if line:
|
||||||
if not line and not error:
|
|
||||||
break
|
|
||||||
l = line.decode('utf-8').strip()
|
l = line.decode('utf-8').strip()
|
||||||
e = error.decode('utf-8').strip()
|
|
||||||
|
|
||||||
if l != "":
|
if l == "":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isStderr:
|
||||||
logger.info(l)
|
logger.info(l)
|
||||||
machine_logs_cache.append({
|
machine_logs.append({
|
||||||
"logs": l,
|
"logs": l,
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
})
|
})
|
||||||
@ -230,7 +274,7 @@ async def build_logic(item: Item):
|
|||||||
url = l
|
url = l
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
machine_logs_cache.append({
|
machine_logs.append({
|
||||||
"logs": f"App image built, url: {url}",
|
"logs": f"App image built, url: {url}",
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
})
|
})
|
||||||
@ -241,10 +285,14 @@ async def build_logic(item: Item):
|
|||||||
"logs": f"App image built, url: {url}",
|
"logs": f"App image built, url: {url}",
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
}}))
|
}}))
|
||||||
|
await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "FINISHED", "data": {
|
||||||
|
"status": "succuss",
|
||||||
|
}}))
|
||||||
|
|
||||||
if e != "":
|
else:
|
||||||
logger.info(e)
|
# is error
|
||||||
machine_logs_cache.append({
|
logger.error(l)
|
||||||
|
machine_logs.append({
|
||||||
"logs": e,
|
"logs": e,
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
})
|
})
|
||||||
@ -255,7 +303,16 @@ async def build_logic(item: Item):
|
|||||||
"logs": e,
|
"logs": e,
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
}}))
|
}}))
|
||||||
|
await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "FINISHED", "data": {
|
||||||
|
"status": "failed",
|
||||||
|
}}))
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
stdout_task = asyncio.create_task(read_stream(process.stdout, False))
|
||||||
|
stderr_task = asyncio.create_task(read_stream(process.stderr, True))
|
||||||
|
|
||||||
|
await asyncio.wait([stdout_task, stderr_task])
|
||||||
|
|
||||||
# Wait for the subprocess to finish
|
# Wait for the subprocess to finish
|
||||||
await process.wait()
|
await process.wait()
|
||||||
@ -273,28 +330,38 @@ async def build_logic(item: Item):
|
|||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
logger.info("An error occurred.")
|
logger.info("An error occurred.")
|
||||||
# Send a post request with the json body machine_id to the callback url
|
# Send a post request with the json body machine_id to the callback url
|
||||||
machine_logs_cache.append({
|
machine_logs.append({
|
||||||
"logs": "Unable to build the app image.",
|
"logs": "Unable to build the app image.",
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
})
|
})
|
||||||
requests.post(item.callback_url, json={"machine_id": item.machine_id, "build_log": json.dumps(machine_logs_cache)})
|
requests.post(item.callback_url, json={"machine_id": item.machine_id, "build_log": json.dumps(machine_logs)})
|
||||||
|
|
||||||
|
if item.machine_id in machine_logs_cache:
|
||||||
|
del machine_logs_cache[item.machine_id]
|
||||||
|
|
||||||
return
|
return
|
||||||
# return JSONResponse(status_code=400, content={"error": "Unable to build the app image."})
|
# return JSONResponse(status_code=400, content={"error": "Unable to build the app image."})
|
||||||
|
|
||||||
# app_suffix = "comfyui-app"
|
# app_suffix = "comfyui-app"
|
||||||
|
|
||||||
if url is None:
|
if url is None:
|
||||||
machine_logs_cache.append({
|
machine_logs.append({
|
||||||
"logs": "App image built, but url is None, unable to parse the url.",
|
"logs": "App image built, but url is None, unable to parse the url.",
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
})
|
})
|
||||||
requests.post(item.callback_url, json={"machine_id": item.machine_id, "build_log": json.dumps(machine_logs_cache)})
|
requests.post(item.callback_url, json={"machine_id": item.machine_id, "build_log": json.dumps(machine_logs)})
|
||||||
|
|
||||||
|
if item.machine_id in machine_logs_cache:
|
||||||
|
del machine_logs_cache[item.machine_id]
|
||||||
|
|
||||||
return
|
return
|
||||||
# return JSONResponse(status_code=400, content={"error": "App image built, but url is None, unable to parse the url."})
|
# return JSONResponse(status_code=400, content={"error": "App image built, but url is None, unable to parse the url."})
|
||||||
# example https://bennykok--my-app-comfyui-app.modal.run/
|
# example https://bennykok--my-app-comfyui-app.modal.run/
|
||||||
# my_url = f"https://{MODAL_ORG}--{item.container_id}-{app_suffix}.modal.run"
|
# my_url = f"https://{MODAL_ORG}--{item.container_id}-{app_suffix}.modal.run"
|
||||||
|
|
||||||
requests.post(item.callback_url, json={"machine_id": item.machine_id, "endpoint": url, "build_log": json.dumps(machine_logs_cache)})
|
requests.post(item.callback_url, json={"machine_id": item.machine_id, "endpoint": url, "build_log": json.dumps(machine_logs)})
|
||||||
|
if item.machine_id in machine_logs_cache:
|
||||||
|
del machine_logs_cache[item.machine_id]
|
||||||
|
|
||||||
logger.info("done")
|
logger.info("done")
|
||||||
logger.info(url)
|
logger.info(url)
|
||||||
|
@ -57,6 +57,7 @@ WORKDIR /
|
|||||||
|
|
||||||
COPY /data/install_deps.py .
|
COPY /data/install_deps.py .
|
||||||
COPY /data/deps.json .
|
COPY /data/deps.json .
|
||||||
|
COPY /data/models.json .
|
||||||
RUN python3 install_deps.py
|
RUN python3 install_deps.py
|
||||||
|
|
||||||
WORKDIR /comfyui/custom_nodes
|
WORKDIR /comfyui/custom_nodes
|
||||||
|
@ -105,7 +105,7 @@ image = Image.debian_slim()
|
|||||||
|
|
||||||
target_image = image if deploy_test else dockerfile_image
|
target_image = image if deploy_test else dockerfile_image
|
||||||
|
|
||||||
@stub.function(image=target_image, gpu="T4")
|
@stub.function(image=target_image, gpu=config["gpu"])
|
||||||
def run(input: Input):
|
def run(input: Input):
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
@ -1 +1 @@
|
|||||||
config = {"name": "my-app", "deploy_test": "True"}
|
config = {"name": "my-app", "deploy_test": "True", "gpu": "T4"}
|
@ -28,9 +28,10 @@ def check_server(url, retries=50, delay=500):
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
check_server("http://127.0.0.1:8188")
|
root_url = "http://127.0.0.1:8188"
|
||||||
|
|
||||||
|
check_server(root_url)
|
||||||
|
|
||||||
url = "http://127.0.0.1:8188/customnode/install"
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
# Load JSON array from deps.json
|
# Load JSON array from deps.json
|
||||||
@ -39,12 +40,15 @@ with open('deps.json') as f:
|
|||||||
|
|
||||||
# Make a POST request for each package
|
# Make a POST request for each package
|
||||||
for package in packages:
|
for package in packages:
|
||||||
response = requests.request("POST", url, json=package, headers=headers)
|
response = requests.request("POST", f"{root_url}/customnode/install", json=package, headers=headers)
|
||||||
print(response.text)
|
print(response.text)
|
||||||
|
|
||||||
# restore_snapshot_url = "http://127.0.0.1:8188/snapshot/restore?target=snapshot"
|
with open('models.json') as f:
|
||||||
# response = requests.request("GET", restore_snapshot_url, headers=headers)
|
models = json.load(f)
|
||||||
# print(response.text)
|
|
||||||
|
for model in models:
|
||||||
|
response = requests.request("POST", f"{root_url}/model/install", json=model, headers=headers)
|
||||||
|
print(response.text)
|
||||||
|
|
||||||
# Close the server
|
# Close the server
|
||||||
server_process.terminate()
|
server_process.terminate()
|
||||||
|
12
builder/modal-builder/src/template/data/models.json
Normal file
12
builder/modal-builder/src/template/data/models.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "v1-5-pruned-emaonly.ckpt",
|
||||||
|
"type": "checkpoints",
|
||||||
|
"base": "SD1.5",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "Stable Diffusion 1.5 base model",
|
||||||
|
"reference": "https://huggingface.co/runwayml/stable-diffusion-v1-5",
|
||||||
|
"filename": "v1-5-pruned-emaonly.ckpt",
|
||||||
|
"url": "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt"
|
||||||
|
}
|
||||||
|
]
|
BIN
web/bun.lockb
BIN
web/bun.lockb
Binary file not shown.
1
web/drizzle/0023_fair_ikaris.sql
Normal file
1
web/drizzle/0023_fair_ikaris.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "comfyui_deploy"."machines" ADD COLUMN "models" jsonb;
|
716
web/drizzle/meta/0023_snapshot.json
Normal file
716
web/drizzle/meta/0023_snapshot.json
Normal file
@ -0,0 +1,716 @@
|
|||||||
|
{
|
||||||
|
"id": "027290ed-c130-4d6f-ae37-3f468c50cbc6",
|
||||||
|
"prevId": "9153404d-9279-4f43-a61f-7f5efefc12b7",
|
||||||
|
"version": "5",
|
||||||
|
"dialect": "pg",
|
||||||
|
"tables": {
|
||||||
|
"api_keys": {
|
||||||
|
"name": "api_keys",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"revoked": {
|
||||||
|
"name": "revoked",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"api_keys_user_id_users_id_fk": {
|
||||||
|
"name": "api_keys_user_id_users_id_fk",
|
||||||
|
"tableFrom": "api_keys",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"api_keys_key_unique": {
|
||||||
|
"name": "api_keys_key_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deployments": {
|
||||||
|
"name": "deployments",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workflow_version_id": {
|
||||||
|
"name": "workflow_version_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"workflow_id": {
|
||||||
|
"name": "workflow_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"machine_id": {
|
||||||
|
"name": "machine_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"name": "environment",
|
||||||
|
"type": "deployment_environment",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"deployments_user_id_users_id_fk": {
|
||||||
|
"name": "deployments_user_id_users_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"deployments_workflow_version_id_workflow_versions_id_fk": {
|
||||||
|
"name": "deployments_workflow_version_id_workflow_versions_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "workflow_versions",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workflow_version_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"deployments_workflow_id_workflows_id_fk": {
|
||||||
|
"name": "deployments_workflow_id_workflows_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "workflows",
|
||||||
|
"columnsFrom": [
|
||||||
|
"workflow_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"deployments_machine_id_machines_id_fk": {
|
||||||
|
"name": "deployments_machine_id_machines_id_fk",
|
||||||
|
"tableFrom": "deployments",
|
||||||
|
"tableTo": "machines",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"machines": {
|
||||||
|
"name": "machines",
|
||||||
|
"schema": "comfyui_deploy",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"org_id": {
|
||||||
|
"name": "org_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"name": "endpoint",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"name": "disabled",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"auth_token": {
|
||||||
|
"name": "auth_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "machine_type",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'classic'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "machine_status",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'ready'"
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"name": "snapshot",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"models": {
|
||||||
|
"name": "models",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
|
}
|
@ -162,6 +162,13 @@
|
|||||||
"when": 1704453649633,
|
"when": 1704453649633,
|
||||||
"tag": "0022_petite_bishop",
|
"tag": "0022_petite_bishop",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 23,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1704540132567,
|
||||||
|
"tag": "0023_fair_ikaris",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import million from 'million/compiler';
|
||||||
import { recmaPlugins } from "./src/mdx/recma.mjs";
|
import { recmaPlugins } from "./src/mdx/recma.mjs";
|
||||||
import { rehypePlugins } from "./src/mdx/rehype.mjs";
|
import { rehypePlugins } from "./src/mdx/rehype.mjs";
|
||||||
import { remarkPlugins } from "./src/mdx/remark.mjs";
|
import { remarkPlugins } from "./src/mdx/remark.mjs";
|
||||||
@ -20,4 +21,6 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withSearch(withMDX(nextConfig));
|
export default million.next(
|
||||||
|
withSearch(withMDX(nextConfig)), { auto: { rsc: true } }
|
||||||
|
);
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
"acorn": "^8.11.2",
|
"acorn": "^8.11.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"cmdk": "^0.2.0",
|
||||||
"date-fns": "^3.0.5",
|
"date-fns": "^3.0.5",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"drizzle-orm": "^0.29.1",
|
"drizzle-orm": "^0.29.1",
|
||||||
@ -61,6 +62,7 @@
|
|||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.294.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"mdx-annotations": "^0.1.4",
|
"mdx-annotations": "^0.1.4",
|
||||||
|
"million": "latest",
|
||||||
"mitata": "^0.1.6",
|
"mitata": "^0.1.6",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
|
@ -33,7 +33,7 @@ export default async function Page({
|
|||||||
endpoint={process.env.MODAL_BUILDER_URL!}
|
endpoint={process.env.MODAL_BUILDER_URL!}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{machine.build_log && (
|
{machine.status !== "building" && machine.build_log && (
|
||||||
<LogsViewer logs={JSON.parse(machine.build_log)} />
|
<LogsViewer logs={JSON.parse(machine.build_log)} />
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { TableCell, TableRow } from "@/components/ui/table";
|
import { TableCell, TableRow } from "@/components/ui/table";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
|
import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
|
||||||
@ -72,13 +73,13 @@ export function DeploymentDisplay({
|
|||||||
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className="capitalize">{deployment.environment}</TableCell>
|
<TableCell className="capitalize">{deployment.environment}</TableCell>
|
||||||
<TableCell className="font-medium">
|
<TableCell className="font-medium truncate">
|
||||||
{deployment.version?.version}
|
{deployment.version?.version}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="font-medium">
|
<TableCell className="font-medium truncate">
|
||||||
{deployment.machine?.name}
|
{deployment.machine?.name}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right truncate">
|
||||||
{getRelativeTime(deployment.updated_at)}
|
{getRelativeTime(deployment.updated_at)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -90,6 +91,7 @@ export function DeploymentDisplay({
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>Code for your deployment client</DialogDescription>
|
<DialogDescription>Code for your deployment client</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
<ScrollArea className="max-h-[600px]">
|
||||||
<Tabs defaultValue="js" className="w-full">
|
<Tabs defaultValue="js" className="w-full">
|
||||||
<TabsList className="grid w-fit grid-cols-2">
|
<TabsList className="grid w-fit grid-cols-2">
|
||||||
<TabsTrigger value="js">js</TabsTrigger>
|
<TabsTrigger value="js">js</TabsTrigger>
|
||||||
@ -118,6 +120,7 @@ export function DeploymentDisplay({
|
|||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</ScrollArea>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -43,6 +43,7 @@ export function InsertModal<
|
|||||||
<DialogTitle>{props.title}</DialogTitle>
|
<DialogTitle>{props.title}</DialogTitle>
|
||||||
<DialogDescription>{props.description}</DialogDescription>
|
<DialogDescription>{props.description}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
{/* <ScrollArea> */}
|
||||||
<AutoForm
|
<AutoForm
|
||||||
fieldConfig={props.fieldConfig}
|
fieldConfig={props.fieldConfig}
|
||||||
formSchema={props.formSchema}
|
formSchema={props.formSchema}
|
||||||
@ -60,6 +61,7 @@ export function InsertModal<
|
|||||||
</AutoFormSubmit>
|
</AutoFormSubmit>
|
||||||
</div>
|
</div>
|
||||||
</AutoForm>
|
</AutoForm>
|
||||||
|
{/* </ScrollArea> */}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -14,12 +14,13 @@ export function MachineBuildLog({
|
|||||||
endpoint: string;
|
endpoint: string;
|
||||||
}) {
|
}) {
|
||||||
const [logs, setLogs] = useState<LogsType>([]);
|
const [logs, setLogs] = useState<LogsType>([]);
|
||||||
|
const [finished, setFinished] = useState(false);
|
||||||
|
|
||||||
const wsEndpoint = endpoint.replace(/^http/, "ws");
|
const wsEndpoint = endpoint.replace(/^http/, "ws");
|
||||||
const { lastMessage, readyState } = useWebSocket(
|
const { lastMessage, readyState } = useWebSocket(
|
||||||
`${wsEndpoint}/ws/${machine_id}`,
|
`${wsEndpoint}/ws/${machine_id}`,
|
||||||
{
|
{
|
||||||
shouldReconnect: () => true,
|
shouldReconnect: () => !finished,
|
||||||
reconnectAttempts: 20,
|
reconnectAttempts: 20,
|
||||||
reconnectInterval: 1000,
|
reconnectInterval: 1000,
|
||||||
}
|
}
|
||||||
@ -36,6 +37,8 @@ export function MachineBuildLog({
|
|||||||
|
|
||||||
if (message?.event === "LOGS") {
|
if (message?.event === "LOGS") {
|
||||||
setLogs((logs) => [...(logs ?? []), message.data]);
|
setLogs((logs) => [...(logs ?? []), message.data]);
|
||||||
|
} else if (message?.event === "FINISHED") {
|
||||||
|
setFinished(true);
|
||||||
}
|
}
|
||||||
}, [lastMessage]);
|
}, [lastMessage]);
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import {
|
|||||||
deleteMachine,
|
deleteMachine,
|
||||||
disableMachine,
|
disableMachine,
|
||||||
enableMachine,
|
enableMachine,
|
||||||
|
updateCustomMachine,
|
||||||
updateMachine,
|
updateMachine,
|
||||||
} from "@/server/curdMachine";
|
} from "@/server/curdMachine";
|
||||||
import type {
|
import type {
|
||||||
@ -95,7 +96,7 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
// <a className="hover:underline" href={`/${row.original.id}`}>
|
// <a className="hover:underline" href={`/${row.original.id}`}>
|
||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center truncate">
|
||||||
<a href={`/machines/${row.original.id}`} className="hover:underline">
|
<a href={`/machines/${row.original.id}`} className="hover:underline">
|
||||||
{row.getValue("name")}
|
{row.getValue("name")}
|
||||||
</a>
|
</a>
|
||||||
@ -115,7 +116,9 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
header: () => <div className="text-left">Endpoint</div>,
|
header: () => <div className="text-left">Endpoint</div>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<div className="text-left font-medium">{row.original.endpoint}</div>
|
<div className="text-left font-medium truncate max-w-[400px]">
|
||||||
|
{row.original.endpoint}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -123,7 +126,11 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
accessorKey: "type",
|
accessorKey: "type",
|
||||||
header: () => <div className="text-left">Type</div>,
|
header: () => <div className="text-left">Type</div>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return <div className="text-left font-medium">{row.original.type}</div>;
|
return (
|
||||||
|
<div className="text-left font-medium truncate">
|
||||||
|
{row.original.type}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -133,7 +140,7 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="w-full flex items-center justify-end hover:underline"
|
className="w-full flex items-center justify-end hover:underline truncate"
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
>
|
>
|
||||||
Update Date
|
Update Date
|
||||||
@ -195,6 +202,33 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
Edit
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
{machine.type === "comfy-deploy-serverless" ? (
|
||||||
|
<UpdateModal
|
||||||
|
data={machine}
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
title="Edit"
|
||||||
|
description="Edit machines"
|
||||||
|
serverAction={updateCustomMachine}
|
||||||
|
formSchema={addCustomMachineSchema}
|
||||||
|
fieldConfig={{
|
||||||
|
type: {
|
||||||
|
fieldType: "fallback",
|
||||||
|
inputProps: {
|
||||||
|
disabled: true,
|
||||||
|
showLabel: false,
|
||||||
|
type: "hidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
snapshot: {
|
||||||
|
fieldType: "snapshot",
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
fieldType: "models",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<UpdateModal
|
<UpdateModal
|
||||||
data={machine}
|
data={machine}
|
||||||
open={open}
|
open={open}
|
||||||
@ -211,6 +245,7 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -273,8 +308,16 @@ export function MachineList({ data }: { data: Machine[] }) {
|
|||||||
fieldType: "fallback",
|
fieldType: "fallback",
|
||||||
inputProps: {
|
inputProps: {
|
||||||
disabled: true,
|
disabled: true,
|
||||||
|
showLabel: false,
|
||||||
|
type: "hidden",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
snapshot: {
|
||||||
|
fieldType: "snapshot",
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
fieldType: "models",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -191,9 +191,11 @@ export function RunWorkflowButton({
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-w-xl">
|
<DialogContent className="max-w-xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Run inputs</DialogTitle>
|
<DialogTitle>Confirm run</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Run your workflow with custom inputs
|
{schema
|
||||||
|
? "Run your workflow with custom inputs"
|
||||||
|
: "Confirm to run your workflow"}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{/* <div className="max-h-96 overflow-y-scroll"> */}
|
{/* <div className="max-h-96 overflow-y-scroll"> */}
|
||||||
@ -203,6 +205,7 @@ export function RunWorkflowButton({
|
|||||||
values={values}
|
values={values}
|
||||||
onValuesChange={setValues}
|
onValuesChange={setValues}
|
||||||
onSubmit={runWorkflow}
|
onSubmit={runWorkflow}
|
||||||
|
className="px-1"
|
||||||
>
|
>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<AutoFormSubmit>
|
<AutoFormSubmit>
|
||||||
|
157
web/src/components/custom-form/ModelPickerView.tsx
Normal file
157
web/src/components/custom-form/ModelPickerView.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const Model = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
base: z.string(),
|
||||||
|
save_path: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
reference: z.string(),
|
||||||
|
filename: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ModelList = z.array(Model);
|
||||||
|
|
||||||
|
export const ModelListWrapper = z.object({
|
||||||
|
models: ModelList,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ModelPickerView({
|
||||||
|
field,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field">) {
|
||||||
|
const value = (field.value as z.infer<typeof ModelList>) ?? [];
|
||||||
|
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
|
const [modelList, setModelList] =
|
||||||
|
React.useState<z.infer<typeof ModelListWrapper>>();
|
||||||
|
|
||||||
|
// const [selectedModels, setSelectedModels] = React.useState<
|
||||||
|
// z.infer<typeof ModelList>
|
||||||
|
// >(field.value ?? []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
fetch(
|
||||||
|
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
|
||||||
|
{
|
||||||
|
signal: controller.signal,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((x) => x.json())
|
||||||
|
.then((a) => {
|
||||||
|
setModelList(ModelListWrapper.parse(a));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function toggleModel(model: z.infer<typeof Model>) {
|
||||||
|
const prevSelectedModels = value;
|
||||||
|
if (
|
||||||
|
prevSelectedModels.some(
|
||||||
|
(selectedModel) =>
|
||||||
|
selectedModel.url + selectedModel.name === model.url + model.name
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
field.onChange(
|
||||||
|
prevSelectedModels.filter(
|
||||||
|
(selectedModel) =>
|
||||||
|
selectedModel.url + selectedModel.name !== model.url + model.name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
field.onChange([...prevSelectedModels, model]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// React.useEffect(() => {
|
||||||
|
// field.onChange(selectedModels);
|
||||||
|
// }, [selectedModels]);
|
||||||
|
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="" ref={containerRef}>
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between flex"
|
||||||
|
>
|
||||||
|
Select models... ({value.length} selected)
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[375px] p-0" side="top">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search framework..." className="h-9" />
|
||||||
|
<CommandEmpty>No framework found.</CommandEmpty>
|
||||||
|
<CommandList className="pointer-events-auto">
|
||||||
|
<CommandGroup>
|
||||||
|
{modelList?.models.map((model) => (
|
||||||
|
<CommandItem
|
||||||
|
key={model.url + model.name}
|
||||||
|
value={model.url}
|
||||||
|
onSelect={() => {
|
||||||
|
toggleModel(model);
|
||||||
|
// Update field.onChange to pass the array of selected models
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{model.name}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
value.some(
|
||||||
|
(selectedModel) => selectedModel.url === model.url
|
||||||
|
)
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
{field.value && (
|
||||||
|
<ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
|
||||||
|
<div className="max-h-[200px]">
|
||||||
|
<pre className="p-2 rounded-md text-xs ">
|
||||||
|
{JSON.stringify(field.value, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
125
web/src/components/custom-form/SnapshotPickerView.tsx
Normal file
125
web/src/components/custom-form/SnapshotPickerView.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { findAllDeployments } from "@/server/curdDeploments";
|
||||||
|
import { Check, ChevronsUpDown } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export function SnapshotPickerView({
|
||||||
|
field,
|
||||||
|
}: Pick<AutoFormInputComponentProps, "field">) {
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [selected, setSelected] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
const [frameworks, setFramework] = React.useState<
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}[]
|
||||||
|
>();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
findAllDeployments().then((a) => {
|
||||||
|
console.log(a);
|
||||||
|
|
||||||
|
const frameworks = a
|
||||||
|
.map((item) => {
|
||||||
|
if (
|
||||||
|
item.deployments.length == 0 ||
|
||||||
|
item.deployments[0].version.snapshot == null
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item.deployments[0].version.id,
|
||||||
|
label: `${item.name} - ${item.deployments[0].environment}`,
|
||||||
|
value: JSON.stringify(item.deployments[0].version.snapshot),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item): item is NonNullable<typeof item> => item != null);
|
||||||
|
|
||||||
|
setFramework(frameworks);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function findItem(value: string) {
|
||||||
|
// console.log(frameworks);
|
||||||
|
|
||||||
|
return frameworks?.find((item) => item.id === value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between flex"
|
||||||
|
>
|
||||||
|
{selected ? findItem(selected)?.label : "Select snapshot..."}
|
||||||
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[375px] p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Search framework..." className="h-9" />
|
||||||
|
<CommandEmpty>No framework found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{frameworks?.map((framework) => (
|
||||||
|
<CommandItem
|
||||||
|
key={framework.id}
|
||||||
|
value={framework.id}
|
||||||
|
onSelect={(currentValue) => {
|
||||||
|
setSelected(currentValue);
|
||||||
|
const json =
|
||||||
|
frameworks?.find((item) => item.id === currentValue)
|
||||||
|
?.value ?? null;
|
||||||
|
field.onChange(json ? JSON.parse(json) : null);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{framework.label}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"ml-auto h-4 w-4",
|
||||||
|
field.value === framework.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
{field.value && (
|
||||||
|
<ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
|
||||||
|
<div className="max-h-[200px]">
|
||||||
|
<pre className="p-2 rounded-md text-xs ">
|
||||||
|
{JSON.stringify(field.value, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
39
web/src/components/custom-form/model-picker.tsx
Normal file
39
web/src/components/custom-form/model-picker.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "../ui/form";
|
||||||
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
|
import { ModelPickerView } from "@/components/custom-form/ModelPickerView";
|
||||||
|
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
|
||||||
|
export default function AutoFormModelsPicker({
|
||||||
|
label,
|
||||||
|
isRequired,
|
||||||
|
field,
|
||||||
|
fieldConfigItem,
|
||||||
|
zodItem,
|
||||||
|
}: AutoFormInputComponentProps) {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{label}
|
||||||
|
{isRequired && <span className="text-destructive"> *</span>}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Suspense fallback={<LoadingIcon />}>
|
||||||
|
<ModelPickerView field={field} />
|
||||||
|
</Suspense>
|
||||||
|
</FormControl>
|
||||||
|
{fieldConfigItem.description && (
|
||||||
|
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
39
web/src/components/custom-form/snapshot-picker.tsx
Normal file
39
web/src/components/custom-form/snapshot-picker.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "../ui/form";
|
||||||
|
import { SnapshotPickerView } from "./SnapshotPickerView";
|
||||||
|
import { LoadingIcon } from "@/components/LoadingIcon";
|
||||||
|
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
|
||||||
|
export default function AutoFormSnapshotPicker({
|
||||||
|
label,
|
||||||
|
isRequired,
|
||||||
|
field,
|
||||||
|
fieldConfigItem,
|
||||||
|
zodItem,
|
||||||
|
}: AutoFormInputComponentProps) {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{label}
|
||||||
|
{isRequired && <span className="text-destructive"> *</span>}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Suspense fallback={<LoadingIcon />}>
|
||||||
|
<SnapshotPickerView field={field} />
|
||||||
|
</Suspense>
|
||||||
|
</FormControl>
|
||||||
|
{fieldConfigItem.description && (
|
||||||
|
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
@ -6,6 +6,8 @@ import AutoFormNumber from "./fields/number";
|
|||||||
import AutoFormRadioGroup from "./fields/radio-group";
|
import AutoFormRadioGroup from "./fields/radio-group";
|
||||||
import AutoFormSwitch from "./fields/switch";
|
import AutoFormSwitch from "./fields/switch";
|
||||||
import AutoFormTextarea from "./fields/textarea";
|
import AutoFormTextarea from "./fields/textarea";
|
||||||
|
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
|
||||||
|
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
|
||||||
|
|
||||||
export const INPUT_COMPONENTS = {
|
export const INPUT_COMPONENTS = {
|
||||||
checkbox: AutoFormCheckbox,
|
checkbox: AutoFormCheckbox,
|
||||||
@ -16,6 +18,10 @@ export const INPUT_COMPONENTS = {
|
|||||||
textarea: AutoFormTextarea,
|
textarea: AutoFormTextarea,
|
||||||
number: AutoFormNumber,
|
number: AutoFormNumber,
|
||||||
fallback: AutoFormInput,
|
fallback: AutoFormInput,
|
||||||
|
|
||||||
|
// Customs
|
||||||
|
snapshot: AutoFormSnapshotPicker,
|
||||||
|
models: AutoFormModelsPicker,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "../../form";
|
} from "../../form";
|
||||||
import { Input } from "../../input";
|
import { Input } from "../../input";
|
||||||
import { AutoFormInputComponentProps } from "../types";
|
import type { AutoFormInputComponentProps } from "../types";
|
||||||
|
|
||||||
export default function AutoFormInput({
|
export default function AutoFormInput({
|
||||||
label,
|
label,
|
||||||
|
@ -6,6 +6,7 @@ import type { FieldConfig } from "./types";
|
|||||||
import type { ZodObjectOrWrapped } from "./utils";
|
import type { ZodObjectOrWrapped } from "./utils";
|
||||||
import { getDefaultValues, getObjectFormSchema } from "./utils";
|
import { getDefaultValues, getObjectFormSchema } from "./utils";
|
||||||
import AutoFormObject from "@/components/ui/auto-form/fields/object";
|
import AutoFormObject from "@/components/ui/auto-form/fields/object";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import type { DefaultValues } from "react-hook-form";
|
import type { DefaultValues } from "react-hook-form";
|
||||||
@ -68,11 +69,15 @@ function AutoForm<SchemaType extends ZodObjectOrWrapped>({
|
|||||||
}}
|
}}
|
||||||
className={cn("space-y-5", className)}
|
className={cn("space-y-5", className)}
|
||||||
>
|
>
|
||||||
|
<ScrollArea>
|
||||||
|
<div className="max-h-[400px] px-1 py-1 w-full">
|
||||||
<AutoFormObject
|
<AutoFormObject
|
||||||
schema={objectFormSchema}
|
schema={objectFormSchema}
|
||||||
form={form}
|
form={form}
|
||||||
fieldConfig={fieldConfig}
|
fieldConfig={fieldConfig}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ControllerRenderProps, FieldValues } from "react-hook-form";
|
import type { INPUT_COMPONENTS } from "./config";
|
||||||
import * as z from "zod";
|
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
|
||||||
import { INPUT_COMPONENTS } from "./config";
|
import type * as z from "zod";
|
||||||
|
|
||||||
export type FieldConfigItem = {
|
export type FieldConfigItem = {
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
|
154
web/src/components/ui/command.tsx
Normal file
154
web/src/components/ui/command.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { type DialogProps } from "@radix-ui/react-dialog";
|
||||||
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
|
import { Search } from "lucide-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
const Command = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Command.displayName = CommandPrimitive.displayName;
|
||||||
|
|
||||||
|
interface CommandDialogProps extends DialogProps {}
|
||||||
|
|
||||||
|
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CommandInput = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
||||||
|
|
||||||
|
const CommandList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
CommandList.displayName = CommandPrimitive.List.displayName;
|
||||||
|
|
||||||
|
const CommandEmpty = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||||
|
>((props, ref) => (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
ref={ref}
|
||||||
|
className="py-6 text-center text-sm"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||||
|
|
||||||
|
const CommandGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
||||||
|
|
||||||
|
const CommandSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
||||||
|
|
||||||
|
const CommandItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
||||||
|
|
||||||
|
const CommandShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
CommandShortcut.displayName = "CommandShortcut";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
};
|
@ -1,6 +1,5 @@
|
|||||||
import * as React from "react"
|
import { cn } from "@/lib/utils";
|
||||||
|
import * as React from "react";
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
@ -17,9 +16,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
Input.displayName = "Input"
|
Input.displayName = "Input";
|
||||||
|
|
||||||
export { Input }
|
export { Input };
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import { cn } from "@/lib/utils";
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
const Popover = PopoverPrimitive.Root;
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root
|
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||||
|
|
||||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
|
||||||
|
|
||||||
const PopoverContent = React.forwardRef<
|
const PopoverContent = React.forwardRef<
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
@ -22,10 +21,24 @@ const PopoverContent = React.forwardRef<
|
|||||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
// https://github.com/shadcn-ui/ui/pull/2123/files#diff-e43c79299129c57a9055c3d6a20ff7bbeea4035dd6aa80eebe381b29f82f90a8
|
||||||
|
onWheel={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const isScrollingDown = e.deltaY > 0;
|
||||||
|
if (isScrollingDown) {
|
||||||
|
e.currentTarget.dispatchEvent(
|
||||||
|
new KeyboardEvent("keydown", { key: "ArrowDown" })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
e.currentTarget.dispatchEvent(
|
||||||
|
new KeyboardEvent("keydown", { key: "ArrowUp" })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</PopoverPrimitive.Portal>
|
</PopoverPrimitive.Portal>
|
||||||
))
|
));
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent }
|
export { Popover, PopoverTrigger, PopoverContent };
|
||||||
|
@ -37,6 +37,7 @@ export const workflowTable = dbSchema.table("workflows", {
|
|||||||
|
|
||||||
export const workflowRelations = relations(workflowTable, ({ many }) => ({
|
export const workflowRelations = relations(workflowTable, ({ many }) => ({
|
||||||
versions: many(workflowVersionTable),
|
versions: many(workflowVersionTable),
|
||||||
|
deployments: many(deploymentsTable),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const workflowType = z.any();
|
export const workflowType = z.any();
|
||||||
@ -203,6 +204,7 @@ export const machinesTable = dbSchema.table("machines", {
|
|||||||
type: machinesType("type").notNull().default("classic"),
|
type: machinesType("type").notNull().default("classic"),
|
||||||
status: machinesStatus("status").notNull().default("ready"),
|
status: machinesStatus("status").notNull().default("ready"),
|
||||||
snapshot: jsonb("snapshot").$type<any>(),
|
snapshot: jsonb("snapshot").$type<any>(),
|
||||||
|
models: jsonb("models").$type<any>(),
|
||||||
build_log: text("build_log"),
|
build_log: text("build_log"),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -255,6 +257,10 @@ export const deploymentsRelations = relations(deploymentsTable, ({ one }) => ({
|
|||||||
fields: [deploymentsTable.workflow_version_id],
|
fields: [deploymentsTable.workflow_version_id],
|
||||||
references: [workflowVersionTable.id],
|
references: [workflowVersionTable.id],
|
||||||
}),
|
}),
|
||||||
|
workflow: one(workflowTable, {
|
||||||
|
fields: [deploymentsTable.workflow_id],
|
||||||
|
references: [workflowTable.id],
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const apiKeyTable = dbSchema.table("api_keys", {
|
export const apiKeyTable = dbSchema.table("api_keys", {
|
||||||
|
@ -17,4 +17,5 @@ export const addCustomMachineSchema = insertCustomMachineSchema.pick({
|
|||||||
name: true,
|
name: true,
|
||||||
type: true,
|
type: true,
|
||||||
snapshot: true,
|
snapshot: true,
|
||||||
|
models: true,
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { db } from "@/db/db";
|
import { db } from "@/db/db";
|
||||||
import { deploymentsTable } from "@/db/schema";
|
import { deploymentsTable, workflowTable } from "@/db/schema";
|
||||||
import { auth } from "@clerk/nextjs";
|
import { auth } from "@clerk/nextjs";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import "server-only";
|
import "server-only";
|
||||||
|
|
||||||
@ -47,3 +47,36 @@ export async function createDeployments(
|
|||||||
message: `Successfully created deployment for ${environment}`,
|
message: `Successfully created deployment for ${environment}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function findAllDeployments() {
|
||||||
|
const { userId, orgId } = auth();
|
||||||
|
if (!userId) throw new Error("No user id");
|
||||||
|
|
||||||
|
const deployments = await db.query.workflowTable.findMany({
|
||||||
|
where: and(
|
||||||
|
orgId
|
||||||
|
? eq(workflowTable.org_id, orgId)
|
||||||
|
: and(eq(workflowTable.user_id, userId), isNull(workflowTable.org_id))
|
||||||
|
),
|
||||||
|
columns: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
with: {
|
||||||
|
deployments: {
|
||||||
|
columns: {
|
||||||
|
environment: true,
|
||||||
|
},
|
||||||
|
with: {
|
||||||
|
version: {
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
snapshot: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return deployments;
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import type {
|
|||||||
} from "./addMachineSchema";
|
} from "./addMachineSchema";
|
||||||
import { withServerPromise } from "./withServerPromise";
|
import { withServerPromise } from "./withServerPromise";
|
||||||
import { db } from "@/db/db";
|
import { db } from "@/db/db";
|
||||||
|
import type { MachineType } from "@/db/schema";
|
||||||
import { machinesTable } from "@/db/schema";
|
import { machinesTable } from "@/db/schema";
|
||||||
import { auth } from "@clerk/nextjs";
|
import { auth } from "@clerk/nextjs";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
@ -25,7 +26,11 @@ export async function getMachines() {
|
|||||||
and(
|
and(
|
||||||
orgId
|
orgId
|
||||||
? eq(machinesTable.org_id, orgId)
|
? eq(machinesTable.org_id, orgId)
|
||||||
: eq(machinesTable.user_id, userId),
|
: // make sure org_id is null
|
||||||
|
and(
|
||||||
|
eq(machinesTable.user_id, userId),
|
||||||
|
isNull(machinesTable.org_id)
|
||||||
|
),
|
||||||
eq(machinesTable.disabled, false)
|
eq(machinesTable.disabled, false)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -67,10 +72,59 @@ export const addMachine = withServerPromise(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const updateCustomMachine = withServerPromise(
|
||||||
|
async ({
|
||||||
|
id,
|
||||||
|
...data
|
||||||
|
}: z.infer<typeof addCustomMachineSchema> & {
|
||||||
|
id: string;
|
||||||
|
}) => {
|
||||||
|
const { userId } = auth();
|
||||||
|
if (!userId) return { error: "No user id" };
|
||||||
|
|
||||||
|
const currentMachine = await db.query.machinesTable.findFirst({
|
||||||
|
where: eq(machinesTable.id, id),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentMachine) return { error: "Machine not found" };
|
||||||
|
|
||||||
|
// Check if snapshot or models have changed
|
||||||
|
const snapshotChanged =
|
||||||
|
JSON.stringify(data.snapshot) !== JSON.stringify(currentMachine.snapshot);
|
||||||
|
const modelsChanged =
|
||||||
|
JSON.stringify(data.models) !== JSON.stringify(currentMachine.models);
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// message: `snapshotChanged: ${snapshotChanged}, modelsChanged: ${modelsChanged}`,
|
||||||
|
// };
|
||||||
|
|
||||||
|
await db.update(machinesTable).set(data).where(eq(machinesTable.id, id));
|
||||||
|
|
||||||
|
// If there are changes
|
||||||
|
if (snapshotChanged || modelsChanged) {
|
||||||
|
// Update status to building
|
||||||
|
await db
|
||||||
|
.update(machinesTable)
|
||||||
|
.set({
|
||||||
|
status: "building",
|
||||||
|
endpoint: "not-ready",
|
||||||
|
})
|
||||||
|
.where(eq(machinesTable.id, id));
|
||||||
|
|
||||||
|
// Perform custom build if there are changes
|
||||||
|
await buildMachine(data, currentMachine);
|
||||||
|
redirect(`/machines/${id}`);
|
||||||
|
} else {
|
||||||
|
revalidatePath("/machines");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: "Machine Updated" };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const addCustomMachine = withServerPromise(
|
export const addCustomMachine = withServerPromise(
|
||||||
async (data: z.infer<typeof addCustomMachineSchema>) => {
|
async (data: z.infer<typeof addCustomMachineSchema>) => {
|
||||||
const { userId, orgId } = auth();
|
const { userId, orgId } = auth();
|
||||||
const headersList = headers();
|
|
||||||
|
|
||||||
if (!userId) return { error: "No user id" };
|
if (!userId) return { error: "No user id" };
|
||||||
|
|
||||||
@ -88,12 +142,21 @@ export const addCustomMachine = withServerPromise(
|
|||||||
|
|
||||||
const b = a[0];
|
const b = a[0];
|
||||||
|
|
||||||
// const origin = new URL(request.url).origin;
|
await buildMachine(data, b);
|
||||||
|
redirect(`/machines/${b.id}`);
|
||||||
|
// revalidatePath("/machines");
|
||||||
|
return { message: "Machine Building" };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function buildMachine(
|
||||||
|
data: z.infer<typeof addCustomMachineSchema>,
|
||||||
|
b: MachineType
|
||||||
|
) {
|
||||||
|
const headersList = headers();
|
||||||
|
|
||||||
const domain = headersList.get("x-forwarded-host") || "";
|
const domain = headersList.get("x-forwarded-host") || "";
|
||||||
const protocol = headersList.get("x-forwarded-proto") || "";
|
const protocol = headersList.get("x-forwarded-proto") || "";
|
||||||
// console.log("domain", domain);
|
|
||||||
// console.log("domain", `${protocol}://${domain}/api/machine-built`);
|
|
||||||
// return { message: "Machine Building" };
|
|
||||||
|
|
||||||
if (domain === "") {
|
if (domain === "") {
|
||||||
throw new Error("No domain");
|
throw new Error("No domain");
|
||||||
@ -108,8 +171,10 @@ export const addCustomMachine = withServerPromise(
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
machine_id: b.id,
|
machine_id: b.id,
|
||||||
name: b.id,
|
name: b.id,
|
||||||
snapshot: JSON.parse(data.snapshot as string),
|
snapshot: data.snapshot, //JSON.parse( as string),
|
||||||
callback_url: `${protocol}://${domain}/api/machine-built`,
|
callback_url: `${protocol}://${domain}/api/machine-built`,
|
||||||
|
models: data.models, //JSON.parse(data.models as string),
|
||||||
|
gpu: "T4",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,13 +190,7 @@ export const addCustomMachine = withServerPromise(
|
|||||||
.where(eq(machinesTable.id, b.id));
|
.where(eq(machinesTable.id, b.id));
|
||||||
throw new Error(`Error: ${result.statusText} ${error_log}`);
|
throw new Error(`Error: ${result.statusText} ${error_log}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect(`/machines/${b.id}`);
|
|
||||||
|
|
||||||
// revalidatePath("/machines");
|
|
||||||
return { message: "Machine Building" };
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
export const updateMachine = withServerPromise(
|
export const updateMachine = withServerPromise(
|
||||||
async ({
|
async ({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user