feat: display runs and create run and update run endpoint
This commit is contained in:
		
							parent
							
								
									6a1c1d0ff5
								
							
						
					
					
						commit
						9c8a518c46
					
				
							
								
								
									
										112
									
								
								routes.py
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								routes.py
									
									
									
									
									
								
							@ -15,13 +15,14 @@ import execution
 | 
				
			|||||||
import random
 | 
					import random
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import uuid
 | 
					import uuid
 | 
				
			||||||
import websockets
 | 
					 | 
				
			||||||
import asyncio
 | 
					import asyncio
 | 
				
			||||||
import atexit
 | 
					import atexit
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					from enum import Enum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api = None
 | 
					api = None
 | 
				
			||||||
api_task = None
 | 
					api_task = None
 | 
				
			||||||
 | 
					prompt_metadata = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
load_dotenv()
 | 
					load_dotenv()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -73,20 +74,15 @@ def randomSeed(num_digits=15):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@server.PromptServer.instance.routes.post("/comfy-deploy/run")
 | 
					@server.PromptServer.instance.routes.post("/comfy-deploy/run")
 | 
				
			||||||
async def comfy_deploy_run(request):
 | 
					async def comfy_deploy_run(request):
 | 
				
			||||||
 | 
					    print("hi")
 | 
				
			||||||
    prompt_server = server.PromptServer.instance
 | 
					    prompt_server = server.PromptServer.instance
 | 
				
			||||||
    data = await request.json()
 | 
					    data = await request.json()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for key in data:
 | 
					 | 
				
			||||||
        if 'inputs' in data[key] and 'seed' in data[key]['inputs']:
 | 
					 | 
				
			||||||
            data[key]['inputs']['seed'] = randomSeed()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if api is None:
 | 
					 | 
				
			||||||
        connect_to_websocket()
 | 
					 | 
				
			||||||
        while api.client_id is None:
 | 
					 | 
				
			||||||
            await asyncio.sleep(0.1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    workflow_api = data.get("workflow_api")
 | 
					    workflow_api = data.get("workflow_api")
 | 
				
			||||||
    # print(workflow_api)
 | 
					
 | 
				
			||||||
 | 
					    for key in workflow_api:
 | 
				
			||||||
 | 
					        if 'inputs' in workflow_api[key] and 'seed' in workflow_api[key]['inputs']:
 | 
				
			||||||
 | 
					            workflow_api[key]['inputs']['seed'] = randomSeed()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prompt = {
 | 
					    prompt = {
 | 
				
			||||||
        "prompt": workflow_api,
 | 
					        "prompt": workflow_api,
 | 
				
			||||||
@ -95,7 +91,9 @@ async def comfy_deploy_run(request):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    res = post_prompt(prompt)
 | 
					    res = post_prompt(prompt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # print(prompt)
 | 
					    prompt_metadata[res['prompt_id']] = {
 | 
				
			||||||
 | 
					        'status_endpoint': data.get('status_endpoint'),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    status = 200
 | 
					    status = 200
 | 
				
			||||||
    if "error" in res:
 | 
					    if "error" in res:
 | 
				
			||||||
@ -105,69 +103,41 @@ async def comfy_deploy_run(request):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
logging.basicConfig(level=logging.INFO)
 | 
					logging.basicConfig(level=logging.INFO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ComfyApi:
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        self.websocket = None
 | 
					 | 
				
			||||||
        self.client_id = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def connect(self, uri):
 | 
					 | 
				
			||||||
        self.websocket = await websockets.connect(uri)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Event listeners
 | 
					 | 
				
			||||||
        await self.on_open()
 | 
					 | 
				
			||||||
        await self.on_message()
 | 
					 | 
				
			||||||
        await self.on_close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def close(self):
 | 
					 | 
				
			||||||
        await self.websocket.close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def on_open(self):
 | 
					 | 
				
			||||||
        print("Connection opened")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def on_message(self):
 | 
					 | 
				
			||||||
        async for message in self.websocket:
 | 
					 | 
				
			||||||
            if isinstance(message, bytes):
 | 
					 | 
				
			||||||
                print("Received binary message, skipping...")
 | 
					 | 
				
			||||||
                continue  # skip to the next message
 | 
					 | 
				
			||||||
            logging.info(f"Received message: {message}")
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                message_data = json.loads(message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                msg_type = message_data["type"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if msg_type == "status" and message_data["data"]["sid"] is not None:
 | 
					 | 
				
			||||||
                    self.client_id = message_data["data"]["sid"]
 | 
					 | 
				
			||||||
                    logging.info(f"Received client_id: {self.client_id}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            except json.JSONDecodeError:
 | 
					 | 
				
			||||||
                logging.info(f"Failed to parse message as JSON: {message}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def on_close(self):
 | 
					 | 
				
			||||||
        print("Connection closed")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def run(self, uri):
 | 
					 | 
				
			||||||
        await self.connect(uri)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def connect_to_websocket():
 | 
					 | 
				
			||||||
    global api, api_task
 | 
					 | 
				
			||||||
    api = ComfyApi()
 | 
					 | 
				
			||||||
    api_task = asyncio.create_task(api.run('ws://localhost:8188/ws'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
prompt_server = server.PromptServer.instance
 | 
					prompt_server = server.PromptServer.instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
send_json = prompt_server.send_json
 | 
					send_json = prompt_server.send_json
 | 
				
			||||||
async def send_json_override(self, event, data, sid=None):
 | 
					async def send_json_override(self, event, data, sid=None):
 | 
				
			||||||
 | 
					    print("INTERNAL:", event, data, sid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    prompt_id = data.get('prompt_id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if event == 'execution_start':
 | 
				
			||||||
 | 
					        update_run(prompt_id, Status.RUNNING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # if event == 'executing':
 | 
				
			||||||
 | 
					    #     update_run(prompt_id, Status.RUNNING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if event == 'executed':
 | 
				
			||||||
 | 
					        update_run(prompt_id, Status.SUCCESS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await self.send_json_original(event, data, sid)
 | 
					    await self.send_json_original(event, data, sid)
 | 
				
			||||||
    print("Sending event:", sid, event, data)
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Status(Enum):
 | 
				
			||||||
 | 
					    NOT_STARTED = "not-started"
 | 
				
			||||||
 | 
					    RUNNING = "running"
 | 
				
			||||||
 | 
					    SUCCESS = "success"
 | 
				
			||||||
 | 
					    FAILED = "failed"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def update_run(prompt_id, status: Status):
 | 
				
			||||||
 | 
					    if prompt_id in prompt_metadata and ('status' not in prompt_metadata[prompt_id] or prompt_metadata[prompt_id]['status'] != status):
 | 
				
			||||||
 | 
					        status_endpoint = prompt_metadata[prompt_id]['status_endpoint']
 | 
				
			||||||
 | 
					        body = {
 | 
				
			||||||
 | 
					            "run_id": prompt_id,
 | 
				
			||||||
 | 
					            "status": status.value,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        prompt_metadata[prompt_id]['status'] = status
 | 
				
			||||||
 | 
					        requests.post(status_endpoint, json=body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
prompt_server.send_json_original = prompt_server.send_json
 | 
					prompt_server.send_json_original = prompt_server.send_json
 | 
				
			||||||
prompt_server.send_json = send_json_override.__get__(prompt_server, server.PromptServer)
 | 
					prompt_server.send_json = send_json_override.__get__(prompt_server, server.PromptServer)
 | 
				
			||||||
 | 
					 | 
				
			||||||
@atexit.register
 | 
					 | 
				
			||||||
def close_websocket():
 | 
					 | 
				
			||||||
    print("Got close_websocket")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    global api, api_task
 | 
					 | 
				
			||||||
    if api_task:
 | 
					 | 
				
			||||||
        api_task.cancel()
 | 
					 | 
				
			||||||
@ -1,4 +1,8 @@
 | 
				
			|||||||
/** @type {import('next').NextConfig} */
 | 
					/** @type {import('next').NextConfig} */
 | 
				
			||||||
const nextConfig = {}
 | 
					const nextConfig = {
 | 
				
			||||||
 | 
					  eslint: {
 | 
				
			||||||
 | 
					    ignoreDuringBuilds: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = nextConfig
 | 
					module.exports = nextConfig;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,8 @@
 | 
				
			|||||||
import { MachineSelect, VersionSelect } from "@/components/VersionSelect";
 | 
					import {
 | 
				
			||||||
import { Button } from "@/components/ui/button";
 | 
					  MachineSelect,
 | 
				
			||||||
 | 
					  RunWorkflowButton,
 | 
				
			||||||
 | 
					  VersionSelect,
 | 
				
			||||||
 | 
					} from "@/components/VersionSelect";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Card,
 | 
					  Card,
 | 
				
			||||||
  CardContent,
 | 
					  CardContent,
 | 
				
			||||||
@ -7,12 +10,24 @@ import {
 | 
				
			|||||||
  CardHeader,
 | 
					  CardHeader,
 | 
				
			||||||
  CardTitle,
 | 
					  CardTitle,
 | 
				
			||||||
} from "@/components/ui/card";
 | 
					} from "@/components/ui/card";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Table,
 | 
				
			||||||
 | 
					  TableBody,
 | 
				
			||||||
 | 
					  TableCaption,
 | 
				
			||||||
 | 
					  TableCell,
 | 
				
			||||||
 | 
					  TableHead,
 | 
				
			||||||
 | 
					  TableHeader,
 | 
				
			||||||
 | 
					  TableRow,
 | 
				
			||||||
 | 
					} from "@/components/ui/table";
 | 
				
			||||||
import { db } from "@/db/db";
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
import { workflowTable, workflowVersionTable } from "@/db/schema";
 | 
					import {
 | 
				
			||||||
 | 
					  workflowRunsTable,
 | 
				
			||||||
 | 
					  workflowTable,
 | 
				
			||||||
 | 
					  workflowVersionTable,
 | 
				
			||||||
 | 
					} from "@/db/schema";
 | 
				
			||||||
import { getRelativeTime } from "@/lib/getRelativeTime";
 | 
					import { getRelativeTime } from "@/lib/getRelativeTime";
 | 
				
			||||||
import { getMachines } from "@/server/curdMachine";
 | 
					import { getMachines } from "@/server/curdMachine";
 | 
				
			||||||
import { desc, eq } from "drizzle-orm";
 | 
					import { desc, eq } from "drizzle-orm";
 | 
				
			||||||
import { Play } from "lucide-react";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function findFirstTableWithVersion(workflow_id: string) {
 | 
					export async function findFirstTableWithVersion(workflow_id: string) {
 | 
				
			||||||
  return await db.query.workflowTable.findFirst({
 | 
					  return await db.query.workflowTable.findFirst({
 | 
				
			||||||
@ -21,6 +36,32 @@ export async function findFirstTableWithVersion(workflow_id: string) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function findAllRuns(workflow_id: string) {
 | 
				
			||||||
 | 
					  const workflowVersion = await db.query.workflowVersionTable.findFirst({
 | 
				
			||||||
 | 
					    where: eq(workflowVersionTable.workflow_id, workflow_id),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!workflowVersion) {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return await db.query.workflowRunsTable.findMany({
 | 
				
			||||||
 | 
					    where: eq(workflowRunsTable.workflow_version_id, workflowVersion?.id),
 | 
				
			||||||
 | 
					    with: {
 | 
				
			||||||
 | 
					      machine: {
 | 
				
			||||||
 | 
					        columns: {
 | 
				
			||||||
 | 
					          name: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      version: {
 | 
				
			||||||
 | 
					        columns: {
 | 
				
			||||||
 | 
					          version: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function Page({
 | 
					export default async function Page({
 | 
				
			||||||
  params,
 | 
					  params,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
@ -45,9 +86,7 @@ export default async function Page({
 | 
				
			|||||||
          <div className="flex gap-2 ">
 | 
					          <div className="flex gap-2 ">
 | 
				
			||||||
            <VersionSelect workflow={workflow} />
 | 
					            <VersionSelect workflow={workflow} />
 | 
				
			||||||
            <MachineSelect machines={machines} />
 | 
					            <MachineSelect machines={machines} />
 | 
				
			||||||
            <Button className="gap-2">
 | 
					            <RunWorkflowButton workflow={workflow} machines={machines} />
 | 
				
			||||||
              Run <Play size={14} />
 | 
					 | 
				
			||||||
            </Button>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </CardContent>
 | 
					        </CardContent>
 | 
				
			||||||
      </Card>
 | 
					      </Card>
 | 
				
			||||||
@ -57,8 +96,37 @@ export default async function Page({
 | 
				
			|||||||
          <CardTitle>Run</CardTitle>
 | 
					          <CardTitle>Run</CardTitle>
 | 
				
			||||||
        </CardHeader>
 | 
					        </CardHeader>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <CardContent />
 | 
					        <CardContent>
 | 
				
			||||||
 | 
					          <RunsTable workflow_id={workflow_id} />
 | 
				
			||||||
 | 
					        </CardContent>
 | 
				
			||||||
      </Card>
 | 
					      </Card>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function RunsTable(props: { workflow_id: string }) {
 | 
				
			||||||
 | 
					  const allRuns = await findAllRuns(props.workflow_id);
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Table>
 | 
				
			||||||
 | 
					      <TableCaption>A list of your recent runs.</TableCaption>
 | 
				
			||||||
 | 
					      <TableHeader>
 | 
				
			||||||
 | 
					        <TableRow>
 | 
				
			||||||
 | 
					          <TableHead className="w-[100px]">Version</TableHead>
 | 
				
			||||||
 | 
					          <TableHead>Machine</TableHead>
 | 
				
			||||||
 | 
					          <TableHead>Time</TableHead>
 | 
				
			||||||
 | 
					          <TableHead className="text-right">Status</TableHead>
 | 
				
			||||||
 | 
					        </TableRow>
 | 
				
			||||||
 | 
					      </TableHeader>
 | 
				
			||||||
 | 
					      <TableBody>
 | 
				
			||||||
 | 
					        {allRuns.map((run) => (
 | 
				
			||||||
 | 
					          <TableRow key={run.id}>
 | 
				
			||||||
 | 
					            <TableCell>{run.version.version}</TableCell>
 | 
				
			||||||
 | 
					            <TableCell className="font-medium">{run.machine.name}</TableCell>
 | 
				
			||||||
 | 
					            <TableCell>{getRelativeTime(run.created_at)}</TableCell>
 | 
				
			||||||
 | 
					            <TableCell className="text-right">{run.status}</TableCell>
 | 
				
			||||||
 | 
					          </TableRow>
 | 
				
			||||||
 | 
					        ))}
 | 
				
			||||||
 | 
					      </TableBody>
 | 
				
			||||||
 | 
					    </Table>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,56 +1,106 @@
 | 
				
			|||||||
import { parseDataSafe } from "../../../lib/parseDataSafe";
 | 
					import { parseDataSafe } from "../../../lib/parseDataSafe";
 | 
				
			||||||
import { db } from "@/db/db";
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
import {
 | 
					import { workflowRunsTable } from "@/db/schema";
 | 
				
			||||||
  workflowRunStatus,
 | 
					import { eq } from "drizzle-orm";
 | 
				
			||||||
  workflowRunsTable,
 | 
					import { revalidatePath } from "next/cache";
 | 
				
			||||||
  workflowTable,
 | 
					 | 
				
			||||||
  workflowVersionTable,
 | 
					 | 
				
			||||||
} from "@/db/schema";
 | 
					 | 
				
			||||||
import { eq, sql } from "drizzle-orm";
 | 
					 | 
				
			||||||
import { NextResponse } from "next/server";
 | 
					import { NextResponse } from "next/server";
 | 
				
			||||||
import { ZodFormattedError, z } from "zod";
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Request = z.object({
 | 
					const Request = z.object({
 | 
				
			||||||
  workflow_version_id: z.string(),
 | 
					  workflow_version_id: z.string(),
 | 
				
			||||||
 | 
					  // workflow_version: z.number().optional(),
 | 
				
			||||||
  machine_id: z.string(),
 | 
					  machine_id: z.string(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function OPTIONS(request: Request) {
 | 
					const ComfyAPI_Run = z.object({
 | 
				
			||||||
  return new Response(null, {
 | 
					  prompt_id: z.string(),
 | 
				
			||||||
    status: 204,
 | 
					  number: z.number(),
 | 
				
			||||||
    headers: {
 | 
					  node_errors: z.any(),
 | 
				
			||||||
      "Access-Control-Allow-Origin": "*",
 | 
					});
 | 
				
			||||||
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
 | 
					 | 
				
			||||||
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function POST(request: Request) {
 | 
					export async function POST(request: Request) {
 | 
				
			||||||
  const [data, error] = await parseDataSafe(Request, request);
 | 
					  const [data, error] = await parseDataSafe(Request, request);
 | 
				
			||||||
  if (!data || error) return error;
 | 
					  if (!data || error) return error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let { workflow_version_id, machine_id } = data;
 | 
					  const origin = new URL(request.url).origin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { workflow_version_id, machine_id } = data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const machine = await db.query.machinesTable.findFirst({
 | 
				
			||||||
 | 
					    where: eq(workflowRunsTable.id, machine_id),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!machine) {
 | 
				
			||||||
 | 
					    return new Response("Machine not found", {
 | 
				
			||||||
 | 
					      status: 404,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const workflow_version_data =
 | 
				
			||||||
 | 
					    // workflow_version_id
 | 
				
			||||||
 | 
					    //   ?
 | 
				
			||||||
 | 
					    await db.query.workflowVersionTable.findFirst({
 | 
				
			||||||
 | 
					      where: eq(workflowRunsTable.id, workflow_version_id),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  // : workflow_version != undefined
 | 
				
			||||||
 | 
					  // ? await db.query.workflowVersionTable.findFirst({
 | 
				
			||||||
 | 
					  //     where: and(
 | 
				
			||||||
 | 
					  //       eq(workflowVersionTable.version, workflow_version),
 | 
				
			||||||
 | 
					  //       eq(workflowVersionTable.workflow_id)
 | 
				
			||||||
 | 
					  //     ),
 | 
				
			||||||
 | 
					  //   })
 | 
				
			||||||
 | 
					  // : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!workflow_version_data) {
 | 
				
			||||||
 | 
					    return new Response("Workflow version not found", {
 | 
				
			||||||
 | 
					      status: 404,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const comfyui_endpoint = `${machine.endpoint}/comfy-deploy/run`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Sending to comfyui
 | 
				
			||||||
 | 
					  const result = await fetch(comfyui_endpoint, {
 | 
				
			||||||
 | 
					    method: "POST",
 | 
				
			||||||
 | 
					    // headers: {
 | 
				
			||||||
 | 
					    //   "Content-Type": "application/json",
 | 
				
			||||||
 | 
					    // },
 | 
				
			||||||
 | 
					    body: JSON.stringify({
 | 
				
			||||||
 | 
					      workflow_api: workflow_version_data.workflow_api,
 | 
				
			||||||
 | 
					      status_endpoint: `${origin}/api/update-run`,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					    .then(async (res) => ComfyAPI_Run.parseAsync(await res.json()))
 | 
				
			||||||
 | 
					    .catch((error) => {
 | 
				
			||||||
 | 
					      console.error(error);
 | 
				
			||||||
 | 
					      return new Response(error.details, {
 | 
				
			||||||
 | 
					        status: 500,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // return the error
 | 
				
			||||||
 | 
					  if (result instanceof Response) {
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Add to our db
 | 
				
			||||||
  const workflow_run = await db
 | 
					  const workflow_run = await db
 | 
				
			||||||
    .insert(workflowRunsTable)
 | 
					    .insert(workflowRunsTable)
 | 
				
			||||||
    .values({
 | 
					    .values({
 | 
				
			||||||
      workflow_version_id,
 | 
					      id: result.prompt_id,
 | 
				
			||||||
 | 
					      workflow_version_id: workflow_version_data.id,
 | 
				
			||||||
      machine_id,
 | 
					      machine_id,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .returning();
 | 
					    .returning();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  revalidatePath(`./${workflow_version_data.workflow_id}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return NextResponse.json(
 | 
					  return NextResponse.json(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      workflow_run_id: workflow_run[0].id,
 | 
					      workflow_run_id: workflow_run[0].id,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      status: 200,
 | 
					      status: 200,
 | 
				
			||||||
      headers: {
 | 
					    }
 | 
				
			||||||
        "Access-Control-Allow-Origin": "*",
 | 
					 | 
				
			||||||
        "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
 | 
					 | 
				
			||||||
        "Access-Control-Allow-Headers": "Content-Type, Authorization",
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import { parseDataSafe } from "../../../lib/parseDataSafe";
 | 
				
			|||||||
import { db } from "@/db/db";
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
import { workflowRunsTable } from "@/db/schema";
 | 
					import { workflowRunsTable } from "@/db/schema";
 | 
				
			||||||
import { eq } from "drizzle-orm";
 | 
					import { eq } from "drizzle-orm";
 | 
				
			||||||
 | 
					import { revalidatePath } from "next/cache";
 | 
				
			||||||
import { NextResponse } from "next/server";
 | 
					import { NextResponse } from "next/server";
 | 
				
			||||||
import { z } from "zod";
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -10,17 +11,6 @@ const Request = z.object({
 | 
				
			|||||||
  status: z.enum(["not-started", "running", "success", "failed"]),
 | 
					  status: z.enum(["not-started", "running", "success", "failed"]),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function OPTIONS(request: Request) {
 | 
					 | 
				
			||||||
  return new Response(null, {
 | 
					 | 
				
			||||||
    status: 204,
 | 
					 | 
				
			||||||
    headers: {
 | 
					 | 
				
			||||||
      "Access-Control-Allow-Origin": "*",
 | 
					 | 
				
			||||||
      "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
 | 
					 | 
				
			||||||
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function POST(request: Request) {
 | 
					export async function POST(request: Request) {
 | 
				
			||||||
  const [data, error] = await parseDataSafe(Request, request);
 | 
					  const [data, error] = await parseDataSafe(Request, request);
 | 
				
			||||||
  if (!data || error) return error;
 | 
					  if (!data || error) return error;
 | 
				
			||||||
@ -32,7 +22,14 @@ export async function POST(request: Request) {
 | 
				
			|||||||
    .set({
 | 
					    .set({
 | 
				
			||||||
      status: status,
 | 
					      status: status,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .where(eq(workflowRunsTable.id, run_id));
 | 
					    .where(eq(workflowRunsTable.id, run_id))
 | 
				
			||||||
 | 
					    .returning();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const workflow_version = await db.query.workflowVersionTable.findFirst({
 | 
				
			||||||
 | 
					    where: eq(workflowRunsTable.id, workflow_run[0].workflow_version_id),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  revalidatePath(`./${workflow_version?.workflow_id}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return NextResponse.json(
 | 
					  return NextResponse.json(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -40,11 +37,6 @@ export async function POST(request: Request) {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      status: 200,
 | 
					      status: 200,
 | 
				
			||||||
      headers: {
 | 
					 | 
				
			||||||
        "Access-Control-Allow-Origin": "*",
 | 
					 | 
				
			||||||
        "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
 | 
					 | 
				
			||||||
        "Access-Control-Allow-Headers": "Content-Type, Authorization",
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { WorkflowList } from "@/components/WorkflowList";
 | 
				
			|||||||
import { db } from "@/db/db";
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
import { usersTable, workflowTable, workflowVersionTable } from "@/db/schema";
 | 
					import { usersTable, workflowTable, workflowVersionTable } from "@/db/schema";
 | 
				
			||||||
import { auth, clerkClient } from "@clerk/nextjs";
 | 
					import { auth, clerkClient } from "@clerk/nextjs";
 | 
				
			||||||
import { desc, eq, sql } from "drizzle-orm";
 | 
					import { desc, eq } from "drizzle-orm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function Home() {
 | 
					export default function Home() {
 | 
				
			||||||
  return <WorkflowServer />;
 | 
					  return <WorkflowServer />;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								web/src/components/LoadingIcon.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								web/src/components/LoadingIcon.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					import { LoaderIcon } from "lucide-react";
 | 
				
			||||||
 | 
					import * as React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function LoadingIcon() {
 | 
				
			||||||
 | 
					  return <LoaderIcon size={14} className="ml-2 animate-spin" />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { getRelativeTime } from "../lib/getRelativeTime";
 | 
					import { getRelativeTime } from "../lib/getRelativeTime";
 | 
				
			||||||
 | 
					import { LoadingIcon } from "./LoadingIcon";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  FormControl,
 | 
					  FormControl,
 | 
				
			||||||
  FormDescription,
 | 
					 | 
				
			||||||
  FormField,
 | 
					  FormField,
 | 
				
			||||||
  FormItem,
 | 
					  FormItem,
 | 
				
			||||||
  FormLabel,
 | 
					  FormLabel,
 | 
				
			||||||
@ -23,15 +23,12 @@ import {
 | 
				
			|||||||
} from "@/components/ui/dialog";
 | 
					} from "@/components/ui/dialog";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  DropdownMenu,
 | 
					  DropdownMenu,
 | 
				
			||||||
  DropdownMenuCheckboxItem,
 | 
					 | 
				
			||||||
  DropdownMenuContent,
 | 
					  DropdownMenuContent,
 | 
				
			||||||
  DropdownMenuItem,
 | 
					  DropdownMenuItem,
 | 
				
			||||||
  DropdownMenuLabel,
 | 
					  DropdownMenuLabel,
 | 
				
			||||||
  DropdownMenuSeparator,
 | 
					 | 
				
			||||||
  DropdownMenuTrigger,
 | 
					  DropdownMenuTrigger,
 | 
				
			||||||
} from "@/components/ui/dropdown-menu";
 | 
					} from "@/components/ui/dropdown-menu";
 | 
				
			||||||
import { Input } from "@/components/ui/input";
 | 
					import { Input } from "@/components/ui/input";
 | 
				
			||||||
import { Label } from "@/components/ui/label";
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Table,
 | 
					  Table,
 | 
				
			||||||
  TableBody,
 | 
					  TableBody,
 | 
				
			||||||
@ -41,7 +38,6 @@ import {
 | 
				
			|||||||
  TableRow,
 | 
					  TableRow,
 | 
				
			||||||
} from "@/components/ui/table";
 | 
					} from "@/components/ui/table";
 | 
				
			||||||
import { addMachine, deleteMachine } from "@/server/curdMachine";
 | 
					import { addMachine, deleteMachine } from "@/server/curdMachine";
 | 
				
			||||||
import { deleteWorkflow } from "@/server/deleteWorkflow";
 | 
					 | 
				
			||||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
					import { zodResolver } from "@hookform/resolvers/zod";
 | 
				
			||||||
import type {
 | 
					import type {
 | 
				
			||||||
  ColumnDef,
 | 
					  ColumnDef,
 | 
				
			||||||
@ -57,14 +53,8 @@ import {
 | 
				
			|||||||
  getSortedRowModel,
 | 
					  getSortedRowModel,
 | 
				
			||||||
  useReactTable,
 | 
					  useReactTable,
 | 
				
			||||||
} from "@tanstack/react-table";
 | 
					} from "@tanstack/react-table";
 | 
				
			||||||
import {
 | 
					import { ArrowUpDown, MoreHorizontal } from "lucide-react";
 | 
				
			||||||
  ArrowUpDown,
 | 
					 | 
				
			||||||
  ChevronDown,
 | 
					 | 
				
			||||||
  LoaderIcon,
 | 
					 | 
				
			||||||
  MoreHorizontal,
 | 
					 | 
				
			||||||
} from "lucide-react";
 | 
					 | 
				
			||||||
import * as React from "react";
 | 
					import * as React from "react";
 | 
				
			||||||
import { useFormStatus } from "react-dom";
 | 
					 | 
				
			||||||
import { useForm } from "react-hook-form";
 | 
					import { useForm } from "react-hook-form";
 | 
				
			||||||
import { z } from "zod";
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -145,7 +135,9 @@ export const columns: ColumnDef<Machine>[] = [
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    cell: ({ row }) => (
 | 
					    cell: ({ row }) => (
 | 
				
			||||||
      <div className="capitalize text-right">{getRelativeTime(row.original.date)}</div>
 | 
					      <div className="capitalize text-right">
 | 
				
			||||||
 | 
					        {getRelativeTime(row.original.date)}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -187,7 +179,7 @@ export const columns: ColumnDef<Machine>[] = [
 | 
				
			|||||||
export function MachineList({ data }: { data: Machine[] }) {
 | 
					export function MachineList({ data }: { data: Machine[] }) {
 | 
				
			||||||
  const [sorting, setSorting] = React.useState<SortingState>([]);
 | 
					  const [sorting, setSorting] = React.useState<SortingState>([]);
 | 
				
			||||||
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
 | 
					  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
 | 
				
			||||||
    [],
 | 
					    []
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  const [columnVisibility, setColumnVisibility] =
 | 
					  const [columnVisibility, setColumnVisibility] =
 | 
				
			||||||
    React.useState<VisibilityState>({});
 | 
					    React.useState<VisibilityState>({});
 | 
				
			||||||
@ -265,7 +257,7 @@ export function MachineList({ data }: { data: Machine[] }) {
 | 
				
			|||||||
                        ? null
 | 
					                        ? null
 | 
				
			||||||
                        : flexRender(
 | 
					                        : flexRender(
 | 
				
			||||||
                            header.column.columnDef.header,
 | 
					                            header.column.columnDef.header,
 | 
				
			||||||
                            header.getContext(),
 | 
					                            header.getContext()
 | 
				
			||||||
                          )}
 | 
					                          )}
 | 
				
			||||||
                    </TableHead>
 | 
					                    </TableHead>
 | 
				
			||||||
                  );
 | 
					                  );
 | 
				
			||||||
@ -284,7 +276,7 @@ export function MachineList({ data }: { data: Machine[] }) {
 | 
				
			|||||||
                    <TableCell key={cell.id}>
 | 
					                    <TableCell key={cell.id}>
 | 
				
			||||||
                      {flexRender(
 | 
					                      {flexRender(
 | 
				
			||||||
                        cell.column.columnDef.cell,
 | 
					                        cell.column.columnDef.cell,
 | 
				
			||||||
                        cell.getContext(),
 | 
					                        cell.getContext()
 | 
				
			||||||
                      )}
 | 
					                      )}
 | 
				
			||||||
                    </TableCell>
 | 
					                    </TableCell>
 | 
				
			||||||
                  ))}
 | 
					                  ))}
 | 
				
			||||||
@ -418,8 +410,7 @@ function AddWorkflowButton({ pending }: { pending: boolean }) {
 | 
				
			|||||||
  // const { pending } = useFormStatus();
 | 
					  // const { pending } = useFormStatus();
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Button type="submit" disabled={pending}>
 | 
					    <Button type="submit" disabled={pending}>
 | 
				
			||||||
      Save changes{" "}
 | 
					      Save changes {pending && <LoadingIcon />}
 | 
				
			||||||
      {pending && <LoaderIcon size={14} className="ml-2 animate-spin" />}
 | 
					 | 
				
			||||||
    </Button>
 | 
					    </Button>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { findFirstTableWithVersion } from "@/app/[workflow_id]/page";
 | 
					import type { findFirstTableWithVersion } from "@/app/[workflow_id]/page";
 | 
				
			||||||
 | 
					import { LoadingIcon } from "@/components/LoadingIcon";
 | 
				
			||||||
 | 
					import { Button } from "@/components/ui/button";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Select,
 | 
					  Select,
 | 
				
			||||||
  SelectContent,
 | 
					  SelectContent,
 | 
				
			||||||
@ -10,15 +12,26 @@ import {
 | 
				
			|||||||
  SelectTrigger,
 | 
					  SelectTrigger,
 | 
				
			||||||
  SelectValue,
 | 
					  SelectValue,
 | 
				
			||||||
} from "@/components/ui/select";
 | 
					} from "@/components/ui/select";
 | 
				
			||||||
import { getMachines } from "@/server/curdMachine";
 | 
					import type { getMachines } from "@/server/curdMachine";
 | 
				
			||||||
 | 
					import { Play } from "lucide-react";
 | 
				
			||||||
 | 
					import { parseAsInteger, useQueryState } from "next-usequerystate";
 | 
				
			||||||
 | 
					import { useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function VersionSelect({
 | 
					export function VersionSelect({
 | 
				
			||||||
  workflow,
 | 
					  workflow,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
 | 
					  workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
 | 
					  const [version, setVersion] = useQueryState("version", {
 | 
				
			||||||
 | 
					    defaultValue: workflow?.versions[0].version?.toString() ?? "",
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Select defaultValue={workflow?.versions[0].version?.toString()}>
 | 
					    <Select
 | 
				
			||||||
 | 
					      value={version}
 | 
				
			||||||
 | 
					      onValueChange={(v) => {
 | 
				
			||||||
 | 
					        setVersion(v);
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
      <SelectTrigger className="w-[180px]">
 | 
					      <SelectTrigger className="w-[180px]">
 | 
				
			||||||
        <SelectValue placeholder="Select a version" />
 | 
					        <SelectValue placeholder="Select a version" />
 | 
				
			||||||
      </SelectTrigger>
 | 
					      </SelectTrigger>
 | 
				
			||||||
@ -26,7 +39,7 @@ export function VersionSelect({
 | 
				
			|||||||
        <SelectGroup>
 | 
					        <SelectGroup>
 | 
				
			||||||
          <SelectLabel>Versions</SelectLabel>
 | 
					          <SelectLabel>Versions</SelectLabel>
 | 
				
			||||||
          {workflow?.versions.map((x) => (
 | 
					          {workflow?.versions.map((x) => (
 | 
				
			||||||
            <SelectItem value={x.version?.toString() ?? ""}>
 | 
					            <SelectItem key={x.id} value={x.version?.toString() ?? ""}>
 | 
				
			||||||
              {x.version}
 | 
					              {x.version}
 | 
				
			||||||
            </SelectItem>
 | 
					            </SelectItem>
 | 
				
			||||||
          ))}
 | 
					          ))}
 | 
				
			||||||
@ -36,28 +49,76 @@ export function VersionSelect({
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
export function MachineSelect({
 | 
					export function MachineSelect({
 | 
				
			||||||
    machines,
 | 
					  machines,
 | 
				
			||||||
  }: {
 | 
					}: {
 | 
				
			||||||
    machines: Awaited<ReturnType<typeof getMachines>>;
 | 
					  machines: Awaited<ReturnType<typeof getMachines>>;
 | 
				
			||||||
  }) {
 | 
					}) {
 | 
				
			||||||
    return (
 | 
					  const [machine, setMachine] = useQueryState("machine", {
 | 
				
			||||||
      <Select defaultValue={machines[0].id}>
 | 
					    defaultValue: machines[0].id ?? "",
 | 
				
			||||||
        <SelectTrigger className="w-[180px]">
 | 
					  });
 | 
				
			||||||
          <SelectValue placeholder="Select a version" />
 | 
					  return (
 | 
				
			||||||
        </SelectTrigger>
 | 
					    <Select
 | 
				
			||||||
        <SelectContent>
 | 
					      value={machine}
 | 
				
			||||||
          <SelectGroup>
 | 
					      onValueChange={(v) => {
 | 
				
			||||||
            <SelectLabel>Versions</SelectLabel>
 | 
					        setMachine(v);
 | 
				
			||||||
            {machines?.map((x) => (
 | 
					      }}
 | 
				
			||||||
              <SelectItem value={x.id ?? ""}>
 | 
					    >
 | 
				
			||||||
                {x.name}
 | 
					      <SelectTrigger className="w-[180px]">
 | 
				
			||||||
              </SelectItem>
 | 
					        <SelectValue placeholder="Select a version" />
 | 
				
			||||||
            ))}
 | 
					      </SelectTrigger>
 | 
				
			||||||
          </SelectGroup>
 | 
					      <SelectContent>
 | 
				
			||||||
        </SelectContent>
 | 
					        <SelectGroup>
 | 
				
			||||||
      </Select>
 | 
					          <SelectLabel>Versions</SelectLabel>
 | 
				
			||||||
    );
 | 
					          {machines?.map((x) => (
 | 
				
			||||||
  }
 | 
					            <SelectItem key={x.id} value={x.id ?? ""}>
 | 
				
			||||||
  
 | 
					              {x.name}
 | 
				
			||||||
 | 
					            </SelectItem>
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					        </SelectGroup>
 | 
				
			||||||
 | 
					      </SelectContent>
 | 
				
			||||||
 | 
					    </Select>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function RunWorkflowButton({
 | 
				
			||||||
 | 
					  workflow,
 | 
				
			||||||
 | 
					  machines,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  workflow: Awaited<ReturnType<typeof findFirstTableWithVersion>>;
 | 
				
			||||||
 | 
					  machines: Awaited<ReturnType<typeof getMachines>>;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const [version] = useQueryState("version", {
 | 
				
			||||||
 | 
					    defaultValue: workflow?.versions[0].version ?? 1,
 | 
				
			||||||
 | 
					    ...parseAsInteger,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const [machine] = useQueryState("machine", {
 | 
				
			||||||
 | 
					    defaultValue: machines[0].id ?? "",
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const [isLoading, setIsLoading] = useState(false);
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Button
 | 
				
			||||||
 | 
					      className="gap-2"
 | 
				
			||||||
 | 
					      disabled={isLoading}
 | 
				
			||||||
 | 
					      onClick={async () => {
 | 
				
			||||||
 | 
					        setIsLoading(true);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          await fetch(`/api/create-run`, {
 | 
				
			||||||
 | 
					            method: "POST",
 | 
				
			||||||
 | 
					            body: JSON.stringify({
 | 
				
			||||||
 | 
					              workflow_version_id: workflow?.versions.find(
 | 
				
			||||||
 | 
					                (x) => x.version === version
 | 
				
			||||||
 | 
					              )?.id,
 | 
				
			||||||
 | 
					              machine_id: machine,
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          setIsLoading(false);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          setIsLoading(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      Run <Play size={14} /> {isLoading && <LoadingIcon />}
 | 
				
			||||||
 | 
					    </Button>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,21 +17,8 @@ export const usersTable = dbSchema.table("users", {
 | 
				
			|||||||
  name: text("name").notNull(),
 | 
					  name: text("name").notNull(),
 | 
				
			||||||
  created_at: timestamp("created_at").defaultNow(),
 | 
					  created_at: timestamp("created_at").defaultNow(),
 | 
				
			||||||
  updated_at: timestamp("updated_at").defaultNow(),
 | 
					  updated_at: timestamp("updated_at").defaultNow(),
 | 
				
			||||||
  // primary_avatar_id: uuid("primary_avatar_id").references(
 | 
					 | 
				
			||||||
  //   () => chatAvatarTable.id,
 | 
					 | 
				
			||||||
  // ),
 | 
					 | 
				
			||||||
  // twitter_initial_json: jsonb("twitter_initial_json").$type<
 | 
					 | 
				
			||||||
  //   Omit<UserV2Result, "errors">
 | 
					 | 
				
			||||||
  // >(),
 | 
					 | 
				
			||||||
  // initial_prompt: text("initial_prompt"),
 | 
					 | 
				
			||||||
  // payment_status: text("payment_status"),
 | 
					 | 
				
			||||||
  // early_access: boolean("early_access").default(false),
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// export const usersRelations = relations(userTable, ({ many }) => ({
 | 
					 | 
				
			||||||
//   chat_avatars: many(chatAvatarTable),
 | 
					 | 
				
			||||||
// }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const workflowTable = dbSchema.table("workflows", {
 | 
					export const workflowTable = dbSchema.table("workflows", {
 | 
				
			||||||
  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
					  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
				
			||||||
  user_id: text("user_id")
 | 
					  user_id: text("user_id")
 | 
				
			||||||
@ -70,7 +57,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", [
 | 
				
			||||||
@ -98,45 +85,30 @@ export const workflowRunsTable = dbSchema.table("workflow_runs", {
 | 
				
			|||||||
  created_at: timestamp("created_at").defaultNow().notNull(),
 | 
					  created_at: timestamp("created_at").defaultNow().notNull(),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const workflowRunRelations = relations(workflowRunsTable, ({ one }) => ({
 | 
				
			||||||
 | 
					  machine: one(machinesTable, {
 | 
				
			||||||
 | 
					    fields: [workflowRunsTable.machine_id],
 | 
				
			||||||
 | 
					    references: [machinesTable.id],
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  version: one(workflowVersionTable, {
 | 
				
			||||||
 | 
					    fields: [workflowRunsTable.workflow_version_id],
 | 
				
			||||||
 | 
					    references: [workflowVersionTable.id],
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// when user delete, also delete all the workflow versions
 | 
					// when user delete, also delete all the workflow versions
 | 
				
			||||||
export const machinesTable = dbSchema.table("machines", {
 | 
					export const machinesTable = dbSchema.table("machines", {
 | 
				
			||||||
  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
					  id: uuid("id").primaryKey().defaultRandom().notNull(),
 | 
				
			||||||
  user_id: text("user_id").references(() => usersTable.id, {
 | 
					  user_id: text("user_id")
 | 
				
			||||||
    onDelete: "no action",
 | 
					    .references(() => usersTable.id, {
 | 
				
			||||||
  }).notNull(),
 | 
					      onDelete: "no action",
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .notNull(),
 | 
				
			||||||
  name: text("name").notNull(),
 | 
					  name: text("name").notNull(),
 | 
				
			||||||
  endpoint: text("endpoint").notNull(),
 | 
					  endpoint: text("endpoint").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 chatAvatarRelations = relations(chatAvatarTable, ({ one }) => ({
 | 
					 | 
				
			||||||
//   author: one(userTable, {
 | 
					 | 
				
			||||||
//     fields: [chatAvatarTable.user_id],
 | 
					 | 
				
			||||||
//     references: [userTable.id],
 | 
					 | 
				
			||||||
//   }),
 | 
					 | 
				
			||||||
// }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// export const subscriptionTable = dbSchema.table("subscription", {
 | 
					 | 
				
			||||||
//   id: text("id").primaryKey().notNull(),
 | 
					 | 
				
			||||||
//   email: text("email"),
 | 
					 | 
				
			||||||
//   user_id: text("user_id"),
 | 
					 | 
				
			||||||
//   status: text("status"),
 | 
					 | 
				
			||||||
//   created_at: timestamp("created_at").defaultNow(),
 | 
					 | 
				
			||||||
//   updated_at: timestamp("updated_at").defaultNow(),
 | 
					 | 
				
			||||||
// });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// export const subscriptionRelations = relations(
 | 
					 | 
				
			||||||
//   subscriptionTable,
 | 
					 | 
				
			||||||
//   ({ one }) => ({
 | 
					 | 
				
			||||||
//     user: one(userTable, {
 | 
					 | 
				
			||||||
//       fields: [subscriptionTable.user_id],
 | 
					 | 
				
			||||||
//       references: [userTable.id],
 | 
					 | 
				
			||||||
//     }),
 | 
					 | 
				
			||||||
//   }),
 | 
					 | 
				
			||||||
// );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 ChatAvatarType = InferSelectModel<typeof chatAvatarTable>;
 | 
					 | 
				
			||||||
// export type SubscriptionType = InferSelectModel<typeof subscriptionTable>;
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
import { NextResponse } from "next/server";
 | 
					import { NextResponse } from "next/server";
 | 
				
			||||||
import { ZodError, ZodType, z } from "zod";
 | 
					import type { ZodType, z } from "zod";
 | 
				
			||||||
 | 
					import { ZodError } from "zod";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function parseDataSafe<T extends ZodType<any, any, any>>(
 | 
					export async function parseDataSafe<T extends ZodType<any, any, any>>(
 | 
				
			||||||
  schema: T,
 | 
					  schema: T,
 | 
				
			||||||
  request: Request,
 | 
					  request: Request,
 | 
				
			||||||
  headers?: HeadersInit,
 | 
					  headers?: HeadersInit
 | 
				
			||||||
): Promise<[z.infer<T> | undefined, NextResponse | undefined]> {
 | 
					): Promise<[z.infer<T> | undefined, NextResponse | undefined]> {
 | 
				
			||||||
  let data: z.infer<T> | undefined = undefined;
 | 
					  let data: z.infer<T> | undefined = undefined;
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@ -30,7 +31,7 @@ export async function parseDataSafe<T extends ZodType<any, any, any>>(
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
          message: "Invalid request",
 | 
					          message: "Invalid request",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        { status: 500, statusText: "Invalid request", headers: headers },
 | 
					        { status: 500, statusText: "Invalid request", headers: headers }
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user