import { useState, useEffect } from 'react' import { Plus, Trash2, Check, Zap, Save, Globe } from 'lucide-react' import { useTranslation } from 'react-i18next' import { useLLMStore } from '../../stores/llm-store' import type { ModelProfile } from '../../shared/ipc-channels' import { randomUUID } from '../../utils/id' import { Button } from '../ui/Button' import { Input } from '../ui/Input' import { Label } from '../ui/Label' import { NativeSelect } from '../ui/NativeSelect' import { cn } from '../../lib/utils' /** 模型设置面板 — 在侧边栏 settings 视图中展示 */ export default function ModelSettings() { const { t } = useTranslation('settings') const models = useLLMStore(s => s.models) const defaultModelId = useLLMStore(s => s.defaultModelId) const loaded = useLLMStore(s => s.loaded) const loadModels = useLLMStore(s => s.loadModels) const saveModel = useLLMStore(s => s.saveModel) const deleteModel = useLLMStore(s => s.deleteModel) const setDefaultModel = useLLMStore(s => s.setDefaultModel) const [editingModel, setEditingModel] = useState(null) const [saving, setSaving] = useState(false) useEffect(() => { if (!loaded) loadModels() }, [loaded, loadModels]) /** 创建新模型配置 */ const handleAddModel = () => { setEditingModel({ id: randomUUID(), name: '', provider: 'openai', protocol: 'openai', modelName: 'gpt-4o', apiKey: '', baseUrl: 'https://api.openai.com', temperature: 0.7, maxTokens: 4096, purposes: ['generation'], }) } /** 保存模型 */ const handleSave = async () => { if (!editingModel) return setSaving(true) await saveModel(editingModel) setEditingModel(null) setSaving(false) } return (
{/* 标题栏 */}
{t('models.title')}
{/* 编辑表单 */} {editingModel && ( setEditingModel(null)} saving={saving} /> )} {/* 空状态 */} {!editingModel && models.length === 0 && (
{t('models.noModels', { label: '' })}
)} {/* 模型列表 */} {!editingModel && models.map((model) => (
{model.name || model.modelName}
{defaultModelId !== model.id && ( )}
{model.provider} · {model.modelName} {defaultModelId === model.id && ( {t('models.default')} )}
))} {/* 代理配置 */} {!editingModel && ( )}
) } /** 模型编辑表单 */ function ModelForm({ model, onChange, onSave, onCancel, saving, }: { model: ModelProfile onChange: (m: ModelProfile) => void onSave: () => void onCancel: () => void saving: boolean }) { const { t } = useTranslation('settings') const testConnection = useLLMStore(s => s.testConnection) const [testing, setTesting] = useState(false) const [testResult, setTestResult] = useState<{ success: boolean, error?: string } | null>(null) const update = (key: K, value: ModelProfile[K]) => { onChange({ ...model, [key]: value }) } const handleTest = async () => { setTesting(true) setTestResult(null) const result = await testConnection(model) setTestResult(result) setTesting(false) setTimeout(() => setTestResult(null), 3000) } return (
update('name', e.target.value)} placeholder={t('models.modelNamePlaceholder')} />
update('provider', e.target.value as ModelProfile['provider'])}>
update('protocol', e.target.value as ModelProfile['protocol'])}>
update('modelName', e.target.value)} placeholder="gpt-4o / deepseek-chat" />
update('baseUrl', e.target.value)} placeholder="https://api.openai.com" />
update('apiKey', e.target.value)} placeholder="sk-..." />
update('temperature', (e.target.value === '' ? '' : parseFloat(e.target.value)) as number)} onBlur={() => { const v = Number(model.temperature); if (isNaN(v)) update('temperature', 0.7); }} />
update('maxTokens', (e.target.value === '' ? '' : parseInt(e.target.value)) as number)} onBlur={() => { const v = Number(model.maxTokens); if (!v || v < 1) update('maxTokens', 4096); }} />
{/* 操作按钮 */}
{testResult && (
{testResult.success ? t('models.connectionSuccess') : t('models.connectionFailed', { error: testResult.error })}
)}
) } /** 代理配置面板 */ function ProxySettings() { const { t } = useTranslation('settings') const [proxy, setProxy] = useState<{ enabled: boolean; type: 'http' | 'socks5'; host: string; port: number }>({ enabled: false, type: 'http', host: '', port: 7890 }) const [saving, setSaving] = useState(false) useEffect(() => { loadProxy() }, []) const loadProxy = async () => { try { const { ipc } = await import('../../services/ipc-client') const config = await ipc.invoke('config:get') if (config.proxy) setProxy(config.proxy) } catch { /* 忽略 */ } } const handleSave = async () => { setSaving(true) try { const { ipc } = await import('../../services/ipc-client') await ipc.invoke('config:set', { proxy }) } catch { /* 忽略 */ } setSaving(false) } return (
{t('models.proxy.title')}
{proxy.enabled && (
setProxy({ ...proxy, type: e.target.value as 'http' | 'socks5' })} className="h-7 text-xs" >
setProxy({ ...proxy, host: e.target.value })} placeholder="127.0.0.1" />
setProxy({ ...proxy, port: (e.target.value === '' ? '' : parseInt(e.target.value)) as number })} onBlur={() => { const v = Number(proxy.port); if (!v) setProxy({ ...proxy, port: 7890 }); }} />
)}
) }