{"repo": "352727664/codexbridge", "files": {"webui/src/pages/ProviderForm.jsx": {"ad38e3f5e781aa726e6a36e681f4a25c05493081": {"t": 1785595171.932739, "n": 19377, "h": "dde539c81afe7228", "c": "import { useState, useEffect } from 'react'\nimport { api } from '../lib/api'\nimport LanguageSwitcher from '../components/LanguageSwitcher'\nimport { useTranslation } from '../i18n'\n\nconst PRESETS = [\n { id: 'deepseek', name: 'DeepSeek', base_url: 'https://api.deepseek.com', model: 'deepseek-chat', has_reasoning: true },\n { id: 'qwen', name: 'Qwen', base_url: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-max', has_reasoning: true },\n { id: 'kimi', name: 'Kimi', base_url: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-128k', has_reasoning: false },\n { id: 'glm', name: 'GLM', base_url: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-plus', has_reasoning: false },\n { id: 'mimo', name: 'MiMo', base_url: 'https://api.xiaomimimo.com/v1', model: 'mimo-v2.5-pro', has_reasoning: true },\n { id: 'minimax', name: 'MiniMax', base_url: 'https://api.minimax.chat/v1', model: 'MiniMax-Text-01', has_reasoning: false },\n { id: 'baichuan', name: 'Baichuan', base_url: 'https://api.baichuan-ai.com/v1', model: 'Baichuan4', has_reasoning: false },\n { id: 'yi', name: 'Yi', base_url: 'https://api.lingyiwanwu.com/v1', model: 'yi-lightning', has_reasoning: false },\n { id: 'doubao', name: 'Doubao', base_url: 'https://ark.cn-beijing.volces.com/api/v3', model: 'doubao-1.5-pro-256k', has_reasoning: true },\n { id: 'stepfun', name: 'StepFun', base_url: 'https://api.stepfun.com/v1', model: 'step-2-16k', has_reasoning: false },\n { id: 'siliconflow', name: 'SiliconFlow', base_url: 'https://api.siliconflow.cn/v1', model: 'deepseek-ai/DeepSeek-V3', has_reasoning: true },\n { id: 'anthropic', name: 'Anthropic', base_url: 'https://api.anthropic.com', model: 'claude-sonnet-4-20250514', has_reasoning: true, api_format: 'anthropic' },\n]\n\nexport default function ProviderForm({ provider, onBack }) {\n const { t } = useTranslation()\n const isEdit = !!provider?.id\n\n const [form, setForm] = useState({\n name: '', api_format: 'openai', base_url: '', api_key: '', model: '', has_reasoning: false, note: '', website: '',\n })\n const [testing, setTesting] = useState(false)\n const [testResult, setTestResult] = useState(null)\n const [showKey, setShowKey] = useState(false)\n const [fetchingModels, setFetchingModels] = useState(false)\n const [fetchedModels, setFetchedModels] = useState([])\n const [showModelDropdown, setShowModelDropdown] = useState(false)\n const [toast, setToast] = useState(null)\n const [showPresets, setShowPresets] = useState(!isEdit)\n const [configText, setConfigText] = useState('')\n const [configError, setConfigError] = useState(null)\n\n useEffect(() => {\n if (provider?.id) {\n const models = provider.models || []\n setForm({\n name: provider.name || '', api_format: provider.api_format || 'openai', base_url: provider.base_url || '',\n api_key: provider.api_key || '', model: models[0] || '', has_reasoning: provider.has_reasoning || false,\n note: provider.note || '', website: provider.website || '',\n })\n }\n }, [provider])\n\n useEffect(() => {\n const preview = {\n name: form.name || 'provider', api_format: form.api_format, base_url: form.base_url,\n api_key: form.api_key ? 'sk-***' : '', models: form.model ? [form.model] : [], has_reasoning: form.has_reasoning,\n }\n setConfigText(JSON.stringify(preview, null, 2))\n setConfigError(null)\n }, [form.name, form.api_format, form.base_url, form.api_key, form.model, form.has_reasoning])\n\n const showToast = (msg, type = 'success') => {\n setToast({ msg, type })\n setTimeout(() => setToast(null), 2500)\n }\n\n const update = (key, val) => setForm(prev => ({ ...prev, [key]: val }))\n\n const applyPreset = (preset) => {\n setForm(prev => ({ ...prev, name: preset.name, base_url: preset.base_url, model: preset.model, has_reasoning: preset.has_reasoning, api_format: preset.api_format || 'openai' }))\n setShowPresets(false)\n }\n\n const handleTest = async () => {\n setTesting(true)\n setTestResult(null)\n try {\n const res = await api.testConnection({ base_url: form.base_url, api_key: form.api_key, api_format: form.api_format, model: form.model || '' })\n setTestResult(res)\n } catch (e) {\n setTestResult({ status: 'error', message: e.message })\n }\n setTesting(false)\n }\n\n const handleFetchModels = async () => {\n setFetchingModels(true)\n try {\n const res = await api.fetchModels({ base_url: form.base_url, api_key: form.api_key, api_format: form.api_format })\n if (res.status === 'ok' && res.models?.length) {\n setFetchedModels(res.models)\n setShowModelDropdown(true)\n showToast(t('providerForm.toastModelsFetched', { count: res.models.length }))\n } else {\n showToast(res.message || t('providerForm.toastNoModels'), 'error')\n }\n } catch (e) {\n showToast(t('providerForm.toastFetchErr') + e.message, 'error')\n }\n setFetchingModels(false)\n }\n\n const handleSave = async () => {\n if (!form.name.trim()) { showToast(t('providerForm.errNoName'), 'error'); return }\n if (!form.model.trim()) { showToast(t('providerForm.errNoModel'), 'error'); return }\n\n let configData\n try { configData = JSON.parse(configText) } catch { showToast(t('providerForm.errBadJson'), 'error'); return }\n\n const data = {\n name: configData.name || form.name.trim(), api_format: form.api_format, base_url: form.base_url.trim(),\n api_key: form.api_key.trim(), models: Array.isArray(configData.models) ? configData.models : [form.model],\n has_reasoning: configData.has_reasoning !== undefined ? configData.has_reasoning : form.has_reasoning,\n note: form.note.trim(), website: form.website.trim(),\n }\n\n try {\n if (isEdit) { await api.updateProvider(provider.id, data); showToast(t('providerForm.toastUpdated')) }\n else { await api.createProvider(data); showToast(t('providerForm.toastAdded')) }\n onBack()\n } catch (e) {\n showToast(t('providerForm.toastSaveErr') + e.message, 'error')\n }\n }\n\n const handleConfigChange = (e) => {\n const val = e.target.value\n setConfigText(val)\n try {\n const parsed = JSON.parse(val)\n setConfigError(null)\n if (parsed.name !== undefined) setForm(prev => ({ ...prev, name: parsed.name }))\n if (parsed.models && Array.isArray(parsed.models) && parsed.models.length > 0) setForm(prev => ({ ...prev, model: parsed.models[0] }))\n if (parsed.has_reasoning !== undefined) setForm(prev => ({ ...prev, has_reasoning: parsed.has_reasoning }))\n } catch { setConfigError(t('providerForm.errInvalidJson')) }\n }\n\n return (\n
\n {toast &&
{toast.msg}
}\n\n
\n
\n \n

{isEdit ? t('providerForm.editTitle') : t('providerForm.addTitle')}

\n
\n \n
\n
\n\n
\n {showPresets && !isEdit && (\n
\n

{t('providerForm.presetHint')}

\n
\n {PRESETS.map(p => (\n \n ))}\n \n
\n
\n )}\n\n
\n
\n
{form.name ? form.name.charAt(0).toUpperCase() : '?'}
\n
\n
\n \n update('name', e.target.value)} placeholder={t('providerForm.namePlaceholder')} className=\"w-full px-3 py-2 bg-canvas border border-hairline rounded-xl text-sm text-ink placeholder:text-muted/50 focus:outline-none focus:border-coral/50 focus:ring-1 focus:ring-coral/20 transition-colors\" />\n
\n
\n \n update('note', e.target.value)} placeholder={t('providerForm.notePlaceholder')} className=\"w-full px-3 py-2 bg-canvas border border-hairline rounded-xl text-sm text-ink placeholder:text-muted/50 focus:outline-none focus:border-coral/50 focus:ring-1 focus:ring-coral/20 transition-colors\" />\n
\n
\n
\n\n
\n \n update('website', e.target.value)} placeholder=\"https://example.com\" className=\"w-full px-3 py-2 bg-canvas border border-hairline rounded-xl text-sm text-ink placeholder:text-muted/50 focus:outline-none focus:border-coral/50 focus:ring-1 focus:ring-coral/20 transition-colors\" />\n
\n\n
\n \n
\n {['openai', 'anthropic'].map(fmt => (\n \n ))}\n
\n
\n\n
\n \n
\n update('api_key', e.target.value)} placeholder=\"sk-...\" className=\"w-full px-3 py-2 pr-10 bg-canvas border border-hairline rounded-xl text-sm text-ink placeholder:text-muted/50 focus:outline-none focus:border-coral/50 focus:ring-1 focus:ring-coral/20 transition-colors font-mono\" />\n \n
\n
\n\n
\n \n update('base_url', e.target.value)} placeholder=\"https://api.example.com/v1\" className=\"w-full px-3 py-2 bg-canvas border border-hairline rounded-xl text-sm text-ink placeholder:text-muted/50 focus:outline-none focus:border-coral/50 focus:ring-1 focus:ring-coral/20 transition-colors\" />\n

{t('providerForm.baseUrlHelp')}

\n
\n\n
\n
\n \n \n
\n
\n { update('model', e.target.value); setShowModelDropdown(false) }} onFocus={() => { if (fetchedModels.length) setShowModelDropdown(true) }} placeholder=\"deepseek-chat\" className=\"w-full px-3 py-2 bg-canvas border border-hairline rounded-xl text-sm text-ink placeholder:text-muted/50 focus:outline-none focus:border-coral/50 focus:ring-1 focus:ring-coral/20 transition-colors\" />\n {showModelDropdown && fetchedModels.length > 0 && (\n
\n \n {fetchedModels.map(m => (\n \n ))}\n
\n )}\n
\n

{t('providerForm.modelHelp')}

\n
\n\n
\n \n {t('providerForm.reasoning')}\n
\n\n
\n
\n {t('providerForm.testSection')}\n \n
\n {testResult && (\n
\n {testResult.status === 'ok' ? t('providerForm.testOk') : t('providerForm.testFail')}\n {testResult.latency_ms && {testResult.latency_ms}ms}\n {testResult.message && {testResult.message}}\n
\n )}\n
\n\n
\n \n