work
This commit is contained in:
		
							parent
							
								
									90cec6b778
								
							
						
					
					
						commit
						fed7b380b6
					
				@ -224,6 +224,20 @@ async def websocket_endpoint(websocket: WebSocket, machine_id: str):
 | 
				
			|||||||
#     return {"Hello": "World"}
 | 
					#     return {"Hello": "World"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UploadBody(BaseModel):
 | 
				
			||||||
 | 
					    download_url: str
 | 
				
			||||||
 | 
					    volume_name: str
 | 
				
			||||||
 | 
					    # callback_url: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.post("/upload_volume")
 | 
				
			||||||
 | 
					async def upload_checkpoint(body: UploadBody):
 | 
				
			||||||
 | 
					    download_url = body.download_url
 | 
				
			||||||
 | 
					    volume_name = body.download_url
 | 
				
			||||||
 | 
					    # callback_url = body.callback_url
 | 
				
			||||||
 | 
					    # check that thi
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.post("/create")
 | 
					@app.post("/create")
 | 
				
			||||||
async def create_machine(item: Item):
 | 
					async def create_machine(item: Item):
 | 
				
			||||||
    global last_activity_time
 | 
					    global last_activity_time
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,9 @@ This is a standalone script to download models into a modal Volume using civitai
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Example Usage
 | 
					Example Usage
 | 
				
			||||||
`modal run insert_models::insert_model --civitai-url https://civitai.com/models/36520/ghostmix`
 | 
					`modal run insert_models::insert_model --civitai-url https://civitai.com/models/36520/ghostmix`
 | 
				
			||||||
This inserts an individual model from a civitai url (public not API url)
 | 
					This inserts an individual model from a civitai url 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`modal run insert_models::insert_models` 
 | 
					`modal run insert_models::insert_models_civitai_api` 
 | 
				
			||||||
This inserts a bunch of models based on the models retrieved by civitai
 | 
					This inserts a bunch of models based on the models retrieved by civitai
 | 
				
			||||||
 | 
					
 | 
				
			||||||
civitai's API reference https://github.com/civitai/civitai/wiki/REST-API-Reference
 | 
					civitai's API reference https://github.com/civitai/civitai/wiki/REST-API-Reference
 | 
				
			||||||
@ -13,27 +13,24 @@ civitai's API reference https://github.com/civitai/civitai/wiki/REST-API-Referen
 | 
				
			|||||||
import modal
 | 
					import modal
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
stub = modal.Stub()
 | 
					stub = modal.Stub()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NOTE: volume name can be variable
 | 
					# NOTE: volume name can be variable
 | 
				
			||||||
volume = modal.Volume.persisted("private-model-store")
 | 
					volume = modal.Volume.persisted("rah")
 | 
				
			||||||
model_store_path = "/vol/models"
 | 
					model_store_path = "/vol/models"
 | 
				
			||||||
MODEL_ROUTE = "models"
 | 
					MODEL_ROUTE = "models"
 | 
				
			||||||
image = (
 | 
					image = (
 | 
				
			||||||
    modal.Image.debian_slim().apt_install("wget").pip_install("requests")
 | 
					    modal.Image.debian_slim().apt_install("wget").pip_install("requests")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@stub.function(volumes={model_store_path: volume}, gpu="any", image=image, timeout=600)
 | 
					@stub.function(volumes={model_store_path: volume}, image=image, timeout=50000, gpu=None)
 | 
				
			||||||
def download_model(model):
 | 
					def download_model(download_url):
 | 
				
			||||||
    # wget https://civitai.com/api/download/models/{modelVersionId} --content-disposition
 | 
					    print(download_url)
 | 
				
			||||||
    # model_id = model['modelVersions'][0]['id']
 | 
					 | 
				
			||||||
    # download_url = f"https://civitai.com/api/download/models/{model_id}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    download_url = model['modelVersions'][0]['downloadUrl'] 
 | 
					 | 
				
			||||||
    subprocess.run(["wget", download_url, "--content-disposition", "-P", model_store_path])
 | 
					    subprocess.run(["wget", download_url, "--content-disposition", "-P", model_store_path])
 | 
				
			||||||
    subprocess.run(["ls", "-la", model_store_path])
 | 
					    subprocess.run(["ls", "-la", model_store_path])
 | 
				
			||||||
    volume.commit()  
 | 
					    volume.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# file is raw output from Civitai API https://github.com/civitai/civitai/wiki/REST-API-Reference
 | 
					# file is raw output from Civitai API https://github.com/civitai/civitai/wiki/REST-API-Reference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,40 +49,53 @@ def get_civitai_models(model_type: str, sort: str = "Highest Rated", page: int =
 | 
				
			|||||||
@stub.function()
 | 
					@stub.function()
 | 
				
			||||||
def get_civitai_model_url(civitai_url: str):
 | 
					def get_civitai_model_url(civitai_url: str):
 | 
				
			||||||
    # Validate the URL
 | 
					    # Validate the URL
 | 
				
			||||||
    if not civitai_url.startswith("https://civitai.com/models/"):
 | 
					
 | 
				
			||||||
 | 
					    if civitai_url.startswith("https://civitai.com/api/"):
 | 
				
			||||||
 | 
					        api_url = civitai_url
 | 
				
			||||||
 | 
					    elif civitai_url.startswith("https://civitai.com/models/"):  
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            model_id = civitai_url.split("/")[4]
 | 
				
			||||||
 | 
					            int(model_id) 
 | 
				
			||||||
 | 
					        except (IndexError, ValueError):
 | 
				
			||||||
 | 
					            return None 
 | 
				
			||||||
 | 
					        api_url = f"https://civitai.com/api/v1/models/{model_id}"
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
        return "Error: URL must be from civitai.com and contain /models/"
 | 
					        return "Error: URL must be from civitai.com and contain /models/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Extract the model ID
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        model_id = civitai_url.split("/")[4]
 | 
					 | 
				
			||||||
        int(model_id)  # Check if the ID is an integer
 | 
					 | 
				
			||||||
    except (IndexError, ValueError):
 | 
					 | 
				
			||||||
        return None #Error: Invalid model ID in URL
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Make the API request
 | 
					 | 
				
			||||||
    api_url = f"https://civitai.com/api/v1/models/{model_id}"
 | 
					 | 
				
			||||||
    response = requests.get(api_url)
 | 
					    response = requests.get(api_url)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Check for successful response
 | 
					    # Check for successful response
 | 
				
			||||||
    if response.status_code != 200:
 | 
					    if response.status_code != 200:
 | 
				
			||||||
        return f"Error: Unable to fetch data from {api_url}"
 | 
					        return f"Error: Unable to fetch data from {api_url}"
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Return the response data
 | 
					    # Return the response data
 | 
				
			||||||
    return response.json()
 | 
					    return response.json()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@stub.local_entrypoint()
 | 
					@stub.local_entrypoint()
 | 
				
			||||||
def insert_models(type: str = "Checkpoint", sort = "Highest Rated", page: int = 1):
 | 
					def insert_models_civitai_api(type: str = "Checkpoint", sort = "Highest Rated", page: int = 1):
 | 
				
			||||||
    civitai_models = get_civitai_models.local(type, sort, page)
 | 
					    civitai_models = get_civitai_models.local(type, sort, page)
 | 
				
			||||||
    if civitai_models:
 | 
					    if civitai_models:
 | 
				
			||||||
        for _ in download_model.map(civitai_models['items'][1:]):
 | 
					        for _ in download_model.map(map(lambda model: model['modelVersions'][0]['downloadUrl'], civitai_models['items'])):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        print("Failed to retrieve models.")
 | 
					        print("Failed to retrieve models.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@stub.local_entrypoint()
 | 
					@stub.local_entrypoint()
 | 
				
			||||||
def insert_model(civitai_url: str):
 | 
					def insert_model(civitai_url: str):
 | 
				
			||||||
    civitai_model = get_civitai_model_url.local(civitai_url)
 | 
					    if civitai_url.startswith("'https://civitai.com/api/download/models/"):
 | 
				
			||||||
    if civitai_model: 
 | 
					        download_url = civitai_url
 | 
				
			||||||
        download_model.remote(civitai_model)
 | 
					    else:
 | 
				
			||||||
 | 
					        civitai_model = get_civitai_model_url.local(civitai_url)
 | 
				
			||||||
 | 
					        if civitai_model:
 | 
				
			||||||
 | 
					            download_url = civitai_model['modelVersions'][0]['downloadUrl']
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return "invalid URL"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    download_model.remote(download_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@stub.local_entrypoint()
 | 
				
			||||||
 | 
					def simple_download():
 | 
				
			||||||
 | 
					    download_urls = ['https://civitai.com/api/download/models/119057', 'https://civitai.com/api/download/models/130090', 'https://civitai.com/api/download/models/31859', 'https://civitai.com/api/download/models/128713', 'https://civitai.com/api/download/models/179657', 'https://civitai.com/api/download/models/143906', 'https://civitai.com/api/download/models/9208', 'https://civitai.com/api/download/models/136078', 'https://civitai.com/api/download/models/134065', 'https://civitai.com/api/download/models/288775', 'https://civitai.com/api/download/models/95263', 'https://civitai.com/api/download/models/288982', 'https://civitai.com/api/download/models/87153', 'https://civitai.com/api/download/models/10638', 'https://civitai.com/api/download/models/263809', 'https://civitai.com/api/download/models/130072', 'https://civitai.com/api/download/models/117019', 'https://civitai.com/api/download/models/95256', 'https://civitai.com/api/download/models/197181', 'https://civitai.com/api/download/models/256915', 'https://civitai.com/api/download/models/118945', 'https://civitai.com/api/download/models/125843', 'https://civitai.com/api/download/models/179015', 'https://civitai.com/api/download/models/245598', 'https://civitai.com/api/download/models/223670', 'https://civitai.com/api/download/models/90072', 'https://civitai.com/api/download/models/290817', 'https://civitai.com/api/download/models/154097', 'https://civitai.com/api/download/models/143497', 'https://civitai.com/api/download/models/5637']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for _ in download_model.map(download_urls):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										54
									
								
								builder/modal-builder/src/volume-builder/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								builder/modal-builder/src/volume-builder/app.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					import modal
 | 
				
			||||||
 | 
					from config import config
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					stub = modal.Stub()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					base_path = "/volumes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Volume names may only contain alphanumeric characters, dashes, periods, and underscores, and must be less than 64 characters in length.
 | 
				
			||||||
 | 
					def is_valid_name(name: str) -> bool:
 | 
				
			||||||
 | 
					    allowed_characters = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._")
 | 
				
			||||||
 | 
					    return 0 < len(name) <= 64 and all(char in allowed_characters for char in name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_volumes(volume_names):
 | 
				
			||||||
 | 
					    path_to_vol = {}
 | 
				
			||||||
 | 
					    vol_to_path = {}
 | 
				
			||||||
 | 
					    for volume_name in volume_names.keys():
 | 
				
			||||||
 | 
					        if not is_valid_name(volume_name):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        modal_volume = modal.Volume.persisted(volume_name)
 | 
				
			||||||
 | 
					        volume_path = create_volume_path(base_path)
 | 
				
			||||||
 | 
					        path_to_vol[volume_path] = modal_volume
 | 
				
			||||||
 | 
					        vol_to_path[volume_name] = volume_path
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					    return (path_to_vol, vol_to_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_volume_path(base_path: str):
 | 
				
			||||||
 | 
					    random_path = str(uuid.uuid4())
 | 
				
			||||||
 | 
					    return os.path.join(base_path, random_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					vol_name_to_links = config["volume_names"]
 | 
				
			||||||
 | 
					(path_to_vol, vol_name_to_path) = create_volumes(vol_name_to_links)
 | 
				
			||||||
 | 
					image = ( 
 | 
				
			||||||
 | 
					   modal.Image.debian_slim().apt_install("wget").pip_install("requests")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(vol_name_to_links)
 | 
				
			||||||
 | 
					print(path_to_vol)
 | 
				
			||||||
 | 
					print(vol_name_to_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@stub.function(volumes=path_to_vol, image=image, timeout=5000, gpu=None)
 | 
				
			||||||
 | 
					def download_model(volume_name, download_url):
 | 
				
			||||||
 | 
					    model_store_path = vol_name_to_path[volume_name]
 | 
				
			||||||
 | 
					    subprocess.run(["wget", download_url, "--content-disposition", "-P", model_store_path])
 | 
				
			||||||
 | 
					    subprocess.run(["ls", "-la", model_store_path])
 | 
				
			||||||
 | 
					    path_to_vol[model_store_path].commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@stub.local_entrypoint()
 | 
				
			||||||
 | 
					def simple_download():
 | 
				
			||||||
 | 
					    print(vol_name_to_links)
 | 
				
			||||||
 | 
					    print([(vol_name, link) for vol_name,link in vol_name_to_links.items()])
 | 
				
			||||||
 | 
					    list(download_model.starmap([(vol_name, link) for vol_name,link in vol_name_to_links.items()]))
 | 
				
			||||||
							
								
								
									
										5
									
								
								builder/modal-builder/src/volume-builder/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								builder/modal-builder/src/volume-builder/config.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					config = {
 | 
				
			||||||
 | 
					    "volume_names": {
 | 
				
			||||||
 | 
					        "eg1": "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose (1).png"
 | 
				
			||||||
 | 
					    }, 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								web/drizzle/0031_common_deathbird.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								web/drizzle/0031_common_deathbird.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					DO $$ BEGIN
 | 
				
			||||||
 | 
					 CREATE TYPE "model_upload_type" AS ENUM('civitai', 'huggingface', 'other');
 | 
				
			||||||
 | 
					EXCEPTION
 | 
				
			||||||
 | 
					 WHEN duplicate_object THEN null;
 | 
				
			||||||
 | 
					END $$;
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					DO $$ BEGIN
 | 
				
			||||||
 | 
					 CREATE TYPE "resource_upload" AS ENUM('started', 'failed', 'succeded');
 | 
				
			||||||
 | 
					EXCEPTION
 | 
				
			||||||
 | 
					 WHEN duplicate_object THEN null;
 | 
				
			||||||
 | 
					END $$;
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS "comfyui_deploy"."checkpoints" (
 | 
				
			||||||
 | 
						"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
 | 
				
			||||||
 | 
						"user_id" text,
 | 
				
			||||||
 | 
						"org_id" text,
 | 
				
			||||||
 | 
						"description" text,
 | 
				
			||||||
 | 
						"checkpoint_volume_id" uuid NOT NULL,
 | 
				
			||||||
 | 
						"model_name" text,
 | 
				
			||||||
 | 
						"civitai_id" text,
 | 
				
			||||||
 | 
						"civitai_version_id" text,
 | 
				
			||||||
 | 
						"civitai_url" text,
 | 
				
			||||||
 | 
						"civitai_download_url" text,
 | 
				
			||||||
 | 
						"civitai_model_response" jsonb,
 | 
				
			||||||
 | 
						"hf_url" text,
 | 
				
			||||||
 | 
						"s3_url" text,
 | 
				
			||||||
 | 
						"client_url" text,
 | 
				
			||||||
 | 
						"is_public" boolean DEFAULT false NOT NULL,
 | 
				
			||||||
 | 
						"status" "resource_upload" DEFAULT 'started' NOT NULL,
 | 
				
			||||||
 | 
						"upload_machine_id" text,
 | 
				
			||||||
 | 
						"upload_type" "model_upload_type" NOT NULL,
 | 
				
			||||||
 | 
						"created_at" timestamp DEFAULT now() NOT NULL,
 | 
				
			||||||
 | 
						"updated_at" timestamp DEFAULT now() NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					CREATE TABLE IF NOT EXISTS "comfyui_deploy"."checkpointVolumeTable" (
 | 
				
			||||||
 | 
						"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
 | 
				
			||||||
 | 
						"user_id" text,
 | 
				
			||||||
 | 
						"org_id" text,
 | 
				
			||||||
 | 
						"volume_name" text NOT NULL,
 | 
				
			||||||
 | 
						"created_at" timestamp DEFAULT now() NOT NULL,
 | 
				
			||||||
 | 
						"updated_at" timestamp DEFAULT now() NOT NULL,
 | 
				
			||||||
 | 
						"disabled" boolean DEFAULT false NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					DO $$ BEGIN
 | 
				
			||||||
 | 
					 ALTER TABLE "comfyui_deploy"."checkpoints" ADD CONSTRAINT "checkpoints_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
 | 
				
			||||||
 | 
					EXCEPTION
 | 
				
			||||||
 | 
					 WHEN duplicate_object THEN null;
 | 
				
			||||||
 | 
					END $$;
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					DO $$ BEGIN
 | 
				
			||||||
 | 
					 ALTER TABLE "comfyui_deploy"."checkpoints" ADD CONSTRAINT "checkpoints_checkpoint_volume_id_workflow_runs_id_fk" FOREIGN KEY ("checkpoint_volume_id") REFERENCES "comfyui_deploy"."workflow_runs"("id") ON DELETE cascade ON UPDATE no action;
 | 
				
			||||||
 | 
					EXCEPTION
 | 
				
			||||||
 | 
					 WHEN duplicate_object THEN null;
 | 
				
			||||||
 | 
					END $$;
 | 
				
			||||||
 | 
					--> statement-breakpoint
 | 
				
			||||||
 | 
					DO $$ BEGIN
 | 
				
			||||||
 | 
					 ALTER TABLE "comfyui_deploy"."checkpointVolumeTable" ADD CONSTRAINT "checkpointVolumeTable_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "comfyui_deploy"."users"("id") ON DELETE no action ON UPDATE no action;
 | 
				
			||||||
 | 
					EXCEPTION
 | 
				
			||||||
 | 
					 WHEN duplicate_object THEN null;
 | 
				
			||||||
 | 
					END $$;
 | 
				
			||||||
							
								
								
									
										1004
									
								
								web/drizzle/meta/0031_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1004
									
								
								web/drizzle/meta/0031_snapshot.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -218,6 +218,13 @@
 | 
				
			|||||||
      "when": 1705716303820,
 | 
					      "when": 1705716303820,
 | 
				
			||||||
      "tag": "0030_kind_doorman",
 | 
					      "tag": "0030_kind_doorman",
 | 
				
			||||||
      "breakpoints": true
 | 
					      "breakpoints": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "idx": 31,
 | 
				
			||||||
 | 
					      "version": "5",
 | 
				
			||||||
 | 
					      "when": 1705963548821,
 | 
				
			||||||
 | 
					      "tag": "0031_common_deathbird",
 | 
				
			||||||
 | 
					      "breakpoints": true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								web/src/app/(app)/api/volume-updated/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								web/src/app/(app)/api/volume-updated/route.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import { parseDataSafe } from "../../../../lib/parseDataSafe";
 | 
				
			||||||
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
 | 
					import { checkpointTable, machinesTable } from "@/db/schema";
 | 
				
			||||||
 | 
					import { eq } from "drizzle-orm";
 | 
				
			||||||
 | 
					import { NextResponse } from "next/server";
 | 
				
			||||||
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Request = z.object({
 | 
				
			||||||
 | 
					  machine_id: z.string(),
 | 
				
			||||||
 | 
					  endpoint: z.string().optional(),
 | 
				
			||||||
 | 
					  build_log: z.string().optional(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function POST(request: Request) {
 | 
				
			||||||
 | 
					  const [data, error] = await parseDataSafe(Request, request);
 | 
				
			||||||
 | 
					  if (!data || error) return error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // console.log(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { machine_id, endpoint, build_log } = data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (endpoint) {
 | 
				
			||||||
 | 
					    await db
 | 
				
			||||||
 | 
					      .update(checkpointTable)
 | 
				
			||||||
 | 
					      .set({
 | 
				
			||||||
 | 
					        // status: "ready",
 | 
				
			||||||
 | 
					        // endpoint: endpoint,
 | 
				
			||||||
 | 
					        // build_log: build_log,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .where(eq(machinesTable.id, machine_id));
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // console.log(data);
 | 
				
			||||||
 | 
					    await db
 | 
				
			||||||
 | 
					      .update(machinesTable)
 | 
				
			||||||
 | 
					      .set({
 | 
				
			||||||
 | 
					        // status: "error",
 | 
				
			||||||
 | 
					        // build_log: build_log,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .where(eq(machinesTable.id, machine_id));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return NextResponse.json(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      message: "success",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      status: 200,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								web/src/app/(app)/storage/loading.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/src/app/(app)/storage/loading.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					import { usePathname } from "next/navigation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Loading() {
 | 
				
			||||||
 | 
					  const pathName = usePathname();
 | 
				
			||||||
 | 
					  return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								web/src/app/(app)/storage/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/src/app/(app)/storage/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import { setInitialUserData } from "../../../lib/setInitialUserData";
 | 
				
			||||||
 | 
					import { auth } from "@clerk/nextjs";
 | 
				
			||||||
 | 
					import { clerkClient } from "@clerk/nextjs/server";
 | 
				
			||||||
 | 
					import { CheckpointList } from "@/components/CheckpointList"
 | 
				
			||||||
 | 
					import { getAllUserCheckpoints } from "@/server/getAllUserCheckpoints";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Page() {
 | 
				
			||||||
 | 
					  return <CheckpointListServer />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function CheckpointListServer() {
 | 
				
			||||||
 | 
					  const { userId } = auth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!userId) {
 | 
				
			||||||
 | 
					    return <div>No auth</div>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const user = await clerkClient.users.getUser(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!user) {
 | 
				
			||||||
 | 
					    await setInitialUserData(userId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const checkpoints  = await getAllUserCheckpoints()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!checkpoints) {
 | 
				
			||||||
 | 
					    return <div>No checkpoints found</div>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="w-full">
 | 
				
			||||||
 | 
					      <CheckpointList data={checkpoints}/>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										315
									
								
								web/src/components/CheckpointList.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								web/src/components/CheckpointList.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,315 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { getRelativeTime } from "../lib/getRelativeTime";
 | 
				
			||||||
 | 
					import { Badge } from "@/components/ui/badge";
 | 
				
			||||||
 | 
					import { Button } from "@/components/ui/button";
 | 
				
			||||||
 | 
					import { Checkbox } from "@/components/ui/checkbox";
 | 
				
			||||||
 | 
					import { InsertModal, UpdateModal } from "./InsertModal";
 | 
				
			||||||
 | 
					import { Input } from "@/components/ui/input";
 | 
				
			||||||
 | 
					import { ScrollArea } from "@/components/ui/scroll-area";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Table,
 | 
				
			||||||
 | 
					  TableBody,
 | 
				
			||||||
 | 
					  TableCell,
 | 
				
			||||||
 | 
					  TableHead,
 | 
				
			||||||
 | 
					  TableHeader,
 | 
				
			||||||
 | 
					  TableRow,
 | 
				
			||||||
 | 
					} from "@/components/ui/table";
 | 
				
			||||||
 | 
					import type { getAllUserCheckpoints } from "@/server/getAllUserCheckpoints";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ColumnDef,
 | 
				
			||||||
 | 
					  ColumnFiltersState,
 | 
				
			||||||
 | 
					  SortingState,
 | 
				
			||||||
 | 
					  VisibilityState,
 | 
				
			||||||
 | 
					} from "@tanstack/react-table";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  flexRender,
 | 
				
			||||||
 | 
					  getCoreRowModel,
 | 
				
			||||||
 | 
					  getFilteredRowModel,
 | 
				
			||||||
 | 
					  getPaginationRowModel,
 | 
				
			||||||
 | 
					  getSortedRowModel,
 | 
				
			||||||
 | 
					  useReactTable,
 | 
				
			||||||
 | 
					} from "@tanstack/react-table";
 | 
				
			||||||
 | 
					import { ArrowUpDown, MoreHorizontal } from "lucide-react";
 | 
				
			||||||
 | 
					import * as React from "react";
 | 
				
			||||||
 | 
					import { insertCivitaiCheckpointSchema } from "@/db/schema";
 | 
				
			||||||
 | 
					import { addCivitaiCheckpoint } from "@/server/curdCheckpoint";
 | 
				
			||||||
 | 
					import { addCivitaiCheckpointSchema } from "@/server/addCheckpointSchema";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type CheckpointItemList = NonNullable<
 | 
				
			||||||
 | 
					  Awaited<ReturnType<typeof getAllUserCheckpoints>>
 | 
				
			||||||
 | 
					>[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const columns: ColumnDef<CheckpointItemList>[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    accessorKey: "id",
 | 
				
			||||||
 | 
					    id: "select",
 | 
				
			||||||
 | 
					    header: ({ table }) => (
 | 
				
			||||||
 | 
					      <Checkbox
 | 
				
			||||||
 | 
					        checked={table.getIsAllPageRowsSelected() ||
 | 
				
			||||||
 | 
					          (table.getIsSomePageRowsSelected() && "indeterminate")}
 | 
				
			||||||
 | 
					        onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
 | 
				
			||||||
 | 
					        aria-label="Select all"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    cell: ({ row }) => (
 | 
				
			||||||
 | 
					      <Checkbox
 | 
				
			||||||
 | 
					        checked={row.getIsSelected()}
 | 
				
			||||||
 | 
					        onCheckedChange={(value) => row.toggleSelected(!!value)}
 | 
				
			||||||
 | 
					        aria-label="Select row"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    enableSorting: false,
 | 
				
			||||||
 | 
					    enableHiding: false,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    accessorKey: "name",
 | 
				
			||||||
 | 
					    header: ({ column }) => {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          className="flex items-center hover:underline"
 | 
				
			||||||
 | 
					          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Name
 | 
				
			||||||
 | 
					          <ArrowUpDown className="ml-2 h-4 w-4" />
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    cell: ({ row }) => {
 | 
				
			||||||
 | 
					      const checkpoint = row.original;
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          className="hover:underline flex gap-2"
 | 
				
			||||||
 | 
					          href={`/storage/${checkpoint.id}`} // TODO
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <span className="truncate max-w-[200px]">{row.original.model_name}</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <Badge variant="default">{}</Badge>
 | 
				
			||||||
 | 
					          {checkpoint.is_public
 | 
				
			||||||
 | 
					            ? <Badge variant="success">Public</Badge>
 | 
				
			||||||
 | 
					            : <Badge variant="teal">Private</Badge>}
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    accessorKey: "creator",
 | 
				
			||||||
 | 
					    header: ({ column }) => {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          className="flex items-center hover:underline"
 | 
				
			||||||
 | 
					          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Creator
 | 
				
			||||||
 | 
					          <ArrowUpDown className="ml-2 h-4 w-4" />
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    cell: ({ row }) => {
 | 
				
			||||||
 | 
					      // return <Badge variant="cyan">{row?.original?.user?.name ? row.original.user.name : "Public"}</Badge>;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    accessorKey: "date",
 | 
				
			||||||
 | 
					    sortingFn: "datetime",
 | 
				
			||||||
 | 
					    enableSorting: true,
 | 
				
			||||||
 | 
					    header: ({ column }) => {
 | 
				
			||||||
 | 
					      return (
 | 
				
			||||||
 | 
					        <button
 | 
				
			||||||
 | 
					          className="w-full flex items-center justify-end hover:underline truncate"
 | 
				
			||||||
 | 
					          // variant="ghost"
 | 
				
			||||||
 | 
					          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Update Date
 | 
				
			||||||
 | 
					          <ArrowUpDown className="ml-2 h-4 w-4" />
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    cell: ({ row }) => (
 | 
				
			||||||
 | 
					      <div className="w-full capitalize text-right truncate">
 | 
				
			||||||
 | 
					        {getRelativeTime(row.original.updated_at)}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // {
 | 
				
			||||||
 | 
					  //   id: "actions",
 | 
				
			||||||
 | 
					  //   enableHiding: false,
 | 
				
			||||||
 | 
					  //   cell: ({ row }) => {
 | 
				
			||||||
 | 
					  //     const checkpoint = row.original;
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  //     return (
 | 
				
			||||||
 | 
					  //       <DropdownMenu>
 | 
				
			||||||
 | 
					  //         <DropdownMenuTrigger asChild>
 | 
				
			||||||
 | 
					  //           <Button variant="ghost" className="h-8 w-8 p-0">
 | 
				
			||||||
 | 
					  //             <span className="sr-only">Open menu</span>
 | 
				
			||||||
 | 
					  //             <MoreHorizontal className="h-4 w-4" />
 | 
				
			||||||
 | 
					  //           </Button>
 | 
				
			||||||
 | 
					  //         </DropdownMenuTrigger>
 | 
				
			||||||
 | 
					  //         <DropdownMenuContent align="end">
 | 
				
			||||||
 | 
					  //           <DropdownMenuLabel>Actions</DropdownMenuLabel>
 | 
				
			||||||
 | 
					  //           <DropdownMenuItem
 | 
				
			||||||
 | 
					  //             className="text-destructive"
 | 
				
			||||||
 | 
					  //             onClick={() => {
 | 
				
			||||||
 | 
					  //               deleteWorkflow(checkpoint.id);
 | 
				
			||||||
 | 
					  //             }}
 | 
				
			||||||
 | 
					  //           >
 | 
				
			||||||
 | 
					  //             Delete Workflow
 | 
				
			||||||
 | 
					  //           </DropdownMenuItem>
 | 
				
			||||||
 | 
					  //         </DropdownMenuContent>
 | 
				
			||||||
 | 
					  //       </DropdownMenu>
 | 
				
			||||||
 | 
					  //     );
 | 
				
			||||||
 | 
					  //   },
 | 
				
			||||||
 | 
					  // },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function CheckpointList({ data }: { data: CheckpointItemList[] }) {
 | 
				
			||||||
 | 
					  const [sorting, setSorting] = React.useState<SortingState>([]);
 | 
				
			||||||
 | 
					  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
 | 
				
			||||||
 | 
					    [],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [columnVisibility, setColumnVisibility] = React.useState<
 | 
				
			||||||
 | 
					    VisibilityState
 | 
				
			||||||
 | 
					  >({});
 | 
				
			||||||
 | 
					  const [rowSelection, setRowSelection] = React.useState({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const table = useReactTable({
 | 
				
			||||||
 | 
					    data,
 | 
				
			||||||
 | 
					    columns,
 | 
				
			||||||
 | 
					    onSortingChange: setSorting,
 | 
				
			||||||
 | 
					    onColumnFiltersChange: setColumnFilters,
 | 
				
			||||||
 | 
					    getCoreRowModel: getCoreRowModel(),
 | 
				
			||||||
 | 
					    getPaginationRowModel: getPaginationRowModel(),
 | 
				
			||||||
 | 
					    getSortedRowModel: getSortedRowModel(),
 | 
				
			||||||
 | 
					    getFilteredRowModel: getFilteredRowModel(),
 | 
				
			||||||
 | 
					    onColumnVisibilityChange: setColumnVisibility,
 | 
				
			||||||
 | 
					    onRowSelectionChange: setRowSelection,
 | 
				
			||||||
 | 
					    state: {
 | 
				
			||||||
 | 
					      sorting,
 | 
				
			||||||
 | 
					      columnFilters,
 | 
				
			||||||
 | 
					      columnVisibility,
 | 
				
			||||||
 | 
					      rowSelection,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="grid grid-rows-[auto,1fr,auto] h-full">
 | 
				
			||||||
 | 
					      <div className="flex flex-row w-full items-center py-4">
 | 
				
			||||||
 | 
					        <Input
 | 
				
			||||||
 | 
					          placeholder="Filter workflows..."
 | 
				
			||||||
 | 
					          value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
 | 
				
			||||||
 | 
					          onChange={(event) =>
 | 
				
			||||||
 | 
					            table.getColumn("name")?.setFilterValue(event.target.value)}
 | 
				
			||||||
 | 
					          className="max-w-sm"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <div className="ml-auto flex gap-2">
 | 
				
			||||||
 | 
					          <InsertModal
 | 
				
			||||||
 | 
					            dialogClassName="sm:max-w-[600px]"
 | 
				
			||||||
 | 
					            disabled={
 | 
				
			||||||
 | 
					              false
 | 
				
			||||||
 | 
					              // TODO: limitations based on plan
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            tooltip={"Add models using their civitai url!"}
 | 
				
			||||||
 | 
					            title="Civitai Checkpoint"
 | 
				
			||||||
 | 
					            description="Pick a model from civitai"
 | 
				
			||||||
 | 
					            serverAction={addCivitaiCheckpoint}
 | 
				
			||||||
 | 
					            formSchema={addCivitaiCheckpointSchema}
 | 
				
			||||||
 | 
					            fieldConfig={{
 | 
				
			||||||
 | 
					              civitai_url: {
 | 
				
			||||||
 | 
					                fieldType: "fallback",
 | 
				
			||||||
 | 
					                // fieldType: "fallback",
 | 
				
			||||||
 | 
					                inputProps: { required: true },
 | 
				
			||||||
 | 
					                description: (
 | 
				
			||||||
 | 
					                  <>
 | 
				
			||||||
 | 
					                    Pick a checkpoint from{" "}
 | 
				
			||||||
 | 
					                    <a
 | 
				
			||||||
 | 
					                      href="https://www.civitai.com/models"
 | 
				
			||||||
 | 
					                      target="_blank"
 | 
				
			||||||
 | 
					                      className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      civitai.com
 | 
				
			||||||
 | 
					                    </a>{" "}
 | 
				
			||||||
 | 
					                    and place it's url here
 | 
				
			||||||
 | 
					                  </>
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <ScrollArea className="h-full w-full rounded-md border">
 | 
				
			||||||
 | 
					        <Table>
 | 
				
			||||||
 | 
					          <TableHeader className="bg-background top-0 sticky">
 | 
				
			||||||
 | 
					            {table.getHeaderGroups().map((headerGroup) => (
 | 
				
			||||||
 | 
					              <TableRow key={headerGroup.id}>
 | 
				
			||||||
 | 
					                {headerGroup.headers.map((header) => {
 | 
				
			||||||
 | 
					                  return (
 | 
				
			||||||
 | 
					                    <TableHead key={header.id}>
 | 
				
			||||||
 | 
					                      {header.isPlaceholder ? null : flexRender(
 | 
				
			||||||
 | 
					                        header.column.columnDef.header,
 | 
				
			||||||
 | 
					                        header.getContext(),
 | 
				
			||||||
 | 
					                      )}
 | 
				
			||||||
 | 
					                    </TableHead>
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                })}
 | 
				
			||||||
 | 
					              </TableRow>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					          </TableHeader>
 | 
				
			||||||
 | 
					          <TableBody>
 | 
				
			||||||
 | 
					            {table.getRowModel().rows?.length
 | 
				
			||||||
 | 
					              ? (
 | 
				
			||||||
 | 
					                table.getRowModel().rows.map((row) => (
 | 
				
			||||||
 | 
					                  <TableRow
 | 
				
			||||||
 | 
					                    key={row.id}
 | 
				
			||||||
 | 
					                    data-state={row.getIsSelected() && "selected"}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    {row.getVisibleCells().map((cell) => (
 | 
				
			||||||
 | 
					                      <TableCell key={cell.id}>
 | 
				
			||||||
 | 
					                        {flexRender(
 | 
				
			||||||
 | 
					                          cell.column.columnDef.cell,
 | 
				
			||||||
 | 
					                          cell.getContext(),
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
 | 
					                      </TableCell>
 | 
				
			||||||
 | 
					                    ))}
 | 
				
			||||||
 | 
					                  </TableRow>
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					              : (
 | 
				
			||||||
 | 
					                <TableRow>
 | 
				
			||||||
 | 
					                  <TableCell
 | 
				
			||||||
 | 
					                    colSpan={columns.length}
 | 
				
			||||||
 | 
					                    className="h-24 text-center"
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    No results.
 | 
				
			||||||
 | 
					                  </TableCell>
 | 
				
			||||||
 | 
					                </TableRow>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					          </TableBody>
 | 
				
			||||||
 | 
					        </Table>
 | 
				
			||||||
 | 
					      </ScrollArea>
 | 
				
			||||||
 | 
					      <div className="flex flex-row items-center justify-end space-x-2 py-4">
 | 
				
			||||||
 | 
					        <div className="flex-1 text-sm text-muted-foreground">
 | 
				
			||||||
 | 
					          {table.getFilteredSelectedRowModel().rows.length} of{" "}
 | 
				
			||||||
 | 
					          {table.getFilteredRowModel().rows.length} row(s) selected.
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div className="space-x-2">
 | 
				
			||||||
 | 
					          <Button
 | 
				
			||||||
 | 
					            variant="outline"
 | 
				
			||||||
 | 
					            size="sm"
 | 
				
			||||||
 | 
					            onClick={() => table.previousPage()}
 | 
				
			||||||
 | 
					            disabled={!table.getCanPreviousPage()}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            Previous
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					          <Button
 | 
				
			||||||
 | 
					            variant="outline"
 | 
				
			||||||
 | 
					            size="sm"
 | 
				
			||||||
 | 
					            onClick={() => table.nextPage()}
 | 
				
			||||||
 | 
					            disabled={!table.getCanNextPage()}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            Next
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -34,6 +34,10 @@ export function NavbarMenu({ className }: { className?: string }) {
 | 
				
			|||||||
      name: "API Keys",
 | 
					      name: "API Keys",
 | 
				
			||||||
      path: "/api-keys",
 | 
					      path: "/api-keys",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      name: "Storage",
 | 
				
			||||||
 | 
					      path: "/storage",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@ -42,9 +46,9 @@ export function NavbarMenu({ className }: { className?: string }) {
 | 
				
			|||||||
      {isDesktop && (
 | 
					      {isDesktop && (
 | 
				
			||||||
        <Tabs
 | 
					        <Tabs
 | 
				
			||||||
          defaultValue={pathname}
 | 
					          defaultValue={pathname}
 | 
				
			||||||
          className="w-[300px] flex pointer-events-auto"
 | 
					          className="w-[400px] flex pointer-events-auto"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <TabsList className="grid w-full grid-cols-3">
 | 
					          <TabsList className="grid w-full grid-cols-4">
 | 
				
			||||||
            {pages.map((page) => (
 | 
					            {pages.map((page) => (
 | 
				
			||||||
              <TabsTrigger
 | 
					              <TabsTrigger
 | 
				
			||||||
                key={page.name}
 | 
					                key={page.name}
 | 
				
			||||||
 | 
				
			|||||||
@ -42,105 +42,105 @@ const Model = z.object({
 | 
				
			|||||||
  url: z.string(),
 | 
					  url: z.string(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CivitalModelSchema = z.object({
 | 
					export const CivitaiModel = z.object({
 | 
				
			||||||
  items: z.array(
 | 
					  id: z.number(),
 | 
				
			||||||
 | 
					  name: z.string(),
 | 
				
			||||||
 | 
					  description: z.string(),
 | 
				
			||||||
 | 
					  type: z.string(),
 | 
				
			||||||
 | 
					  // poi: z.boolean(),
 | 
				
			||||||
 | 
					  // nsfw: z.boolean(),
 | 
				
			||||||
 | 
					  // allowNoCredit: z.boolean(),
 | 
				
			||||||
 | 
					  // allowCommercialUse: z.string(),
 | 
				
			||||||
 | 
					  // allowDerivatives: z.boolean(),
 | 
				
			||||||
 | 
					  // allowDifferentLicense: z.boolean(),
 | 
				
			||||||
 | 
					  // stats: z.object({
 | 
				
			||||||
 | 
					  //   downloadCount: z.number(),
 | 
				
			||||||
 | 
					  //   favoriteCount: z.number(),
 | 
				
			||||||
 | 
					  //   commentCount: z.number(),
 | 
				
			||||||
 | 
					  //   ratingCount: z.number(),
 | 
				
			||||||
 | 
					  //   rating: z.number(),
 | 
				
			||||||
 | 
					  //   tippedAmountCount: z.number(),
 | 
				
			||||||
 | 
					  // }),
 | 
				
			||||||
 | 
					  creator: z
 | 
				
			||||||
 | 
					    .object({
 | 
				
			||||||
 | 
					      username: z.string().nullable(),
 | 
				
			||||||
 | 
					      image: z.string().nullable().default(null),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .nullable(),
 | 
				
			||||||
 | 
					  tags: z.array(z.string()),
 | 
				
			||||||
 | 
					  modelVersions: z.array(
 | 
				
			||||||
    z.object({
 | 
					    z.object({
 | 
				
			||||||
      id: z.number(),
 | 
					      id: z.number(),
 | 
				
			||||||
 | 
					      modelId: z.number(),
 | 
				
			||||||
      name: z.string(),
 | 
					      name: z.string(),
 | 
				
			||||||
      description: z.string(),
 | 
					      createdAt: z.string(),
 | 
				
			||||||
      type: z.string(),
 | 
					      updatedAt: z.string(),
 | 
				
			||||||
      // poi: z.boolean(),
 | 
					      status: z.string(),
 | 
				
			||||||
      // nsfw: z.boolean(),
 | 
					      publishedAt: z.string(),
 | 
				
			||||||
      // allowNoCredit: z.boolean(),
 | 
					      trainedWords: z.array(z.unknown()),
 | 
				
			||||||
      // allowCommercialUse: z.string(),
 | 
					      trainingStatus: z.string().nullable(),
 | 
				
			||||||
      // allowDerivatives: z.boolean(),
 | 
					      trainingDetails: z.string().nullable(),
 | 
				
			||||||
      // allowDifferentLicense: z.boolean(),
 | 
					      baseModel: z.string(),
 | 
				
			||||||
      // stats: z.object({
 | 
					      baseModelType: z.string().nullable(),
 | 
				
			||||||
      //   downloadCount: z.number(),
 | 
					      earlyAccessTimeFrame: z.number(),
 | 
				
			||||||
      //   favoriteCount: z.number(),
 | 
					      description: z.string().nullable(),
 | 
				
			||||||
      //   commentCount: z.number(),
 | 
					      vaeId: z.number().nullable(),
 | 
				
			||||||
      //   ratingCount: z.number(),
 | 
					      stats: z.object({
 | 
				
			||||||
      //   rating: z.number(),
 | 
					        downloadCount: z.number(),
 | 
				
			||||||
      //   tippedAmountCount: z.number(),
 | 
					        ratingCount: z.number(),
 | 
				
			||||||
      // }),
 | 
					        rating: z.number(),
 | 
				
			||||||
      creator: z
 | 
					      }),
 | 
				
			||||||
        .object({
 | 
					      files: z.array(
 | 
				
			||||||
          username: z.string().nullable(),
 | 
					 | 
				
			||||||
          image: z.string().nullable().default(null),
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .nullable(),
 | 
					 | 
				
			||||||
      tags: z.array(z.string()),
 | 
					 | 
				
			||||||
      modelVersions: z.array(
 | 
					 | 
				
			||||||
        z.object({
 | 
					        z.object({
 | 
				
			||||||
          id: z.number(),
 | 
					          id: z.number(),
 | 
				
			||||||
          modelId: z.number(),
 | 
					          sizeKB: z.number(),
 | 
				
			||||||
          name: z.string(),
 | 
					          name: z.string(),
 | 
				
			||||||
          createdAt: z.string(),
 | 
					          type: z.string(),
 | 
				
			||||||
          updatedAt: z.string(),
 | 
					          // metadata: z.object({
 | 
				
			||||||
          status: z.string(),
 | 
					          //   fp: z.string().nullable().optional(),
 | 
				
			||||||
          publishedAt: z.string(),
 | 
					          //   size: z.string().nullable().optional(),
 | 
				
			||||||
          trainedWords: z.array(z.unknown()),
 | 
					          //   format: z.string().nullable().optional(),
 | 
				
			||||||
          trainingStatus: z.string().nullable(),
 | 
					          // }),
 | 
				
			||||||
          trainingDetails: z.string().nullable(),
 | 
					          // pickleScanResult: z.string(),
 | 
				
			||||||
          baseModel: z.string(),
 | 
					          // pickleScanMessage: z.string(),
 | 
				
			||||||
          baseModelType: z.string().nullable(),
 | 
					          // virusScanResult: z.string(),
 | 
				
			||||||
          earlyAccessTimeFrame: z.number(),
 | 
					          // virusScanMessage: z.string().nullable(),
 | 
				
			||||||
          description: z.string().nullable(),
 | 
					          // scannedAt: z.string(),
 | 
				
			||||||
          vaeId: z.number().nullable(),
 | 
					          // hashes: z.object({
 | 
				
			||||||
          stats: z.object({
 | 
					          //   AutoV1: z.string().nullable().optional(),
 | 
				
			||||||
            downloadCount: z.number(),
 | 
					          //   AutoV2: z.string().nullable().optional(),
 | 
				
			||||||
            ratingCount: z.number(),
 | 
					          //   SHA256: z.string().nullable().optional(),
 | 
				
			||||||
            rating: z.number(),
 | 
					          //   CRC32: z.string().nullable().optional(),
 | 
				
			||||||
          }),
 | 
					          //   BLAKE3: z.string().nullable().optional(),
 | 
				
			||||||
          files: z.array(
 | 
					          // }),
 | 
				
			||||||
            z.object({
 | 
					 | 
				
			||||||
              id: z.number(),
 | 
					 | 
				
			||||||
              sizeKB: z.number(),
 | 
					 | 
				
			||||||
              name: z.string(),
 | 
					 | 
				
			||||||
              type: z.string(),
 | 
					 | 
				
			||||||
              // metadata: z.object({
 | 
					 | 
				
			||||||
              //   fp: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              //   size: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              //   format: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              // }),
 | 
					 | 
				
			||||||
              // pickleScanResult: z.string(),
 | 
					 | 
				
			||||||
              // pickleScanMessage: z.string(),
 | 
					 | 
				
			||||||
              // virusScanResult: z.string(),
 | 
					 | 
				
			||||||
              // virusScanMessage: z.string().nullable(),
 | 
					 | 
				
			||||||
              // scannedAt: z.string(),
 | 
					 | 
				
			||||||
              // hashes: z.object({
 | 
					 | 
				
			||||||
              //   AutoV1: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              //   AutoV2: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              //   SHA256: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              //   CRC32: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              //   BLAKE3: z.string().nullable().optional(),
 | 
					 | 
				
			||||||
              // }),
 | 
					 | 
				
			||||||
              downloadUrl: z.string(),
 | 
					 | 
				
			||||||
              // primary: z.boolean().default(false),
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          images: z.array(
 | 
					 | 
				
			||||||
            z.object({
 | 
					 | 
				
			||||||
              id: z.number(),
 | 
					 | 
				
			||||||
              url: z.string(),
 | 
					 | 
				
			||||||
              nsfw: z.string(),
 | 
					 | 
				
			||||||
              width: z.number(),
 | 
					 | 
				
			||||||
              height: z.number(),
 | 
					 | 
				
			||||||
              hash: z.string(),
 | 
					 | 
				
			||||||
              type: z.string(),
 | 
					 | 
				
			||||||
              metadata: z.object({
 | 
					 | 
				
			||||||
                hash: z.string(),
 | 
					 | 
				
			||||||
                width: z.number(),
 | 
					 | 
				
			||||||
                height: z.number(),
 | 
					 | 
				
			||||||
              }),
 | 
					 | 
				
			||||||
              meta: z.any(),
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          downloadUrl: z.string(),
 | 
					          downloadUrl: z.string(),
 | 
				
			||||||
        })
 | 
					          // primary: z.boolean().default(false),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    })
 | 
					      images: z.array(
 | 
				
			||||||
 | 
					        z.object({
 | 
				
			||||||
 | 
					          id: z.number(),
 | 
				
			||||||
 | 
					          url: z.string(),
 | 
				
			||||||
 | 
					          nsfw: z.string(),
 | 
				
			||||||
 | 
					          width: z.number(),
 | 
				
			||||||
 | 
					          height: z.number(),
 | 
				
			||||||
 | 
					          hash: z.string(),
 | 
				
			||||||
 | 
					          type: z.string(),
 | 
				
			||||||
 | 
					          metadata: z.object({
 | 
				
			||||||
 | 
					            hash: z.string(),
 | 
				
			||||||
 | 
					            width: z.number(),
 | 
				
			||||||
 | 
					            height: z.number(),
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					          meta: z.any(),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      downloadUrl: z.string(),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
  ),
 | 
					  ),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CivitalModelSchema = z.object({
 | 
				
			||||||
 | 
					  items: z.array(CivitaiModel),
 | 
				
			||||||
  metadata: z.object({
 | 
					  metadata: z.object({
 | 
				
			||||||
    totalItems: z.number(),
 | 
					    totalItems: z.number(),
 | 
				
			||||||
    currentPage: z.number(),
 | 
					    currentPage: z.number(),
 | 
				
			||||||
@ -197,7 +197,7 @@ function mapType(type: string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function mapModelsList(
 | 
					function mapModelsList(
 | 
				
			||||||
  models: z.infer<typeof CivitalModelSchema>
 | 
					  models: z.infer<typeof CivitalModelSchema>,
 | 
				
			||||||
): z.infer<typeof ModelListWrapper> {
 | 
					): z.infer<typeof ModelListWrapper> {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    models: models.items.flatMap((item) => {
 | 
					    models: models.items.flatMap((item) => {
 | 
				
			||||||
@ -241,8 +241,9 @@ function getUrl(search?: string) {
 | 
				
			|||||||
export function CivitaiModelRegistry({
 | 
					export function CivitaiModelRegistry({
 | 
				
			||||||
  field,
 | 
					  field,
 | 
				
			||||||
}: Pick<AutoFormInputComponentProps, "field">) {
 | 
					}: Pick<AutoFormInputComponentProps, "field">) {
 | 
				
			||||||
  const [modelList, setModelList] =
 | 
					  const [modelList, setModelList] = React.useState<
 | 
				
			||||||
    React.useState<z.infer<typeof ModelListWrapper>>();
 | 
					    z.infer<typeof ModelListWrapper>
 | 
				
			||||||
 | 
					  >();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [loading, setLoading] = React.useState(false);
 | 
					  const [loading, setLoading] = React.useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -301,8 +302,9 @@ export function CivitaiModelRegistry({
 | 
				
			|||||||
export function ComfyUIManagerModelRegistry({
 | 
					export function ComfyUIManagerModelRegistry({
 | 
				
			||||||
  field,
 | 
					  field,
 | 
				
			||||||
}: Pick<AutoFormInputComponentProps, "field">) {
 | 
					}: Pick<AutoFormInputComponentProps, "field">) {
 | 
				
			||||||
  const [modelList, setModelList] =
 | 
					  const [modelList, setModelList] = React.useState<
 | 
				
			||||||
    React.useState<z.infer<typeof ModelListWrapper>>();
 | 
					    z.infer<typeof ModelListWrapper>
 | 
				
			||||||
 | 
					  >();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  React.useEffect(() => {
 | 
					  React.useEffect(() => {
 | 
				
			||||||
    const controller = new AbortController();
 | 
					    const controller = new AbortController();
 | 
				
			||||||
@ -310,7 +312,7 @@ export function ComfyUIManagerModelRegistry({
 | 
				
			|||||||
      "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
 | 
					      "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        signal: controller.signal,
 | 
					        signal: controller.signal,
 | 
				
			||||||
      }
 | 
					      },
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
      .then((x) => x.json())
 | 
					      .then((x) => x.json())
 | 
				
			||||||
      .then((a) => {
 | 
					      .then((a) => {
 | 
				
			||||||
@ -353,14 +355,14 @@ export function ModelSelector({
 | 
				
			|||||||
    if (
 | 
					    if (
 | 
				
			||||||
      prevSelectedModels.some(
 | 
					      prevSelectedModels.some(
 | 
				
			||||||
        (selectedModel) =>
 | 
					        (selectedModel) =>
 | 
				
			||||||
          selectedModel.url + selectedModel.name === model.url + model.name
 | 
					          selectedModel.url + selectedModel.name === model.url + model.name,
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      field.onChange(
 | 
					      field.onChange(
 | 
				
			||||||
        prevSelectedModels.filter(
 | 
					        prevSelectedModels.filter(
 | 
				
			||||||
          (selectedModel) =>
 | 
					          (selectedModel) =>
 | 
				
			||||||
            selectedModel.url + selectedModel.name !== model.url + model.name
 | 
					            selectedModel.url + selectedModel.name !== model.url + model.name,
 | 
				
			||||||
        )
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      field.onChange([...prevSelectedModels, model]);
 | 
					      field.onChange([...prevSelectedModels, model]);
 | 
				
			||||||
@ -408,10 +410,10 @@ export function ModelSelector({
 | 
				
			|||||||
                      className={cn(
 | 
					                      className={cn(
 | 
				
			||||||
                        "ml-auto h-4 w-4",
 | 
					                        "ml-auto h-4 w-4",
 | 
				
			||||||
                        value.some(
 | 
					                        value.some(
 | 
				
			||||||
                          (selectedModel) => selectedModel.url === model.url
 | 
					                            (selectedModel) => selectedModel.url === model.url,
 | 
				
			||||||
                        )
 | 
					                          )
 | 
				
			||||||
                          ? "opacity-100"
 | 
					                          ? "opacity-100"
 | 
				
			||||||
                          : "opacity-0"
 | 
					                          : "opacity-0",
 | 
				
			||||||
                      )}
 | 
					                      )}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                  </CommandItem>
 | 
					                  </CommandItem>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										89
									
								
								web/src/components/custom-form/checkpoint-input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								web/src/components/custom-form/checkpoint-input.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
 | 
				
			||||||
 | 
					import { FormControl, FormItem, FormLabel } from "../ui/form";
 | 
				
			||||||
 | 
					import { LoadingIcon } from "@/components/LoadingIcon";
 | 
				
			||||||
 | 
					import * as React from "react";
 | 
				
			||||||
 | 
					import AutoFormInput from "../ui/auto-form/fields/input";
 | 
				
			||||||
 | 
					import { useDebouncedCallback } from "use-debounce";
 | 
				
			||||||
 | 
					import { CivitaiModel } from "./ModelPickerView";
 | 
				
			||||||
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					import { insertCivitaiCheckpointSchema } from "@/db/schema";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getUrl(civitai_url: string) {
 | 
				
			||||||
 | 
					  // expect to be a URL to be https://civitai.com/models/36520
 | 
				
			||||||
 | 
					  // possiblity with slugged name and query-param modelVersionId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const baseUrl = "https://civitai.com/api/v1/models/";
 | 
				
			||||||
 | 
					  const url = new URL(civitai_url);
 | 
				
			||||||
 | 
					  const pathSegments = url.pathname.split("/");
 | 
				
			||||||
 | 
					  const modelId = pathSegments[pathSegments.indexOf("models") + 1];
 | 
				
			||||||
 | 
					  const modelVersionId = url.searchParams.get("modelVersionId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { url: baseUrl + modelId, modelVersionId };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function AutoFormCheckpointInput(
 | 
				
			||||||
 | 
					  props: AutoFormInputComponentProps,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const [loading, setLoading] = React.useState(false);
 | 
				
			||||||
 | 
					  const [modelRes, setModelRes] = React.useState<
 | 
				
			||||||
 | 
					    z.infer<typeof CivitaiModel>
 | 
				
			||||||
 | 
					  >();
 | 
				
			||||||
 | 
					  const [modelVersionid, setModelVersionId] = React.useState<string | null>();
 | 
				
			||||||
 | 
					  const { label, isRequired, fieldProps, zodItem, fieldConfigItem } = props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSearch = useDebouncedCallback((search) => {
 | 
				
			||||||
 | 
					    const validationResult = insertCivitaiCheckpointSchema.shape.civitai_url
 | 
				
			||||||
 | 
					      .safeParse(search);
 | 
				
			||||||
 | 
					    if (!validationResult.success) {
 | 
				
			||||||
 | 
					      console.error(validationResult.error);
 | 
				
			||||||
 | 
					      // Optionally set an error state here
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setLoading(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const controller = new AbortController();
 | 
				
			||||||
 | 
					    const { url, modelVersionId: versionId } = getUrl(search);
 | 
				
			||||||
 | 
					    setModelVersionId(versionId);
 | 
				
			||||||
 | 
					    fetch(url, {
 | 
				
			||||||
 | 
					      signal: controller.signal,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					      .then((x) => x.json())
 | 
				
			||||||
 | 
					      .then((a) => {
 | 
				
			||||||
 | 
					        const res = CivitaiModel.parse(a);
 | 
				
			||||||
 | 
					        console.log(a);
 | 
				
			||||||
 | 
					        console.log(res);
 | 
				
			||||||
 | 
					        setModelRes(res);
 | 
				
			||||||
 | 
					        setLoading(false);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      controller.abort();
 | 
				
			||||||
 | 
					      setLoading(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const modifiedField = {
 | 
				
			||||||
 | 
					    ...fieldProps,
 | 
				
			||||||
 | 
					    // onChange: (event: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					    //   handleSearch(event.target.value);
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <FormItem>
 | 
				
			||||||
 | 
					      {fieldConfigItem.inputProps?.showLabel && (
 | 
				
			||||||
 | 
					        <FormLabel>
 | 
				
			||||||
 | 
					          {label}
 | 
				
			||||||
 | 
					          {isRequired && <span className="text-destructive">*</span>}
 | 
				
			||||||
 | 
					        </FormLabel>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      <FormControl>
 | 
				
			||||||
 | 
					        <AutoFormInput
 | 
				
			||||||
 | 
					          {...props}
 | 
				
			||||||
 | 
					          fieldProps={modifiedField}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </FormControl>
 | 
				
			||||||
 | 
					    </FormItem>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -8,6 +8,7 @@ import AutoFormSwitch from "./fields/switch";
 | 
				
			|||||||
import AutoFormTextarea from "./fields/textarea";
 | 
					import AutoFormTextarea from "./fields/textarea";
 | 
				
			||||||
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
 | 
					import AutoFormModelsPicker from "@/components/custom-form/model-picker";
 | 
				
			||||||
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
 | 
					import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
 | 
				
			||||||
 | 
					import AutoFormCheckpointInput from "@/components/custom-form/checkpoint-input";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const INPUT_COMPONENTS = {
 | 
					export const INPUT_COMPONENTS = {
 | 
				
			||||||
  checkbox: AutoFormCheckbox,
 | 
					  checkbox: AutoFormCheckbox,
 | 
				
			||||||
@ -22,6 +23,7 @@ export const INPUT_COMPONENTS = {
 | 
				
			|||||||
  // Customs
 | 
					  // Customs
 | 
				
			||||||
  snapshot: AutoFormSnapshotPicker,
 | 
					  snapshot: AutoFormSnapshotPicker,
 | 
				
			||||||
  models: AutoFormModelsPicker,
 | 
					  models: AutoFormModelsPicker,
 | 
				
			||||||
 | 
					  checkpoints: AutoFormCheckpointInput,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,13 @@
 | 
				
			|||||||
import { relations, type InferSelectModel } from "drizzle-orm";
 | 
					import { type InferSelectModel, relations } from "drizzle-orm";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  text,
 | 
					  boolean,
 | 
				
			||||||
  pgSchema,
 | 
					 | 
				
			||||||
  uuid,
 | 
					 | 
				
			||||||
  integer,
 | 
					  integer,
 | 
				
			||||||
  timestamp,
 | 
					 | 
				
			||||||
  jsonb,
 | 
					  jsonb,
 | 
				
			||||||
  pgEnum,
 | 
					  pgEnum,
 | 
				
			||||||
  boolean,
 | 
					  pgSchema,
 | 
				
			||||||
 | 
					  text,
 | 
				
			||||||
 | 
					  timestamp,
 | 
				
			||||||
 | 
					  uuid,
 | 
				
			||||||
} from "drizzle-orm/pg-core";
 | 
					} from "drizzle-orm/pg-core";
 | 
				
			||||||
import { createInsertSchema } from "drizzle-zod";
 | 
					import { createInsertSchema } from "drizzle-zod";
 | 
				
			||||||
import { z } from "zod";
 | 
					import { z } from "zod";
 | 
				
			||||||
@ -87,7 +87,7 @@ export const workflowVersionRelations = relations(
 | 
				
			|||||||
      fields: [workflowVersionTable.workflow_id],
 | 
					      fields: [workflowVersionTable.workflow_id],
 | 
				
			||||||
      references: [workflowTable.id],
 | 
					      references: [workflowTable.id],
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
  })
 | 
					  }),
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const workflowRunStatus = pgEnum("workflow_run_status", [
 | 
					export const workflowRunStatus = pgEnum("workflow_run_status", [
 | 
				
			||||||
@ -136,10 +136,11 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
 | 
				
			|||||||
    () => workflowVersionTable.id,
 | 
					    () => workflowVersionTable.id,
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      onDelete: "set null",
 | 
					      onDelete: "set null",
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
  ),
 | 
					  ),
 | 
				
			||||||
  workflow_inputs:
 | 
					  workflow_inputs: jsonb("workflow_inputs").$type<
 | 
				
			||||||
    jsonb("workflow_inputs").$type<Record<string, string | number>>(),
 | 
					    Record<string, string | number>
 | 
				
			||||||
 | 
					  >(),
 | 
				
			||||||
  workflow_id: uuid("workflow_id")
 | 
					  workflow_id: uuid("workflow_id")
 | 
				
			||||||
    .notNull()
 | 
					    .notNull()
 | 
				
			||||||
    .references(() => workflowTable.id, {
 | 
					    .references(() => workflowTable.id, {
 | 
				
			||||||
@ -171,7 +172,7 @@ export const workflowRunRelations = relations(
 | 
				
			|||||||
      fields: [workflowRunsTable.workflow_id],
 | 
					      fields: [workflowRunsTable.workflow_id],
 | 
				
			||||||
      references: [workflowTable.id],
 | 
					      references: [workflowTable.id],
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
  })
 | 
					  }),
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// We still want to keep the workflow run record.
 | 
					// We still want to keep the workflow run record.
 | 
				
			||||||
@ -195,7 +196,7 @@ export const workflowOutputRelations = relations(
 | 
				
			|||||||
      fields: [workflowRunOutputs.run_id],
 | 
					      fields: [workflowRunOutputs.run_id],
 | 
				
			||||||
      references: [workflowRunsTable.id],
 | 
					      references: [workflowRunsTable.id],
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
  })
 | 
					  }),
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// when user delete, also delete all the workflow versions
 | 
					// when user delete, also delete all the workflow versions
 | 
				
			||||||
@ -228,7 +229,7 @@ export const snapshotType = z.object({
 | 
				
			|||||||
    z.object({
 | 
					    z.object({
 | 
				
			||||||
      hash: z.string(),
 | 
					      hash: z.string(),
 | 
				
			||||||
      disabled: z.boolean(),
 | 
					      disabled: z.boolean(),
 | 
				
			||||||
    })
 | 
					    }),
 | 
				
			||||||
  ),
 | 
					  ),
 | 
				
			||||||
  file_custom_nodes: z.array(z.any()),
 | 
					  file_custom_nodes: z.array(z.any()),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@ -243,7 +244,7 @@ export const showcaseMedia = z.array(
 | 
				
			|||||||
  z.object({
 | 
					  z.object({
 | 
				
			||||||
    url: z.string(),
 | 
					    url: z.string(),
 | 
				
			||||||
    isCover: z.boolean().default(false),
 | 
					    isCover: z.boolean().default(false),
 | 
				
			||||||
  })
 | 
					  }),
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const showcaseMediaNullable = z
 | 
					export const showcaseMediaNullable = z
 | 
				
			||||||
@ -251,7 +252,7 @@ export const showcaseMediaNullable = z
 | 
				
			|||||||
    z.object({
 | 
					    z.object({
 | 
				
			||||||
      url: z.string(),
 | 
					      url: z.string(),
 | 
				
			||||||
      isCover: z.boolean().default(false),
 | 
					      isCover: z.boolean().default(false),
 | 
				
			||||||
    })
 | 
					    }),
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  .nullable();
 | 
					  .nullable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -275,8 +276,9 @@ export const deploymentsTable = dbSchema.table("deployments", {
 | 
				
			|||||||
    .notNull()
 | 
					    .notNull()
 | 
				
			||||||
    .references(() => machinesTable.id),
 | 
					    .references(() => machinesTable.id),
 | 
				
			||||||
  description: text("description"),
 | 
					  description: text("description"),
 | 
				
			||||||
  showcase_media:
 | 
					  showcase_media: jsonb("showcase_media").$type<
 | 
				
			||||||
    jsonb("showcase_media").$type<z.infer<typeof showcaseMedia>>(),
 | 
					    z.infer<typeof showcaseMedia>
 | 
				
			||||||
 | 
					  >(),
 | 
				
			||||||
  environment: deploymentEnvironment("environment").notNull(),
 | 
					  environment: deploymentEnvironment("environment").notNull(),
 | 
				
			||||||
  created_at: timestamp("created_at").defaultNow().notNull(),
 | 
					  created_at: timestamp("created_at").defaultNow().notNull(),
 | 
				
			||||||
  updated_at: timestamp("updated_at").defaultNow().notNull(),
 | 
					  updated_at: timestamp("updated_at").defaultNow().notNull(),
 | 
				
			||||||
@ -329,121 +331,291 @@ export const apiKeyTable = dbSchema.table("api_keys", {
 | 
				
			|||||||
  updated_at: timestamp("updated_at").defaultNow().notNull(),
 | 
					  updated_at: timestamp("updated_at").defaultNow().notNull(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const civitaiModelVersion = z.object({
 | 
					// const civitaiModelVersion = z.object({
 | 
				
			||||||
  id: z.number(),
 | 
					//   id: z.number(),
 | 
				
			||||||
  modelId: z.number(),
 | 
					//   modelId: z.number(),
 | 
				
			||||||
  name: z.string(),
 | 
					//   name: z.string(),
 | 
				
			||||||
  createdAt: z.string(),
 | 
					//   createdAt: z.string(),
 | 
				
			||||||
  updatedAt: z.string(),
 | 
					//   updatedAt: z.string(),
 | 
				
			||||||
  status: z.string(),
 | 
					//   status: z.string(),
 | 
				
			||||||
  publishedAt: z.string(),
 | 
					//   publishedAt: z.string(),
 | 
				
			||||||
  trainedWords: z.array(z.string()).optional(),
 | 
					//   trainedWords: z.array(z.string()).optional(),
 | 
				
			||||||
  trainingStatus: z.string().optional(),
 | 
					//   trainingStatus: z.string().optional(),
 | 
				
			||||||
  trainingDetails: z.string().optional(),
 | 
					//   trainingDetails: z.string().optional(),
 | 
				
			||||||
  baseModel: z.string(),
 | 
					//   baseModel: z.string(),
 | 
				
			||||||
  baseModelType: z.string(),
 | 
					//   baseModelType: z.string(),
 | 
				
			||||||
  earlyAccessTimeFrame: z.number(),
 | 
					//   earlyAccessTimeFrame: z.number(),
 | 
				
			||||||
  description: z.string().optional(),
 | 
					//   description: z.string().optional(),
 | 
				
			||||||
  vaeId: z.string().optional(),
 | 
					//   vaeId: z.string().optional(),
 | 
				
			||||||
  stats: z.object({
 | 
					//   stats: z.object({
 | 
				
			||||||
    downloadCount: z.number(),
 | 
					//     downloadCount: z.number(),
 | 
				
			||||||
    ratingCount: z.number(),
 | 
					//     ratingCount: z.number(),
 | 
				
			||||||
    rating: z.number()
 | 
					//     rating: z.number(),
 | 
				
			||||||
  }),
 | 
					//   }),
 | 
				
			||||||
  files: z.array(z.object({
 | 
					//   files: z.array(z.object({
 | 
				
			||||||
    id: z.number(),
 | 
					//     id: z.number(),
 | 
				
			||||||
    sizeKB: z.number(),
 | 
					//     sizeKB: z.number(),
 | 
				
			||||||
    name: z.string(),
 | 
					//     name: z.string(),
 | 
				
			||||||
    type: z.string(),
 | 
					//     type: z.string(),
 | 
				
			||||||
    metadata: z.object({
 | 
					//     metadata: z.object({
 | 
				
			||||||
      fp: z.string(),
 | 
					//       fp: z.string(),
 | 
				
			||||||
      size: z.string(),
 | 
					//       size: z.string(),
 | 
				
			||||||
      format: z.string()
 | 
					//       format: z.string(),
 | 
				
			||||||
    }),
 | 
					//     }),
 | 
				
			||||||
    pickleScanResult: z.string(),
 | 
					//     pickleScanResult: z.string(),
 | 
				
			||||||
    pickleScanMessage: z.string().optional(),
 | 
					//     pickleScanMessage: z.string().optional(),
 | 
				
			||||||
    virusScanResult: z.string(),
 | 
					//     virusScanResult: z.string(),
 | 
				
			||||||
    virusScanMessage: z.string().optional(),
 | 
					//     virusScanMessage: z.string().optional(),
 | 
				
			||||||
    scannedAt: z.string(),
 | 
					//     scannedAt: z.string(),
 | 
				
			||||||
    hashes: z.object({
 | 
					//     hashes: z.object({
 | 
				
			||||||
      AutoV1: z.string(),
 | 
					//       AutoV1: z.string(),
 | 
				
			||||||
      AutoV2: z.string(),
 | 
					//       AutoV2: z.string(),
 | 
				
			||||||
      SHA256: z.string(),
 | 
					//       SHA256: z.string(),
 | 
				
			||||||
      CRC32: z.string(),
 | 
					//       CRC32: z.string(),
 | 
				
			||||||
      BLAKE3: z.string(),
 | 
					//       BLAKE3: z.string(),
 | 
				
			||||||
      AutoV3: z.string()
 | 
					//       AutoV3: z.string(),
 | 
				
			||||||
    }),
 | 
					//     }),
 | 
				
			||||||
    downloadUrl: z.string(),
 | 
					//     downloadUrl: z.string(),
 | 
				
			||||||
    primary: z.boolean()
 | 
					//     primary: z.boolean(),
 | 
				
			||||||
  })),
 | 
					//   })),
 | 
				
			||||||
  images: z.array(z.object({
 | 
					//   images: z.array(z.object({
 | 
				
			||||||
    url: z.string(),
 | 
					//     url: z.string(),
 | 
				
			||||||
    nsfw: z.string(),
 | 
					//     nsfw: z.string(),
 | 
				
			||||||
    width: z.number(),
 | 
					//     width: z.number(),
 | 
				
			||||||
    height: z.number(),
 | 
					//     height: z.number(),
 | 
				
			||||||
    hash: z.string(),
 | 
					//     hash: z.string(),
 | 
				
			||||||
    type: z.string(),
 | 
					//     type: z.string(),
 | 
				
			||||||
    metadata: z.object({
 | 
					//     metadata: z.object({
 | 
				
			||||||
      hash: z.string(),
 | 
					//       hash: z.string(),
 | 
				
			||||||
      size: z.number(),
 | 
					//       size: z.number(),
 | 
				
			||||||
      width: z.number(),
 | 
					//       width: z.number(),
 | 
				
			||||||
      height: z.number()
 | 
					//       height: z.number(),
 | 
				
			||||||
    }),
 | 
					//     }),
 | 
				
			||||||
    meta: z.any()
 | 
					//     meta: z.any(),
 | 
				
			||||||
  })),
 | 
					//   })),
 | 
				
			||||||
  downloadUrl: z.string()
 | 
					//   downloadUrl: z.string(),
 | 
				
			||||||
});
 | 
					// });
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// const civitaiModelResponseType = z.object({
 | 
				
			||||||
 | 
					//   id: z.number(),
 | 
				
			||||||
 | 
					//   name: z.string(),
 | 
				
			||||||
 | 
					//   description: z.string().optional(),
 | 
				
			||||||
 | 
					//   type: z.string(),
 | 
				
			||||||
 | 
					//   poi: z.boolean(),
 | 
				
			||||||
 | 
					//   nsfw: z.boolean(),
 | 
				
			||||||
 | 
					//   allowNoCredit: z.boolean(),
 | 
				
			||||||
 | 
					//   allowCommercialUse: z.string(),
 | 
				
			||||||
 | 
					//   allowDerivatives: z.boolean(),
 | 
				
			||||||
 | 
					//   allowDifferentLicense: z.boolean(),
 | 
				
			||||||
 | 
					//   stats: z.object({
 | 
				
			||||||
 | 
					//     downloadCount: z.number(),
 | 
				
			||||||
 | 
					//     favoriteCount: z.number(),
 | 
				
			||||||
 | 
					//     commentCount: z.number(),
 | 
				
			||||||
 | 
					//     ratingCount: z.number(),
 | 
				
			||||||
 | 
					//     rating: z.number(),
 | 
				
			||||||
 | 
					//     tippedAmountCount: z.number(),
 | 
				
			||||||
 | 
					//   }),
 | 
				
			||||||
 | 
					//   creator: z.object({
 | 
				
			||||||
 | 
					//     username: z.string(),
 | 
				
			||||||
 | 
					//     image: z.string(),
 | 
				
			||||||
 | 
					//   }),
 | 
				
			||||||
 | 
					//   tags: z.array(z.string()),
 | 
				
			||||||
 | 
					//   modelVersions: z.array(civitaiModelVersion),
 | 
				
			||||||
 | 
					// });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const civitaiModelResponseType = z.object({
 | 
					export const CivitaiModel = z.object({
 | 
				
			||||||
  id: z.number(),
 | 
					  id: z.number(),
 | 
				
			||||||
  name: z.string(),
 | 
					  name: z.string(),
 | 
				
			||||||
  description: z.string().optional(),
 | 
					  description: z.string(),
 | 
				
			||||||
  type: z.string(),
 | 
					  type: z.string(),
 | 
				
			||||||
  poi: z.boolean(),
 | 
					  // poi: z.boolean(),
 | 
				
			||||||
  nsfw: z.boolean(),
 | 
					  // nsfw: z.boolean(),
 | 
				
			||||||
  allowNoCredit: z.boolean(),
 | 
					  // allowNoCredit: z.boolean(),
 | 
				
			||||||
  allowCommercialUse: z.string(),
 | 
					  // allowCommercialUse: z.string(),
 | 
				
			||||||
  allowDerivatives: z.boolean(),
 | 
					  // allowDerivatives: z.boolean(),
 | 
				
			||||||
  allowDifferentLicense: z.boolean(),
 | 
					  // allowDifferentLicense: z.boolean(),
 | 
				
			||||||
  stats: z.object({
 | 
					  // stats: z.object({
 | 
				
			||||||
    downloadCount: z.number(),
 | 
					  //   downloadCount: z.number(),
 | 
				
			||||||
    favoriteCount: z.number(),
 | 
					  //   favoriteCount: z.number(),
 | 
				
			||||||
    commentCount: z.number(),
 | 
					  //   commentCount: z.number(),
 | 
				
			||||||
    ratingCount: z.number(),
 | 
					  //   ratingCount: z.number(),
 | 
				
			||||||
    rating: z.number(),
 | 
					  //   rating: z.number(),
 | 
				
			||||||
    tippedAmountCount: z.number()
 | 
					  //   tippedAmountCount: z.number(),
 | 
				
			||||||
  }),
 | 
					  // }),
 | 
				
			||||||
  creator: z.object({
 | 
					  creator: z
 | 
				
			||||||
    username: z.string(),
 | 
					    .object({
 | 
				
			||||||
    image: z.string()
 | 
					      username: z.string().nullable(),
 | 
				
			||||||
  }),
 | 
					      image: z.string().nullable().default(null),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .nullable(),
 | 
				
			||||||
  tags: z.array(z.string()),
 | 
					  tags: z.array(z.string()),
 | 
				
			||||||
  modelVersions: z.array(civitaiModelVersion)
 | 
					  modelVersions: z.array(
 | 
				
			||||||
 | 
					    z.object({
 | 
				
			||||||
 | 
					      id: z.number(),
 | 
				
			||||||
 | 
					      modelId: z.number(),
 | 
				
			||||||
 | 
					      name: z.string(),
 | 
				
			||||||
 | 
					      createdAt: z.string(),
 | 
				
			||||||
 | 
					      updatedAt: z.string(),
 | 
				
			||||||
 | 
					      status: z.string(),
 | 
				
			||||||
 | 
					      publishedAt: z.string(),
 | 
				
			||||||
 | 
					      trainedWords: z.array(z.unknown()),
 | 
				
			||||||
 | 
					      trainingStatus: z.string().nullable(),
 | 
				
			||||||
 | 
					      trainingDetails: z.string().nullable(),
 | 
				
			||||||
 | 
					      baseModel: z.string(),
 | 
				
			||||||
 | 
					      baseModelType: z.string().nullable(),
 | 
				
			||||||
 | 
					      earlyAccessTimeFrame: z.number(),
 | 
				
			||||||
 | 
					      description: z.string().nullable(),
 | 
				
			||||||
 | 
					      vaeId: z.number().nullable(),
 | 
				
			||||||
 | 
					      stats: z.object({
 | 
				
			||||||
 | 
					        downloadCount: z.number(),
 | 
				
			||||||
 | 
					        ratingCount: z.number(),
 | 
				
			||||||
 | 
					        rating: z.number(),
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					      files: z.array(
 | 
				
			||||||
 | 
					        z.object({
 | 
				
			||||||
 | 
					          id: z.number(),
 | 
				
			||||||
 | 
					          sizeKB: z.number(),
 | 
				
			||||||
 | 
					          name: z.string(),
 | 
				
			||||||
 | 
					          type: z.string(),
 | 
				
			||||||
 | 
					          // metadata: z.object({
 | 
				
			||||||
 | 
					          //   fp: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          //   size: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          //   format: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          // }),
 | 
				
			||||||
 | 
					          // pickleScanResult: z.string(),
 | 
				
			||||||
 | 
					          // pickleScanMessage: z.string(),
 | 
				
			||||||
 | 
					          // virusScanResult: z.string(),
 | 
				
			||||||
 | 
					          // virusScanMessage: z.string().nullable(),
 | 
				
			||||||
 | 
					          // scannedAt: z.string(),
 | 
				
			||||||
 | 
					          // hashes: z.object({
 | 
				
			||||||
 | 
					          //   AutoV1: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          //   AutoV2: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          //   SHA256: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          //   CRC32: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          //   BLAKE3: z.string().nullable().optional(),
 | 
				
			||||||
 | 
					          // }),
 | 
				
			||||||
 | 
					          downloadUrl: z.string(),
 | 
				
			||||||
 | 
					          // primary: z.boolean().default(false),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      images: z.array(
 | 
				
			||||||
 | 
					        z.object({
 | 
				
			||||||
 | 
					          id: z.number(),
 | 
				
			||||||
 | 
					          url: z.string(),
 | 
				
			||||||
 | 
					          nsfw: z.string(),
 | 
				
			||||||
 | 
					          width: z.number(),
 | 
				
			||||||
 | 
					          height: z.number(),
 | 
				
			||||||
 | 
					          hash: z.string(),
 | 
				
			||||||
 | 
					          type: z.string(),
 | 
				
			||||||
 | 
					          metadata: z.object({
 | 
				
			||||||
 | 
					            hash: z.string(),
 | 
				
			||||||
 | 
					            width: z.number(),
 | 
				
			||||||
 | 
					            height: z.number(),
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					          meta: z.any(),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      downloadUrl: z.string(),
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const resourceUpload = pgEnum("resource_upload", [
 | 
				
			||||||
 | 
					  "started",
 | 
				
			||||||
 | 
					  "failed",
 | 
				
			||||||
 | 
					  "succeded",
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const checkpoints = dbSchema.table("checkpoints", {
 | 
					export const modelUploadType = pgEnum("model_upload_type", [
 | 
				
			||||||
 | 
					  "civitai",
 | 
				
			||||||
 | 
					  "huggingface",
 | 
				
			||||||
 | 
					  "other",
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const checkpointTable = dbSchema.table("checkpoints", {
 | 
				
			||||||
  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
					  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
				
			||||||
  user_id: text("user_id")
 | 
					  user_id: text("user_id")
 | 
				
			||||||
    .references(() => usersTable.id, {
 | 
					    .references(() => usersTable.id, {}), // perhaps a "special" user_id for global checkpoints
 | 
				
			||||||
      // onDelete: "cascade",
 | 
					 | 
				
			||||||
    }), // if null it's global?
 | 
					 | 
				
			||||||
  org_id: text("org_id"),
 | 
					  org_id: text("org_id"),
 | 
				
			||||||
  description: text("description"),
 | 
					  description: text("description"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  civitai_id : text('civitai_id'),
 | 
					  checkpoint_volume_id: uuid("checkpoint_volume_id")
 | 
				
			||||||
  civitai_url : text('civitai_url'),
 | 
					    .notNull()
 | 
				
			||||||
  civitai_details: jsonb("civitai_model_response").$type<z.infer<typeof civitaiModelResponseType >>(),
 | 
					    .references(() => workflowRunsTable.id, {
 | 
				
			||||||
 | 
					      onDelete: "cascade",
 | 
				
			||||||
 | 
					    }).notNull(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  hf_url: text('hf_url'),
 | 
					  model_name: text("model_name"),
 | 
				
			||||||
  s3_url: text('s3_url'),
 | 
					
 | 
				
			||||||
 | 
					  civitai_id: text("civitai_id"),
 | 
				
			||||||
 | 
					  civitai_version_id: text("civitai_version_id"),
 | 
				
			||||||
 | 
					  civitai_url: text("civitai_url"),
 | 
				
			||||||
 | 
					  civitai_download_url: text("civitai_download_url"),
 | 
				
			||||||
 | 
					  civitai_model_response: jsonb("civitai_model_response").$type<
 | 
				
			||||||
 | 
					    z.infer<typeof CivitaiModel>
 | 
				
			||||||
 | 
					  >(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hf_url: text("hf_url"),
 | 
				
			||||||
 | 
					  s3_url: text("s3_url"),
 | 
				
			||||||
 | 
					  user_url: text("client_url"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  is_public: boolean("is_public").notNull().default(false),
 | 
				
			||||||
 | 
					  status: resourceUpload("status").notNull().default("started"),
 | 
				
			||||||
 | 
					  upload_machine_id: text("upload_machine_id"),
 | 
				
			||||||
 | 
					  upload_type: modelUploadType("upload_type").notNull(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  created_at: timestamp("created_at").defaultNow().notNull(),
 | 
					  created_at: timestamp("created_at").defaultNow().notNull(),
 | 
				
			||||||
  updated_at: timestamp("updated_at").defaultNow().notNull(),
 | 
					  updated_at: timestamp("updated_at").defaultNow().notNull(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const insertCivitaiCheckpointSchema = createInsertSchema(
 | 
				
			||||||
 | 
					  checkpointTable,
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    civitai_url: (schema) =>
 | 
				
			||||||
 | 
					      schema.civitai_url.trim().url({ message: "URL required" }).includes(
 | 
				
			||||||
 | 
					        "civitai.com/models",
 | 
				
			||||||
 | 
					        { message: "civitai.com/models link required" },
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const checkpointVolumeTable = dbSchema.table("checkpointVolumeTable", {
 | 
				
			||||||
 | 
					  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
				
			||||||
 | 
					  user_id: text("user_id")
 | 
				
			||||||
 | 
					    .references(() => usersTable.id, {
 | 
				
			||||||
 | 
					      // onDelete: "cascade",
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  org_id: text("org_id"),
 | 
				
			||||||
 | 
					  volume_name: text("volume_name").notNull(),
 | 
				
			||||||
 | 
					  created_at: timestamp("created_at").defaultNow().notNull(),
 | 
				
			||||||
 | 
					  updated_at: timestamp("updated_at").defaultNow().notNull(),
 | 
				
			||||||
 | 
					  disabled: boolean("disabled").default(false).notNull(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const checkpointRelations = relations(checkpointTable, ({ one }) => ({
 | 
				
			||||||
 | 
					  user: one(usersTable, {
 | 
				
			||||||
 | 
					    fields: [checkpointTable.user_id],
 | 
				
			||||||
 | 
					    references: [usersTable.id],
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  volume: one(checkpointVolumeTable, {
 | 
				
			||||||
 | 
					    fields: [checkpointTable.checkpoint_volume_id],
 | 
				
			||||||
 | 
					    references: [checkpointVolumeTable.id],
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const checkpointVolumeRelations = relations(
 | 
				
			||||||
 | 
					  checkpointVolumeTable,
 | 
				
			||||||
 | 
					  ({ many }) => ({
 | 
				
			||||||
 | 
					    checkpoint: many(checkpointTable),
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type UserType = InferSelectModel<typeof usersTable>;
 | 
					export type UserType = InferSelectModel<typeof usersTable>;
 | 
				
			||||||
export type WorkflowType = InferSelectModel<typeof workflowTable>;
 | 
					export type WorkflowType = InferSelectModel<typeof workflowTable>;
 | 
				
			||||||
export type MachineType = InferSelectModel<typeof machinesTable>;
 | 
					export type MachineType = InferSelectModel<typeof machinesTable>;
 | 
				
			||||||
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
 | 
					export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
 | 
				
			||||||
export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
 | 
					export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
 | 
				
			||||||
 | 
					export type CheckpointType = InferSelectModel<typeof checkpointTable>;
 | 
				
			||||||
 | 
					export type CheckpointVolumeType = InferSelectModel<
 | 
				
			||||||
 | 
					  typeof checkpointVolumeTable
 | 
				
			||||||
 | 
					>;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								web/src/server/addCheckpointSchema.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								web/src/server/addCheckpointSchema.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					import { insertCivitaiCheckpointSchema } from "@/db/schema";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addCivitaiCheckpointSchema = insertCivitaiCheckpointSchema.pick({
 | 
				
			||||||
 | 
					  civitai_url: true,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										222
									
								
								web/src/server/curdCheckpoint.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								web/src/server/curdCheckpoint.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					"use server";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { auth } from "@clerk/nextjs";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  checkpointTable,
 | 
				
			||||||
 | 
					  CheckpointType,
 | 
				
			||||||
 | 
					  checkpointVolumeTable,
 | 
				
			||||||
 | 
					  CheckpointVolumeType,
 | 
				
			||||||
 | 
					  CivitaiModel,
 | 
				
			||||||
 | 
					} from "@/db/schema";
 | 
				
			||||||
 | 
					import { withServerPromise } from "./withServerPromise";
 | 
				
			||||||
 | 
					import { redirect } from "next/navigation";
 | 
				
			||||||
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
 | 
					import type { z } from "zod";
 | 
				
			||||||
 | 
					import { headers } from "next/headers";
 | 
				
			||||||
 | 
					import { addCivitaiCheckpointSchema } from "./addCheckpointSchema";
 | 
				
			||||||
 | 
					import { and, eq, isNull } from "drizzle-orm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getCheckpoints() {
 | 
				
			||||||
 | 
					  const { userId, orgId } = auth();
 | 
				
			||||||
 | 
					  if (!userId) throw new Error("No user id");
 | 
				
			||||||
 | 
					  const checkpoints = await db
 | 
				
			||||||
 | 
					    .select()
 | 
				
			||||||
 | 
					    .from(checkpointTable)
 | 
				
			||||||
 | 
					    .where(
 | 
				
			||||||
 | 
					      orgId
 | 
				
			||||||
 | 
					        ? eq(checkpointTable.org_id, orgId)
 | 
				
			||||||
 | 
					        // make sure org_id is null
 | 
				
			||||||
 | 
					        : and(
 | 
				
			||||||
 | 
					          eq(checkpointTable.user_id, userId),
 | 
				
			||||||
 | 
					          isNull(checkpointTable.org_id),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  return checkpoints;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getCheckpointById(id: string) {
 | 
				
			||||||
 | 
					  const { userId, orgId } = auth();
 | 
				
			||||||
 | 
					  if (!userId) throw new Error("No user id");
 | 
				
			||||||
 | 
					  const checkpoint = await db
 | 
				
			||||||
 | 
					    .select()
 | 
				
			||||||
 | 
					    .from(checkpointTable)
 | 
				
			||||||
 | 
					    .where(
 | 
				
			||||||
 | 
					      and(
 | 
				
			||||||
 | 
					        orgId ? eq(checkpointTable.org_id, orgId) : and(
 | 
				
			||||||
 | 
					          eq(checkpointTable.user_id, userId),
 | 
				
			||||||
 | 
					          isNull(checkpointTable.org_id),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        eq(checkpointTable.id, id),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  return checkpoint[0];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getCheckpointVolumes() {
 | 
				
			||||||
 | 
					  const { userId, orgId } = auth();
 | 
				
			||||||
 | 
					  if (!userId) throw new Error("No user id");
 | 
				
			||||||
 | 
					  const checkpointVolume = await db
 | 
				
			||||||
 | 
					    .select()
 | 
				
			||||||
 | 
					    .from(checkpointVolumeTable)
 | 
				
			||||||
 | 
					    .where(
 | 
				
			||||||
 | 
					      and(
 | 
				
			||||||
 | 
					        orgId
 | 
				
			||||||
 | 
					          ? eq(checkpointVolumeTable.org_id, orgId)
 | 
				
			||||||
 | 
					          // make sure org_id is null
 | 
				
			||||||
 | 
					          : and(
 | 
				
			||||||
 | 
					            eq(checkpointVolumeTable.user_id, userId),
 | 
				
			||||||
 | 
					            isNull(checkpointVolumeTable.org_id),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        eq(checkpointVolumeTable.disabled, false),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  return checkpointVolume;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function addCheckpointVolume() {
 | 
				
			||||||
 | 
					  const { userId, orgId } = auth();
 | 
				
			||||||
 | 
					  if (!userId) throw new Error("No user id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Insert the new volume into the checkpointVolumeTable
 | 
				
			||||||
 | 
					  const insertedVolume = await db
 | 
				
			||||||
 | 
					    .insert(checkpointVolumeTable)
 | 
				
			||||||
 | 
					    .values({
 | 
				
			||||||
 | 
					      user_id: userId,
 | 
				
			||||||
 | 
					      org_id: orgId,
 | 
				
			||||||
 | 
					      volume_name: `checkpoints_${userId}`,
 | 
				
			||||||
 | 
					      // created_at and updated_at will be set to current timestamp by default
 | 
				
			||||||
 | 
					      disabled: false, // Default value
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .returning(); // Returns the inserted row
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return insertedVolume;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getUrl(civitai_url: string) {
 | 
				
			||||||
 | 
					  // expect to be a URL to be https://civitai.com/models/36520
 | 
				
			||||||
 | 
					  // possiblity with slugged name and query-param modelVersionId
 | 
				
			||||||
 | 
					  const baseUrl = "https://civitai.com/api/v1/models/";
 | 
				
			||||||
 | 
					  const url = new URL(civitai_url);
 | 
				
			||||||
 | 
					  const pathSegments = url.pathname.split("/");
 | 
				
			||||||
 | 
					  const modelId = pathSegments[pathSegments.indexOf("models") + 1];
 | 
				
			||||||
 | 
					  const modelVersionId = url.searchParams.get("modelVersionId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { url: baseUrl + modelId, modelVersionId };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const addCivitaiCheckpoint = withServerPromise(
 | 
				
			||||||
 | 
					  async (data: z.infer<typeof addCivitaiCheckpointSchema>) => {
 | 
				
			||||||
 | 
					    const { userId, orgId } = auth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!data.civitai_url) return { error: "no civitai_url" };
 | 
				
			||||||
 | 
					    if (!userId) return { error: "No user id" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { url, modelVersionId } = getUrl(data?.civitai_url);
 | 
				
			||||||
 | 
					    const civitaiModelRes = await fetch(url)
 | 
				
			||||||
 | 
					      .then((x) => x.json())
 | 
				
			||||||
 | 
					      .then((a) => {
 | 
				
			||||||
 | 
					        console.log(a)
 | 
				
			||||||
 | 
					        return CivitaiModel.parse(a);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (civitaiModelRes.modelVersions?.length === 0) {
 | 
				
			||||||
 | 
					      return; // no versions to download
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let selectedModelVersion;
 | 
				
			||||||
 | 
					    let selectedModelVersionId: string | null = modelVersionId;
 | 
				
			||||||
 | 
					    if (!selectedModelVersionId) {
 | 
				
			||||||
 | 
					      selectedModelVersion = civitaiModelRes.modelVersions[0];
 | 
				
			||||||
 | 
					      selectedModelVersionId = civitaiModelRes.modelVersions[0].id.toString();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      selectedModelVersion = civitaiModelRes.modelVersions.find((version) =>
 | 
				
			||||||
 | 
					        version.id.toString() === selectedModelVersionId
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      if (!selectedModelVersion) {
 | 
				
			||||||
 | 
					        return; // version id is wrong
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      selectedModelVersionId = selectedModelVersion?.id.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const checkpointVolumes = await getCheckpointVolumes();
 | 
				
			||||||
 | 
					    let cVolume;
 | 
				
			||||||
 | 
					    if (checkpointVolumes.length === 0) {
 | 
				
			||||||
 | 
					      const volume = await addCheckpointVolume();
 | 
				
			||||||
 | 
					      cVolume = volume[0];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      cVolume = checkpointVolumes[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const a = await db
 | 
				
			||||||
 | 
					      .insert(checkpointTable)
 | 
				
			||||||
 | 
					      .values({
 | 
				
			||||||
 | 
					        user_id: userId,
 | 
				
			||||||
 | 
					        org_id: orgId,
 | 
				
			||||||
 | 
					        upload_type: "civitai",
 | 
				
			||||||
 | 
					        civitai_id: civitaiModelRes.id.toString(),
 | 
				
			||||||
 | 
					        civitai_version_id: selectedModelVersionId,
 | 
				
			||||||
 | 
					        civitai_model_response: civitaiModelRes,
 | 
				
			||||||
 | 
					        checkpoint_volume_id: cVolume.id,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .returning();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const b = a[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await uploadCheckpoint(data, b, cVolume);
 | 
				
			||||||
 | 
					    redirect(`/checkpoints/${b.id}`);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function uploadCheckpoint(
 | 
				
			||||||
 | 
					  data: z.infer<typeof addCivitaiCheckpointSchema>,
 | 
				
			||||||
 | 
					  b: CheckpointType,
 | 
				
			||||||
 | 
					  v: CheckpointVolumeType,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const headersList = headers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const domain = headersList.get("x-forwarded-host") || "";
 | 
				
			||||||
 | 
					  const protocol = headersList.get("x-forwarded-proto") || "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (domain === "") {
 | 
				
			||||||
 | 
					    throw new Error("No domain");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Call remote builder
 | 
				
			||||||
 | 
					  const result = await fetch(
 | 
				
			||||||
 | 
					    `${process.env.MODAL_BUILDER_URL!}/upload_volume`,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      method: "POST",
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        "Content-Type": "application/json",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      body: JSON.stringify({
 | 
				
			||||||
 | 
					        download_url: data.civitai_url,
 | 
				
			||||||
 | 
					        volume_name: v.volume_name,
 | 
				
			||||||
 | 
					        callback_url: `${protocol}://${domain}/api/volume-updated`,
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!result.ok) {
 | 
				
			||||||
 | 
					    // const error_log = await result.text();
 | 
				
			||||||
 | 
					    // await db
 | 
				
			||||||
 | 
					    //   .update(checkpointTable)
 | 
				
			||||||
 | 
					    //   .set({
 | 
				
			||||||
 | 
					    //     ...data,
 | 
				
			||||||
 | 
					    //     status: "error",
 | 
				
			||||||
 | 
					    //     build_log: error_log,
 | 
				
			||||||
 | 
					    //   })
 | 
				
			||||||
 | 
					    //   .where(eq(machinesTable.id, b.id));
 | 
				
			||||||
 | 
					    // throw new Error(`Error: ${result.statusText} ${error_log}`);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // setting the build machine id
 | 
				
			||||||
 | 
					    const json = await result.json();
 | 
				
			||||||
 | 
					    await db
 | 
				
			||||||
 | 
					      .update(checkpointTable)
 | 
				
			||||||
 | 
					      .set({
 | 
				
			||||||
 | 
					        ...data,
 | 
				
			||||||
 | 
					        // build_machine_instance_id: json.build_machine_instance_id,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .where(eq(machinesTable.id, b.id));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								web/src/server/getAllUserCheckpoints.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								web/src/server/getAllUserCheckpoints.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  checkpointTable,
 | 
				
			||||||
 | 
					} from "@/db/schema";
 | 
				
			||||||
 | 
					import { auth } from "@clerk/nextjs";
 | 
				
			||||||
 | 
					import { and, desc, eq, isNull } from "drizzle-orm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getAllUserCheckpoints() {
 | 
				
			||||||
 | 
					  const { userId, orgId } = await auth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!userId) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const checkpoints = await db.query.checkpointTable.findMany({
 | 
				
			||||||
 | 
					    with: {
 | 
				
			||||||
 | 
					      user: {
 | 
				
			||||||
 | 
					        columns: {
 | 
				
			||||||
 | 
					          name: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    columns: {
 | 
				
			||||||
 | 
					      id: true,
 | 
				
			||||||
 | 
					      updated_at: true,
 | 
				
			||||||
 | 
					      name: true,
 | 
				
			||||||
 | 
					      civitai_url: true,
 | 
				
			||||||
 | 
					      civitai_model_response: true,
 | 
				
			||||||
 | 
					      is_public: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    orderBy: desc(checkpointTable.updated_at),
 | 
				
			||||||
 | 
					    where: 
 | 
				
			||||||
 | 
					      orgId != undefined
 | 
				
			||||||
 | 
					        ? eq(checkpointTable.org_id, orgId)
 | 
				
			||||||
 | 
					        : and(eq(checkpointTable.user_id, userId), isNull(checkpointTable.org_id)),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return checkpoints;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								web/src/types/civitai.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								web/src/types/civitai.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					import { z } from 'zod';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const creatorSchema = z.object({
 | 
				
			||||||
 | 
					  username: z.string(),
 | 
				
			||||||
 | 
					  image: z.string(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const statsSchema = z.object({
 | 
				
			||||||
 | 
					  downloadCount: z.number(),
 | 
				
			||||||
 | 
					  favoriteCount: z.number(),
 | 
				
			||||||
 | 
					  commentCount: z.number(),
 | 
				
			||||||
 | 
					  ratingCount: z.number(),
 | 
				
			||||||
 | 
					  rating: z.number(),
 | 
				
			||||||
 | 
					  tippedAmountCount: z.number(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const modelVersionSchema = z.object({
 | 
				
			||||||
 | 
					  id: z.number(),
 | 
				
			||||||
 | 
					  modelId: z.number(),
 | 
				
			||||||
 | 
					  name: z.string(),
 | 
				
			||||||
 | 
					  createdAt: z.string(),
 | 
				
			||||||
 | 
					  updatedAt: z.string(),
 | 
				
			||||||
 | 
					  status: z.string(),
 | 
				
			||||||
 | 
					  publishedAt: z.string(),
 | 
				
			||||||
 | 
					  trainedWords: z.array(z.any()), // Replace with more specific type if known
 | 
				
			||||||
 | 
					  trainingStatus: z.any().optional(),
 | 
				
			||||||
 | 
					  trainingDetails: z.any().optional(),
 | 
				
			||||||
 | 
					  baseModel: z.string(),
 | 
				
			||||||
 | 
					  baseModelType: z.string(),
 | 
				
			||||||
 | 
					  earlyAccessTimeFrame: z.number(),
 | 
				
			||||||
 | 
					  description: z.string().optional(),
 | 
				
			||||||
 | 
					  vaeId: z.any().optional(), // Replace with more specific type if known
 | 
				
			||||||
 | 
					  stats: statsSchema.optional(), // If stats structure is known, replace with specific type
 | 
				
			||||||
 | 
					  files: z.array(z.any()), // Replace with more specific type if known
 | 
				
			||||||
 | 
					  images: z.array(z.any()), // Replace with more specific type if known
 | 
				
			||||||
 | 
					  downloadUrl: z.string(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CivitaiModel = z.object({
 | 
				
			||||||
 | 
					  id: z.number(),
 | 
				
			||||||
 | 
					  name: z.string(),
 | 
				
			||||||
 | 
					  description: z.string(),
 | 
				
			||||||
 | 
					  type: z.string(),
 | 
				
			||||||
 | 
					  poi: z.boolean(),
 | 
				
			||||||
 | 
					  nsfw: z.boolean(),
 | 
				
			||||||
 | 
					  allowNoCredit: z.boolean(),
 | 
				
			||||||
 | 
					  allowCommercialUse: z.string(),
 | 
				
			||||||
 | 
					  allowDerivatives: z.boolean(),
 | 
				
			||||||
 | 
					  allowDifferentLicense: z.boolean(),
 | 
				
			||||||
 | 
					  stats: statsSchema,
 | 
				
			||||||
 | 
					  creator: creatorSchema,
 | 
				
			||||||
 | 
					  tags: z.array(z.string()),
 | 
				
			||||||
 | 
					  modelVersions: z.array(modelVersionSchema),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user