From 16a1686911a07666083d2420a8b2e169477d2bbc Mon Sep 17 00:00:00 2001 From: xiamuceer Date: Mon, 1 Dec 2025 21:16:35 +0800 Subject: [PATCH] =?UTF-8?q?update:1.=E4=BC=98=E5=8C=96=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=881->N=EF=BC=89=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=B8=8B=E6=94=AF=E6=8C=81=E4=BF=AE=E6=94=B9=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E6=A0=87=E9=A2=98=202.AI=E5=88=9B=E4=BD=9C=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E6=94=AF=E6=8C=81=E5=8A=A0=E8=BD=BD=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=88=97=E8=A1=A8=EF=BC=8C=E4=BD=BF=E7=94=A8=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E6=A8=A1=E5=9E=8B=E5=88=9B=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- backend/app/api/chapters.py | 16 ++++++- backend/app/schemas/chapter.py | 2 + frontend/package.json | 2 +- frontend/src/pages/Chapters.tsx | 84 +++++++++++++++++++++++++++++++-- frontend/src/store/hooks.ts | 6 ++- 6 files changed, 104 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e67f8b0..db74d3d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-![Version](https://img.shields.io/badge/version-1.0.5-blue.svg) +![Version](https://img.shields.io/badge/version-1.0.9-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) @@ -351,7 +351,7 @@ MuMuAINovel/ - 提交 [Issue](https://github.com/xiamuceer-j/MuMuAINovel/issues) - Linux DO [讨论](https://linux.do/t/topic/1106333) - 加入QQ群 [QQ群](frontend/public/qq.jpg) -- 加入WX群 [WX群](frontend/public/WX.jpg) +- 加入WX群 [WX群](frontend/public/WX.png) --- diff --git a/backend/app/api/chapters.py b/backend/app/api/chapters.py index 8e88c29..f250a94 100644 --- a/backend/app/api/chapters.py +++ b/backend/app/api/chapters.py @@ -953,6 +953,7 @@ async def generate_chapter_content_stream( style_id = generate_request.style_id target_word_count = generate_request.target_word_count or 3000 enable_mcp = generate_request.enable_mcp if hasattr(generate_request, 'enable_mcp') else True + custom_model = generate_request.model if hasattr(generate_request, 'model') else None # 预先验证章节存在性(使用临时会话) async for temp_db in get_db(request): try: @@ -1310,12 +1311,20 @@ async def generate_chapter_content_stream( logger.info(f"开始AI流式创作章节 {chapter_id}") + # 准备生成参数 + generate_kwargs = {"prompt": prompt} + if custom_model: + logger.info(f" 使用自定义模型: {custom_model}") + generate_kwargs["model"] = custom_model + # 注意:这里使用用户配置的AI服务,模型参数会覆盖默认模型 + # 如果需要切换provider,需要在前端传递provider参数 + # 流式生成内容 full_content = "" chunk_count = 0 last_progress = 0 - async for chunk in user_ai_service.generate_text_stream(prompt=prompt): + async for chunk in user_ai_service.generate_text_stream(**generate_kwargs): full_content += chunk chunk_count += 1 @@ -2494,7 +2503,10 @@ async def generate_single_chapter_for_batch( # 非流式生成内容 full_content = "" - async for chunk in ai_service.generate_text_stream(prompt=prompt): + async for chunk in ai_service.generate_text_stream( + prompt=prompt, + model=None # 批量生成时使用用户默认模型,后续可扩展 + ): full_content += chunk # 更新章节内容到数据库(使用锁保护) diff --git a/backend/app/schemas/chapter.py b/backend/app/schemas/chapter.py index d6e27db..2427a10 100644 --- a/backend/app/schemas/chapter.py +++ b/backend/app/schemas/chapter.py @@ -78,6 +78,7 @@ class ChapterGenerateRequest(BaseModel): le=10000 # 最大10000字 ) enable_mcp: bool = Field(True, description="是否启用MCP工具增强(搜索参考资料)") + model: Optional[str] = Field(None, description="指定使用的AI模型,不提供则使用用户默认模型") class BatchGenerateRequest(BaseModel): @@ -94,6 +95,7 @@ class BatchGenerateRequest(BaseModel): enable_analysis: bool = Field(False, description="是否启用同步分析") enable_mcp: bool = Field(True, description="是否启用MCP工具增强(搜索参考资料)") max_retries: int = Field(3, description="每个章节的最大重试次数", ge=0, le=5) + model: Optional[str] = Field(None, description="指定使用的AI模型,不提供则使用用户默认模型") class BatchGenerateResponse(BaseModel): diff --git a/frontend/package.json b/frontend/package.json index 8990ee7..43f1fc8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "1.0.8", + "version": "1.0.9", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/Chapters.tsx b/frontend/src/pages/Chapters.tsx index 159857a..329a7e6 100644 --- a/frontend/src/pages/Chapters.tsx +++ b/frontend/src/pages/Chapters.tsx @@ -27,6 +27,8 @@ export default function Chapters() { const [writingStyles, setWritingStyles] = useState([]); const [selectedStyleId, setSelectedStyleId] = useState(); const [targetWordCount, setTargetWordCount] = useState(3000); + const [availableModels, setAvailableModels] = useState>([]); + const [selectedModel, setSelectedModel] = useState(); const [analysisVisible, setAnalysisVisible] = useState(false); const [analysisChapterId, setAnalysisChapterId] = useState(null); // 分析任务状态管理 @@ -187,6 +189,37 @@ export default function Chapters() { } }; + const loadAvailableModels = async () => { + try { + // 从设置API获取用户配置的模型列表 + const settingsResponse = await fetch('/api/settings'); + if (settingsResponse.ok) { + const settings = await settingsResponse.json(); + const { api_key, api_base_url, api_provider } = settings; + + if (api_key && api_base_url) { + try { + const modelsResponse = await fetch( + `/api/settings/models?api_key=${encodeURIComponent(api_key)}&api_base_url=${encodeURIComponent(api_base_url)}&provider=${api_provider}` + ); + if (modelsResponse.ok) { + const data = await modelsResponse.json(); + if (data.models && data.models.length > 0) { + setAvailableModels(data.models); + // 设置默认模型为当前配置的模型 + setSelectedModel(settings.llm_model); + } + } + } catch (error) { + console.log('获取模型列表失败,将使用默认模型'); + } + } + } + } catch (error) { + console.error('加载可用模型失败:', error); + } + }; + // 检查并恢复批量生成任务 const checkAndRestoreBatchTask = async () => { if (!currentProject?.id) return; @@ -270,6 +303,10 @@ export default function Chapters() { try { await updateChapter(editingId, values); + + // 刷新章节列表以获取完整的章节数据(包括outline_title等联查字段) + await refreshChapters(); + message.success('章节更新成功'); setIsModalOpen(false); form.resetFields(); @@ -288,6 +325,8 @@ export default function Chapters() { }); setEditingId(id); setIsEditorOpen(true); + // 打开编辑窗口时加载模型列表 + loadAvailableModels(); } }; @@ -335,7 +374,8 @@ export default function Chapters() { // 进度回调 setSingleChapterProgress(progressValue); setSingleChapterProgressMessage(progressMsg); - } + }, + selectedModel // 传递选中的模型 ); message.success('AI创作成功,正在分析章节内容...'); @@ -1517,9 +1557,21 @@ export default function Chapters() { - + + + +
+ 不同模型有不同的特点和定价,请根据需要选择 +
+
+