Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 73 additions & 22 deletions frontend/src/components/settings/AgentDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Textarea } from '@/components/ui/textarea'
import { Switch } from '@/components/ui/switch'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Combobox, type ComboboxOption } from '@/components/ui/combobox'
import { getProvidersWithModels } from '@/api/providers'

const agentFormSchema = z.object({
name: z.string().min(1, 'Agent name is required').regex(/^[a-z0-9-]+$/, 'Must be lowercase letters, numbers, and hyphens only'),
Expand Down Expand Up @@ -36,10 +40,8 @@ interface Agent {
mode?: 'subagent' | 'primary' | 'all'
temperature?: number
topP?: number
model?: {
modelID: string
providerID: string
}
top_p?: number
model?: string
tools?: Record<string, boolean>
permission?: {
edit?: 'ask' | 'allow' | 'deny'
Expand All @@ -50,6 +52,12 @@ interface Agent {
[key: string]: unknown
}

function parseModelString(model?: string): { providerId: string; modelId: string } {
if (!model) return { providerId: '', modelId: '' }
const [providerId, ...rest] = model.split('/')
return { providerId: providerId || '', modelId: rest.join('/') || '' }
}

interface AgentDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
Expand All @@ -58,6 +66,29 @@ interface AgentDialogProps {
}

export function AgentDialog({ open, onOpenChange, onSubmit, editingAgent }: AgentDialogProps) {
const parsedModel = parseModelString(editingAgent?.agent.model)

const { data: providers = [] } = useQuery({
queryKey: ['providers-with-models'],
queryFn: () => getProvidersWithModels(),
enabled: open,
staleTime: 5 * 60 * 1000,
})

const providerOptions: ComboboxOption[] = useMemo(() => {
const sourceLabels: Record<string, string> = {
configured: 'Custom',
local: 'Local',
builtin: 'Built-in',
}
return providers.map(p => ({
value: p.id,
label: p.name || p.id,
description: p.models.length > 0 ? `${p.models.length} models` : undefined,
group: sourceLabels[p.source] || 'Other',
}))
}, [providers])

const form = useForm<AgentFormValues>({
resolver: zodResolver(agentFormSchema),
defaultValues: {
Expand All @@ -66,9 +97,9 @@ export function AgentDialog({ open, onOpenChange, onSubmit, editingAgent }: Agen
prompt: editingAgent?.agent.prompt || '',
mode: editingAgent?.agent.mode || 'subagent',
temperature: editingAgent?.agent.temperature ?? 0.7,
topP: editingAgent?.agent.topP ?? 1,
modelId: editingAgent?.agent.model?.modelID || '',
providerId: editingAgent?.agent.model?.providerID || '',
topP: editingAgent?.agent.topP ?? editingAgent?.agent.top_p ?? 1,
modelId: parsedModel.modelId,
providerId: parsedModel.providerId,
write: editingAgent?.agent.tools?.write ?? true,
edit: editingAgent?.agent.tools?.edit ?? true,
bash: editingAgent?.agent.tools?.bash ?? true,
Expand All @@ -80,6 +111,23 @@ export function AgentDialog({ open, onOpenChange, onSubmit, editingAgent }: Agen
}
})

const selectedProviderId = form.watch('providerId')

const modelOptions: ComboboxOption[] = useMemo(() => {
const selectedProvider = providers.find(p => p.id === selectedProviderId)
if (selectedProvider && selectedProvider.models.length > 0) {
return selectedProvider.models.map(m => ({
value: m.id,
label: m.name || m.id,
}))
}
return providers.flatMap(p => p.models.map(m => ({
value: m.id,
label: m.name || m.id,
group: p.name || p.id,
})))
}, [providers, selectedProviderId])

const handleSubmit = (values: AgentFormValues) => {
const agent: Agent = {
prompt: values.prompt,
Expand All @@ -101,11 +149,8 @@ export function AgentDialog({ open, onOpenChange, onSubmit, editingAgent }: Agen
}
}

if (values.modelId || values.providerId) {
agent.model = {
modelID: values.modelId || '',
providerID: values.providerId || ''
}
if (values.modelId && values.providerId) {
agent.model = `${values.providerId}/${values.modelId}`
}

onSubmit(values.name, agent)
Expand Down Expand Up @@ -259,14 +304,17 @@ export function AgentDialog({ open, onOpenChange, onSubmit, editingAgent }: Agen
<div className="flex flex-col sm:grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="modelId"
name="providerId"
render={({ field }) => (
<FormItem>
<FormLabel>Model ID</FormLabel>
<FormLabel>Provider ID</FormLabel>
<FormControl>
<Input
{...field}
placeholder="claude-3-5-sonnet-20241022"
<Combobox
value={field.value || ''}
onChange={field.onChange}
options={providerOptions}
placeholder="Select or type provider..."
allowCustomValue
/>
</FormControl>
<FormMessage />
Expand All @@ -276,14 +324,17 @@ export function AgentDialog({ open, onOpenChange, onSubmit, editingAgent }: Agen

<FormField
control={form.control}
name="providerId"
name="modelId"
render={({ field }) => (
<FormItem>
<FormLabel>Provider ID</FormLabel>
<FormLabel>Model ID</FormLabel>
<FormControl>
<Input
{...field}
placeholder="anthropic"
<Combobox
value={field.value || ''}
onChange={field.onChange}
options={modelOptions}
placeholder="Select or type model..."
allowCustomValue
/>
</FormControl>
<FormMessage />
Expand Down
8 changes: 3 additions & 5 deletions frontend/src/components/settings/AgentsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ interface Agent {
mode?: 'subagent' | 'primary' | 'all'
temperature?: number
topP?: number
model?: {
modelID: string
providerID: string
}
top_p?: number
model?: string
tools?: Record<string, boolean>
permission?: {
edit?: 'ask' | 'allow' | 'deny'
Expand Down Expand Up @@ -120,7 +118,7 @@ export function AgentsEditor({ agents, onChange }: AgentsEditorProps) {
<p>Mode: {agent.mode}</p>
{agent.temperature !== undefined && <p>Temperature: {agent.temperature}</p>}
{agent.topP !== undefined && <p>Top P: {agent.topP}</p>}
{agent.model?.modelID && <p>Model: {agent.model.providerID}/{agent.model.modelID}</p>}
{agent.model && <p>Model: {agent.model}</p>}
{agent.disable && <p>Status: Disabled</p>}
</div>
{agent.prompt && (
Expand Down
Loading