mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
More progress
This commit is contained in:
parent
e9fadc7cb1
commit
568bc5e576
8 changed files with 234 additions and 21 deletions
|
|
@ -37,6 +37,7 @@
|
|||
"react-use": "^17.6.0",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,33 @@
|
|||
"use client"
|
||||
|
||||
import { LoaderCircle } from "lucide-react"
|
||||
import { useState, useRef, useEffect } from "react"
|
||||
import { LoaderCircle, RectangleEllipsis } from "lucide-react"
|
||||
|
||||
import * as db from "@benchmark/db"
|
||||
|
||||
import { useRunStatus } from "@/hooks/use-run-status"
|
||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, ScrollArea } from "@/components/ui"
|
||||
|
||||
import { TaskStatus } from "./task-status"
|
||||
import { ConnectionStatus } from "./connection-status"
|
||||
|
||||
export function Run({ run }: { run: db.Run }) {
|
||||
const { tasks, status, clientId, runningTaskId } = useRunStatus(run)
|
||||
const { tasks, status, clientId, runningTaskId, output, outputCounts } = useRunStatus(run)
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||
const [selectedTask, setSelectedTask] = useState<db.Task>()
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTask) {
|
||||
const scrollArea = scrollAreaRef.current
|
||||
|
||||
if (scrollArea) {
|
||||
scrollArea.scrollTo({
|
||||
top: scrollArea.scrollHeight,
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [selectedTask, outputCounts])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -29,6 +46,14 @@ export function Run({ run }: { run: db.Run }) {
|
|||
<div>
|
||||
{task.language}/{task.exercise}
|
||||
</div>
|
||||
{(outputCounts[task.id] ?? 0) > 0 && (
|
||||
<div
|
||||
className="flex items-center gap-1 cursor-pointer"
|
||||
onClick={() => setSelectedTask(task)}>
|
||||
<RectangleEllipsis className="size-4" />
|
||||
<div className="text-xs">({outputCounts[task.id]})</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
|
@ -36,6 +61,27 @@ export function Run({ run }: { run: db.Run }) {
|
|||
<div className="absolute top-5 right-5">
|
||||
<ConnectionStatus status={status} clientId={clientId} pid={run.pid} />
|
||||
</div>
|
||||
<Drawer open={!!selectedTask} onOpenChange={() => setSelectedTask(undefined)}>
|
||||
<DrawerContent>
|
||||
<div className="mx-auto w-full max-w-2xl">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>
|
||||
{selectedTask?.language}/{selectedTask?.exercise}
|
||||
</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<div className="font-mono text-xs pb-12">
|
||||
{selectedTask && (
|
||||
<ScrollArea viewportRef={scrollAreaRef} className="h-96 rounded-sm border">
|
||||
<div className="p-4">
|
||||
<h4 className="mb-4 text-sm font-medium leading-none">Tags</h4>
|
||||
{output.get(selectedTask.id)?.map((line, i) => <div key={i}>{line}</div>)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
98
benchmark/apps/web/src/components/ui/drawer.tsx
Normal file
98
benchmark/apps/web/src/components/ui/drawer.tsx
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Drawer as DrawerPrimitive } from "vaul"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
||||
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
||||
}
|
||||
|
||||
function DrawerTrigger({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
||||
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
||||
}
|
||||
|
||||
function DrawerPortal({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
||||
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
|
||||
}
|
||||
|
||||
function DrawerClose({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
||||
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
||||
}
|
||||
|
||||
function DrawerOverlay({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
||||
return (
|
||||
<DrawerPrimitive.Overlay
|
||||
data-slot="drawer-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DrawerContent({ className, children, ...props }: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
||||
return (
|
||||
<DrawerPortal data-slot="drawer-portal">
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
data-slot="drawer-content"
|
||||
className={cn(
|
||||
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
|
||||
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-sm data-[vaul-drawer-direction=top]:border-b",
|
||||
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-sm data-[vaul-drawer-direction=bottom]:border-t",
|
||||
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
)
|
||||
}
|
||||
|
||||
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return <div data-slot="drawer-header" className={cn("flex flex-col gap-1.5 py-4", className)} {...props} />
|
||||
}
|
||||
|
||||
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return <div data-slot="drawer-footer" className={cn("mt-auto flex flex-col gap-2 py-4", className)} {...props} />
|
||||
}
|
||||
|
||||
function DrawerTitle({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
||||
return (
|
||||
<DrawerPrimitive.Title
|
||||
data-slot="drawer-title"
|
||||
className={cn("text-foreground font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DrawerDescription({ className, ...props }: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
||||
return (
|
||||
<DrawerPrimitive.Description
|
||||
data-slot="drawer-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ export * from "./badge"
|
|||
export * from "./button"
|
||||
export * from "./command"
|
||||
export * from "./dialog"
|
||||
export * from "./drawer"
|
||||
export * from "./form"
|
||||
export * from "./input"
|
||||
export * from "./label"
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||
type ScrollAreaProps = React.ComponentProps<typeof ScrollAreaPrimitive.Root> & {
|
||||
viewportRef?: React.RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
function ScrollArea({ className, children, viewportRef, ...props }: ScrollAreaProps) {
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
ref={viewportRef}
|
||||
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}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback } from "react"
|
||||
import { useState, useCallback, useRef } from "react"
|
||||
import { useQuery, keepPreviousData } from "@tanstack/react-query"
|
||||
|
||||
import { Run } from "@benchmark/db"
|
||||
|
|
@ -10,6 +10,8 @@ import { useEventSource } from "@/hooks/use-event-source"
|
|||
export const useRunStatus = (run: Run) => {
|
||||
const [clientId, setClientId] = useState<string>()
|
||||
const [runningTaskId, setRunningTaskId] = useState<number>()
|
||||
const outputRef = useRef<Map<number, string[]>>(new Map())
|
||||
const [outputCounts, setOutputCounts] = useState<Record<number, number>>({})
|
||||
|
||||
const { data: tasks } = useQuery({
|
||||
queryKey: ["run", run.id, runningTaskId],
|
||||
|
|
@ -42,20 +44,28 @@ export const useRunStatus = (run: Run) => {
|
|||
if (payload.type === "Ack") {
|
||||
setClientId(payload.data.clientId as string)
|
||||
} else if (payload.type === "TaskEvent") {
|
||||
const {
|
||||
eventName,
|
||||
data: { task },
|
||||
} = payload.data
|
||||
const taskId = payload.data.data.task.id
|
||||
|
||||
if (eventName === "connect" || eventName === "taskStarted") {
|
||||
setRunningTaskId(task.id)
|
||||
} else if (eventName === "taskFinished") {
|
||||
if (payload.data.eventName === "connect" || payload.data.eventName === "taskStarted") {
|
||||
setRunningTaskId(taskId)
|
||||
} else if (payload.data.eventName === "taskFinished") {
|
||||
setRunningTaskId(undefined)
|
||||
} else if (payload.data.eventName === "message") {
|
||||
const { text } = payload.data.data.message.message
|
||||
console.log(`message: ${taskId} ->`, text)
|
||||
outputRef.current.set(taskId, [...(outputRef.current.get(taskId) || []), text])
|
||||
const outputCounts: Record<number, number> = {}
|
||||
|
||||
for (const [taskId, messages] of outputRef.current.entries()) {
|
||||
outputCounts[taskId] = messages.length
|
||||
}
|
||||
|
||||
setOutputCounts(outputCounts)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const status = useEventSource({ url, onMessage })
|
||||
|
||||
return { tasks, status, clientId, runningTaskId }
|
||||
return { tasks, status, clientId, runningTaskId, output: outputRef.current, outputCounts }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,48 @@ export const createRunSchema = z
|
|||
|
||||
export type CreateRun = z.infer<typeof createRunSchema>
|
||||
|
||||
/**
|
||||
* TaskEvent
|
||||
*/
|
||||
|
||||
export const taskEventSchema = z.discriminatedUnion("eventName", [
|
||||
z.object({
|
||||
eventName: z.literal("connect"),
|
||||
data: z.object({ task: z.object({ id: z.number() }) }),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal("taskStarted"),
|
||||
data: z.object({ task: z.object({ id: z.number() }) }),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal("message"),
|
||||
data: z.object({
|
||||
task: z.object({ id: z.number() }),
|
||||
message: z.object({
|
||||
taskId: z.string(),
|
||||
action: z.enum(["created", "updated"]),
|
||||
message: z.object({
|
||||
ask: z.string().optional(),
|
||||
say: z.string().optional(),
|
||||
partial: z.boolean(),
|
||||
text: z.string(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal("taskTokenUsageUpdated"),
|
||||
data: z.object({
|
||||
task: z.object({ id: z.number() }),
|
||||
usage: z.object({}),
|
||||
}),
|
||||
}),
|
||||
z.object({
|
||||
eventName: z.literal("taskFinished"),
|
||||
data: z.object({ task: z.object({ id: z.number() }) }),
|
||||
}),
|
||||
])
|
||||
|
||||
/**
|
||||
* IpcServerMessage
|
||||
*/
|
||||
|
|
@ -28,15 +70,7 @@ export const ipcServerMessageSchema = z.discriminatedUnion("type", [
|
|||
}),
|
||||
z.object({
|
||||
type: z.literal("TaskEvent"),
|
||||
data: z.object({
|
||||
eventName: z.enum(["connect", "taskStarted", "message", "taskTokenUsageUpdated", "taskFinished"]),
|
||||
data: z.object({
|
||||
task: z.object({ id: z.number() }),
|
||||
// message: z.object({}).optional(),
|
||||
// usage: z.object({}).optional(),
|
||||
// taskMetrics: z.object({}).optional(),
|
||||
}),
|
||||
}),
|
||||
data: taskEventSchema,
|
||||
}),
|
||||
])
|
||||
|
||||
|
|
|
|||
18
benchmark/pnpm-lock.yaml
generated
18
benchmark/pnpm-lock.yaml
generated
|
|
@ -150,6 +150,9 @@ importers:
|
|||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@4.0.14)
|
||||
vaul:
|
||||
specifier: ^1.1.2
|
||||
version: 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)
|
||||
zod:
|
||||
specifier: ^3.24.2
|
||||
version: 3.24.2
|
||||
|
|
@ -4190,6 +4193,12 @@ packages:
|
|||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
vaul@1.1.2:
|
||||
resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||
|
||||
wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
|
||||
|
||||
|
|
@ -8149,6 +8158,15 @@ snapshots:
|
|||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vaul@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-dialog': 1.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)
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
|
||||
wcwidth@1.0.1:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue