|
| 1 | +'use client'; |
| 2 | + |
| 3 | +import { useCallback, useEffect } from 'react'; |
| 4 | + |
| 5 | +import { RotateCcw } from 'lucide-react'; |
| 6 | +import { useForm } from 'react-hook-form'; |
| 7 | + |
| 8 | +import { TagInput } from '@/components/custom/tag-input'; |
| 9 | +import { Button } from '@/components/ui/button'; |
| 10 | +import { |
| 11 | + Form, |
| 12 | + FormControl, |
| 13 | + FormField, |
| 14 | + FormItem, |
| 15 | + FormLabel, |
| 16 | +} from '@/components/ui/form'; |
| 17 | +import { |
| 18 | + Select, |
| 19 | + SelectContent, |
| 20 | + SelectItem, |
| 21 | + SelectTrigger, |
| 22 | + SelectValue, |
| 23 | +} from '@/components/ui/select'; |
| 24 | +import { Textarea } from '@/components/ui/textarea'; |
| 25 | + |
| 26 | +export type GoldenAgentConfig = { |
| 27 | + harness: string; |
| 28 | + model: string; |
| 29 | + system_prompt: string; |
| 30 | + allowed_tools: string[]; |
| 31 | +}; |
| 32 | + |
| 33 | +const SUPPORTED_HARNESSES = [ |
| 34 | + 'sandbox-claude', |
| 35 | + 'claude-code', |
| 36 | + 'codex', |
| 37 | + 'agentex', |
| 38 | +] as const; |
| 39 | + |
| 40 | +const DEFAULT_MODEL_BY_HARNESS: Record<string, string> = { |
| 41 | + 'sandbox-claude': 'claude-opus-4-6', |
| 42 | + 'claude-code': 'claude-opus-4-6', |
| 43 | + codex: 'gpt-5.4', |
| 44 | + agentex: 'gpt-5.4', |
| 45 | +}; |
| 46 | + |
| 47 | +export const DEFAULT_CONFIG: GoldenAgentConfig = { |
| 48 | + harness: 'sandbox-claude', |
| 49 | + model: 'claude-opus-4-6', |
| 50 | + system_prompt: |
| 51 | + "You are a developer for Scale AI's SGP team. Your primary sources of truth are scaleapi/packages/egp-api-backend and scaleapi/packages/egp-annotation. Use these packages to do the development you need to do. Make sure to read the READMEs and CLAUDE.mds in those directories to get the context you need to address any issues. Your main goal is to take in the information from the prompt, use your sources of truth to come up with a course of action to address it, and then make a PR with the solution.", |
| 52 | + allowed_tools: [ |
| 53 | + 'Read', |
| 54 | + 'Write', |
| 55 | + 'Edit', |
| 56 | + 'Glob', |
| 57 | + 'Grep', |
| 58 | + 'Bash', |
| 59 | + 'WebSearch', |
| 60 | + 'WebFetch', |
| 61 | + 'List', |
| 62 | + ], |
| 63 | +}; |
| 64 | + |
| 65 | +type ConfigPanelProps = { |
| 66 | + disabled: boolean; |
| 67 | + onConfigChange: (config: GoldenAgentConfig) => void; |
| 68 | + onReset: () => void; |
| 69 | +}; |
| 70 | + |
| 71 | +export function ConfigPanel({ |
| 72 | + disabled, |
| 73 | + onConfigChange, |
| 74 | + onReset, |
| 75 | +}: ConfigPanelProps) { |
| 76 | + const form = useForm<GoldenAgentConfig>({ |
| 77 | + defaultValues: DEFAULT_CONFIG, |
| 78 | + }); |
| 79 | + |
| 80 | + const harness = form.watch('harness'); |
| 81 | + |
| 82 | + useEffect(() => { |
| 83 | + const subscription = form.watch(values => { |
| 84 | + onConfigChange(values as GoldenAgentConfig); |
| 85 | + }); |
| 86 | + return () => subscription.unsubscribe(); |
| 87 | + }, [form, onConfigChange]); |
| 88 | + |
| 89 | + const handleHarnessChange = useCallback( |
| 90 | + (value: string) => { |
| 91 | + form.setValue('harness', value); |
| 92 | + form.setValue('model', DEFAULT_MODEL_BY_HARNESS[value] ?? ''); |
| 93 | + }, |
| 94 | + [form] |
| 95 | + ); |
| 96 | + |
| 97 | + const handleReset = useCallback(() => { |
| 98 | + form.reset(DEFAULT_CONFIG); |
| 99 | + onReset(); |
| 100 | + }, [form, onReset]); |
| 101 | + |
| 102 | + return ( |
| 103 | + <div className="flex h-full flex-col"> |
| 104 | + <div className="border-b px-4 py-3"> |
| 105 | + <div className="flex items-center justify-between"> |
| 106 | + <h2 className="text-sm font-semibold">Configuration</h2> |
| 107 | + {disabled && ( |
| 108 | + <Button |
| 109 | + variant="ghost" |
| 110 | + size="sm" |
| 111 | + onClick={handleReset} |
| 112 | + className="gap-1.5 text-xs" |
| 113 | + > |
| 114 | + <RotateCcw className="size-3" /> |
| 115 | + New Chat |
| 116 | + </Button> |
| 117 | + )} |
| 118 | + </div> |
| 119 | + <p className="text-muted-foreground mt-0.5 text-xs">golden-agent</p> |
| 120 | + </div> |
| 121 | + |
| 122 | + <Form {...form}> |
| 123 | + <form className="flex-1 space-y-4 overflow-y-auto p-4"> |
| 124 | + <FormField |
| 125 | + control={form.control} |
| 126 | + name="harness" |
| 127 | + render={({ field }) => ( |
| 128 | + <FormItem> |
| 129 | + <FormLabel className="text-xs">Harness</FormLabel> |
| 130 | + <Select |
| 131 | + value={field.value} |
| 132 | + onValueChange={handleHarnessChange} |
| 133 | + disabled={disabled} |
| 134 | + > |
| 135 | + <FormControl> |
| 136 | + <SelectTrigger className="w-full"> |
| 137 | + <SelectValue /> |
| 138 | + </SelectTrigger> |
| 139 | + </FormControl> |
| 140 | + <SelectContent> |
| 141 | + {SUPPORTED_HARNESSES.map(h => ( |
| 142 | + <SelectItem key={h} value={h}> |
| 143 | + {h} |
| 144 | + </SelectItem> |
| 145 | + ))} |
| 146 | + </SelectContent> |
| 147 | + </Select> |
| 148 | + </FormItem> |
| 149 | + )} |
| 150 | + /> |
| 151 | + |
| 152 | + <FormField |
| 153 | + control={form.control} |
| 154 | + name="model" |
| 155 | + render={({ field }) => ( |
| 156 | + <FormItem> |
| 157 | + <FormLabel className="text-xs">Model</FormLabel> |
| 158 | + <FormControl> |
| 159 | + <input |
| 160 | + type="text" |
| 161 | + value={field.value} |
| 162 | + onChange={field.onChange} |
| 163 | + disabled={disabled} |
| 164 | + placeholder={ |
| 165 | + DEFAULT_MODEL_BY_HARNESS[harness] ?? 'Enter model name' |
| 166 | + } |
| 167 | + className="border-input placeholder:text-muted-foreground flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs outline-none focus-visible:ring-[1px] focus-visible:ring-[#756BA2] disabled:cursor-not-allowed disabled:opacity-50" |
| 168 | + /> |
| 169 | + </FormControl> |
| 170 | + </FormItem> |
| 171 | + )} |
| 172 | + /> |
| 173 | + |
| 174 | + <FormField |
| 175 | + control={form.control} |
| 176 | + name="system_prompt" |
| 177 | + render={({ field }) => ( |
| 178 | + <FormItem> |
| 179 | + <FormLabel className="text-xs">System Prompt</FormLabel> |
| 180 | + <FormControl> |
| 181 | + <Textarea |
| 182 | + value={field.value} |
| 183 | + onChange={field.onChange} |
| 184 | + disabled={disabled} |
| 185 | + placeholder="Enter system instructions..." |
| 186 | + className="min-h-32 resize-y" |
| 187 | + /> |
| 188 | + </FormControl> |
| 189 | + </FormItem> |
| 190 | + )} |
| 191 | + /> |
| 192 | + |
| 193 | + <FormField |
| 194 | + control={form.control} |
| 195 | + name="allowed_tools" |
| 196 | + render={({ field }) => ( |
| 197 | + <FormItem> |
| 198 | + <FormLabel className="text-xs">Allowed Tools</FormLabel> |
| 199 | + <FormControl> |
| 200 | + <TagInput |
| 201 | + value={field.value} |
| 202 | + onChange={field.onChange} |
| 203 | + disabled={disabled} |
| 204 | + placeholder="Add tool name and press Enter" |
| 205 | + /> |
| 206 | + </FormControl> |
| 207 | + </FormItem> |
| 208 | + )} |
| 209 | + /> |
| 210 | + </form> |
| 211 | + </Form> |
| 212 | + </div> |
| 213 | + ); |
| 214 | +} |
0 commit comments