More progress

This commit is contained in:
cte 2025-03-21 14:47:53 -07:00
parent e4d5ea9bae
commit 9f23b63b05
22 changed files with 738 additions and 122 deletions

View file

@ -11,8 +11,7 @@
"dependencies": {
"@benchmark/db": "workspace:^",
"@vscode/test-electron": "^2.4.0",
"gluegun": "^5.1.2",
"uuid": "^11.1.0"
"gluegun": "^5.1.2"
},
"devDependencies": {
"@benchmark/eslint-config": "workspace:^",

View file

@ -2,7 +2,6 @@ import * as fs from "fs"
import * as path from "path"
import * as os from "os"
import { v4 as uuidv4 } from "uuid"
import { build, filesystem, GluegunPrompt, GluegunToolbox } from "gluegun"
import { runTests } from "@vscode/test-electron"
@ -152,7 +151,11 @@ const askExercise = async (prompt: GluegunPrompt, language: Language) => {
const findOrCreateRun = async ({ id, model = "anthropic/claude-3.7-sonnet" }: { id?: number; model?: string }) =>
id
? findRun(id)
: createRun({ model, pid: process.pid, socketPath: path.resolve(os.tmpdir(), `benchmark-${uuidv4()}.sock`) })
: createRun({
model,
pid: process.pid,
socketPath: path.resolve(os.tmpdir(), `benchmark-${crypto.randomUUID()}.sock`),
})
const findOrCreatePendingTask = async ({
runId,

View file

@ -14,6 +14,7 @@
"@benchmark/ipc": "workspace:^",
"@hookform/resolvers": "^4.1.3",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-query": "^5.69.0",
@ -25,9 +26,9 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"react-use": "^17.6.0",
"tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7",
"uuid": "^11.1.0",
"zod": "^3.24.2"
},
"devDependencies": {

View file

@ -1,5 +1,4 @@
import type { NextRequest } from "next/server"
import { v4 as uuidv4 } from "uuid"
import { findRun } from "@benchmark/db"
import { IpcClient } from "@benchmark/ipc"
@ -10,7 +9,7 @@ export const dynamic = "force-dynamic"
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const requestId = uuidv4()
const requestId = crypto.randomUUID()
const stream = new SSEStream()
const run = await findRun(Number(id))
const client = new IpcClient(run.socketPath, () => {})

View file

@ -0,0 +1,37 @@
import type { NextRequest } from "next/server"
import { findRun } from "@benchmark/db"
import { IpcClient } from "@benchmark/ipc"
import { SSEStream } from "@/lib/server/sse-stream"
export const dynamic = "force-dynamic"
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const requestId = crypto.randomUUID()
const stream = new SSEStream()
const run = await findRun(Number(id))
const client = new IpcClient(run.socketPath, () => {})
const write = async (data: string | object) => {
const success = await stream.write(data)
if (!success) {
client.disconnect()
}
}
console.log(`[stream#${requestId}] connect`)
client.on("connect", () => write("connect"))
client.on("disconnect", () => write("disconnect"))
client.on("message", write)
request.signal.addEventListener("abort", () => {
console.log(`[stream#${requestId}] abort`)
client.disconnect()
stream.close().catch(() => {})
})
return stream.getResponse()
}

View file

@ -109,6 +109,20 @@
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--animate-hop: hop 0.8s ease-in-out infinite;
@keyframes hop {
0%,
100% {
transform: none;
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
50% {
transform: translateY(-8px);
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
}
}
@layer base {

View file

@ -5,8 +5,8 @@ import { Rocket } from "lucide-react"
import { getRuns } from "@benchmark/db"
import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"
import { formatCurrency, formatDuration } from "@/lib"
import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"
type Run = Awaited<ReturnType<typeof getRuns>>[number]
@ -15,39 +15,37 @@ export function Home({ runs }: { runs: Run[] }) {
return (
<>
<div className="max-w-4xl px-12 mx-auto py-24">
<Table className="border">
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Model</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead>Passed</TableHead>
<TableHead>Failed</TableHead>
<TableHead>% Correct</TableHead>
<TableHead>Cost</TableHead>
<TableHead>Duration</TableHead>
<Table className="border">
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Model</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead>Passed</TableHead>
<TableHead>Failed</TableHead>
<TableHead>% Correct</TableHead>
<TableHead>Cost</TableHead>
<TableHead>Duration</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{runs.map((run) => (
<TableRow key={run.id}>
<TableCell>{run.id}</TableCell>
<TableCell>{run.model}</TableCell>
<TableCell>{new Date(run.createdAt).toLocaleString()}</TableCell>
<TableCell>{run.passed}</TableCell>
<TableCell>{run.failed}</TableCell>
<TableCell>{(run.rate * 100).toFixed(1)}%</TableCell>
<TableCell>{formatCurrency(run.cost)}</TableCell>
<TableCell>{formatDuration(run.duration)}</TableCell>
</TableRow>
</TableHeader>
<TableBody>
{runs.map((run) => (
<TableRow key={run.id}>
<TableCell>{run.id}</TableCell>
<TableCell>{run.model}</TableCell>
<TableCell>{new Date(run.createdAt).toLocaleString()}</TableCell>
<TableCell>{run.passed}</TableCell>
<TableCell>{run.failed}</TableCell>
<TableCell>{(run.rate * 100).toFixed(1)}%</TableCell>
<TableCell>{formatCurrency(run.cost)}</TableCell>
<TableCell>{formatDuration(run.duration)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
))}
</TableBody>
</Table>
<Button
variant="default"
className="absolute top-5 right-5 size-12 rounded-full"
className="absolute top-4 right-12 size-12 rounded-full"
onClick={() => router.push("/runs/new")}>
<Rocket className="size-6" />
</Button>

View file

@ -2,6 +2,7 @@ import type { Metadata } from "next"
import { Geist, Geist_Mono } from "next/font/google"
import { ThemeProvider, ReactQueryProvider } from "@/components/providers"
import { Header } from "@/components/layout/header"
import "./globals.css"
@ -21,7 +22,10 @@ export default function RootLayout({
<html lang="en">
<body className={`${fontSans.variable} ${fontMono.variable} font-sans antialiased`}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
<ReactQueryProvider>{children}</ReactQueryProvider>
<ReactQueryProvider>
<Header />
<div className="max-w-3xl mx-auto">{children}</div>
</ReactQueryProvider>
</ThemeProvider>
</body>
</html>

View file

@ -1,14 +1,16 @@
import { findRun } from "@benchmark/db"
import { findRun, getTasks, getPendingTasks } from "@benchmark/db"
import { ShowRun } from "./show-run"
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const run = await findRun(Number(id))
const tasks = await getTasks(run.id)
const pendingTasks = await getPendingTasks(run.id)
if (!run) {
return <div>Run not found</div>
}
return <ShowRun run={run} />
return <ShowRun run={{ ...run, tasks, pendingTasks }} />
}

View file

@ -1,19 +1,156 @@
"use client"
import { useCallback } from "react"
import { useCallback, useState } from "react"
import { z } from "zod"
import { CircleDashed, CircleCheck, CircleSlash, LoaderCircle, Dot } from "lucide-react"
import { Run } from "@benchmark/db"
import { type Run, type PendingTask, Task } from "@benchmark/db"
import { useEventSource } from "@/hooks/use-event-source"
import { cn } from "@/lib/utils"
export function ShowRun({ run }: { run: Run }) {
const messageSchema = z.object({
type: z.enum(["hello", "data"]),
data: z.record(z.string(), z.unknown()),
})
const taskEventSchema = z.discriminatedUnion("event", [
// {"event":"client","runId":4,"language":"cpp","exercise":"queen-attack","prompt":"Your job is to complete a coding exercise described by `.docs/instructions.md`...","workspacePath":"/Users/cte/Documents/exercises/cpp/queen-attack"}
z.object({
event: z.literal("client"),
runId: z.number(),
language: z.string(),
exercise: z.string(),
prompt: z.string(),
workspacePath: z.string(),
}),
// {"event":"message","taskId":"acfdc39d-2c21-484b-9fb0-c6670b1a7439","action":"updated","message":{"ts":1742574782085,"type":"say","say":"text","text":"Great! All the tests have passed...","partial":true}}
z.object({
event: z.literal("message"),
taskId: z.string(),
action: z.enum(["created", "updated"]),
message: z.object({
ts: z.number(),
type: z.enum(["ask", "say"]),
text: z.string(),
partial: z.boolean(),
}),
}),
// {"event":"taskTokenUsageUpdated","taskId":"acfdc39d-2c21-484b-9fb0-c6670b1a7439","usage":{"totalTokensIn":102069,"totalTokensOut":1700,"totalCacheWrites":0,"totalCacheReads":0,"totalCost":0.1212111,"contextTokens":19286}}
z.object({
event: z.literal("taskTokenUsageUpdated"),
taskId: z.string(),
usage: z.object({
totalTokensIn: z.number(),
totalTokensOut: z.number(),
totalCacheWrites: z.number(),
totalCacheReads: z.number(),
totalCost: z.number(),
contextTokens: z.number(),
}),
}),
])
type CurrentRun = Run & { tasks: Task[]; pendingTasks: PendingTask[] }
export function ShowRun({ run }: { run: CurrentRun }) {
const [clientId, setClientId] = useState<string>()
const [runningTask, setRunningTask] = useState<string>()
const url = `/api/runs/${run.id}/stream`
const onMessage = useCallback(({ data }: MessageEvent) => console.log(data), [])
const onMessage = useCallback((messageEvent: MessageEvent) => {
// console.log(messageEvent.data)
let data
try {
data = JSON.parse(messageEvent.data)
} catch (_) {
// console.log(`invalid JSON: ${messageEvent.data}`)
return
}
const result = messageSchema.safeParse(data)
if (!result.success) {
// console.log(`unrecognized messageEvent.data: ${messageEvent.data}`)
return
}
const payload = result.data
if (payload.type === "hello") {
setClientId(payload.data.clientId as string)
} else if (payload.type === "data") {
const taskEvent = taskEventSchema.safeParse(payload.data)
if (!taskEvent.success) {
console.log(`unrecognized payload.data`, payload.data, taskEvent.error)
return
}
if (taskEvent.data.event === "client") {
console.log(`client`, taskEvent.data)
const { language, exercise } = taskEvent.data
setRunningTask(`${language}/${exercise}`)
} else if (taskEvent.data.event === "message") {
console.log(`message: ${taskEvent.data.message.text}`)
} else if (taskEvent.data.event === "taskTokenUsageUpdated") {
console.log(`taskTokenUsageUpdated: ${taskEvent.data.usage}`)
}
}
}, [])
const status = useEventSource({ url, onMessage })
const pendingTasks = run.pendingTasks.filter((pendingTask) => !run.tasks.find((task) => task.id === pendingTask.id))
return (
<div>
Show Run: {run.id} | {status}
</div>
<>
<div className="flex flex-col gap-2">
<div className="border-b mb-2 pb-2">
<div>Run #{run.id}</div>
<div>{run.model}</div>
{run.description && <div className="text-sm text-muted-foreground">{run.description}</div>}
</div>
{run.tasks.map((task) => (
<div key={task.id} className="flex items-center gap-2">
{task.passed ? (
<CircleCheck className="size-4 text-green-500" />
) : (
<CircleSlash className="size-4 text-destructive" />
)}
<div>
{task.language}/{task.exercise}
</div>
</div>
))}
{pendingTasks.map((task) => (
<div key={task.id} className="flex items-center gap-2">
{runningTask === `${task.language}/${task.exercise}` ? (
<LoaderCircle className="size-4 animate-spin" />
) : (
<CircleDashed className="size-4" />
)}
<div>
{task.language}/{task.exercise}
</div>
</div>
))}
</div>
<div className="absolute top-5 right-5">
<div className="flex items-center gap-2">
<div className="animate-ping">
<div
className={cn("size-2 rounded-full", {
"bg-green-500": status === "open",
"bg-amber-300": status === "init",
"bg-rose-500": status === "error",
})}
/>
</div>
<div className="font-mono text-xs">{clientId}</div>
</div>
</div>
</>
)
}

View file

@ -1,12 +1,16 @@
"use server"
import { spawn } from "child_process"
import path from "path"
import os from "os"
import { revalidatePath } from "next/cache"
import * as db from "@benchmark/db"
export async function createRun(data: db.InsertRun) {
const run = await db.createRun(data)
export async function createRun(data: Omit<db.InsertRun, "socketPath">) {
const socketPath = path.join(os.tmpdir(), `benchmark-${crypto.randomUUID()}.sock`)
const run = await db.createRun({ ...data, socketPath })
revalidatePath("/runs")
try {
@ -20,8 +24,10 @@ export async function createRun(data: db.InsertRun) {
)
process.unref()
return process.pid
} catch (_) {
return undefined
await db.updateRun(run.id, { pid: process.pid })
} catch (error) {
console.error(error)
}
return run
}

View file

@ -51,8 +51,7 @@ export function NewRun() {
try {
const run = await createRun(data)
router.push(`/runs/${run.id}`)
} catch (error) {
console.error("Error creating run:", error)
} catch (_) {
setIsSubmitting(false)
}
}
@ -60,10 +59,7 @@ export function NewRun() {
return (
<>
<FormProvider {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col justify-center gap-6 h-dvh max-w-xl mx-auto">
<div>__dirname: {__dirname}</div>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col justify-center gap-6">
<FormField
control={form.control}
name="model"
@ -117,7 +113,7 @@ export function NewRun() {
</FormProvider>
<Button
variant="default"
className="absolute top-5 right-5 size-12 rounded-full"
className="absolute top-4 right-12 size-12 rounded-full"
onClick={() => router.push("/")}>
<X className="size-6" />
</Button>

View file

@ -0,0 +1,7 @@
import { HoppingLogo } from "./logo"
export const Header = () => (
<div className="flex items-center justify-between border-b px-12 py-6 mb-12">
<HoppingLogo />
</div>
)

View file

@ -0,0 +1,54 @@
"use client"
import { SVGProps, useEffect, useRef } from "react"
import { useRouter } from "next/navigation"
import { useHover } from "react-use"
import { cn } from "@/lib/utils"
type LogoProps = Omit<SVGProps<SVGSVGElement>, "xmlns" | "viewBox" | "onClick">
export const Logo = ({ width = 50, height = 32, fill = "#fff", className, ...props }: LogoProps) => {
const router = useRouter()
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="90 12 100 64"
onClick={() => router.push("/")}
className={cn("logo cursor-pointer", className)}
{...props}>
<path
d="M171.633,15.8336l-1.7284,6.2499c-.0915.3309-.4369.5221-.7659.4239l-28.9937-8.6507c-.1928-.0575-.4016-.0167-.5586.1092l-28.7143,23.0269c-.0838.0672-.1839.1112-.2901.1276l-17.0849,2.6329c-.3163.0488-.5419.3327-.5178.6519l.0742.9817c.0237.3136.2809.5583.5953.5664l19.8448.513.2263.0063,14.6634-7.8328c.2053-.1097.455-.0936.6445.0415l10.3884,7.4053c.1629.1161.2589.3045.2571.5045l-.0876,9.826c-.0011.1272.0373.2515.11.3559l14.6133,20.9682c.1146.1644.3024.2624.5028.2624h4.626c.4615,0,.7574-.4908.542-.8989l-10.4155-19.7312c-.1019-.193-.0934-.4255.0221-.6106l5.4305-8.6994c.0591-.0947.143-.1715.2425-.222l19.415-9.8522c.1973-.1001.4332-.0861.6172.0366l5.5481,3.6981c.1007.0671.2189.1029.3399.1029h5.0407c.4881,0,.7804-.5429.5116-.9503l-13.9967-21.2171c-.2898-.4393-.962-.3331-1.1022.1741Z"
fill={fill}
strokeWidth="0"
/>
</svg>
)
}
export const HoppingLogo = (props: LogoProps) => {
const ref = useRef<SVGSVGElement>(null)
const logo = <Logo ref={ref} {...props} />
const [hoverable, hovered] = useHover(logo)
useEffect(() => {
const element = ref.current
const isHopping = element !== null && element.classList.contains("animate-hop")
if (hovered && element && !isHopping) {
element.classList.add("animate-hop")
} else if (element && isHopping) {
const onAnimationEnd = () => {
element.classList.remove("animate-hop")
element.removeEventListener("animationiteration", onAnimationEnd)
}
element.addEventListener("animationiteration", onAnimationEnd)
}
}, [hovered])
return hoverable
}

View file

@ -2,6 +2,7 @@ export * from "./button"
export * from "./form"
export * from "./input"
export * from "./label"
export * from "./scroll-area"
export * from "./select"
export * from "./table"
export * from "./textarea"

View file

@ -0,0 +1,46 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}
function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
className,
)}
{...props}>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
}
export { ScrollArea, ScrollBar }

View file

@ -1,24 +1,24 @@
export { db } from "./db.js"
// export { db } from "./db.js"
export { type Language, languages } from "./enums.js"
export { schema } from "./schema.js"
// export { schema } from "./schema.js"
/**
* runs
*/
export { type Run, type InsertRun, insertRunSchema } from "./schema.js"
export { findRun, createRun, getRuns } from "./queries/runs.js"
export type { Run, InsertRun, UpdateRun } from "./schema.js"
export * from "./queries/runs.js"
/**
* tasks
*/
export { type Task, type InsertTask, insertTaskSchema } from "./schema.js"
export { findTask, createTask, getTask } from "./queries/tasks.js"
export type { Task, InsertTask, UpdateTask } from "./schema.js"
export * from "./queries/tasks.js"
/**
* pendingTasks
*/
export { type PendingTask, type InsertPendingTask, insertPendingTaskSchema } from "./schema.js"
export { findPendingTask, createPendingTask, getPendingTask } from "./queries/pendingTasks.js"
export type { PendingTask, InsertPendingTask, UpdatePendingTask } from "./schema.js"
export * from "./queries/pendingTasks.js"

View file

@ -1,13 +1,15 @@
import { and, eq } from "drizzle-orm"
import { db } from "../db.js"
import { type InsertPendingTask, insertPendingTaskSchema, pendingTasks } from "../schema.js"
import { Language } from "../enums.js"
import type { Language } from "../enums.js"
import { RecordNotFoundError, RecordNotCreatedError } from "./errors.js"
import type { InsertPendingTask, UpdatePendingTask } from "../schema.js"
import { insertPendingTaskSchema, pendingTasks } from "../schema.js"
import { db } from "../db.js"
const table = pendingTasks
export const findPendingTask = async (id: number) => {
const run = await db.query.pendingTasks.findFirst({ where: eq(pendingTasks.id, id) })
const run = await db.query.pendingTasks.findFirst({ where: eq(table.id, id) })
if (!run) {
throw new RecordNotFoundError()
@ -17,21 +19,32 @@ export const findPendingTask = async (id: number) => {
}
export const createPendingTask = async (args: InsertPendingTask) => {
const result = await db
.insert(pendingTasks)
const records = await db
.insert(table)
.values({
...insertPendingTaskSchema.parse(args),
createdAt: new Date(),
})
.returning()
const task = result[0]
const record = records[0]
if (!task) {
if (!record) {
throw new RecordNotCreatedError()
}
return task
return record
}
export const updatePendingTask = async (id: number, values: UpdatePendingTask) => {
const records = await db.update(table).set(values).where(eq(table.id, id)).returning()
const record = records[0]
if (!record) {
throw new RecordNotFoundError()
}
return record
}
type GetPendingTask = {
@ -42,9 +55,8 @@ type GetPendingTask = {
export const getPendingTask = async ({ runId, language, exercise }: GetPendingTask) =>
db.query.pendingTasks.findFirst({
where: and(
eq(pendingTasks.runId, runId),
eq(pendingTasks.language, language),
eq(pendingTasks.exercise, exercise),
),
where: and(eq(table.runId, runId), eq(table.language, language), eq(table.exercise, exercise)),
})
export const getPendingTasks = async (runId: number) =>
db.query.pendingTasks.findMany({ where: eq(table.runId, runId) })

View file

@ -1,12 +1,14 @@
import { desc, eq, sql } from "drizzle-orm"
import { db } from "../db.js"
import { type InsertRun, insertRunSchema, runs, tasks } from "../schema.js"
import { RecordNotFoundError, RecordNotCreatedError } from "./errors.js"
import type { InsertRun, UpdateRun } from "../schema.js"
import { insertRunSchema, runs, tasks } from "../schema.js"
import { db } from "../db.js"
const table = runs
export const findRun = async (id: number) => {
const run = await db.query.runs.findFirst({ where: eq(runs.id, id) })
const run = await db.query.runs.findFirst({ where: eq(table.id, id) })
if (!run) {
throw new RecordNotFoundError()
@ -16,30 +18,41 @@ export const findRun = async (id: number) => {
}
export const createRun = async (args: InsertRun) => {
const result = await db
.insert(runs)
const records = await db
.insert(table)
.values({
...insertRunSchema.parse(args),
createdAt: new Date(),
})
.returning()
const run = result[0]
const record = records[0]
if (!run) {
if (!record) {
throw new RecordNotCreatedError()
}
return run
return record
}
export const updateRun = async (id: number, values: UpdateRun) => {
const records = await db.update(table).set(values).where(eq(table.id, id)).returning()
const record = records[0]
if (!record) {
throw new RecordNotFoundError()
}
return record
}
export const getRuns = () =>
db
.select({
id: runs.id,
model: runs.model,
description: runs.description,
createdAt: runs.createdAt,
id: table.id,
model: table.model,
description: table.description,
createdAt: table.createdAt,
passed: sql<number>`sum(${tasks.passed})`,
failed: sql<number>`sum(${tasks.passed} = 0)`,
total: sql<number>`count(${tasks.id})`,
@ -47,6 +60,6 @@ export const getRuns = () =>
cost: sql<number>`sum(${tasks.cost})`,
duration: sql<number>`sum(${tasks.duration})`,
})
.from(runs)
.leftJoin(tasks, eq(runs.id, tasks.runId))
.orderBy(desc(runs.id))
.from(table)
.leftJoin(tasks, eq(table.id, tasks.runId))
.orderBy(desc(table.id))

View file

@ -1,13 +1,15 @@
import { and, eq } from "drizzle-orm"
import { db } from "../db.js"
import { type InsertTask, insertTaskSchema, tasks } from "../schema.js"
import { type Language } from "../enums.js"
import type { Language } from "../enums.js"
import { RecordNotFoundError, RecordNotCreatedError } from "./errors.js"
import type { InsertTask, UpdateTask } from "../schema.js"
import { insertTaskSchema, tasks } from "../schema.js"
import { db } from "../db.js"
const table = tasks
export const findTask = async (id: number) => {
const run = await db.query.tasks.findFirst({ where: eq(tasks.id, id) })
const run = await db.query.tasks.findFirst({ where: eq(table.id, id) })
if (!run) {
throw new RecordNotFoundError()
@ -17,23 +19,33 @@ export const findTask = async (id: number) => {
}
export const createTask = async (args: InsertTask) => {
const result = await db
.insert(tasks)
const records = await db
.insert(table)
.values({
...insertTaskSchema.parse(args),
createdAt: new Date(),
})
.returning()
const task = result[0]
const record = records[0]
if (!task) {
if (!record) {
throw new RecordNotCreatedError()
}
return task
return record
}
export const updateTask = async (id: number, values: UpdateTask) => {
const records = await db.update(table).set(values).where(eq(table.id, id)).returning()
const record = records[0]
if (!record) {
throw new RecordNotFoundError()
}
return record
}
type GetTask = {
runId: number
language: Language
@ -42,5 +54,7 @@ type GetTask = {
export const getTask = async ({ runId, language, exercise }: GetTask) =>
db.query.tasks.findFirst({
where: and(eq(tasks.runId, runId), eq(tasks.language, language), eq(tasks.exercise, exercise)),
where: and(eq(table.runId, runId), eq(table.language, language), eq(table.exercise, exercise)),
})
export const getTasks = async (runId: number) => db.query.tasks.findMany({ where: eq(table.runId, runId) })

View file

@ -27,6 +27,8 @@ export const insertRunSchema = createInsertSchema(runs).omit({
export type InsertRun = z.infer<typeof insertRunSchema>
export type UpdateRun = Partial<Omit<Run, "id" | "createdAt">>
/**
* tasks
*/
@ -60,6 +62,8 @@ export const insertTaskSchema = createInsertSchema(tasks).omit({
export type InsertTask = z.infer<typeof insertTaskSchema>
export type UpdateTask = Partial<Omit<Task, "id" | "createdAt">>
/**
* pendingTasks
*/
@ -86,6 +90,8 @@ export const insertPendingTaskSchema = createInsertSchema(pendingTasks).omit({
export type InsertPendingTask = z.infer<typeof insertPendingTaskSchema>
export type UpdatePendingTask = Partial<Omit<PendingTask, "id" | "createdAt">>
/**
* schema
*/

291
benchmark/pnpm-lock.yaml generated
View file

@ -53,9 +53,6 @@ importers:
gluegun:
specifier: ^5.1.2
version: 5.2.0
uuid:
specifier: ^11.1.0
version: 11.1.0
devDependencies:
'@benchmark/eslint-config':
specifier: workspace:^
@ -78,6 +75,9 @@ importers:
'@radix-ui/react-label':
specifier: ^2.1.2
version: 2.1.2(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-scroll-area':
specifier: ^1.2.3
version: 1.2.3(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-select':
specifier: ^2.1.6
version: 2.1.6(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@ -111,15 +111,15 @@ importers:
react-hook-form:
specifier: ^7.54.2
version: 7.54.2(react@19.0.0)
react-use:
specifier: ^17.6.0
version: 17.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
tailwind-merge:
specifier: ^3.0.2
version: 3.0.2
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@4.0.14)
uuid:
specifier: ^11.1.0
version: 11.1.0
zod:
specifier: ^3.24.2
version: 3.24.2
@ -258,6 +258,10 @@ packages:
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'}
'@babel/runtime@7.26.10':
resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==}
engines: {node: '>=6.9.0'}
'@dotenvx/dotenvx@1.39.0':
resolution: {integrity: sha512-qGfDpL/3S17MQYXpR3HkBS5xNQ7wiFlqLdpr+iIQzv17aMRcSlgL4EjMIsYFZ540Dq17J+y5FVElA1AkVoXiUA==}
hasBin: true
@ -1188,6 +1192,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-presence@1.1.2':
resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-primitive@2.0.2':
resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==}
peerDependencies:
@ -1201,6 +1218,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-scroll-area@1.2.3':
resolution: {integrity: sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-select@2.1.6':
resolution: {integrity: sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==}
peerDependencies:
@ -1493,6 +1523,9 @@ packages:
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
'@types/js-cookie@2.2.7':
resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@ -1570,6 +1603,9 @@ packages:
resolution: {integrity: sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==}
engines: {node: '>=16'}
'@xobotyi/scrollbar-width@1.9.5':
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -1819,6 +1855,9 @@ packages:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
copyfiles@2.4.1:
resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==}
hasBin: true
@ -1838,6 +1877,13 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
css-in-js-utils@3.1.0:
resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==}
css-tree@1.1.3:
resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
engines: {node: '>=8.0.0'}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@ -2046,6 +2092,9 @@ packages:
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
error-stack-parser@2.1.4:
resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==}
es-abstract@1.23.9:
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
engines: {node: '>= 0.4'}
@ -2205,6 +2254,12 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-shallow-equal@1.0.0:
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
fastest-stable-stringify@2.0.2:
resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==}
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@ -2406,6 +2461,9 @@ packages:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
hyphenate-style-name@1.1.0:
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@ -2431,6 +2489,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
inline-style-prefixer@7.0.1:
resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==}
internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
@ -2600,6 +2661,9 @@ packages:
js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
js-cookie@2.2.1:
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
js-message@1.0.7:
resolution: {integrity: sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==}
engines: {node: '>=0.6.0'}
@ -2810,6 +2874,9 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
mdn-data@2.0.14:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@ -2855,6 +2922,12 @@ packages:
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
nano-css@5.6.2:
resolution: {integrity: sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==}
peerDependencies:
react: '*'
react-dom: '*'
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@ -3131,6 +3204,18 @@ packages:
'@types/react':
optional: true
react-universal-interface@0.6.2:
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
peerDependencies:
react: '*'
tslib: '*'
react-use@17.6.0:
resolution: {integrity: sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g==}
peerDependencies:
react: '*'
react-dom: '*'
react@19.0.0:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
@ -3153,6 +3238,9 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
@ -3161,6 +3249,9 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@ -3203,6 +3294,9 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
rtl-css-js@1.16.1:
resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@ -3227,6 +3321,10 @@ packages:
scheduler@0.25.0:
resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
screenfull@5.2.0:
resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
engines: {node: '>=0.10.0'}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@ -3249,6 +3347,10 @@ packages:
resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
engines: {node: '>= 0.4'}
set-harmonic-interval@1.0.1:
resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==}
engines: {node: '>=6.9'}
set-proto@1.0.0:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'}
@ -3305,6 +3407,10 @@ packages:
source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
source-map@0.5.6:
resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==}
engines: {node: '>=0.10.0'}
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
@ -3313,6 +3419,18 @@ packages:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
stack-generator@2.0.10:
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
stackframe@1.3.4:
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
stacktrace-gps@3.1.2:
resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==}
stacktrace-js@2.0.2:
resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==}
stdin-discarder@0.1.0:
resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -3402,6 +3520,9 @@ packages:
babel-plugin-macros:
optional: true
stylis@4.3.6:
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
@ -3441,6 +3562,10 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
throttle-debounce@3.0.1:
resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==}
engines: {node: '>=10'}
through2@2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
@ -3455,6 +3580,9 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
@ -3468,6 +3596,9 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
ts-easing@0.2.0:
resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@ -3601,10 +3732,6 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
uuid@11.1.0:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
@ -3717,6 +3844,10 @@ snapshots:
'@babel/helper-validator-identifier@7.25.9': {}
'@babel/runtime@7.26.10':
dependencies:
regenerator-runtime: 0.14.1
'@dotenvx/dotenvx@1.39.0':
dependencies:
commander: 11.1.0
@ -4370,6 +4501,16 @@ snapshots:
'@types/react': 19.0.12
'@types/react-dom': 19.0.4(@types/react@19.0.12)
'@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.12)(react@19.0.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.12)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.12
'@types/react-dom': 19.0.4(@types/react@19.0.12)
'@radix-ui/react-primitive@2.0.2(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-slot': 1.1.2(@types/react@19.0.12)(react@19.0.0)
@ -4379,6 +4520,23 @@ snapshots:
'@types/react': 19.0.12
'@types/react-dom': 19.0.4(@types/react@19.0.12)
'@radix-ui/react-scroll-area@1.2.3(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/number': 1.1.0
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.12)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.12)(react@19.0.0)
'@radix-ui/react-direction': 1.1.0(@types/react@19.0.12)(react@19.0.0)
'@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.12)(react@19.0.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.12)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.12
'@types/react-dom': 19.0.4(@types/react@19.0.12)
'@radix-ui/react-select@2.1.6(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/number': 1.1.0
@ -4608,6 +4766,8 @@ snapshots:
'@types/estree@1.0.6': {}
'@types/js-cookie@2.2.7': {}
'@types/json-schema@7.0.15': {}
'@types/node-ipc@9.2.3':
@ -4721,6 +4881,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@xobotyi/scrollbar-width@1.9.5': {}
acorn-jsx@5.3.2(acorn@8.14.1):
dependencies:
acorn: 8.14.1
@ -4985,6 +5147,10 @@ snapshots:
consola@3.4.2: {}
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
copyfiles@2.4.1:
dependencies:
glob: 7.2.3
@ -5017,6 +5183,15 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
css-in-js-utils@3.1.0:
dependencies:
hyphenate-style-name: 1.1.0
css-tree@1.1.3:
dependencies:
mdn-data: 2.0.14
source-map: 0.6.1
csstype@3.1.3: {}
data-uri-to-buffer@4.0.1: {}
@ -5137,6 +5312,10 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
error-stack-parser@2.1.4:
dependencies:
stackframe: 1.3.4
es-abstract@1.23.9:
dependencies:
array-buffer-byte-length: 1.0.2
@ -5473,6 +5652,10 @@ snapshots:
fast-levenshtein@2.0.6: {}
fast-shallow-equal@1.0.0: {}
fastest-stable-stringify@2.0.2: {}
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@ -5718,6 +5901,8 @@ snapshots:
human-signals@2.1.0: {}
hyphenate-style-name@1.1.0: {}
ieee754@1.2.1: {}
ignore@5.3.2: {}
@ -5738,6 +5923,10 @@ snapshots:
inherits@2.0.4: {}
inline-style-prefixer@7.0.1:
dependencies:
css-in-js-utils: 3.1.0
internal-slot@1.1.0:
dependencies:
es-errors: 1.3.0
@ -5906,6 +6095,8 @@ snapshots:
js-base64@3.7.7: {}
js-cookie@2.2.1: {}
js-message@1.0.7: {}
js-queue@2.0.2:
@ -6082,6 +6273,8 @@ snapshots:
math-intrinsics@1.1.0: {}
mdn-data@2.0.14: {}
merge-stream@2.0.0: {}
merge2@1.4.1: {}
@ -6121,6 +6314,19 @@ snapshots:
object-assign: 4.1.1
thenify-all: 1.6.0
nano-css@5.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
css-tree: 1.1.3
csstype: 3.1.3
fastest-stable-stringify: 2.0.2
inline-style-prefixer: 7.0.1
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
rtl-css-js: 1.16.1
stacktrace-js: 2.0.2
stylis: 4.3.6
nanoid@3.3.11: {}
natural-compare@1.4.0: {}
@ -6393,6 +6599,30 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.12
react-universal-interface@0.6.2(react@19.0.0)(tslib@2.8.1):
dependencies:
react: 19.0.0
tslib: 2.8.1
react-use@17.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@types/js-cookie': 2.2.7
'@xobotyi/scrollbar-width': 1.9.5
copy-to-clipboard: 3.3.3
fast-deep-equal: 3.1.3
fast-shallow-equal: 1.0.0
js-cookie: 2.2.1
nano-css: 5.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
react-universal-interface: 0.6.2(react@19.0.0)(tslib@2.8.1)
resize-observer-polyfill: 1.5.1
screenfull: 5.2.0
set-harmonic-interval: 1.0.1
throttle-debounce: 3.0.1
ts-easing: 0.2.0
tslib: 2.8.1
react@19.0.0: {}
readable-stream@1.0.34:
@ -6431,6 +6661,8 @@ snapshots:
get-proto: 1.0.1
which-builtin-type: 1.2.1
regenerator-runtime@0.14.1: {}
regexp.prototype.flags@1.5.4:
dependencies:
call-bind: 1.0.8
@ -6442,6 +6674,8 @@ snapshots:
require-directory@2.1.1: {}
resize-observer-polyfill@1.5.1: {}
resolve-from@4.0.0: {}
resolve-from@5.0.0: {}
@ -6500,6 +6734,10 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.36.0
fsevents: 2.3.3
rtl-css-js@1.16.1:
dependencies:
'@babel/runtime': 7.26.10
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@ -6529,6 +6767,8 @@ snapshots:
scheduler@0.25.0: {}
screenfull@5.2.0: {}
semver@6.3.1: {}
semver@7.3.5:
@ -6553,6 +6793,8 @@ snapshots:
functions-have-names: 1.2.3
has-property-descriptors: 1.0.2
set-harmonic-interval@1.0.1: {}
set-proto@1.0.0:
dependencies:
dunder-proto: 1.0.1
@ -6640,12 +6882,31 @@ snapshots:
buffer-from: 1.1.2
source-map: 0.6.1
source-map@0.5.6: {}
source-map@0.6.1: {}
source-map@0.8.0-beta.0:
dependencies:
whatwg-url: 7.1.0
stack-generator@2.0.10:
dependencies:
stackframe: 1.3.4
stackframe@1.3.4: {}
stacktrace-gps@3.1.2:
dependencies:
source-map: 0.5.6
stackframe: 1.3.4
stacktrace-js@2.0.2:
dependencies:
error-stack-parser: 2.1.4
stack-generator: 2.0.10
stacktrace-gps: 3.1.2
stdin-discarder@0.1.0:
dependencies:
bl: 5.1.0
@ -6749,6 +7010,8 @@ snapshots:
client-only: 0.0.1
react: 19.0.0
stylis@4.3.6: {}
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.8
@ -6787,6 +7050,8 @@ snapshots:
dependencies:
any-promise: 1.3.0
throttle-debounce@3.0.1: {}
through2@2.0.5:
dependencies:
readable-stream: 2.3.8
@ -6803,6 +7068,8 @@ snapshots:
dependencies:
is-number: 7.0.0
toggle-selection@1.0.6: {}
tr46@1.0.1:
dependencies:
punycode: 2.3.1
@ -6813,6 +7080,8 @@ snapshots:
dependencies:
typescript: 5.8.2
ts-easing@0.2.0: {}
ts-interface-checker@0.1.13: {}
tslib@2.8.1: {}
@ -6959,8 +7228,6 @@ snapshots:
util-deprecate@1.0.2: {}
uuid@11.1.0: {}
wcwidth@1.0.1:
dependencies:
defaults: 1.0.4