Просмотр исходного кода

fix(plan-execute): 修复计划弹窗有时不出现的问题

- 放宽任务路由注入条件:只要 planExecuteActive 为 true 就注入计划协议,
  不再依赖 routeTask 识别为写作任务,避免 "帮我写一段" 等请求被误识别
  为 general_chat 导致协议不注入
- 放宽 extractChapterPlan 匹配:新增 7 种 AI 常见计划标题模式
  (本章概要/写作思路/情节安排/人物状态/伏笔安排/章末状态/本章计划),
  matchedSections==1 且内容≥200字也认为计划内容
- 加诊断日志:在 extractChapterPlan 前后输出 console.info/warn,
  记录 fullContentLength、hasChapterPlanMarker、提取结果等
Mochocyang 2 месяцев назад
Родитель
Сommit
34565d778c

+ 15 - 3
src/components/chat/chapter-plan-confirm-dialog.tsx

@@ -24,6 +24,14 @@ const EXTRA_PLAN_KEYWORD_PATTERNS = [
   /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:缺失资料|缺失信息|缺失内容)/u,
   /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:确认后动作|确认动作|后续动作)/u,
   /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:计划概览|创作计划|写作计划|章节计划)/u,
+  // 新增:AI 常见但之前未覆盖的计划标题
+  /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:本章概要|章节概要|本章简介|章节简介)/u,
+  /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:写作思路|创作思路|写作方向|创作方向)/u,
+  /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:情节安排|剧情安排|场景安排|场景计划)/u,
+  /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:人物状态|角色状态|人物变化|角色变化)/u,
+  /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:伏笔安排|信息揭示|信息流安排)/u,
+  /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:章末状态|结尾状态|收束状态)/u,
+  /(?:^|\n)\s*(?:#{1,4}\s*)?(?:\d+[.、]\s*)?(?:本章计划|本章策划|章节策划)/u,
 ]
 
 export function extractChapterPlan(fullContent: string): { plan: string; body: string } | null {
@@ -44,9 +52,13 @@ function extractUnmarkedChapterPlan(fullContent: string): { plan: string; body:
   if (!plan) return null
   const allPatterns = [...FALLBACK_PLAN_SECTION_PATTERNS, ...EXTRA_PLAN_KEYWORD_PATTERNS]
   const matchedSections = allPatterns.filter((pattern) => pattern.test(plan)).length
-  // 至少匹配 2 个计划相关段落即认为是计划内容
-  if (matchedSections < 2) return null
-  return { plan, body: "" }
+  // 放宽匹配策略:
+  // - matchedSections >= 2:明确是计划,直接返回(原逻辑)
+  // - matchedSections == 1:可能是计划,但要求内容足够长(>=200字)避免短回复误识别
+  // - matchedSections == 0:不是计划
+  if (matchedSections >= 2) return { plan, body: "" }
+  if (matchedSections === 1 && plan.length >= 200) return { plan, body: "" }
+  return null
 }
 
 export function buildPlanConfirmMessage(plan: string): string {

+ 19 - 0
src/components/chat/chat-panel.tsx

@@ -1965,8 +1965,21 @@ export function ChatPanel() {
             (m) => m.id === assistantMessage.id && m.role === "assistant",
           )
           const fullContent = lastAssistant?.content || record.finalText || ""
+          // 诊断日志:帮助定位"计划弹窗有时不出现"问题
+          const hasMarker = fullContent.includes("<!-- chapter_plan -->")
+          console.info("[PlanExecute] 检查计划提取", {
+            conversationId: capturedConvId,
+            fullContentLength: fullContent.length,
+            hasChapterPlanMarker: hasMarker,
+            messageContentLength: lastAssistant?.content?.length ?? 0,
+            recordFinalTextLength: record.finalText?.length ?? 0,
+          })
           const extracted = extractChapterPlan(fullContent)
           if (extracted) {
+            console.info("[PlanExecute] 计划提取成功,弹出确认对话框", {
+              planLength: extracted.plan.length,
+              bodyLength: extracted.body.length,
+            })
             const action = await requestChapterPlanConfirm(
               extracted.plan,
               fullContent,
@@ -1989,6 +2002,12 @@ export function ChatPanel() {
               }
               await handleSendRef.current(followupText, [], "执行已确认计划", confirmedBlueprint, capturedConvId)
             }
+          } else {
+            console.warn("[PlanExecute] 计划提取失败,不弹窗", {
+              fullContentLength: fullContent.length,
+              hasChapterPlanMarker: hasMarker,
+              preview: fullContent.slice(0, 200),
+            })
           }
         }
       } catch (error) {

+ 7 - 9
src/lib/agent/plugins/build-system-prompt-plugin.ts

@@ -7,7 +7,6 @@ import {
 } from "@/lib/novel/outline-find-protocol"
 import { buildSelectedSkillsPrompt } from "./select-skills-plugin"
 import { getWorkflowModeLabel, resolveAiWorkflowMode, type LegacyAiWorkflowMode } from "../workflow-mode"
-import { WRITING_INTENTS } from "../plan-execute-policy"
 
 export interface BuildSystemPromptPluginDeps {
   baseSystemPrompt?: string
@@ -50,9 +49,6 @@ export function createBuildSystemPromptPlugin(deps: BuildSystemPromptPluginDeps
         }
 
         const routeForWriting = input.effectiveTaskRoute || input.taskRoute
-        const isWritingTask = Boolean(
-          routeForWriting?.intent && WRITING_INTENTS.has(routeForWriting.intent),
-        )
 
         if (shouldIncludeOutlineFindProtocol(routeForWriting?.intent)) {
           const outlineProtocol = buildOutlineFindProtocol(routeForWriting?.chapterNumber)
@@ -61,11 +57,13 @@ export function createBuildSystemPromptPlugin(deps: BuildSystemPromptPluginDeps
         }
 
         if (input.planExecuteEnabled && input.aiWorkflowMode) {
-          if (isWritingTask) {
-            const planProtocol = buildChapterPlanProtocol(input.aiWorkflowMode)
-            parts.push(planProtocol)
-            rulesParts.push(planProtocol)
-          }
+          // 放宽条件:只要用户开启了计划执行就注入计划协议,不再强制要求 isWritingTask。
+          // 原因:routeTask 基于正则匹配覆盖有限,"帮我写一段"、"继续故事" 等写作请求
+          // 会被误识别为 general_chat,导致计划协议不注入、AI 不输出计划、弹窗不出现。
+          // 让 AI 自行判断当前请求是否需要输出计划,避免漏弹窗。
+          const planProtocol = buildChapterPlanProtocol(input.aiWorkflowMode)
+          parts.push(planProtocol)
+          rulesParts.push(planProtocol)
         }
 
         if (route) {