diff --git a/README.md b/README.md
index 39f3abf..d7a6bf2 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-
+



@@ -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 }}
>
-