From 187feac671a866057ff24743daa8d5eb188eb11e Mon Sep 17 00:00:00 2001 From: xiamuceer Date: Thu, 4 Dec 2025 21:14:44 +0800 Subject: [PATCH] =?UTF-8?q?update:1.=E5=A2=9E=E5=8A=A0AI=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=9B=E4=BD=9C=E7=AB=A0=E8=8A=82=E5=86=85=E5=AE=B9=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8A=A0=E8=BD=BD=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=88=97=E8=A1=A8=EF=BC=8C=E4=BD=BF=E7=94=A8=E4=B8=8D=E5=90=8C?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=89=B9=E9=87=8F=E7=94=9F=E6=88=90=E5=86=85?= =?UTF-8?q?=E5=AE=B9=202.=E6=94=AF=E6=8C=81AI=E7=94=9F=E6=88=90/=E7=BB=AD?= =?UTF-8?q?=E5=86=99=E5=A4=A7=E7=BA=B2=E6=97=B6=EF=BC=8C=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=A8=A1=E5=9E=8B=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- backend/app/api/chapters.py | 28 ++++++---- backend/app/api/outlines.py | 23 +++++++-- frontend/package.json | 2 +- frontend/src/pages/Chapters.tsx | 92 +++++++++++++++++++++++++++++---- frontend/src/pages/Outline.tsx | 83 +++++++++++++++++++++++++++-- 6 files changed, 199 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index db74d3d..f6dcbf4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-![Version](https://img.shields.io/badge/version-1.0.9-blue.svg) +![Version](https://img.shields.io/badge/version-1.0.10-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) diff --git a/backend/app/api/chapters.py b/backend/app/api/chapters.py index f250a94..6e90595 100644 --- a/backend/app/api/chapters.py +++ b/backend/app/api/chapters.py @@ -1973,12 +1973,13 @@ async def batch_generate_chapters_in_order( logger.info(f"📦 创建批量生成任务: {batch_id}, 章节: 第{start_number}-{end_number}章, 预估耗时: {estimated_time}分钟") - # 启动后台批量生成任务 + # 启动后台批量生成任务,传递model参数 background_tasks.add_task( execute_batch_generation_in_order, batch_id=batch_id, user_id=user_id, - ai_service=user_ai_service + ai_service=user_ai_service, + custom_model=batch_request.model ) return BatchGenerateResponse( @@ -2105,7 +2106,8 @@ async def cancel_batch_generation( async def execute_batch_generation_in_order( batch_id: str, user_id: str, - ai_service: AIService + ai_service: AIService, + custom_model: Optional[str] = None ): """ 按顺序执行批量生成任务(后台任务) @@ -2194,7 +2196,7 @@ async def execute_batch_generation_in_order( if not can_generate: raise Exception(f"前置条件不满足: {error_msg}") - # 生成章节内容(复用现有流式生成逻辑的核心部分) + # 生成章节内容(复用现有流式生成逻辑的核心部分),传递model参数 await generate_single_chapter_for_batch( db_session=db_session, chapter=chapter, @@ -2202,7 +2204,8 @@ async def execute_batch_generation_in_order( style_id=task.style_id, target_word_count=task.target_word_count, ai_service=ai_service, - write_lock=write_lock + write_lock=write_lock, + custom_model=custom_model ) logger.info(f"✅ 章节生成完成: 第{chapter.chapter_number}章") @@ -2316,7 +2319,8 @@ async def generate_single_chapter_for_batch( style_id: Optional[int], target_word_count: int, ai_service: AIService, - write_lock: Lock + write_lock: Lock, + custom_model: Optional[str] = None ): """ 为批量生成执行单个章节的生成(非流式) @@ -2503,10 +2507,14 @@ async def generate_single_chapter_for_batch( # 非流式生成内容 full_content = "" - async for chunk in ai_service.generate_text_stream( - prompt=prompt, - model=None # 批量生成时使用用户默认模型,后续可扩展 - ): + # 准备生成参数 + generate_kwargs = {"prompt": prompt} + # 如果传入了自定义模型,使用指定的模型 + if custom_model: + generate_kwargs["model"] = custom_model + logger.info(f" 批量生成使用自定义模型: {custom_model}") + + async for chunk in ai_service.generate_text_stream(**generate_kwargs): full_content += chunk # 更新章节内容到数据库(使用锁保护) diff --git a/backend/app/api/outlines.py b/backend/app/api/outlines.py index a5eb214..db9ed7b 100644 --- a/backend/app/api/outlines.py +++ b/backend/app/api/outlines.py @@ -1111,10 +1111,19 @@ async def new_outline_generator( # 调用AI yield await SSEResponse.send_progress("🤖 正在调用AI生成...", 30) + + # 添加调试日志 + model_param = data.get("model") + provider_param = data.get("provider") + logger.info(f"=== 大纲生成AI调用参数 ===") + logger.info(f" provider参数: {provider_param}") + logger.info(f" model参数: {model_param}") + logger.info(f" 完整data: {data}") + ai_response = await user_ai_service.generate_text( prompt=prompt, - provider=data.get("provider"), - model=data.get("model") + provider=provider_param, + model=model_param ) yield await SSEResponse.send_progress("✅ AI生成完成,正在解析...", 70) @@ -1446,10 +1455,16 @@ async def continue_outline_generator( ) # 调用AI生成当前批次 + model_param = data.get("model") + provider_param = data.get("provider") + logger.info(f"=== 续写批次{batch_num + 1} AI调用参数 ===") + logger.info(f" provider参数: {provider_param}") + logger.info(f" model参数: {model_param}") + ai_response = await user_ai_service.generate_text( prompt=prompt, - provider=data.get("provider"), - model=data.get("model") + provider=provider_param, + model=model_param ) yield await SSEResponse.send_progress( diff --git a/frontend/package.json b/frontend/package.json index 43f1fc8..a6a66e3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "1.0.9", + "version": "1.0.10", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/Chapters.tsx b/frontend/src/pages/Chapters.tsx index 329a7e6..67238dc 100644 --- a/frontend/src/pages/Chapters.tsx +++ b/frontend/src/pages/Chapters.tsx @@ -29,6 +29,7 @@ export default function Chapters() { const [targetWordCount, setTargetWordCount] = useState(3000); const [availableModels, setAvailableModels] = useState>([]); const [selectedModel, setSelectedModel] = useState(); + const [batchSelectedModel, setBatchSelectedModel] = useState(); // 批量生成的模型选择 const [analysisVisible, setAnalysisVisible] = useState(false); const [analysisChapterId, setAnalysisChapterId] = useState(null); // 分析任务状态管理 @@ -48,6 +49,7 @@ export default function Chapters() { const [batchGenerateVisible, setBatchGenerateVisible] = useState(false); const [batchGenerating, setBatchGenerating] = useState(false); const [batchTaskId, setBatchTaskId] = useState(null); + const [batchForm] = Form.useForm(); const [batchProgress, setBatchProgress] = useState<{ status: string; total: number; @@ -208,6 +210,7 @@ export default function Chapters() { setAvailableModels(data.models); // 设置默认模型为当前配置的模型 setSelectedModel(settings.llm_model); + return settings.llm_model; // 返回模型名称 } } } catch (error) { @@ -218,6 +221,7 @@ export default function Chapters() { } catch (error) { console.error('加载可用模型失败:', error); } + return null; }; // 检查并恢复批量生成任务 @@ -590,13 +594,23 @@ export default function Chapters() { enableAnalysis: boolean; styleId?: number; targetWordCount?: number; + model?: string; }) => { if (!currentProject?.id) return; + // 调试日志 + console.log('[批量生成] 表单values:', values); + console.log('[批量生成] batchSelectedModel状态:', batchSelectedModel); + // 使用批量生成对话框中选择的风格和字数,如果没有选择则使用默认值 const styleId = values.styleId || selectedStyleId; const wordCount = values.targetWordCount || targetWordCount; + // 使用批量生成专用的模型状态 + const model = batchSelectedModel; + + console.log('[批量生成] 最终使用的model:', model); + if (!styleId) { message.error('请选择写作风格'); return; @@ -606,18 +620,30 @@ export default function Chapters() { setBatchGenerating(true); setBatchGenerateVisible(false); // 关闭配置对话框,避免遮挡进度弹窗 + const requestBody: any = { + start_chapter_number: values.startChapterNumber, + count: values.count, + enable_analysis: values.enableAnalysis, + style_id: styleId, + target_word_count: wordCount, + }; + + // 如果有模型参数,添加到请求体中 + if (model) { + requestBody.model = model; + console.log('[批量生成] 请求体包含model:', model); + } else { + console.log('[批量生成] 请求体不包含model,使用后端默认模型'); + } + + console.log('[批量生成] 完整请求体:', JSON.stringify(requestBody, null, 2)); + const response = await fetch(`/api/chapters/project/${currentProject.id}/batch-generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ - start_chapter_number: values.startChapterNumber, - count: values.count, - enable_analysis: values.enableAnalysis, - style_id: styleId, - target_word_count: wordCount, - }), + body: JSON.stringify(requestBody), }); if (!response.ok) { @@ -725,7 +751,7 @@ export default function Chapters() { }; // 打开批量生成对话框 - const handleOpenBatchGenerate = () => { + const handleOpenBatchGenerate = async () => { // 找到第一个未生成的章节 const firstIncompleteChapter = sortedChapters.find( ch => !ch.content || ch.content.trim() === '' @@ -743,6 +769,24 @@ export default function Chapters() { return; } + // 打开对话框时加载模型列表,等待完成 + const defaultModel = await loadAvailableModels(); + + console.log('[打开批量生成] defaultModel:', defaultModel); + console.log('[打开批量生成] selectedStyleId:', selectedStyleId); + + // 设置批量生成的模型选择状态 + setBatchSelectedModel(defaultModel || undefined); + + // 重置表单并设置初始值 + batchForm.setFieldsValue({ + startChapterNumber: firstIncompleteChapter.chapter_number, + count: 5, + enableAnalysis: false, + styleId: selectedStyleId, + targetWordCount: 3000, + }); + setBatchGenerateVisible(true); }; @@ -1723,7 +1767,7 @@ export default function Chapters() { tooltip="选择用于生成章节内容的AI模型,不选择则使用默认模型" >
- 不同模型有不同的特点和定价,请根据需要选择 + {selectedModel ? `当前默认模型: ${availableModels.find(m => m.value === selectedModel)?.label || selectedModel}` : '加载模型列表中...'}
@@ -1889,6 +1934,7 @@ export default function Chapters() { > {!batchGenerating ? (
+ + +
+ {batchSelectedModel ? `当前默认模型: ${availableModels.find(m => m.value === batchSelectedModel)?.label || batchSelectedModel}` : '加载模型列表中...'} +
+
+ { + const showGenerateModal = async () => { const hasOutlines = outlines.length > 0; const initialMode = hasOutlines ? 'continue' : 'new'; + // 直接加载可用模型列表 + const settingsResponse = await fetch('/api/settings'); + const settings = await settingsResponse.json(); + const { api_key, api_base_url, api_provider } = settings; + + let loadedModels: Array<{value: string, label: string}> = []; + let defaultModel: string | undefined = undefined; + + 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) { + loadedModels = data.models; + defaultModel = settings.llm_model; + } + } + } catch (error) { + console.log('获取模型列表失败,将使用默认模型'); + } + } + Modal.confirm({ title: hasOutlines ? ( @@ -249,6 +295,7 @@ export default function Outline() { plot_stage: 'development', keep_existing: true, theme: currentProject.theme || '', + model: defaultModel, // 添加默认模型 }} > {hasOutlines && ( @@ -360,6 +407,32 @@ export default function Outline() { ); }} + + {/* 自定义模型选择 - 移到外层,所有模式都显示 */} + {loadedModels.length > 0 && ( + +