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:
@@ -352,13 +352,14 @@ export class ApiClient {
|
||||
machineId: string,
|
||||
directory: string,
|
||||
agent?: 'claude' | 'codex' | 'gemini',
|
||||
model?: string,
|
||||
yolo?: boolean,
|
||||
sessionType?: 'simple' | 'worktree',
|
||||
worktreeName?: string
|
||||
): Promise<SpawnResponse> {
|
||||
return await this.request<SpawnResponse>(`/api/machines/${encodeURIComponent(machineId)}/spawn`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ directory, agent, yolo, sessionType, worktreeName })
|
||||
body: JSON.stringify({ directory, agent, model, yolo, sessionType, worktreeName })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ type SpawnInput = {
|
||||
machineId: string
|
||||
directory: string
|
||||
agent?: 'claude' | 'codex' | 'gemini'
|
||||
model?: string
|
||||
yolo?: boolean
|
||||
sessionType?: 'simple' | 'worktree'
|
||||
worktreeName?: string
|
||||
@@ -28,6 +29,7 @@ export function useSpawnSession(api: ApiClient | null): {
|
||||
input.machineId,
|
||||
input.directory,
|
||||
input.agent,
|
||||
input.model,
|
||||
input.yolo,
|
||||
input.sessionType,
|
||||
input.worktreeName
|
||||
|
||||
@@ -100,6 +100,8 @@ export default {
|
||||
'newSession.type.worktree.desc': 'Create a new worktree next to repo',
|
||||
'newSession.type.worktree.placeholder': 'feature-x (default 1228-xxxx)',
|
||||
'newSession.agent': 'Agent',
|
||||
'newSession.model': 'Model',
|
||||
'newSession.model.optional': 'optional',
|
||||
'newSession.yolo': 'YOLO mode',
|
||||
'newSession.yolo.title': 'Bypass approvals and sandbox',
|
||||
'newSession.yolo.desc': 'Uses dangerous agent flags when spawning.',
|
||||
|
||||
@@ -102,6 +102,8 @@ export default {
|
||||
'newSession.type.worktree.desc': '在仓库旁创建新工作树',
|
||||
'newSession.type.worktree.placeholder': 'feature-x (默认 1228-xxxx)',
|
||||
'newSession.agent': '代理',
|
||||
'newSession.model': '模型',
|
||||
'newSession.model.optional': '可选',
|
||||
'newSession.yolo': 'YOLO 模式',
|
||||
'newSession.yolo.title': '跳过审批和沙箱',
|
||||
'newSession.yolo.desc': '启动时使用危险的代理标志。',
|
||||
|
||||
Reference in New Issue
Block a user