|
|
@@ -1,23 +1,29 @@
|
|
|
"use client"
|
|
|
|
|
|
-import { useCallback, useRef, useState } from "react"
|
|
|
+import { useCallback, useEffect, useMemo, useState } from "react"
|
|
|
import { useRouter } from "next/navigation"
|
|
|
import { z } from "zod"
|
|
|
import { useQuery } from "@tanstack/react-query"
|
|
|
import { useForm, FormProvider } from "react-hook-form"
|
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
|
-import fuzzysort from "fuzzysort"
|
|
|
import { toast } from "sonner"
|
|
|
-import { X, Rocket, Check, ChevronsUpDown, SlidersHorizontal, CircleCheck } from "lucide-react"
|
|
|
+import { X, Rocket, Check, ChevronsUpDown, SlidersHorizontal } from "lucide-react"
|
|
|
|
|
|
-import { globalSettingsSchema, providerSettingsSchema, EVALS_SETTINGS, getModelId } from "@roo-code/types"
|
|
|
+import {
|
|
|
+ globalSettingsSchema,
|
|
|
+ providerSettingsSchema,
|
|
|
+ EVALS_SETTINGS,
|
|
|
+ getModelId,
|
|
|
+ type ProviderSettings,
|
|
|
+ type GlobalSettings,
|
|
|
+} from "@roo-code/types"
|
|
|
|
|
|
import { createRun } from "@/actions/runs"
|
|
|
import { getExercises } from "@/actions/exercises"
|
|
|
+
|
|
|
import {
|
|
|
- createRunSchema,
|
|
|
type CreateRun,
|
|
|
- MODEL_DEFAULT,
|
|
|
+ createRunSchema,
|
|
|
CONCURRENCY_MIN,
|
|
|
CONCURRENCY_MAX,
|
|
|
CONCURRENCY_DEFAULT,
|
|
|
@@ -26,14 +32,19 @@ import {
|
|
|
TIMEOUT_DEFAULT,
|
|
|
} from "@/lib/schemas"
|
|
|
import { cn } from "@/lib/utils"
|
|
|
+
|
|
|
import { useOpenRouterModels } from "@/hooks/use-open-router-models"
|
|
|
+import { useRooCodeCloudModels } from "@/hooks/use-roo-code-cloud-models"
|
|
|
+
|
|
|
import {
|
|
|
Button,
|
|
|
+ Checkbox,
|
|
|
FormControl,
|
|
|
FormField,
|
|
|
FormItem,
|
|
|
FormLabel,
|
|
|
FormMessage,
|
|
|
+ Input,
|
|
|
Textarea,
|
|
|
Tabs,
|
|
|
TabsList,
|
|
|
@@ -48,36 +59,54 @@ import {
|
|
|
Popover,
|
|
|
PopoverContent,
|
|
|
PopoverTrigger,
|
|
|
- ScrollArea,
|
|
|
- ScrollBar,
|
|
|
Slider,
|
|
|
+ Label,
|
|
|
+ FormDescription,
|
|
|
} from "@/components/ui"
|
|
|
|
|
|
import { SettingsDiff } from "./settings-diff"
|
|
|
|
|
|
+type ImportedSettings = {
|
|
|
+ apiConfigs: Record<string, ProviderSettings>
|
|
|
+ globalSettings: GlobalSettings
|
|
|
+ currentApiConfigName: string
|
|
|
+}
|
|
|
+
|
|
|
export function NewRun() {
|
|
|
const router = useRouter()
|
|
|
|
|
|
- const [mode, setMode] = useState<"openrouter" | "settings">("openrouter")
|
|
|
- const [modelSearchValue, setModelSearchValue] = useState("")
|
|
|
+ const [provider, setModelSource] = useState<"roo" | "openrouter" | "other">("roo")
|
|
|
const [modelPopoverOpen, setModelPopoverOpen] = useState(false)
|
|
|
+ const [useNativeToolProtocol, setUseNativeToolProtocol] = useState(true)
|
|
|
|
|
|
- const modelSearchResultsRef = useRef<Map<string, number>>(new Map())
|
|
|
- const modelSearchValueRef = useRef("")
|
|
|
+ // State for imported settings with config selection
|
|
|
+ const [importedSettings, setImportedSettings] = useState<ImportedSettings | null>(null)
|
|
|
+ const [selectedConfigName, setSelectedConfigName] = useState<string>("")
|
|
|
+ const [configPopoverOpen, setConfigPopoverOpen] = useState(false)
|
|
|
+
|
|
|
+ const openRouter = useOpenRouterModels()
|
|
|
+ const rooCodeCloud = useRooCodeCloudModels()
|
|
|
+ const models = provider === "openrouter" ? openRouter.data : rooCodeCloud.data
|
|
|
+ const searchValue = provider === "openrouter" ? openRouter.searchValue : rooCodeCloud.searchValue
|
|
|
+ const setSearchValue = provider === "openrouter" ? openRouter.setSearchValue : rooCodeCloud.setSearchValue
|
|
|
+ const onFilter = provider === "openrouter" ? openRouter.onFilter : rooCodeCloud.onFilter
|
|
|
|
|
|
- const models = useOpenRouterModels()
|
|
|
const exercises = useQuery({ queryKey: ["getExercises"], queryFn: () => getExercises() })
|
|
|
|
|
|
+ // State for selected exercises (needed for language toggle buttons)
|
|
|
+ const [selectedExercises, setSelectedExercises] = useState<string[]>([])
|
|
|
+
|
|
|
const form = useForm<CreateRun>({
|
|
|
resolver: zodResolver(createRunSchema),
|
|
|
defaultValues: {
|
|
|
- model: MODEL_DEFAULT,
|
|
|
+ model: "",
|
|
|
description: "",
|
|
|
suite: "full",
|
|
|
exercises: [],
|
|
|
settings: undefined,
|
|
|
concurrency: CONCURRENCY_DEFAULT,
|
|
|
timeout: TIMEOUT_DEFAULT,
|
|
|
+ jobToken: "",
|
|
|
},
|
|
|
})
|
|
|
|
|
|
@@ -90,11 +119,105 @@ export function NewRun() {
|
|
|
|
|
|
const [model, suite, settings] = watch(["model", "suite", "settings", "concurrency"])
|
|
|
|
|
|
+ // Load concurrency and timeout from localStorage on mount
|
|
|
+ useEffect(() => {
|
|
|
+ const savedConcurrency = localStorage.getItem("evals-concurrency")
|
|
|
+ if (savedConcurrency) {
|
|
|
+ const parsed = parseInt(savedConcurrency, 10)
|
|
|
+ if (!isNaN(parsed) && parsed >= CONCURRENCY_MIN && parsed <= CONCURRENCY_MAX) {
|
|
|
+ setValue("concurrency", parsed)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const savedTimeout = localStorage.getItem("evals-timeout")
|
|
|
+ if (savedTimeout) {
|
|
|
+ const parsed = parseInt(savedTimeout, 10)
|
|
|
+ if (!isNaN(parsed) && parsed >= TIMEOUT_MIN && parsed <= TIMEOUT_MAX) {
|
|
|
+ setValue("timeout", parsed)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, [setValue])
|
|
|
+
|
|
|
+ // Extract unique languages from exercises
|
|
|
+ const languages = useMemo(() => {
|
|
|
+ if (!exercises.data) return []
|
|
|
+ const langs = new Set<string>()
|
|
|
+ for (const path of exercises.data) {
|
|
|
+ const lang = path.split("/")[0]
|
|
|
+ if (lang) langs.add(lang)
|
|
|
+ }
|
|
|
+ return Array.from(langs).sort()
|
|
|
+ }, [exercises.data])
|
|
|
+
|
|
|
+ // Get exercises for a specific language
|
|
|
+ const getExercisesForLanguage = useCallback(
|
|
|
+ (lang: string) => {
|
|
|
+ if (!exercises.data) return []
|
|
|
+ return exercises.data.filter((path) => path.startsWith(`${lang}/`))
|
|
|
+ },
|
|
|
+ [exercises.data],
|
|
|
+ )
|
|
|
+
|
|
|
+ // Toggle all exercises for a language
|
|
|
+ const toggleLanguage = useCallback(
|
|
|
+ (lang: string) => {
|
|
|
+ const langExercises = getExercisesForLanguage(lang)
|
|
|
+ const allSelected = langExercises.every((ex) => selectedExercises.includes(ex))
|
|
|
+
|
|
|
+ let newSelected: string[]
|
|
|
+ if (allSelected) {
|
|
|
+ // Remove all exercises for this language
|
|
|
+ newSelected = selectedExercises.filter((ex) => !ex.startsWith(`${lang}/`))
|
|
|
+ } else {
|
|
|
+ // Add all exercises for this language (avoiding duplicates)
|
|
|
+ const existing = new Set(selectedExercises)
|
|
|
+ for (const ex of langExercises) {
|
|
|
+ existing.add(ex)
|
|
|
+ }
|
|
|
+ newSelected = Array.from(existing)
|
|
|
+ }
|
|
|
+
|
|
|
+ setSelectedExercises(newSelected)
|
|
|
+ setValue("exercises", newSelected)
|
|
|
+ },
|
|
|
+ [getExercisesForLanguage, selectedExercises, setValue],
|
|
|
+ )
|
|
|
+
|
|
|
+ // Check if all exercises for a language are selected
|
|
|
+ const isLanguageSelected = useCallback(
|
|
|
+ (lang: string) => {
|
|
|
+ const langExercises = getExercisesForLanguage(lang)
|
|
|
+ return langExercises.length > 0 && langExercises.every((ex) => selectedExercises.includes(ex))
|
|
|
+ },
|
|
|
+ [getExercisesForLanguage, selectedExercises],
|
|
|
+ )
|
|
|
+
|
|
|
+ // Check if some (but not all) exercises for a language are selected
|
|
|
+ const isLanguagePartiallySelected = useCallback(
|
|
|
+ (lang: string) => {
|
|
|
+ const langExercises = getExercisesForLanguage(lang)
|
|
|
+ const selectedCount = langExercises.filter((ex) => selectedExercises.includes(ex)).length
|
|
|
+ return selectedCount > 0 && selectedCount < langExercises.length
|
|
|
+ },
|
|
|
+ [getExercisesForLanguage, selectedExercises],
|
|
|
+ )
|
|
|
+
|
|
|
const onSubmit = useCallback(
|
|
|
async (values: CreateRun) => {
|
|
|
try {
|
|
|
- if (mode === "openrouter") {
|
|
|
- values.settings = { ...(values.settings || {}), openRouterModelId: model }
|
|
|
+ if (provider === "openrouter") {
|
|
|
+ values.settings = {
|
|
|
+ ...(values.settings || {}),
|
|
|
+ apiProvider: "openrouter",
|
|
|
+ openRouterModelId: model,
|
|
|
+ toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
|
|
+ }
|
|
|
+ } else if (provider === "roo") {
|
|
|
+ values.settings = {
|
|
|
+ ...(values.settings || {}),
|
|
|
+ apiProvider: "roo",
|
|
|
+ apiModelId: model,
|
|
|
+ toolProtocol: useNativeToolProtocol ? "native" : "xml",
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const { id } = await createRun(values)
|
|
|
@@ -103,28 +226,7 @@ export function NewRun() {
|
|
|
toast.error(e instanceof Error ? e.message : "An unknown error occurred.")
|
|
|
}
|
|
|
},
|
|
|
- [mode, model, router],
|
|
|
- )
|
|
|
-
|
|
|
- const onFilterModels = useCallback(
|
|
|
- (value: string, search: string) => {
|
|
|
- if (modelSearchValueRef.current !== search) {
|
|
|
- modelSearchValueRef.current = search
|
|
|
- modelSearchResultsRef.current.clear()
|
|
|
-
|
|
|
- for (const {
|
|
|
- obj: { id },
|
|
|
- score,
|
|
|
- } of fuzzysort.go(search, models.data || [], {
|
|
|
- key: "name",
|
|
|
- })) {
|
|
|
- modelSearchResultsRef.current.set(id, score)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return modelSearchResultsRef.current.get(value) ?? 0
|
|
|
- },
|
|
|
- [models.data],
|
|
|
+ [provider, model, router, useNativeToolProtocol],
|
|
|
)
|
|
|
|
|
|
const onSelectModel = useCallback(
|
|
|
@@ -132,7 +234,7 @@ export function NewRun() {
|
|
|
setValue("model", model)
|
|
|
setModelPopoverOpen(false)
|
|
|
},
|
|
|
- [setValue],
|
|
|
+ [setValue, setModelPopoverOpen],
|
|
|
)
|
|
|
|
|
|
const onImportSettings = useCallback(
|
|
|
@@ -156,11 +258,21 @@ export function NewRun() {
|
|
|
})
|
|
|
.parse(JSON.parse(await file.text()))
|
|
|
|
|
|
- const providerSettings = providerProfiles.apiConfigs[providerProfiles.currentApiConfigName] ?? {}
|
|
|
+ // Store all imported configs for user selection
|
|
|
+ setImportedSettings({
|
|
|
+ apiConfigs: providerProfiles.apiConfigs,
|
|
|
+ globalSettings,
|
|
|
+ currentApiConfigName: providerProfiles.currentApiConfigName,
|
|
|
+ })
|
|
|
|
|
|
+ // Default to the current config
|
|
|
+ const defaultConfigName = providerProfiles.currentApiConfigName
|
|
|
+ setSelectedConfigName(defaultConfigName)
|
|
|
+
|
|
|
+ // Apply the default config
|
|
|
+ const providerSettings = providerProfiles.apiConfigs[defaultConfigName] ?? {}
|
|
|
setValue("model", getModelId(providerSettings) ?? "")
|
|
|
setValue("settings", { ...EVALS_SETTINGS, ...providerSettings, ...globalSettings })
|
|
|
- setMode("settings")
|
|
|
|
|
|
event.target.value = ""
|
|
|
} catch (e) {
|
|
|
@@ -171,19 +283,123 @@ export function NewRun() {
|
|
|
[clearErrors, setValue],
|
|
|
)
|
|
|
|
|
|
+ const onSelectConfig = useCallback(
|
|
|
+ (configName: string) => {
|
|
|
+ if (!importedSettings) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ setSelectedConfigName(configName)
|
|
|
+ setConfigPopoverOpen(false)
|
|
|
+
|
|
|
+ const providerSettings = importedSettings.apiConfigs[configName] ?? {}
|
|
|
+ setValue("model", getModelId(providerSettings) ?? "")
|
|
|
+ setValue("settings", { ...EVALS_SETTINGS, ...providerSettings, ...importedSettings.globalSettings })
|
|
|
+ },
|
|
|
+ [importedSettings, setValue],
|
|
|
+ )
|
|
|
+
|
|
|
return (
|
|
|
<>
|
|
|
<FormProvider {...form}>
|
|
|
<form
|
|
|
onSubmit={form.handleSubmit(onSubmit)}
|
|
|
className="flex flex-col justify-center divide-y divide-primary *:py-5">
|
|
|
- <div className="flex flex-row justify-between gap-4">
|
|
|
- {mode === "openrouter" && (
|
|
|
- <FormField
|
|
|
- control={form.control}
|
|
|
- name="model"
|
|
|
- render={() => (
|
|
|
- <FormItem className="flex-1">
|
|
|
+ <FormField
|
|
|
+ control={form.control}
|
|
|
+ name="model"
|
|
|
+ render={() => (
|
|
|
+ <FormItem>
|
|
|
+ <Tabs
|
|
|
+ value={provider}
|
|
|
+ onValueChange={(value) => setModelSource(value as "roo" | "openrouter" | "other")}>
|
|
|
+ <TabsList className="mb-2">
|
|
|
+ <TabsTrigger value="roo">Roo Code Cloud</TabsTrigger>
|
|
|
+ <TabsTrigger value="openrouter">OpenRouter</TabsTrigger>
|
|
|
+ <TabsTrigger value="other">Other</TabsTrigger>
|
|
|
+ </TabsList>
|
|
|
+ </Tabs>
|
|
|
+
|
|
|
+ {provider === "other" ? (
|
|
|
+ <div className="space-y-2 overflow-auto">
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ variant="secondary"
|
|
|
+ onClick={() => document.getElementById("json-upload")?.click()}
|
|
|
+ className="w-full">
|
|
|
+ <SlidersHorizontal />
|
|
|
+ Import Settings
|
|
|
+ </Button>
|
|
|
+ <input
|
|
|
+ id="json-upload"
|
|
|
+ type="file"
|
|
|
+ accept="application/json"
|
|
|
+ className="hidden"
|
|
|
+ onChange={onImportSettings}
|
|
|
+ />
|
|
|
+
|
|
|
+ {importedSettings && Object.keys(importedSettings.apiConfigs).length > 1 && (
|
|
|
+ <div className="space-y-1">
|
|
|
+ <Label>API Config</Label>
|
|
|
+ <Popover open={configPopoverOpen} onOpenChange={setConfigPopoverOpen}>
|
|
|
+ <PopoverTrigger asChild>
|
|
|
+ <Button
|
|
|
+ variant="input"
|
|
|
+ role="combobox"
|
|
|
+ aria-expanded={configPopoverOpen}
|
|
|
+ className="flex items-center justify-between w-full">
|
|
|
+ <div>{selectedConfigName || "Select config"}</div>
|
|
|
+ <ChevronsUpDown className="opacity-50" />
|
|
|
+ </Button>
|
|
|
+ </PopoverTrigger>
|
|
|
+ <PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
|
|
|
+ <Command>
|
|
|
+ <CommandInput
|
|
|
+ placeholder="Search configs..."
|
|
|
+ className="h-9"
|
|
|
+ />
|
|
|
+ <CommandList>
|
|
|
+ <CommandEmpty>No config found.</CommandEmpty>
|
|
|
+ <CommandGroup>
|
|
|
+ {Object.keys(importedSettings.apiConfigs).map(
|
|
|
+ (configName) => (
|
|
|
+ <CommandItem
|
|
|
+ key={configName}
|
|
|
+ value={configName}
|
|
|
+ onSelect={onSelectConfig}>
|
|
|
+ {configName}
|
|
|
+ {configName ===
|
|
|
+ importedSettings.currentApiConfigName && (
|
|
|
+ <span className="ml-2 text-xs text-muted-foreground">
|
|
|
+ (default)
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
+ <Check
|
|
|
+ className={cn(
|
|
|
+ "ml-auto size-4",
|
|
|
+ configName ===
|
|
|
+ selectedConfigName
|
|
|
+ ? "opacity-100"
|
|
|
+ : "opacity-0",
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </CommandItem>
|
|
|
+ ),
|
|
|
+ )}
|
|
|
+ </CommandGroup>
|
|
|
+ </CommandList>
|
|
|
+ </Command>
|
|
|
+ </PopoverContent>
|
|
|
+ </Popover>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {settings && (
|
|
|
+ <SettingsDiff defaultSettings={EVALS_SETTINGS} customSettings={settings} />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
<Popover open={modelPopoverOpen} onOpenChange={setModelPopoverOpen}>
|
|
|
<PopoverTrigger asChild>
|
|
|
<Button
|
|
|
@@ -192,25 +408,23 @@ export function NewRun() {
|
|
|
aria-expanded={modelPopoverOpen}
|
|
|
className="flex items-center justify-between">
|
|
|
<div>
|
|
|
- {models.data?.find(({ id }) => id === model)?.name ||
|
|
|
- model ||
|
|
|
- "Select OpenRouter Model"}
|
|
|
+ {models?.find(({ id }) => id === model)?.name || `Select`}
|
|
|
</div>
|
|
|
<ChevronsUpDown className="opacity-50" />
|
|
|
</Button>
|
|
|
</PopoverTrigger>
|
|
|
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
|
|
|
- <Command filter={onFilterModels}>
|
|
|
+ <Command filter={onFilter}>
|
|
|
<CommandInput
|
|
|
placeholder="Search"
|
|
|
- value={modelSearchValue}
|
|
|
- onValueChange={setModelSearchValue}
|
|
|
+ value={searchValue}
|
|
|
+ onValueChange={setSearchValue}
|
|
|
className="h-9"
|
|
|
/>
|
|
|
<CommandList>
|
|
|
<CommandEmpty>No model found.</CommandEmpty>
|
|
|
<CommandGroup>
|
|
|
- {models.data?.map(({ id, name }) => (
|
|
|
+ {models?.map(({ id, name }) => (
|
|
|
<CommandItem
|
|
|
key={id}
|
|
|
value={id}
|
|
|
@@ -229,45 +443,49 @@ export function NewRun() {
|
|
|
</Command>
|
|
|
</PopoverContent>
|
|
|
</Popover>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
- )}
|
|
|
|
|
|
- <FormItem className="flex-1">
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- variant="secondary"
|
|
|
- onClick={() => document.getElementById("json-upload")?.click()}>
|
|
|
- <SlidersHorizontal />
|
|
|
- Import Settings
|
|
|
- </Button>
|
|
|
- <input
|
|
|
- id="json-upload"
|
|
|
- type="file"
|
|
|
- accept="application/json"
|
|
|
- className="hidden"
|
|
|
- onChange={onImportSettings}
|
|
|
- />
|
|
|
- {settings && (
|
|
|
- <ScrollArea className="max-h-64 border rounded-sm">
|
|
|
- <>
|
|
|
- <div className="flex items-center gap-1 p-2 border-b">
|
|
|
- <CircleCheck className="size-4 text-ring" />
|
|
|
- <div className="text-sm">
|
|
|
- Imported valid Kilo Code settings. Showing differences from default
|
|
|
- settings.
|
|
|
- </div>
|
|
|
+ <div className="flex items-center gap-1.5">
|
|
|
+ <Checkbox
|
|
|
+ id="native"
|
|
|
+ checked={useNativeToolProtocol}
|
|
|
+ onCheckedChange={(checked) =>
|
|
|
+ setUseNativeToolProtocol(checked === true)
|
|
|
+ }
|
|
|
+ />
|
|
|
+ <Label htmlFor="native">Use Native Tool Calls</Label>
|
|
|
</div>
|
|
|
- <SettingsDiff defaultSettings={EVALS_SETTINGS} customSettings={settings} />
|
|
|
</>
|
|
|
- <ScrollBar orientation="horizontal" />
|
|
|
- </ScrollArea>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ {provider === "roo" && (
|
|
|
+ <FormField
|
|
|
+ control={form.control}
|
|
|
+ name="jobToken"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>Roo Code Cloud Token</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input type="password" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ <FormDescription>
|
|
|
+ If you have access to the Roo Code Cloud repository then you can generate a
|
|
|
+ token with:
|
|
|
+ <br />
|
|
|
+ <code className="text-xs">
|
|
|
+ pnpm --filter @roo-code-cloud/auth production:create-job-token [org]
|
|
|
+ [timeout]
|
|
|
+ </code>
|
|
|
+ </FormDescription>
|
|
|
+ </FormItem>
|
|
|
)}
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- </div>
|
|
|
+ />
|
|
|
+ )}
|
|
|
|
|
|
<FormField
|
|
|
control={form.control}
|
|
|
@@ -275,18 +493,51 @@ export function NewRun() {
|
|
|
render={() => (
|
|
|
<FormItem>
|
|
|
<FormLabel>Exercises</FormLabel>
|
|
|
- <Tabs
|
|
|
- defaultValue="full"
|
|
|
- onValueChange={(value) => setValue("suite", value as "full" | "partial")}>
|
|
|
- <TabsList>
|
|
|
- <TabsTrigger value="full">All</TabsTrigger>
|
|
|
- <TabsTrigger value="partial">Some</TabsTrigger>
|
|
|
- </TabsList>
|
|
|
- </Tabs>
|
|
|
+ <div className="flex items-center gap-2 flex-wrap">
|
|
|
+ <Tabs
|
|
|
+ defaultValue="full"
|
|
|
+ onValueChange={(value) => {
|
|
|
+ setValue("suite", value as "full" | "partial")
|
|
|
+ if (value === "full") {
|
|
|
+ setSelectedExercises([])
|
|
|
+ setValue("exercises", [])
|
|
|
+ }
|
|
|
+ }}>
|
|
|
+ <TabsList>
|
|
|
+ <TabsTrigger value="full">All</TabsTrigger>
|
|
|
+ <TabsTrigger value="partial">Some</TabsTrigger>
|
|
|
+ </TabsList>
|
|
|
+ </Tabs>
|
|
|
+ {suite === "partial" && languages.length > 0 && (
|
|
|
+ <div className="flex items-center gap-1 flex-wrap">
|
|
|
+ {languages.map((lang) => (
|
|
|
+ <Button
|
|
|
+ key={lang}
|
|
|
+ type="button"
|
|
|
+ variant={
|
|
|
+ isLanguageSelected(lang)
|
|
|
+ ? "default"
|
|
|
+ : isLanguagePartiallySelected(lang)
|
|
|
+ ? "secondary"
|
|
|
+ : "outline"
|
|
|
+ }
|
|
|
+ size="sm"
|
|
|
+ onClick={() => toggleLanguage(lang)}
|
|
|
+ className="text-xs capitalize">
|
|
|
+ {lang}
|
|
|
+ </Button>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
{suite === "partial" && (
|
|
|
<MultiSelect
|
|
|
options={exercises.data?.map((path) => ({ value: path, label: path })) || []}
|
|
|
- onValueChange={(value) => setValue("exercises", value)}
|
|
|
+ value={selectedExercises}
|
|
|
+ onValueChange={(value) => {
|
|
|
+ setSelectedExercises(value)
|
|
|
+ setValue("exercises", value)
|
|
|
+ }}
|
|
|
placeholder="Select"
|
|
|
variant="inverted"
|
|
|
maxCount={4}
|
|
|
@@ -306,11 +557,14 @@ export function NewRun() {
|
|
|
<FormControl>
|
|
|
<div className="flex flex-row items-center gap-2">
|
|
|
<Slider
|
|
|
- defaultValue={[field.value]}
|
|
|
+ value={[field.value]}
|
|
|
min={CONCURRENCY_MIN}
|
|
|
max={CONCURRENCY_MAX}
|
|
|
step={1}
|
|
|
- onValueChange={(value) => field.onChange(value[0])}
|
|
|
+ onValueChange={(value) => {
|
|
|
+ field.onChange(value[0])
|
|
|
+ localStorage.setItem("evals-concurrency", String(value[0]))
|
|
|
+ }}
|
|
|
/>
|
|
|
<div>{field.value}</div>
|
|
|
</div>
|
|
|
@@ -329,11 +583,14 @@ export function NewRun() {
|
|
|
<FormControl>
|
|
|
<div className="flex flex-row items-center gap-2">
|
|
|
<Slider
|
|
|
- defaultValue={[field.value]}
|
|
|
+ value={[field.value]}
|
|
|
min={TIMEOUT_MIN}
|
|
|
max={TIMEOUT_MAX}
|
|
|
step={1}
|
|
|
- onValueChange={(value) => field.onChange(value[0])}
|
|
|
+ onValueChange={(value) => {
|
|
|
+ field.onChange(value[0])
|
|
|
+ localStorage.setItem("evals-timeout", String(value[0]))
|
|
|
+ }}
|
|
|
/>
|
|
|
<div>{field.value}</div>
|
|
|
</div>
|