Kaynağa Gözat

fix(agent): 去AI味单独显示为阶段

严格模式在校验完成后发出 final_polish 阶段并保持进行中,避免去AI味运行时界面空白。

Co-authored-by: Cursor <cursoragent@cursor.com>
darknessomi 1 ay önce
ebeveyn
işleme
2569900621

+ 16 - 0
src/lib/agent/activity-trace.spec.ts

@@ -269,10 +269,26 @@ describe("activity trace", () => {
   })
 
   it("resolves titles for post-draft strict stages", () => {
+    expect(resolveAgentStageTitle("final_polish")).toBe("去AI味")
     expect(resolveAgentStageTitle("execution_report")).toBe("执行报告")
     expect(resolveAgentStageTitle("execution_recheck")).toBe("执行复检")
     expect(resolveAgentStageTitle("plan_compliance")).toBe("计划履约")
     expect(resolveAgentStageTitle("plan_deviation_repair")).toBe("计划偏离返修")
     expect(resolveAgentStageTitle("plan_deviation_recheck")).toBe("计划偏离复检")
   })
+
+  it("places 去AI味 between 校验与修正 and 最终输出", () => {
+    const stages: AgentStageTrace[] = [
+      { id: "final_output", title: "最终输出", status: "done", summary: "完成", events: [], startedAt: 400 },
+      { id: "final_polish", title: "去AI味", status: "running", summary: "去AI味中", events: [], startedAt: 300 },
+      { id: "validate_revision", title: "校验与修正", status: "done", summary: "校验", events: [], startedAt: 200 },
+    ]
+
+    expect(prepareAgentStagesForDisplay(stages).map((stage) => stage.id)).toEqual([
+      "validate_revision",
+      "final_polish",
+      "final_output",
+    ])
+    expect(getDefaultOpenAgentStageId(stages)).toBe("final_polish")
+  })
 })

+ 3 - 0
src/lib/agent/activity-trace.ts

@@ -15,6 +15,7 @@ export const DETAILED_CHAPTER_STAGE_IDS = new Set([
   "plot_analysis",
   "generate_draft",
   "validate_revision",
+  "final_polish",
   "execution_report",
   "execution_recheck",
   "plan_compliance",
@@ -30,6 +31,7 @@ export const AGENT_STAGE_DISPLAY_ORDER = [
   "plot_analysis",
   "generate_draft",
   "validate_revision",
+  "final_polish",
   "execution_report",
   "execution_recheck",
   "plan_compliance",
@@ -296,6 +298,7 @@ export function resolveAgentStageTitle(stageId: string, fallbackTitle?: string):
     chapter_workflow: "多任务写作循环",
     generate_draft: "生成章节草稿",
     validate_revision: "校验与修正",
+    final_polish: "去AI味",
     execution_report: "执行报告",
     execution_recheck: "执行复检",
     plan_compliance: "计划履约",

+ 40 - 0
src/lib/novel/deep-chapter-generation.spec.ts

@@ -1219,6 +1219,46 @@ describe("runDeepChapterGeneration", () => {
     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)
+  })
+
+  it("emits a dedicated 去AI味 stage between 校验与修正 and 最终输出", async () => {
+    const deps = createDeps()
+    const activityEvents: AgentActivityEvent[] = []
+
+    await runDeepChapterGeneration(
+      {
+        projectPath: "E:/Novel",
+        userRequest: "生成第3章",
+        chapterNumber: 3,
+        llmConfig,
+        aiWorkflowMode: "strict",
+      },
+      { onActivityEvent: (event) => activityEvents.push(event) },
+      deps,
+    )
+
+    const polishStarted = activityEvents.find(
+      (event) => event.stageId === "final_polish" && event.kind === "stage_started",
+    )
+    const polishOutput = activityEvents.find(
+      (event) => event.stageId === "final_polish" && event.kind === "stage_output",
+    )
+    const validateOutputIndex = activityEvents.findIndex(
+      (event) => event.stageId === "validate_revision" && event.kind === "stage_output",
+    )
+    const polishStartedIndex = activityEvents.findIndex(
+      (event) => event.stageId === "final_polish" && event.kind === "stage_started",
+    )
+    const finalOutputIndex = activityEvents.findIndex(
+      (event) => event.stageId === "final_output" && event.kind === "final_output",
+    )
+
+    expect(polishStarted?.content).toContain("去除复读、机械套话和 AI 味")
+    expect(polishOutput?.content).toContain("简单审查与去AI味完成")
+    expect(validateOutputIndex).toBeGreaterThanOrEqual(0)
+    expect(polishStartedIndex).toBeGreaterThan(validateOutputIndex)
+    expect(finalOutputIndex).toBeGreaterThan(polishStartedIndex)
   })
 
   it("injects the enabled writing style into the stage 3 draft prompt", async () => {

+ 16 - 0
src/lib/novel/deep-chapter-generation.ts

@@ -1401,6 +1401,14 @@ export async function runDeepChapterGeneration(
     detail: "做最后一遍简单审查,减少复读、机械套话和 AI 味。",
     params: workflowBaseParams,
   };
+  if (workflowProfile.runFinalPolish) {
+    emitDeepChapterStageStarted(
+      callbacks,
+      "final_polish",
+      "去AI味",
+      "正在做最后一遍简单审查,去除复读、机械套话和 AI 味。",
+    );
+  }
   let finalContent = workflowProfile.runFinalPolish
     ? await runChapterWorkflowStep(
         callbacks,
@@ -1433,6 +1441,14 @@ export async function runDeepChapterGeneration(
       "快速模式跳过最终去AI味,直接采用阶段3正文作为最终正文。",
       { skipped: true, chars: countChapterChars(finalContent) },
     );
+  } else {
+    emitDeepChapterActivity(callbacks, {
+      id: `deep_chapter:final_polish:output:${Date.now()}`,
+      stageId: "final_polish",
+      kind: "stage_output",
+      title: "去AI味",
+      content: `简单审查与去AI味完成,最终正文约 ${countChapterChars(finalContent)} 字。`,
+    });
   }
   callbacks.onThinking?.(
     formatStageThinking(