From 01a9c1a1d687baabd6c22127b1f503ea6944a255 Mon Sep 17 00:00:00 2001 From: BennyKok Date: Sun, 7 Jan 2024 17:22:28 +0800 Subject: [PATCH] feat: add millon js, add models picker dialog, update builder --- builder/modal-builder/src/main.py | 183 +++-- builder/modal-builder/src/template/Dockerfile | 1 + builder/modal-builder/src/template/app.py | 2 +- builder/modal-builder/src/template/config.py | 2 +- .../src/template/data/install_deps.py | 16 +- .../src/template/data/models.json | 12 + web/bun.lockb | Bin 452063 -> 471928 bytes web/drizzle/0023_fair_ikaris.sql | 1 + web/drizzle/meta/0023_snapshot.json | 716 ++++++++++++++++++ web/drizzle/meta/_journal.json | 7 + web/next.config.mjs | 5 +- web/package.json | 2 + .../app/(app)/machines/[machine_id]/page.tsx | 2 +- web/src/components/DeploymentDisplay.tsx | 65 +- web/src/components/InsertModal.tsx | 2 + web/src/components/MachineBuildLog.tsx | 5 +- web/src/components/MachineList.tsx | 81 +- web/src/components/VersionSelect.tsx | 7 +- .../custom-form/ModelPickerView.tsx | 157 ++++ .../custom-form/SnapshotPickerView.tsx | 125 +++ .../components/custom-form/model-picker.tsx | 39 + .../custom-form/snapshot-picker.tsx | 39 + web/src/components/ui/auto-form/config.ts | 6 + .../components/ui/auto-form/fields/input.tsx | 2 +- web/src/components/ui/auto-form/index.tsx | 15 +- web/src/components/ui/auto-form/types.ts | 6 +- web/src/components/ui/command.tsx | 154 ++++ web/src/components/ui/input.tsx | 13 +- web/src/components/ui/popover.tsx | 33 +- web/src/db/schema.ts | 6 + web/src/server/addMachineSchema.ts | 1 + web/src/server/curdDeploments.ts | 37 +- web/src/server/curdMachine.ts | 141 +++- 33 files changed, 1693 insertions(+), 190 deletions(-) create mode 100644 builder/modal-builder/src/template/data/models.json create mode 100644 web/drizzle/0023_fair_ikaris.sql create mode 100644 web/drizzle/meta/0023_snapshot.json create mode 100644 web/src/components/custom-form/ModelPickerView.tsx create mode 100644 web/src/components/custom-form/SnapshotPickerView.tsx create mode 100644 web/src/components/custom-form/model-picker.tsx create mode 100644 web/src/components/custom-form/snapshot-picker.tsx create mode 100644 web/src/components/ui/command.tsx diff --git a/builder/modal-builder/src/main.py b/builder/modal-builder/src/main.py index 72874f6..f717d7c 100644 --- a/builder/modal-builder/src/main.py +++ b/builder/modal-builder/src/main.py @@ -1,9 +1,10 @@ -from typing import Union, Optional, Dict -from pydantic import BaseModel +from typing import Union, Optional, Dict, List +from pydantic import BaseModel, Field, field_validator from fastapi import FastAPI, HTTPException, WebSocket, BackgroundTasks, WebSocketDisconnect from fastapi.responses import JSONResponse from fastapi.logger import logger as fastapi_logger import os +from enum import Enum import json import subprocess import time @@ -104,17 +105,49 @@ class Snapshot(BaseModel): comfyui: str 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): machine_id: str name: str snapshot: Snapshot + models: List[Model] 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}") async def websocket_endpoint(websocket: WebSocket, machine_id: str): await websocket.accept() 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: while True: data = await websocket.receive_text() @@ -156,6 +189,9 @@ async def create_item(item: Item): return JSONResponse(status_code=200, content={"message": "Build Queued"}) +# Initialize the logs cache +machine_logs_cache = {} + async def build_logic(item: Item): # Deploy to modal folder_path = f"/app/builds/{item.machine_id}" @@ -175,7 +211,8 @@ async def build_logic(item: Item): # Write the config file config = { "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: f.write("config = " + json.dumps(config)) @@ -183,79 +220,99 @@ async def build_logic(item: Item): with open(f"{folder_path}/data/snapshot.json", "w") as f: 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) # 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( f"modal deploy app.py", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, - cwd=folder_path + cwd=folder_path, + # env={**os.environ, "PYTHONUNBUFFERED": "1"} ) url = None - # Initialize the logs cache - machine_logs_cache = [] - - # Stream the output - # Read output - while True: - line = await process.stdout.readline() - error = await process.stderr.readline() - if not line and not error: - break - l = line.decode('utf-8').strip() - e = error.decode('utf-8').strip() + if item.machine_id not in machine_logs_cache: + machine_logs_cache[item.machine_id] = [] - if l != "": - logger.info(l) - machine_logs_cache.append({ - "logs": l, - "timestamp": time.time() - }) + machine_logs = machine_logs_cache[item.machine_id] - if item.machine_id in machine_id_websocket_dict: - await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "LOGS", "data": { - "machine_id": item.machine_id, - "logs": l, - "timestamp": time.time() - }})) + async def read_stream(stream, isStderr): + while True: + line = await stream.readline() + if line: + l = line.decode('utf-8').strip() + if l == "": + continue - if "Created comfyui_app =>" in l or (l.startswith("https://") and l.endswith(".modal.run")): - if "Created comfyui_app =>" in l: - url = l.split("=>")[1].strip() - else: - # Some case it only prints the url on a blank line - url = l - - if url: - machine_logs_cache.append({ - "logs": f"App image built, url: {url}", + if not isStderr: + logger.info(l) + machine_logs.append({ + "logs": l, "timestamp": time.time() }) if item.machine_id in machine_id_websocket_dict: await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "LOGS", "data": { "machine_id": item.machine_id, - "logs": f"App image built, url: {url}", + "logs": l, "timestamp": time.time() }})) - - if e != "": - logger.info(e) - machine_logs_cache.append({ - "logs": e, - "timestamp": time.time() - }) - if item.machine_id in machine_id_websocket_dict: - await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "LOGS", "data": { - "machine_id": item.machine_id, - "logs": e, - "timestamp": time.time() - }})) + if "Created comfyui_app =>" in l or (l.startswith("https://") and l.endswith(".modal.run")): + if "Created comfyui_app =>" in l: + url = l.split("=>")[1].strip() + else: + # Some case it only prints the url on a blank line + url = l + + if url: + machine_logs.append({ + "logs": f"App image built, url: {url}", + "timestamp": time.time() + }) + + if item.machine_id in machine_id_websocket_dict: + await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "LOGS", "data": { + "machine_id": item.machine_id, + "logs": f"App image built, url: {url}", + "timestamp": time.time() + }})) + await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "FINISHED", "data": { + "status": "succuss", + }})) + + else: + # is error + logger.error(l) + machine_logs.append({ + "logs": e, + "timestamp": time.time() + }) + + if item.machine_id in machine_id_websocket_dict: + await machine_id_websocket_dict[item.machine_id].send_text(json.dumps({"event": "LOGS", "data": { + "machine_id": item.machine_id, + "logs": e, + "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 await process.wait() @@ -273,29 +330,39 @@ async def build_logic(item: Item): if process.returncode != 0: logger.info("An error occurred.") # 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.", "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 JSONResponse(status_code=400, content={"error": "Unable to build the app image."}) # app_suffix = "comfyui-app" if url is None: - machine_logs_cache.append({ + machine_logs.append({ "logs": "App image built, but url is None, unable to parse the url.", "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 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/ # 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(url) diff --git a/builder/modal-builder/src/template/Dockerfile b/builder/modal-builder/src/template/Dockerfile index 3cd223c..ee4c9ec 100644 --- a/builder/modal-builder/src/template/Dockerfile +++ b/builder/modal-builder/src/template/Dockerfile @@ -57,6 +57,7 @@ WORKDIR / COPY /data/install_deps.py . COPY /data/deps.json . +COPY /data/models.json . RUN python3 install_deps.py WORKDIR /comfyui/custom_nodes diff --git a/builder/modal-builder/src/template/app.py b/builder/modal-builder/src/template/app.py index e8867d0..0e0fd04 100644 --- a/builder/modal-builder/src/template/app.py +++ b/builder/modal-builder/src/template/app.py @@ -105,7 +105,7 @@ image = Image.debian_slim() 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): import subprocess import time diff --git a/builder/modal-builder/src/template/config.py b/builder/modal-builder/src/template/config.py index a4a97e9..e59020b 100644 --- a/builder/modal-builder/src/template/config.py +++ b/builder/modal-builder/src/template/config.py @@ -1 +1 @@ -config = {"name": "my-app", "deploy_test": "True"} \ No newline at end of file +config = {"name": "my-app", "deploy_test": "True", "gpu": "T4"} \ No newline at end of file diff --git a/builder/modal-builder/src/template/data/install_deps.py b/builder/modal-builder/src/template/data/install_deps.py index 01eeca2..568afe6 100644 --- a/builder/modal-builder/src/template/data/install_deps.py +++ b/builder/modal-builder/src/template/data/install_deps.py @@ -28,9 +28,10 @@ def check_server(url, retries=50, delay=500): ) 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"} # Load JSON array from deps.json @@ -39,12 +40,15 @@ with open('deps.json') as f: # Make a POST request for each package 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) -# restore_snapshot_url = "http://127.0.0.1:8188/snapshot/restore?target=snapshot" -# response = requests.request("GET", restore_snapshot_url, 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() diff --git a/builder/modal-builder/src/template/data/models.json b/builder/modal-builder/src/template/data/models.json new file mode 100644 index 0000000..6913dac --- /dev/null +++ b/builder/modal-builder/src/template/data/models.json @@ -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" + } +] \ No newline at end of file diff --git a/web/bun.lockb b/web/bun.lockb index 83956cba26f71f2db40f320f5491e5a649fed4db..907bdf8329065de2550274520529899af032c768 100755 GIT binary patch delta 84710 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 DY(>~i delta 73903 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 )} - {machine.build_log && ( + {machine.status !== "building" && machine.build_log && ( )} diff --git a/web/src/components/DeploymentDisplay.tsx b/web/src/components/DeploymentDisplay.tsx index 023e590..721b490 100644 --- a/web/src/components/DeploymentDisplay.tsx +++ b/web/src/components/DeploymentDisplay.tsx @@ -7,6 +7,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { TableCell, TableRow } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow"; @@ -72,13 +73,13 @@ export function DeploymentDisplay({ {deployment.environment} - + {deployment.version?.version} - + {deployment.machine?.name} - + {getRelativeTime(deployment.updated_at)} @@ -90,34 +91,36 @@ export function DeploymentDisplay({ Code for your deployment client - - - js - curl - - - Trigger the workflow - - Check the status of the run, and retrieve the outputs - - - - - - - + + + + js + curl + + + Trigger the workflow + + Check the status of the run, and retrieve the outputs + + + + + + + + ); diff --git a/web/src/components/InsertModal.tsx b/web/src/components/InsertModal.tsx index 3d0e2f0..64a5039 100644 --- a/web/src/components/InsertModal.tsx +++ b/web/src/components/InsertModal.tsx @@ -43,6 +43,7 @@ export function InsertModal< {props.title} {props.description} + {/* */} + {/* */} ); diff --git a/web/src/components/MachineBuildLog.tsx b/web/src/components/MachineBuildLog.tsx index 13b25be..d03ff7c 100644 --- a/web/src/components/MachineBuildLog.tsx +++ b/web/src/components/MachineBuildLog.tsx @@ -14,12 +14,13 @@ export function MachineBuildLog({ endpoint: string; }) { const [logs, setLogs] = useState([]); + const [finished, setFinished] = useState(false); const wsEndpoint = endpoint.replace(/^http/, "ws"); const { lastMessage, readyState } = useWebSocket( `${wsEndpoint}/ws/${machine_id}`, { - shouldReconnect: () => true, + shouldReconnect: () => !finished, reconnectAttempts: 20, reconnectInterval: 1000, } @@ -36,6 +37,8 @@ export function MachineBuildLog({ if (message?.event === "LOGS") { setLogs((logs) => [...(logs ?? []), message.data]); + } else if (message?.event === "FINISHED") { + setFinished(true); } }, [lastMessage]); diff --git a/web/src/components/MachineList.tsx b/web/src/components/MachineList.tsx index b1c3f5c..7f22958 100644 --- a/web/src/components/MachineList.tsx +++ b/web/src/components/MachineList.tsx @@ -33,6 +33,7 @@ import { deleteMachine, disableMachine, enableMachine, + updateCustomMachine, updateMachine, } from "@/server/curdMachine"; import type { @@ -95,7 +96,7 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => { return ( // -
+
{row.getValue("name")} @@ -115,7 +116,9 @@ export const columns: ColumnDef[] = [ header: () =>
Endpoint
, cell: ({ row }) => { return ( -
{row.original.endpoint}
+
+ {row.original.endpoint} +
); }, }, @@ -123,7 +126,11 @@ export const columns: ColumnDef[] = [ accessorKey: "type", header: () =>
Type
, cell: ({ row }) => { - return
{row.original.type}
; + return ( +
+ {row.original.type} +
+ ); }, }, { @@ -133,7 +140,7 @@ export const columns: ColumnDef[] = [ header: ({ column }) => { return (
diff --git a/web/src/components/VersionSelect.tsx b/web/src/components/VersionSelect.tsx index 9da1669..3ba2dd9 100644 --- a/web/src/components/VersionSelect.tsx +++ b/web/src/components/VersionSelect.tsx @@ -191,9 +191,11 @@ export function RunWorkflowButton({ - Run inputs + Confirm run - Run your workflow with custom inputs + {schema + ? "Run your workflow with custom inputs" + : "Confirm to run your workflow"} {/*
*/} @@ -203,6 +205,7 @@ export function RunWorkflowButton({ values={values} onValuesChange={setValues} onSubmit={runWorkflow} + className="px-1" >
diff --git a/web/src/components/custom-form/ModelPickerView.tsx b/web/src/components/custom-form/ModelPickerView.tsx new file mode 100644 index 0000000..5c1abe8 --- /dev/null +++ b/web/src/components/custom-form/ModelPickerView.tsx @@ -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) { + const value = (field.value as z.infer) ?? []; + + const [open, setOpen] = React.useState(false); + + const [modelList, setModelList] = + React.useState>(); + + // const [selectedModels, setSelectedModels] = React.useState< + // z.infer + // >(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) { + 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(null); + + return ( +
+ + + + + + + + No framework found. + + + {modelList?.models.map((model) => ( + { + toggleModel(model); + // Update field.onChange to pass the array of selected models + }} + > + {model.name} + selectedModel.url === model.url + ) + ? "opacity-100" + : "opacity-0" + )} + /> + + ))} + + + + + + {field.value && ( + +
+
+              {JSON.stringify(field.value, null, 2)}
+            
+
+
+ )} +
+ ); +} diff --git a/web/src/components/custom-form/SnapshotPickerView.tsx b/web/src/components/custom-form/SnapshotPickerView.tsx new file mode 100644 index 0000000..675393c --- /dev/null +++ b/web/src/components/custom-form/SnapshotPickerView.tsx @@ -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) { + const [open, setOpen] = React.useState(false); + const [selected, setSelected] = React.useState(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 => item != null); + + setFramework(frameworks); + }); + }, []); + + function findItem(value: string) { + // console.log(frameworks); + + return frameworks?.find((item) => item.id === value); + } + + return ( +
+ + + + + + + + No framework found. + + {frameworks?.map((framework) => ( + { + setSelected(currentValue); + const json = + frameworks?.find((item) => item.id === currentValue) + ?.value ?? null; + field.onChange(json ? JSON.parse(json) : null); + setOpen(false); + }} + > + {framework.label} + + + ))} + + + + + {field.value && ( + +
+
+              {JSON.stringify(field.value, null, 2)}
+            
+
+
+ )} +
+ ); +} diff --git a/web/src/components/custom-form/model-picker.tsx b/web/src/components/custom-form/model-picker.tsx new file mode 100644 index 0000000..0d2d0fe --- /dev/null +++ b/web/src/components/custom-form/model-picker.tsx @@ -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 ( + + + {label} + {isRequired && *} + + + }> + + + + {fieldConfigItem.description && ( + {fieldConfigItem.description} + )} + + + ); +} diff --git a/web/src/components/custom-form/snapshot-picker.tsx b/web/src/components/custom-form/snapshot-picker.tsx new file mode 100644 index 0000000..e825173 --- /dev/null +++ b/web/src/components/custom-form/snapshot-picker.tsx @@ -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 ( + + + {label} + {isRequired && *} + + + }> + + + + {fieldConfigItem.description && ( + {fieldConfigItem.description} + )} + + + ); +} diff --git a/web/src/components/ui/auto-form/config.ts b/web/src/components/ui/auto-form/config.ts index b94b9b0..4fdb9ea 100644 --- a/web/src/components/ui/auto-form/config.ts +++ b/web/src/components/ui/auto-form/config.ts @@ -6,6 +6,8 @@ import AutoFormNumber from "./fields/number"; import AutoFormRadioGroup from "./fields/radio-group"; 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"; export const INPUT_COMPONENTS = { checkbox: AutoFormCheckbox, @@ -16,6 +18,10 @@ export const INPUT_COMPONENTS = { textarea: AutoFormTextarea, number: AutoFormNumber, fallback: AutoFormInput, + + // Customs + snapshot: AutoFormSnapshotPicker, + models: AutoFormModelsPicker, }; /** diff --git a/web/src/components/ui/auto-form/fields/input.tsx b/web/src/components/ui/auto-form/fields/input.tsx index 242556b..dbc7fee 100644 --- a/web/src/components/ui/auto-form/fields/input.tsx +++ b/web/src/components/ui/auto-form/fields/input.tsx @@ -6,7 +6,7 @@ import { FormMessage, } from "../../form"; import { Input } from "../../input"; -import { AutoFormInputComponentProps } from "../types"; +import type { AutoFormInputComponentProps } from "../types"; export default function AutoFormInput({ label, diff --git a/web/src/components/ui/auto-form/index.tsx b/web/src/components/ui/auto-form/index.tsx index fa5d7e3..c2b7505 100644 --- a/web/src/components/ui/auto-form/index.tsx +++ b/web/src/components/ui/auto-form/index.tsx @@ -6,6 +6,7 @@ import type { FieldConfig } from "./types"; import type { ZodObjectOrWrapped } from "./utils"; import { getDefaultValues, getObjectFormSchema } from "./utils"; import AutoFormObject from "@/components/ui/auto-form/fields/object"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; import type { DefaultValues } from "react-hook-form"; @@ -68,11 +69,15 @@ function AutoForm({ }} className={cn("space-y-5", className)} > - + +
+ +
+
{children} diff --git a/web/src/components/ui/auto-form/types.ts b/web/src/components/ui/auto-form/types.ts index d0e2ab6..50d55bc 100644 --- a/web/src/components/ui/auto-form/types.ts +++ b/web/src/components/ui/auto-form/types.ts @@ -1,6 +1,6 @@ -import { ControllerRenderProps, FieldValues } from "react-hook-form"; -import * as z from "zod"; -import { INPUT_COMPONENTS } from "./config"; +import type { INPUT_COMPONENTS } from "./config"; +import type { ControllerRenderProps, FieldValues } from "react-hook-form"; +import type * as z from "zod"; export type FieldConfigItem = { description?: React.ReactNode; diff --git a/web/src/components/ui/command.tsx b/web/src/components/ui/command.tsx new file mode 100644 index 0000000..c2d1b36 --- /dev/null +++ b/web/src/components/ui/command.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/web/src/components/ui/input.tsx b/web/src/components/ui/input.tsx index 677d05f..78d3d19 100644 --- a/web/src/components/ui/input.tsx +++ b/web/src/components/ui/input.tsx @@ -1,6 +1,5 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; +import * as React from "react"; export interface InputProps extends React.InputHTMLAttributes {} @@ -17,9 +16,9 @@ const Input = React.forwardRef( ref={ref} {...props} /> - ) + ); } -) -Input.displayName = "Input" +); +Input.displayName = "Input"; -export { Input } +export { Input }; diff --git a/web/src/components/ui/popover.tsx b/web/src/components/ui/popover.tsx index a0ec48b..ef29e37 100644 --- a/web/src/components/ui/popover.tsx +++ b/web/src/components/ui/popover.tsx @@ -1,13 +1,12 @@ -"use client" +"use client"; -import * as React from "react" -import * as PopoverPrimitive from "@radix-ui/react-popover" +import { cn } from "@/lib/utils"; +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< React.ElementRef, @@ -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", 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} /> -)) -PopoverContent.displayName = PopoverPrimitive.Content.displayName +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; -export { Popover, PopoverTrigger, PopoverContent } +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/web/src/db/schema.ts b/web/src/db/schema.ts index 694b999..8b7b884 100644 --- a/web/src/db/schema.ts +++ b/web/src/db/schema.ts @@ -37,6 +37,7 @@ export const workflowTable = dbSchema.table("workflows", { export const workflowRelations = relations(workflowTable, ({ many }) => ({ versions: many(workflowVersionTable), + deployments: many(deploymentsTable), })); export const workflowType = z.any(); @@ -203,6 +204,7 @@ export const machinesTable = dbSchema.table("machines", { type: machinesType("type").notNull().default("classic"), status: machinesStatus("status").notNull().default("ready"), snapshot: jsonb("snapshot").$type(), + models: jsonb("models").$type(), build_log: text("build_log"), }); @@ -255,6 +257,10 @@ export const deploymentsRelations = relations(deploymentsTable, ({ one }) => ({ fields: [deploymentsTable.workflow_version_id], references: [workflowVersionTable.id], }), + workflow: one(workflowTable, { + fields: [deploymentsTable.workflow_id], + references: [workflowTable.id], + }), })); export const apiKeyTable = dbSchema.table("api_keys", { diff --git a/web/src/server/addMachineSchema.ts b/web/src/server/addMachineSchema.ts index 9d36624..639def4 100644 --- a/web/src/server/addMachineSchema.ts +++ b/web/src/server/addMachineSchema.ts @@ -17,4 +17,5 @@ export const addCustomMachineSchema = insertCustomMachineSchema.pick({ name: true, type: true, snapshot: true, + models: true, }); diff --git a/web/src/server/curdDeploments.ts b/web/src/server/curdDeploments.ts index 74dc3f2..ff0e89c 100644 --- a/web/src/server/curdDeploments.ts +++ b/web/src/server/curdDeploments.ts @@ -1,9 +1,9 @@ "use server"; import { db } from "@/db/db"; -import { deploymentsTable } from "@/db/schema"; +import { deploymentsTable, workflowTable } from "@/db/schema"; import { auth } from "@clerk/nextjs"; -import { and, eq } from "drizzle-orm"; +import { and, eq, isNull } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import "server-only"; @@ -47,3 +47,36 @@ export async function createDeployments( 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; +} diff --git a/web/src/server/curdMachine.ts b/web/src/server/curdMachine.ts index 48e886a..3dec0aa 100644 --- a/web/src/server/curdMachine.ts +++ b/web/src/server/curdMachine.ts @@ -6,6 +6,7 @@ import type { } from "./addMachineSchema"; import { withServerPromise } from "./withServerPromise"; import { db } from "@/db/db"; +import type { MachineType } from "@/db/schema"; import { machinesTable } from "@/db/schema"; import { auth } from "@clerk/nextjs"; import { and, eq, isNull } from "drizzle-orm"; @@ -25,7 +26,11 @@ export async function getMachines() { and( 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) ) ); @@ -67,10 +72,59 @@ export const addMachine = withServerPromise( } ); +export const updateCustomMachine = withServerPromise( + async ({ + id, + ...data + }: z.infer & { + 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( async (data: z.infer) => { const { userId, orgId } = auth(); - const headersList = headers(); if (!userId) return { error: "No user id" }; @@ -88,51 +142,56 @@ export const addCustomMachine = withServerPromise( const b = a[0]; - // const origin = new URL(request.url).origin; - const domain = headersList.get("x-forwarded-host") || ""; - 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 === "") { - throw new Error("No domain"); - } - - // Call remote builder - const result = await fetch(`${process.env.MODAL_BUILDER_URL!}/create`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - machine_id: b.id, - name: b.id, - snapshot: JSON.parse(data.snapshot as string), - callback_url: `${protocol}://${domain}/api/machine-built`, - }), - }); - - if (!result.ok) { - const error_log = await result.text(); - await db - .update(machinesTable) - .set({ - ...data, - status: "error", - build_log: error_log, - }) - .where(eq(machinesTable.id, b.id)); - throw new Error(`Error: ${result.statusText} ${error_log}`); - } - + await buildMachine(data, b); redirect(`/machines/${b.id}`); - // revalidatePath("/machines"); return { message: "Machine Building" }; } ); +async function buildMachine( + data: z.infer, + b: MachineType +) { + 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!}/create`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + machine_id: b.id, + name: b.id, + snapshot: data.snapshot, //JSON.parse( as string), + callback_url: `${protocol}://${domain}/api/machine-built`, + models: data.models, //JSON.parse(data.models as string), + gpu: "T4", + }), + }); + + if (!result.ok) { + const error_log = await result.text(); + await db + .update(machinesTable) + .set({ + ...data, + status: "error", + build_log: error_log, + }) + .where(eq(machinesTable.id, b.id)); + throw new Error(`Error: ${result.statusText} ${error_log}`); + } +} + export const updateMachine = withServerPromise( async ({ id,