From d87a6d0c2b88efaa1311f7c0adc33d663d35f4e0 Mon Sep 17 00:00:00 2001 From: xiamuceer-j Date: Wed, 21 Jan 2026 14:52:45 +0800 Subject: [PATCH] =?UTF-8?q?update=EF=BC=9A=E6=94=AF=E6=8C=81API=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=9D=A2-=E9=A2=84=E8=AE=BE=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E7=AA=97=E5=8F=A3=E6=A8=A1=E5=9E=8B=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- backend/.env.example | 2 +- frontend/package.json | 2 +- frontend/src/pages/Settings.tsx | 218 +++++++++++++++++++++++++++++++- 4 files changed, 218 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 39f3abf..d7a6bf2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-![Version](https://img.shields.io/badge/version-1.3.0-blue.svg) +![Version](https://img.shields.io/badge/version-1.3.1-blue.svg) ![Python](https://img.shields.io/badge/python-3.11-blue.svg) ![FastAPI](https://img.shields.io/badge/FastAPI-0.109.0-green.svg) ![React](https://img.shields.io/badge/react-18.3.1-blue.svg) @@ -97,10 +97,10 @@ - [x] **Linux DO 自动创建账号** - OAuth 登录自动生成账号 - [x] **职业等级体系** - 自定义职业和等级系统,支持修仙境界、魔法等级等多种体系 - [x] **角色/组织卡片导入导出** - 单独导出角色和组织卡片,支持跨项目数据共享 +- [x] **伏笔管理** - 智能追踪剧情伏笔,提醒未回收线索,可视化伏笔时间线 ### 📝 规划中功能 -- [ ] **伏笔管理** - 智能追踪剧情伏笔,提醒未回收线索,可视化伏笔时间线 - [ ] **提示词工坊** - 社区驱动的 Prompt 模板分享平台,一键导入优质提示词 > 💡 欢迎提交 Issue 或 Pull Request! diff --git a/backend/.env.example b/backend/.env.example index 698c936..b63ce6e 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -8,7 +8,7 @@ # 应用配置 # ========================================== APP_NAME=MuMuAINovel -APP_VERSION=1.3.0 +APP_VERSION=1.3.1 APP_HOST=0.0.0.0 APP_PORT=8000 DEBUG=false diff --git a/frontend/package.json b/frontend/package.json index ec3888e..4cd18c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "1.3.0", + "version": "1.3.1", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 6f20e0d..db39186 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -43,6 +43,11 @@ export default function SettingsPage() { const [isPresetModalVisible, setIsPresetModalVisible] = useState(false); const [testingPresetId, setTestingPresetId] = useState(null); const [presetForm] = Form.useForm(); + + // 预设编辑窗口的模型列表状态(独立于当前配置的模型列表) + const [presetModelOptions, setPresetModelOptions] = useState>([]); + const [fetchingPresetModels, setFetchingPresetModels] = useState(false); + const [presetModelsFetched, setPresetModelsFetched] = useState(false); useEffect(() => { loadSettings(); @@ -55,7 +60,14 @@ export default function SettingsPage() { useEffect(() => { if (activeTab === 'presets') { loadPresets(); + } else if (activeTab === 'current') { + // 切换到当前配置Tab时,刷新设置以获取最新数据 + loadSettings(); + // 清除旧的测试结果,因为可能是其他配置的测试结果 + setTestResult(null); + setShowTestResult(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeTab]); const loadSettings = async () => { @@ -117,6 +129,36 @@ export default function SettingsPage() { setHasSettings(true); setIsDefaultSettings(false); + // 保存后清除测试结果,因为配置可能已变更 + setTestResult(null); + setShowTestResult(false); + + // 手动保存配置后,需要同步更新预设激活状态 + // 因为用户手动修改的配置可能与之前激活的预设不一致了 + // 重新加载预设列表以确保状态正确(后端在save时会自动取消激活状态) + if (activePresetId) { + // 检查当前保存的配置是否与激活预设一致 + const activePreset = presets.find(p => p.id === activePresetId); + if (activePreset) { + const presetConfig = activePreset.config; + const configMismatch = + presetConfig.api_provider !== values.api_provider || + presetConfig.api_key !== values.api_key || + presetConfig.api_base_url !== values.api_base_url || + presetConfig.llm_model !== values.llm_model || + presetConfig.temperature !== values.temperature || + presetConfig.max_tokens !== values.max_tokens; + + if (configMismatch) { + // 配置已变更,清除前端的激活状态标记 + setActivePresetId(undefined); + message.info('配置已更改,预设激活状态已取消'); + // 刷新预设列表以同步后端取消激活的状态 + loadPresets(); + } + } + } + // 如果配置发生变化,需要处理 MCP 插件 if (configChanged) { // 清除 MCP 验证缓存 @@ -357,6 +399,10 @@ export default function SettingsPage() { }; const showPresetModal = (preset?: APIKeyPreset) => { + // 重置预设模型列表状态 + setPresetModelOptions([]); + setPresetModelsFetched(false); + if (preset) { setEditingPreset(preset); presetForm.setFieldsValue({ @@ -369,6 +415,7 @@ export default function SettingsPage() { presetForm.resetFields(); presetForm.setFieldsValue({ api_provider: 'openai', + api_base_url: 'https://api.openai.com/v1', temperature: 0.7, max_tokens: 2000, }); @@ -380,6 +427,66 @@ export default function SettingsPage() { setIsPresetModalVisible(false); setEditingPreset(null); presetForm.resetFields(); + // 清除预设模型列表状态 + setPresetModelOptions([]); + setPresetModelsFetched(false); + }; + + // 预设编辑窗口:获取模型列表 + const handleFetchPresetModels = async (silent: boolean = false) => { + const apiKey = presetForm.getFieldValue('api_key'); + const apiBaseUrl = presetForm.getFieldValue('api_base_url'); + const provider = presetForm.getFieldValue('api_provider'); + + if (!apiKey || !apiBaseUrl) { + if (!silent) { + message.warning('请先填写 API 密钥和 API 地址'); + } + return; + } + + setFetchingPresetModels(true); + try { + const response = await settingsApi.getAvailableModels({ + api_key: apiKey, + api_base_url: apiBaseUrl, + provider: provider || 'openai' + }); + + setPresetModelOptions(response.models); + setPresetModelsFetched(true); + if (!silent) { + message.success(`成功获取 ${response.count || response.models.length} 个可用模型`); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + const errorMsg = error?.response?.data?.detail || '获取模型列表失败'; + if (!silent) { + message.error(errorMsg); + } + setPresetModelOptions([]); + setPresetModelsFetched(true); + } finally { + setFetchingPresetModels(false); + } + }; + + // 预设编辑窗口:模型选择框获得焦点时自动获取 + const handlePresetModelSelectFocus = () => { + if (!presetModelsFetched && !fetchingPresetModels) { + handleFetchPresetModels(true); + } + }; + + // 预设编辑窗口:提供商变更时更新默认URL并清空模型列表 + const handlePresetProviderChange = (value: string) => { + const provider = apiProviders.find(p => p.value === value); + if (provider && provider.defaultUrl) { + presetForm.setFieldValue('api_base_url', provider.defaultUrl); + } + // 清空模型列表,需要重新获取 + setPresetModelOptions([]); + setPresetModelsFetched(false); }; const handlePresetSave = async () => { @@ -437,6 +544,15 @@ export default function SettingsPage() { await settingsApi.activatePreset(presetId); message.success(`已激活预设: ${presetName}`); + + // 激活预设后清除当前配置Tab的测试结果 + setTestResult(null); + setShowTestResult(false); + + // 清除模型列表缓存,因为API配置可能已变更 + setModelOptions([]); + setModelsFetched(false); + loadPresets(); loadSettings(); // 重新加载当前配置 @@ -1419,7 +1535,7 @@ export default function SettingsPage() { rules={[{ required: true, message: '请选择' }]} style={{ marginBottom: 16 }} > - OpenAI Google Gemini @@ -1464,11 +1580,105 @@ export default function SettingsPage() { + 模型名称 + + + } + rules={[{ required: true, message: '请选择或输入模型名称' }]} style={{ marginBottom: 16 }} > - +