mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: add model selection for AI agents (claude, codex, gemini)
Add a new ModelSelector component that allows users to choose specific model variants for different AI agents. This includes: - ModelSelector UI component with agent-specific model options - Model parameter propagation through API, RPC, and spawning layers - Support for auto model selection (default behavior) - Localization strings for English and Chinese - Type definitions for model options per agent
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import type { AgentType } from './types'
|
||||
import { MODEL_OPTIONS } from './types'
|
||||
import { useTranslation } from '@/lib/use-translation'
|
||||
|
||||
export function ModelSelector(props: {
|
||||
agent: AgentType
|
||||
model: string
|
||||
isDisabled: boolean
|
||||
onModelChange: (value: string) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const options = MODEL_OPTIONS[props.agent]
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5 px-3 py-3">
|
||||
<label className="text-xs font-medium text-[var(--app-hint)]">
|
||||
{t('newSession.model')}{' '}
|
||||
<span className="font-normal">({t('newSession.model.optional')})</span>
|
||||
</label>
|
||||
<select
|
||||
value={props.model}
|
||||
onChange={(e) => props.onModelChange(e.target.value)}
|
||||
disabled={props.isDisabled}
|
||||
className="w-full px-3 py-2 text-sm rounded-lg border border-[var(--app-divider)] bg-[var(--app-bg)] text-[var(--app-text)] focus:outline-none focus:ring-2 focus:ring-[var(--app-link)] disabled:opacity-50"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { ActionButtons } from './ActionButtons'
|
||||
import { AgentSelector } from './AgentSelector'
|
||||
import { DirectorySection } from './DirectorySection'
|
||||
import { MachineSelector } from './MachineSelector'
|
||||
import { ModelSelector } from './ModelSelector'
|
||||
import { SessionTypeSelector } from './SessionTypeSelector'
|
||||
import { YoloToggle } from './YoloToggle'
|
||||
|
||||
@@ -34,6 +35,7 @@ export function NewSession(props: {
|
||||
const [isDirectoryFocused, setIsDirectoryFocused] = useState(false)
|
||||
const [pathExistence, setPathExistence] = useState<Record<string, boolean>>({})
|
||||
const [agent, setAgent] = useState<AgentType>('claude')
|
||||
const [model, setModel] = useState('auto')
|
||||
const [yoloMode, setYoloMode] = useState(false)
|
||||
const [sessionType, setSessionType] = useState<SessionType>('simple')
|
||||
const [worktreeName, setWorktreeName] = useState('')
|
||||
@@ -46,6 +48,10 @@ export function NewSession(props: {
|
||||
}
|
||||
}, [sessionType])
|
||||
|
||||
useEffect(() => {
|
||||
setModel('auto')
|
||||
}, [agent])
|
||||
|
||||
useEffect(() => {
|
||||
if (props.machines.length === 0) return
|
||||
if (machineId && props.machines.find((m) => m.id === machineId)) return
|
||||
@@ -193,6 +199,7 @@ export function NewSession(props: {
|
||||
machineId,
|
||||
directory: directory.trim(),
|
||||
agent,
|
||||
model: model !== 'auto' ? model : undefined,
|
||||
yolo: yoloMode,
|
||||
sessionType,
|
||||
worktreeName: sessionType === 'worktree' ? (worktreeName.trim() || undefined) : undefined
|
||||
@@ -251,6 +258,12 @@ export function NewSession(props: {
|
||||
isDisabled={isFormDisabled}
|
||||
onAgentChange={setAgent}
|
||||
/>
|
||||
<ModelSelector
|
||||
agent={agent}
|
||||
model={model}
|
||||
isDisabled={isFormDisabled}
|
||||
onModelChange={setModel}
|
||||
/>
|
||||
<YoloToggle
|
||||
yoloMode={yoloMode}
|
||||
isDisabled={isFormDisabled}
|
||||
|
||||
@@ -1,2 +1,23 @@
|
||||
export type AgentType = 'claude' | 'codex' | 'gemini'
|
||||
export type SessionType = 'simple' | 'worktree'
|
||||
|
||||
export const MODEL_OPTIONS: Record<AgentType, { value: string; label: string }[]> = {
|
||||
claude: [
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'opus', label: 'Opus' },
|
||||
{ value: 'sonnet', label: 'Sonnet' },
|
||||
],
|
||||
codex: [
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
|
||||
{ value: 'gpt-5.2', label: 'GPT-5.2' },
|
||||
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
||||
{ value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
|
||||
],
|
||||
gemini: [
|
||||
{ value: 'auto', label: 'Auto' },
|
||||
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
|
||||
{ value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
|
||||
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user