Explorar el Código

fix(writing): 快速/标准模式不再声称会进入去AI味 (#70)

上下文交接和跳过审稿文案按工作流 profile 生成,避免标准模式写成「将进入阶段6」,也避免快速模式被提前告知会审稿、去AI味。

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: darknessomi <darknessomi@users.noreply.github.com>
darknessomi hace 1 mes
padre
commit
1ea0d12804

+ 24 - 2
src/lib/novel/deep-chapter-generation.spec.ts

@@ -1215,11 +1215,18 @@ describe("runDeepChapterGeneration", () => {
       deps,
     )
 
+    const contextOutput = activityEvents.find(
+      (event) => event.stageId === "read_context" && event.kind === "stage_output",
+    )
+
     expect(activityEvents.some((event) => event.kind === "read_source" && event.content.includes("上一章结尾"))).toBe(true)
     expect(activityEvents.some((event) => event.kind === "extract_goal" && event.content.includes("上一章结尾"))).toBe(true)
     expect(activityEvents.some((event) => event.kind === "extract_result" && event.content.includes("门缝里传来金属拖拽声"))).toBe(true)
     expect(activityEvents.some((event) => event.kind === "stage_output" && event.content.includes("任务书"))).toBe(true)
     expect(activityEvents.some((event) => event.stageId === "final_polish")).toBe(false)
+    expect(contextOutput?.content).toContain("写作任务书和正文初稿后直接完成")
+    expect(contextOutput?.content).not.toContain("审稿和最终去AI味")
+    expect(activityEvents.some((event) => event.content.includes("进入阶段6"))).toBe(false)
   })
 
   it("emits a dedicated 去AI味 stage between 校验与修正 and 最终输出", async () => {
@@ -1254,8 +1261,13 @@ describe("runDeepChapterGeneration", () => {
       (event) => event.stageId === "final_output" && event.kind === "final_output",
     )
 
+    const contextOutput = activityEvents.find(
+      (event) => event.stageId === "read_context" && event.kind === "stage_output",
+    )
+
     expect(polishStarted?.content).toContain("去除复读、机械套话和 AI 味")
     expect(polishOutput?.content).toContain("简单审查与去AI味完成")
+    expect(contextOutput?.content).toContain("审稿和最终去AI味阶段")
     expect(validateOutputIndex).toBeGreaterThanOrEqual(0)
     expect(polishStartedIndex).toBeGreaterThan(validateOutputIndex)
     expect(finalOutputIndex).toBeGreaterThan(polishStartedIndex)
@@ -1443,26 +1455,36 @@ describe("runDeepChapterGeneration", () => {
   it("uses fast, standard, and strict workflow routes", async () => {
     const skippedCollect = vi.fn(async () => ({ markdown: "", searchedNames: [], notes: [] }))
     const fastDeps = { ...createDeps(), collectWritingEntityWebSearch: skippedCollect }
+    const fastActivity: AgentActivityEvent[] = []
     await runDeepChapterGeneration(
       { projectPath: "E:/Novel", userRequest: "生成第三章", chapterNumber: 3, llmConfig, aiWorkflowMode: "fast" },
-      {},
+      { onActivityEvent: (event) => fastActivity.push(event) },
       fastDeps,
     )
     expect(fastDeps.streamChat).toHaveBeenCalledTimes(2)
     expect(fastDeps.reviewChapter).not.toHaveBeenCalled()
     expect(skippedCollect).not.toHaveBeenCalled()
+    expect(fastActivity.some((event) => event.content.includes("进入阶段6"))).toBe(false)
+    expect(fastActivity.some((event) => event.content.includes("审稿和最终去AI味"))).toBe(false)
 
     const standardDeps = { ...createDeps(), collectWritingEntityWebSearch: skippedCollect }
     const standardThinking: string[] = []
+    const standardActivity: AgentActivityEvent[] = []
     await runDeepChapterGeneration(
       { projectPath: "E:/Novel", userRequest: "生成第三章", chapterNumber: 3, llmConfig, aiWorkflowMode: "standard" },
-      { onThinking: (content) => standardThinking.push(content) },
+      {
+        onThinking: (content) => standardThinking.push(content),
+        onActivityEvent: (event) => standardActivity.push(event),
+      },
       standardDeps,
     )
     expect(standardDeps.streamChat).toHaveBeenCalledTimes(2)
     expect(standardDeps.reviewChapter).not.toHaveBeenCalled()
     expect(standardThinking.join("\n")).toContain("阶段4:标准完成")
     expect(standardThinking.join("\n")).not.toContain("快速模式")
+    expect(standardThinking.join("\n")).not.toContain("进入阶段6")
+    expect(standardActivity.some((event) => event.content.includes("进入阶段6"))).toBe(false)
+    expect(standardActivity.some((event) => event.content.includes("审稿和最终去AI味"))).toBe(false)
     expect(skippedCollect).not.toHaveBeenCalled()
 
     const collectWritingEntityWebSearch = vi.fn(async () => ({

+ 33 - 14
src/lib/novel/deep-chapter-generation.ts

@@ -263,6 +263,33 @@ function resolveChapterWorkflowProfile(
   };
 }
 
+function workflowModeLabel(profile: ChapterWorkflowProfile): string {
+  if (profile.mode === "fast") return "快速模式";
+  if (profile.mode === "standard") return "标准模式";
+  return "严格模式";
+}
+
+function describeContextPackHandoff(profile: ChapterWorkflowProfile): string {
+  if (profile.runAiReview || profile.runFinalPolish) {
+    return "已形成章节生成约束包,将传入写作任务书、正文初稿、审稿和最终去AI味阶段。";
+  }
+  return "已形成章节生成约束包,将传入写作任务书和正文初稿后直接完成。";
+}
+
+function describeSkippedReviewAndPolish(profile: ChapterWorkflowProfile): string {
+  const skipped = ["AI 审稿", "返修"];
+  if (!profile.runFinalPolish) skipped.push("最终去AI味");
+  return `${workflowModeLabel(profile)}跳过 ${skipped.join("、")},直接使用阶段3正文初稿。`;
+}
+
+function describeSkippedFinalPolish(profile: ChapterWorkflowProfile): string {
+  return `${workflowModeLabel(profile)}跳过最终去AI味,直接采用阶段3正文作为最终正文。`;
+}
+
+function describeSkippedPostDraftCompletion(profile: ChapterWorkflowProfile): string {
+  return `${workflowModeLabel(profile)}已完成任务书与正文初稿生成,直接采用阶段3正文作为最终正文。`;
+}
+
 interface ChapterWorkflowStepSpec {
   name: string;
   title: string;
@@ -651,8 +678,7 @@ export async function runDeepChapterGeneration(
       stageId: "read_context",
       kind: "stage_output",
       title: "阶段产物",
-      content:
-        "已形成章节生成约束包,将传入写作任务书、正文初稿、审稿和最终去AI味阶段。",
+      content: describeContextPackHandoff(workflowProfile),
     });
   }
 
@@ -1066,17 +1092,13 @@ export async function runDeepChapterGeneration(
           detail: "根据当前模式决定是否执行 AI 审稿。",
           params: workflowBaseParams,
         },
-        workflowProfile.mode === "fast"
-          ? "快速模式跳过 AI 审稿、返修和最终去AI味,直接使用阶段3正文初稿。"
-          : "标准模式跳过 AI 审稿与自动返修,初稿将进入阶段6简单审查与去AI味。",
+        describeSkippedReviewAndPolish(workflowProfile),
         { skipped: true },
       );
       callbacks.onThinking?.(
         formatStageThinking(
           "阶段4-5:已跳过审稿与返修",
-          workflowProfile.mode === "fast"
-            ? "快速模式跳过 AI 审稿、返修和最终去AI味,直接使用阶段3正文初稿。"
-            : "标准模式跳过 AI 审稿与自动返修,初稿将进入阶段6简单审查与去AI味。",
+          describeSkippedReviewAndPolish(workflowProfile),
         ),
       );
       emitDeepChapterActivity(callbacks, {
@@ -1084,10 +1106,7 @@ export async function runDeepChapterGeneration(
         stageId: "validate_revision",
         kind: "analysis",
         title: "审稿分析",
-        content:
-          workflowProfile.mode === "fast"
-            ? "快速模式跳过 AI 审稿、返修和最终去AI味,直接使用阶段3正文初稿。"
-            : "标准模式跳过 AI 审稿与自动返修,初稿将进入阶段6简单审查与去AI味。",
+        content: describeSkippedReviewAndPolish(workflowProfile),
       });
     } else {
       const reviewWorkflowStep: ChapterWorkflowStepSpec = {
@@ -1443,7 +1462,7 @@ export async function runDeepChapterGeneration(
     completeChapterWorkflowStep(
       callbacks,
       finalPolishWorkflowStep,
-      "快速模式跳过最终去AI味,直接采用阶段3正文作为最终正文。",
+      describeSkippedFinalPolish(workflowProfile),
       { skipped: true, chars: countChapterChars(finalContent) },
     );
   } else {
@@ -1464,7 +1483,7 @@ export async function runDeepChapterGeneration(
           : revised
           ? "采用返修并完成简单审查、去AI味后的正文作为最终正文。"
           : "未发现阻断问题,已完成最后一遍简单审查与去AI味。"
-        : "快速模式已完成任务书与正文初稿生成,直接采用阶段3正文作为最终正文。",
+        : describeSkippedPostDraftCompletion(workflowProfile),
     ),
   );
   emitDeepChapterActivity(callbacks, {