feat: 结局AI改写支持pathHistory回放和完成通知
This commit is contained in:
@@ -164,12 +164,15 @@ export default class StoryManager {
|
|||||||
// 先标记之前的未读草稿为已读
|
// 先标记之前的未读草稿为已读
|
||||||
await this.markAllDraftsRead(userId);
|
await this.markAllDraftsRead(userId);
|
||||||
|
|
||||||
|
console.log('[rewriteEndingAsync] pathHistory:', JSON.stringify(this.pathHistory));
|
||||||
|
|
||||||
const result = await post(`/drafts/ending`, {
|
const result = await post(`/drafts/ending`, {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
storyId: storyId,
|
storyId: storyId,
|
||||||
endingName: ending?.name || '未知结局',
|
endingName: ending?.name || '未知结局',
|
||||||
endingContent: ending?.content || '',
|
endingContent: ending?.content || '',
|
||||||
prompt: prompt
|
prompt: prompt,
|
||||||
|
pathHistory: this.pathHistory || [] // 传递游玩路径
|
||||||
}, { timeout: 30000 });
|
}, { timeout: 30000 });
|
||||||
|
|
||||||
if (result && result.draftId) {
|
if (result && result.draftId) {
|
||||||
@@ -195,7 +198,8 @@ export default class StoryManager {
|
|||||||
storyId: storyId,
|
storyId: storyId,
|
||||||
endingName: ending?.name || '未知结局',
|
endingName: ending?.name || '未知结局',
|
||||||
endingContent: ending?.content || '',
|
endingContent: ending?.content || '',
|
||||||
prompt: prompt
|
prompt: prompt,
|
||||||
|
pathHistory: this.pathHistory || [] // 传递游玩路径
|
||||||
}, { timeout: 30000 });
|
}, { timeout: 30000 });
|
||||||
|
|
||||||
if (result && result.draftId) {
|
if (result && result.draftId) {
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ export default class Main {
|
|||||||
try {
|
try {
|
||||||
if (!this.userManager.isLoggedIn) return;
|
if (!this.userManager.isLoggedIn) return;
|
||||||
|
|
||||||
|
// 如果结局页正在轮询,跳过全局检查
|
||||||
|
const currentScene = this.sceneManager.currentScene;
|
||||||
|
if (currentScene && currentScene.draftPollTimer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.storyManager.checkNewDrafts(this.userManager.userId);
|
const result = await this.storyManager.checkNewDrafts(this.userManager.userId);
|
||||||
|
|
||||||
if (result && result.hasNew && result.count > 0) {
|
if (result && result.hasNew && result.count > 0) {
|
||||||
|
|||||||
@@ -1080,6 +1080,9 @@ export default class EndingScene extends BaseScene {
|
|||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: '知道了'
|
confirmText: '知道了'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 启动专门的草稿检查(每5秒检查一次,持续2分钟)
|
||||||
|
this.startDraftPolling(result.draftId);
|
||||||
} else {
|
} else {
|
||||||
wx.showToast({ title: '提交失败,请重试', icon: 'none' });
|
wx.showToast({ title: '提交失败,请重试', icon: 'none' });
|
||||||
}
|
}
|
||||||
@@ -1130,6 +1133,9 @@ export default class EndingScene extends BaseScene {
|
|||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: '知道了'
|
confirmText: '知道了'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 启动专门的草稿检查(每5秒检查一次,持续2分钟)
|
||||||
|
this.startDraftPolling(result.draftId);
|
||||||
} else {
|
} else {
|
||||||
wx.showToast({ title: '提交失败,请重试', icon: 'none' });
|
wx.showToast({ title: '提交失败,请重试', icon: 'none' });
|
||||||
}
|
}
|
||||||
@@ -1189,4 +1195,69 @@ export default class EndingScene extends BaseScene {
|
|||||||
this.isCollected = !this.isCollected;
|
this.isCollected = !this.isCollected;
|
||||||
this.main.userManager.collectStory(this.storyId, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,14 @@ export default class StoryScene extends BaseScene {
|
|||||||
|
|
||||||
const draft = await this.main.storyManager.getDraftDetail(this.draftId);
|
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) {
|
if (draft && draft.aiNodes && draft.storyId) {
|
||||||
// 先加载原故事
|
// 先加载原故事
|
||||||
this.story = await this.main.storyManager.loadStoryDetail(draft.storyId);
|
this.story = await this.main.storyManager.loadStoryDetail(draft.storyId);
|
||||||
@@ -1197,8 +1205,8 @@ export default class StoryScene extends BaseScene {
|
|||||||
if (this.waitingForClick) {
|
if (this.waitingForClick) {
|
||||||
this.waitingForClick = false;
|
this.waitingForClick = false;
|
||||||
|
|
||||||
// AI改写内容 - 直接跳转到新结局
|
// AI改写内容 - 直接跳转到新结局(但回放模式下先显示选项)
|
||||||
if (this.aiContent && this.aiContent.is_ending) {
|
if (this.aiContent && this.aiContent.is_ending && !this.isReplayMode) {
|
||||||
console.log('AI改写内容:', JSON.stringify(this.aiContent));
|
console.log('AI改写内容:', JSON.stringify(this.aiContent));
|
||||||
this.main.sceneManager.switchScene('ending', {
|
this.main.sceneManager.switchScene('ending', {
|
||||||
storyId: this.storyId,
|
storyId: this.storyId,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class CreateEndingDraftRequest(BaseModel):
|
|||||||
endingName: str
|
endingName: str
|
||||||
endingContent: str
|
endingContent: str
|
||||||
prompt: str
|
prompt: str
|
||||||
|
pathHistory: list = [] # 游玩路径历史(可选)
|
||||||
|
|
||||||
|
|
||||||
class ContinueEndingDraftRequest(BaseModel):
|
class ContinueEndingDraftRequest(BaseModel):
|
||||||
@@ -48,6 +49,7 @@ class ContinueEndingDraftRequest(BaseModel):
|
|||||||
endingName: str
|
endingName: str
|
||||||
endingContent: str
|
endingContent: str
|
||||||
prompt: str
|
prompt: str
|
||||||
|
pathHistory: list = [] # 游玩路径历史(可选)
|
||||||
|
|
||||||
|
|
||||||
class DraftResponse(BaseModel):
|
class DraftResponse(BaseModel):
|
||||||
@@ -169,10 +171,9 @@ async def process_ai_rewrite_ending(draft_id: int):
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
return
|
return
|
||||||
|
|
||||||
# 从 path_history 获取结局信息
|
# 从草稿字段获取结局信息
|
||||||
ending_info = draft.path_history or {}
|
ending_name = draft.current_node_key or "未知结局"
|
||||||
ending_name = ending_info.get("endingName", "未知结局")
|
ending_content = draft.current_content or ""
|
||||||
ending_content = ending_info.get("endingContent", "")
|
|
||||||
|
|
||||||
# 调用AI服务改写结局
|
# 调用AI服务改写结局
|
||||||
ai_result = await ai_service.rewrite_ending(
|
ai_result = await ai_service.rewrite_ending(
|
||||||
@@ -201,16 +202,17 @@ async def process_ai_rewrite_ending(draft_id: int):
|
|||||||
except (json.JSONDecodeError, AttributeError):
|
except (json.JSONDecodeError, AttributeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 成功 - 存储为单节点结局格式
|
# 成功 - 存储为对象格式(与故事节点格式一致)
|
||||||
draft.status = DraftStatus.completed
|
draft.status = DraftStatus.completed
|
||||||
draft.ai_nodes = [{
|
draft.ai_nodes = {
|
||||||
"nodeKey": "ending_rewrite",
|
"ending_rewrite": {
|
||||||
"content": content,
|
"content": content,
|
||||||
"speaker": "旁白",
|
"speaker": "旁白",
|
||||||
"isEnding": True,
|
"is_ending": True,
|
||||||
"endingName": new_ending_name,
|
"ending_name": new_ending_name,
|
||||||
"endingType": "rewrite"
|
"ending_type": "rewrite"
|
||||||
}]
|
}
|
||||||
|
}
|
||||||
draft.entry_node_key = "ending_rewrite"
|
draft.entry_node_key = "ending_rewrite"
|
||||||
draft.tokens_used = ai_result.get("tokens_used", 0)
|
draft.tokens_used = ai_result.get("tokens_used", 0)
|
||||||
draft.title = f"{story.title}-{new_ending_name}"
|
draft.title = f"{story.title}-{new_ending_name}"
|
||||||
@@ -264,10 +266,9 @@ async def process_ai_continue_ending(draft_id: int):
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
return
|
return
|
||||||
|
|
||||||
# 从 path_history 获取结局信息
|
# 从草稿字段获取结局信息
|
||||||
ending_info = draft.path_history or {}
|
ending_name = draft.current_node_key or "未知结局"
|
||||||
ending_name = ending_info.get("endingName", "未知结局")
|
ending_content = draft.current_content or ""
|
||||||
ending_content = ending_info.get("endingContent", "")
|
|
||||||
|
|
||||||
# 调用AI服务续写结局
|
# 调用AI服务续写结局
|
||||||
ai_result = await ai_service.continue_ending(
|
ai_result = await ai_service.continue_ending(
|
||||||
@@ -376,14 +377,14 @@ async def create_ending_draft(
|
|||||||
if not story:
|
if not story:
|
||||||
raise HTTPException(status_code=404, detail="故事不存在")
|
raise HTTPException(status_code=404, detail="故事不存在")
|
||||||
|
|
||||||
# 创建草稿记录,将结局信息存在 path_history
|
# 创建草稿记录,保存游玩路径和结局信息
|
||||||
draft = StoryDraft(
|
draft = StoryDraft(
|
||||||
user_id=request.userId,
|
user_id=request.userId,
|
||||||
story_id=request.storyId,
|
story_id=request.storyId,
|
||||||
title=f"{story.title}-结局改写",
|
title=f"{story.title}-结局改写",
|
||||||
path_history={"endingName": request.endingName, "endingContent": request.endingContent},
|
path_history=request.pathHistory, # 保存游玩路径
|
||||||
current_node_key="ending",
|
current_node_key=request.endingName, # 保存结局名称
|
||||||
current_content=request.endingContent,
|
current_content=request.endingContent, # 保存结局内容
|
||||||
user_prompt=request.prompt,
|
user_prompt=request.prompt,
|
||||||
status=DraftStatus.pending
|
status=DraftStatus.pending
|
||||||
)
|
)
|
||||||
@@ -421,14 +422,14 @@ async def create_continue_ending_draft(
|
|||||||
if not story:
|
if not story:
|
||||||
raise HTTPException(status_code=404, detail="故事不存在")
|
raise HTTPException(status_code=404, detail="故事不存在")
|
||||||
|
|
||||||
# 创建草稿记录,将结局信息存在 path_history
|
# 创建草稿记录,保存游玩路径和结局信息
|
||||||
draft = StoryDraft(
|
draft = StoryDraft(
|
||||||
user_id=request.userId,
|
user_id=request.userId,
|
||||||
story_id=request.storyId,
|
story_id=request.storyId,
|
||||||
title=f"{story.title}-结局续写",
|
title=f"{story.title}-结局续写",
|
||||||
path_history={"endingName": request.endingName, "endingContent": request.endingContent},
|
path_history=request.pathHistory, # 保存游玩路径
|
||||||
current_node_key="ending",
|
current_node_key=request.endingName, # 保存结局名称
|
||||||
current_content=request.endingContent,
|
current_content=request.endingContent, # 保存结局内容
|
||||||
user_prompt=request.prompt,
|
user_prompt=request.prompt,
|
||||||
status=DraftStatus.pending
|
status=DraftStatus.pending
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user