diff --git a/client/js/data/StoryManager.js b/client/js/data/StoryManager.js index 3d0062f..e145f1f 100644 --- a/client/js/data/StoryManager.js +++ b/client/js/data/StoryManager.js @@ -164,12 +164,15 @@ export default class StoryManager { // 先标记之前的未读草稿为已读 await this.markAllDraftsRead(userId); + console.log('[rewriteEndingAsync] pathHistory:', JSON.stringify(this.pathHistory)); + const result = await post(`/drafts/ending`, { userId: userId, storyId: storyId, endingName: ending?.name || '未知结局', endingContent: ending?.content || '', - prompt: prompt + prompt: prompt, + pathHistory: this.pathHistory || [] // 传递游玩路径 }, { timeout: 30000 }); if (result && result.draftId) { @@ -195,7 +198,8 @@ export default class StoryManager { storyId: storyId, endingName: ending?.name || '未知结局', endingContent: ending?.content || '', - prompt: prompt + prompt: prompt, + pathHistory: this.pathHistory || [] // 传递游玩路径 }, { timeout: 30000 }); if (result && result.draftId) { diff --git a/client/js/main.js b/client/js/main.js index a9dddca..0b7cb2c 100644 --- a/client/js/main.js +++ b/client/js/main.js @@ -128,6 +128,12 @@ export default class Main { try { if (!this.userManager.isLoggedIn) return; + // 如果结局页正在轮询,跳过全局检查 + const currentScene = this.sceneManager.currentScene; + if (currentScene && currentScene.draftPollTimer) { + return; + } + const result = await this.storyManager.checkNewDrafts(this.userManager.userId); if (result && result.hasNew && result.count > 0) { diff --git a/client/js/scenes/EndingScene.js b/client/js/scenes/EndingScene.js index 917e8e7..3abfb8b 100644 --- a/client/js/scenes/EndingScene.js +++ b/client/js/scenes/EndingScene.js @@ -1080,6 +1080,9 @@ export default class EndingScene extends BaseScene { showCancel: false, confirmText: '知道了' }); + + // 启动专门的草稿检查(每5秒检查一次,持续2分钟) + this.startDraftPolling(result.draftId); } else { wx.showToast({ title: '提交失败,请重试', icon: 'none' }); } @@ -1130,6 +1133,9 @@ export default class EndingScene extends BaseScene { showCancel: false, confirmText: '知道了' }); + + // 启动专门的草稿检查(每5秒检查一次,持续2分钟) + this.startDraftPolling(result.draftId); } else { wx.showToast({ title: '提交失败,请重试', icon: 'none' }); } @@ -1189,4 +1195,69 @@ export default class EndingScene extends BaseScene { this.isCollected = !this.isCollected; this.main.userManager.collectStory(this.storyId, this.isCollected); } + + // 启动草稿完成轮询(每5秒检查一次,持续2分钟) + startDraftPolling(draftId) { + // 清除之前的轮询 + if (this.draftPollTimer) { + clearInterval(this.draftPollTimer); + } + + let pollCount = 0; + const maxPolls = 24; // 2分钟 / 5秒 = 24次 + + console.log('[EndingScene] 启动草稿轮询, draftId:', draftId); + + this.draftPollTimer = setInterval(async () => { + pollCount++; + + if (pollCount > maxPolls) { + console.log('[EndingScene] 轮询超时,停止检查'); + clearInterval(this.draftPollTimer); + this.draftPollTimer = null; + return; + } + + try { + const userId = this.main.userManager.userId; + if (!userId) return; + + const result = await this.main.storyManager.checkNewDrafts(userId); + + if (result && result.hasNew && result.count > 0) { + console.log('[EndingScene] 检测到新草稿:', result.count); + + // 停止轮询 + clearInterval(this.draftPollTimer); + this.draftPollTimer = null; + + // 标记为已读 + await this.main.storyManager.markAllDraftsRead(userId); + + // 弹窗通知 + wx.showModal({ + title: 'AI改写完成', + content: `您有 ${result.count} 个新的AI改写已完成,是否前往查看?`, + confirmText: '查看', + cancelText: '稍后', + success: (res) => { + if (res.confirm) { + this.main.sceneManager.switchScene('profile', { tab: 1 }); + } + } + }); + } + } catch (e) { + console.warn('[EndingScene] 草稿检查失败:', e); + } + }, 5000); // 每5秒检查一次 + } + + // 场景销毁时清理轮询 + destroy() { + if (this.draftPollTimer) { + clearInterval(this.draftPollTimer); + this.draftPollTimer = null; + } + } } diff --git a/client/js/scenes/StoryScene.js b/client/js/scenes/StoryScene.js index 7714b4b..46cc9cd 100644 --- a/client/js/scenes/StoryScene.js +++ b/client/js/scenes/StoryScene.js @@ -128,6 +128,14 @@ export default class StoryScene extends BaseScene { const draft = await this.main.storyManager.getDraftDetail(this.draftId); + console.log('[StoryScene] 草稿详情:', JSON.stringify({ + hasDraft: !!draft, + hasAiNodes: !!draft?.aiNodes, + aiNodesKeys: draft?.aiNodes ? Object.keys(draft.aiNodes) : [], + entryNodeKey: draft?.entryNodeKey, + pathHistoryLength: draft?.pathHistory?.length + })); + if (draft && draft.aiNodes && draft.storyId) { // 先加载原故事 this.story = await this.main.storyManager.loadStoryDetail(draft.storyId); @@ -1197,8 +1205,8 @@ export default class StoryScene extends BaseScene { if (this.waitingForClick) { this.waitingForClick = false; - // AI改写内容 - 直接跳转到新结局 - if (this.aiContent && this.aiContent.is_ending) { + // AI改写内容 - 直接跳转到新结局(但回放模式下先显示选项) + if (this.aiContent && this.aiContent.is_ending && !this.isReplayMode) { console.log('AI改写内容:', JSON.stringify(this.aiContent)); this.main.sceneManager.switchScene('ending', { storyId: this.storyId, diff --git a/server/app/routers/drafts.py b/server/app/routers/drafts.py index 4192158..1cdfa26 100644 --- a/server/app/routers/drafts.py +++ b/server/app/routers/drafts.py @@ -39,6 +39,7 @@ class CreateEndingDraftRequest(BaseModel): endingName: str endingContent: str prompt: str + pathHistory: list = [] # 游玩路径历史(可选) class ContinueEndingDraftRequest(BaseModel): @@ -48,6 +49,7 @@ class ContinueEndingDraftRequest(BaseModel): endingName: str endingContent: str prompt: str + pathHistory: list = [] # 游玩路径历史(可选) class DraftResponse(BaseModel): @@ -169,10 +171,9 @@ async def process_ai_rewrite_ending(draft_id: int): await db.commit() return - # 从 path_history 获取结局信息 - ending_info = draft.path_history or {} - ending_name = ending_info.get("endingName", "未知结局") - ending_content = ending_info.get("endingContent", "") + # 从草稿字段获取结局信息 + ending_name = draft.current_node_key or "未知结局" + ending_content = draft.current_content or "" # 调用AI服务改写结局 ai_result = await ai_service.rewrite_ending( @@ -201,16 +202,17 @@ async def process_ai_rewrite_ending(draft_id: int): except (json.JSONDecodeError, AttributeError): pass - # 成功 - 存储为单节点结局格式 + # 成功 - 存储为对象格式(与故事节点格式一致) draft.status = DraftStatus.completed - draft.ai_nodes = [{ - "nodeKey": "ending_rewrite", - "content": content, - "speaker": "旁白", - "isEnding": True, - "endingName": new_ending_name, - "endingType": "rewrite" - }] + draft.ai_nodes = { + "ending_rewrite": { + "content": content, + "speaker": "旁白", + "is_ending": True, + "ending_name": new_ending_name, + "ending_type": "rewrite" + } + } draft.entry_node_key = "ending_rewrite" draft.tokens_used = ai_result.get("tokens_used", 0) draft.title = f"{story.title}-{new_ending_name}" @@ -264,10 +266,9 @@ async def process_ai_continue_ending(draft_id: int): await db.commit() return - # 从 path_history 获取结局信息 - ending_info = draft.path_history or {} - ending_name = ending_info.get("endingName", "未知结局") - ending_content = ending_info.get("endingContent", "") + # 从草稿字段获取结局信息 + ending_name = draft.current_node_key or "未知结局" + ending_content = draft.current_content or "" # 调用AI服务续写结局 ai_result = await ai_service.continue_ending( @@ -376,14 +377,14 @@ async def create_ending_draft( if not story: raise HTTPException(status_code=404, detail="故事不存在") - # 创建草稿记录,将结局信息存在 path_history + # 创建草稿记录,保存游玩路径和结局信息 draft = StoryDraft( user_id=request.userId, story_id=request.storyId, title=f"{story.title}-结局改写", - path_history={"endingName": request.endingName, "endingContent": request.endingContent}, - current_node_key="ending", - current_content=request.endingContent, + path_history=request.pathHistory, # 保存游玩路径 + current_node_key=request.endingName, # 保存结局名称 + current_content=request.endingContent, # 保存结局内容 user_prompt=request.prompt, status=DraftStatus.pending ) @@ -421,14 +422,14 @@ async def create_continue_ending_draft( if not story: raise HTTPException(status_code=404, detail="故事不存在") - # 创建草稿记录,将结局信息存在 path_history + # 创建草稿记录,保存游玩路径和结局信息 draft = StoryDraft( user_id=request.userId, story_id=request.storyId, title=f"{story.title}-结局续写", - path_history={"endingName": request.endingName, "endingContent": request.endingContent}, - current_node_key="ending", - current_content=request.endingContent, + path_history=request.pathHistory, # 保存游玩路径 + current_node_key=request.endingName, # 保存结局名称 + current_content=request.endingContent, # 保存结局内容 user_prompt=request.prompt, status=DraftStatus.pending )