import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Card, Form, Input, Button, Select, Slider, InputNumber, message, Space, Typography, Spin, Modal, Alert, Grid, Tabs, List, Tag, Popconfirm, Empty, Row, Col } from 'antd'; import { SettingOutlined, SaveOutlined, DeleteOutlined, ReloadOutlined, ArrowLeftOutlined, InfoCircleOutlined, CheckCircleOutlined, CloseCircleOutlined, ThunderboltOutlined, PlusOutlined, EditOutlined, CopyOutlined } from '@ant-design/icons'; import { settingsApi } from '../services/api'; import type { SettingsUpdate, APIKeyPreset, PresetCreateRequest, APIKeyPresetConfig } from '../types'; const { Title, Text } = Typography; const { Option } = Select; const { useBreakpoint } = Grid; const { TextArea } = Input; export default function SettingsPage() { const navigate = useNavigate(); const screens = useBreakpoint(); const isMobile = !screens.md; // md断点是768px const [form] = Form.useForm(); const [modal, contextHolder] = Modal.useModal(); const [loading, setLoading] = useState(false); const [initialLoading, setInitialLoading] = useState(true); const [hasSettings, setHasSettings] = useState(false); const [isDefaultSettings, setIsDefaultSettings] = useState(false); const [modelOptions, setModelOptions] = useState>([]); const [fetchingModels, setFetchingModels] = useState(false); const [modelsFetched, setModelsFetched] = useState(false); const [testingApi, setTestingApi] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string; response_time_ms?: number; response_preview?: string; error?: string; error_type?: string; suggestions?: string[]; } | null>(null); const [showTestResult, setShowTestResult] = useState(false); // 预设相关状态 const [activeTab, setActiveTab] = useState('current'); const [presets, setPresets] = useState([]); const [presetsLoading, setPresetsLoading] = useState(false); const [activePresetId, setActivePresetId] = useState(); const [editingPreset, setEditingPreset] = useState(null); const [isPresetModalVisible, setIsPresetModalVisible] = useState(false); const [testingPresetId, setTestingPresetId] = useState(null); const [presetForm] = Form.useForm(); useEffect(() => { loadSettings(); if (activeTab === 'presets') { loadPresets(); } }, []); useEffect(() => { if (activeTab === 'presets') { loadPresets(); } }, [activeTab]); const loadSettings = async () => { setInitialLoading(true); try { const settings = await settingsApi.getSettings(); form.setFieldsValue(settings); // 判断是否为默认设置(id='0'表示来自.env的默认配置) if (settings.id === '0' || !settings.id) { setIsDefaultSettings(true); setHasSettings(false); } else { setIsDefaultSettings(false); setHasSettings(true); } } catch (error: any) { // 如果404表示还没有设置,使用默认值 if (error?.response?.status === 404) { setHasSettings(false); setIsDefaultSettings(true); form.setFieldsValue({ api_provider: 'openai', api_base_url: 'https://api.openai.com/v1', llm_model: 'gpt-4', temperature: 0.7, max_tokens: 2000, }); } else { message.error('加载设置失败'); } } finally { setInitialLoading(false); } }; const handleSave = async (values: SettingsUpdate) => { setLoading(true); try { await settingsApi.saveSettings(values); message.success('设置已保存'); setHasSettings(true); setIsDefaultSettings(false); } catch (error) { message.error('保存设置失败'); } finally { setLoading(false); } }; const handleReset = () => { modal.confirm({ title: '重置设置', content: '确定要重置为默认值吗?', centered: true, okText: '确定', cancelText: '取消', onOk: () => { form.setFieldsValue({ api_provider: 'openai', api_key: '', api_base_url: 'https://api.openai.com/v1', llm_model: 'gpt-4', temperature: 0.7, max_tokens: 2000, }); message.info('已重置为默认值,请点击保存'); }, }); }; const handleDelete = () => { modal.confirm({ title: '删除设置', content: '确定要删除所有设置吗?此操作不可恢复。', centered: true, okText: '确定', cancelText: '取消', okType: 'danger', onOk: async () => { setLoading(true); try { await settingsApi.deleteSettings(); message.success('设置已删除'); setHasSettings(false); form.resetFields(); } catch (error) { message.error('删除设置失败'); } finally { setLoading(false); } }, }); }; const apiProviders = [ { value: 'openai', label: 'OpenAI Compatible', defaultUrl: 'https://api.openai.com/v1' }, // { value: 'anthropic', label: 'Anthropic (Claude)', defaultUrl: 'https://api.anthropic.com' }, { value: 'gemini', label: 'Google Gemini', defaultUrl: 'https://generativelanguage.googleapis.com/v1beta' }, ]; const handleProviderChange = (value: string) => { const provider = apiProviders.find(p => p.value === value); if (provider && provider.defaultUrl) { form.setFieldValue('api_base_url', provider.defaultUrl); } // 清空模型列表,需要重新获取 setModelOptions([]); setModelsFetched(false); }; const handleFetchModels = async (silent: boolean = false) => { const apiKey = form.getFieldValue('api_key'); const apiBaseUrl = form.getFieldValue('api_base_url'); const provider = form.getFieldValue('api_provider'); if (!apiKey || !apiBaseUrl) { if (!silent) { message.warning('请先填写 API 密钥和 API 地址'); } return; } setFetchingModels(true); try { const response = await settingsApi.getAvailableModels({ api_key: apiKey, api_base_url: apiBaseUrl, provider: provider || 'openai' }); setModelOptions(response.models); setModelsFetched(true); if (!silent) { message.success(`成功获取 ${response.count || response.models.length} 个可用模型`); } } catch (error: any) { const errorMsg = error?.response?.data?.detail || '获取模型列表失败'; if (!silent) { message.error(errorMsg); } setModelOptions([]); setModelsFetched(true); // 即使失败也标记为已尝试,避免重复请求 } finally { setFetchingModels(false); } }; const handleModelSelectFocus = () => { // 如果还没有获取过模型列表,自动获取 if (!modelsFetched && !fetchingModels) { handleFetchModels(true); // silent模式,不显示成功消息 } }; const handleTestConnection = async () => { const apiKey = form.getFieldValue('api_key'); const apiBaseUrl = form.getFieldValue('api_base_url'); const provider = form.getFieldValue('api_provider'); const modelName = form.getFieldValue('llm_model'); if (!apiKey || !apiBaseUrl || !provider || !modelName) { message.warning('请先填写完整的配置信息'); return; } setTestingApi(true); setTestResult(null); try { const result = await settingsApi.testApiConnection({ api_key: apiKey, api_base_url: apiBaseUrl, provider: provider, llm_model: modelName }); setTestResult(result); setShowTestResult(true); if (result.success) { message.success(`测试成功!响应时间: ${result.response_time_ms}ms`); } else { message.error('API 测试失败,请查看详细信息'); } } catch (error: any) { const errorMsg = error?.response?.data?.detail || '测试请求失败'; message.error(errorMsg); setTestResult({ success: false, message: '测试请求失败', error: errorMsg, error_type: 'RequestError', suggestions: ['请检查网络连接', '请确认后端服务是否正常运行'] }); setShowTestResult(true); } finally { setTestingApi(false); } }; // ========== 预设管理函数 ========== const loadPresets = async () => { setPresetsLoading(true); try { const response = await settingsApi.getPresets(); setPresets(response.presets); setActivePresetId(response.active_preset_id); } catch (error) { message.error('加载预设失败'); console.error(error); } finally { setPresetsLoading(false); } }; const showPresetModal = (preset?: APIKeyPreset) => { if (preset) { setEditingPreset(preset); presetForm.setFieldsValue({ name: preset.name, description: preset.description, ...preset.config, }); } else { setEditingPreset(null); presetForm.resetFields(); presetForm.setFieldsValue({ api_provider: 'openai', temperature: 0.7, max_tokens: 2000, }); } setIsPresetModalVisible(true); }; const handlePresetCancel = () => { setIsPresetModalVisible(false); setEditingPreset(null); presetForm.resetFields(); }; const handlePresetSave = async () => { try { const values = await presetForm.validateFields(); const config: APIKeyPresetConfig = { api_provider: values.api_provider, api_key: values.api_key, api_base_url: values.api_base_url, llm_model: values.llm_model, temperature: values.temperature, max_tokens: values.max_tokens, }; if (editingPreset) { await settingsApi.updatePreset(editingPreset.id, { name: values.name, description: values.description, config, }); message.success('预设已更新'); } else { const request: PresetCreateRequest = { name: values.name, description: values.description, config, }; await settingsApi.createPreset(request); message.success('预设已创建'); } handlePresetCancel(); loadPresets(); } catch (error) { console.error('保存失败:', error); } }; const handlePresetDelete = async (presetId: string) => { try { await settingsApi.deletePreset(presetId); message.success('预设已删除'); loadPresets(); } catch (error: any) { message.error(error.response?.data?.detail || '删除失败'); console.error(error); } }; const handlePresetActivate = async (presetId: string, presetName: string) => { try { await settingsApi.activatePreset(presetId); message.success(`已激活预设: ${presetName}`); loadPresets(); loadSettings(); // 重新加载当前配置 } catch (error) { message.error('激活失败'); console.error(error); } }; const handlePresetTest = async (presetId: string) => { setTestingPresetId(presetId); try { const result = await settingsApi.testPreset(presetId); if (result.success) { modal.success({ title: '测试成功', centered: true, width: isMobile ? '90%' : 600, content: (
✓ API 连接正常
提供商: {result.provider?.toUpperCase() || 'N/A'}
模型: {result.model || 'N/A'}
{result.response_time_ms !== undefined && (
响应时间: {result.response_time_ms}ms
)}
), }); } else { modal.error({ title: '测试失败', centered: true, width: isMobile ? '90%' : 600, content: (
{result.error && (
错误信息: {result.error}
)} {result.suggestions && result.suggestions.length > 0 && (
💡 建议:
    {result.suggestions.map((s, i) => (
  • {s}
  • ))}
)}
), }); } } catch (error) { message.error('测试失败'); console.error(error); } finally { setTestingPresetId(null); } }; const handleCreateFromCurrent = () => { const currentConfig = form.getFieldsValue(); presetForm.setFieldsValue({ name: '', description: '', ...currentConfig, }); setEditingPreset(null); setIsPresetModalVisible(true); }; const getProviderColor = (provider: string) => { switch (provider) { case 'openai': return 'blue'; // case 'anthropic': // return 'purple'; case 'gemini': return 'green'; default: return 'default'; } }; // ========== 渲染预设列表 ========== const renderPresetsList = () => (
管理你的API配置预设,快速切换不同的配置
{presets.length === 0 ? ( ) : ( { const isActive = preset.id === activePresetId; return ( handlePresetActivate(preset.id, preset.name)} > 激活 ), , , handlePresetDelete(preset.id)} disabled={isActive} okText="确定" cancelText="取消" > , ].filter(Boolean)} > ) } title={ {preset.name} {isActive && 激活中} } description={ {preset.description && (
{preset.description}
)} {preset.config.api_provider.toUpperCase()} {preset.config.llm_model} 温度: {preset.config.temperature} Tokens: {preset.config.max_tokens}
创建于: {new Date(preset.created_at).toLocaleString()}
} />
); }} /> )}
); return ( <> {contextHolder}
{/* 顶部导航卡片 */} {/* 装饰性背景元素 */}
<SettingOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 8 }} /> AI API 设置 配置AI接口参数,管理多个API配置预设 {/* 主内容卡片 */} {/* 默认配置提示 */} {isDefaultSettings && (

当前显示的是从服务器 .env 文件读取的默认配置。

点击"保存设置"后,配置将保存到数据库并同步更新到 .env 文件。

} type="info" showIcon style={{ marginBottom: isMobile ? 12 : 16 }} /> )} {/* 已保存配置提示 */} {hasSettings && !isDefaultSettings && ( )} {/* 表单 */}
API 提供商 } name="api_provider" rules={[{ required: true, message: '请选择API提供商' }]} > API 密钥 } name="api_key" rules={[{ required: true, message: '请输入API密钥' }]} > API 地址 } name="api_base_url" rules={[ { required: true, message: '请输入API地址' }, { type: 'url', message: '请输入有效的URL' } ]} > 模型名称 } name="llm_model" rules={[{ required: true, message: '请输入或选择模型名称' }]} >