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

feat(interaction): add deterministic language routing

Ma 7 месяцев назад
Родитель
Сommit
561c6cd7ce

+ 60 - 0
packages/core/src/__tests__/interaction-nl-router.test.ts

@@ -0,0 +1,60 @@
+import { describe, expect, it } from "vitest";
+import { routeNaturalLanguageIntent } from "../interaction/nl-router.js";
+
+describe("interaction natural-language router", () => {
+  it("maps continue-style requests to write_next", () => {
+    expect(routeNaturalLanguageIntent("continue", { activeBookId: "harbor" })).toEqual({
+      intent: "write_next",
+      bookId: "harbor",
+    });
+    expect(routeNaturalLanguageIntent("继续写", { activeBookId: "harbor" })).toEqual({
+      intent: "write_next",
+      bookId: "harbor",
+    });
+  });
+
+  it("maps pause requests to pause_book", () => {
+    expect(routeNaturalLanguageIntent("pause this book", { activeBookId: "harbor" })).toEqual({
+      intent: "pause_book",
+      bookId: "harbor",
+    });
+  });
+
+  it("maps rewrite chapter requests with chapter numbers", () => {
+    expect(routeNaturalLanguageIntent("rewrite chapter 3", { activeBookId: "harbor" })).toEqual({
+      intent: "rewrite_chapter",
+      bookId: "harbor",
+      chapterNumber: 3,
+    });
+    expect(routeNaturalLanguageIntent("重写第3章", { activeBookId: "harbor" })).toEqual({
+      intent: "rewrite_chapter",
+      bookId: "harbor",
+      chapterNumber: 3,
+    });
+  });
+
+  it("maps revise chapter requests with freeform instructions", () => {
+    expect(routeNaturalLanguageIntent("revise chapter 5 ending only", { activeBookId: "harbor" })).toEqual({
+      intent: "revise_chapter",
+      bookId: "harbor",
+      chapterNumber: 5,
+      instruction: "ending only",
+    });
+  });
+
+  it("maps focus updates to update_focus", () => {
+    expect(routeNaturalLanguageIntent("把 focus 拉回旧案线", { activeBookId: "harbor" })).toEqual({
+      intent: "update_focus",
+      bookId: "harbor",
+      instruction: "把 focus 拉回旧案线",
+    });
+  });
+
+  it("maps explanation requests to explain_failure", () => {
+    expect(routeNaturalLanguageIntent("为什么主角名字没按设定走", { activeBookId: "harbor" })).toEqual({
+      intent: "explain_failure",
+      bookId: "harbor",
+      instruction: "为什么主角名字没按设定走",
+    });
+  });
+});

+ 4 - 0
packages/core/src/index.ts

@@ -98,6 +98,10 @@ export {
   updateAutomationMode,
 } from "./interaction/session.js";
 export { routeInteractionRequest } from "./interaction/request-router.js";
+export {
+  routeNaturalLanguageIntent,
+  type NaturalLanguageRoutingContext,
+} from "./interaction/nl-router.js";
 export {
   runInteractionRequest,
   type InteractionRuntimeTools,

+ 70 - 0
packages/core/src/interaction/nl-router.ts

@@ -0,0 +1,70 @@
+import type { InteractionRequest } from "./intents.js";
+
+export interface NaturalLanguageRoutingContext {
+  readonly activeBookId?: string;
+}
+
+export function routeNaturalLanguageIntent(
+  input: string,
+  context: NaturalLanguageRoutingContext = {},
+): InteractionRequest {
+  const trimmed = input.trim();
+  const lower = trimmed.toLowerCase();
+  const bookId = context.activeBookId;
+
+  if (/^(continue|继续|继续写|写下一章|write next)$/i.test(trimmed)) {
+    return {
+      intent: "write_next",
+      ...(bookId ? { bookId } : {}),
+    };
+  }
+
+  if (/^(pause|pause this book|暂停|暂停这本书)$/i.test(trimmed)) {
+    return {
+      intent: "pause_book",
+      ...(bookId ? { bookId } : {}),
+    };
+  }
+
+  const rewriteMatch = trimmed.match(/(?:rewrite chapter|重写第)\s*(\d+)\s*(?:章)?/i);
+  if (rewriteMatch) {
+    return {
+      intent: "rewrite_chapter",
+      ...(bookId ? { bookId } : {}),
+      chapterNumber: parseInt(rewriteMatch[1]!, 10),
+    };
+  }
+
+  const reviseMatch = trimmed.match(/revise chapter\s*(\d+)\s*(.*)$/i);
+  if (reviseMatch) {
+    const trailing = reviseMatch[2]?.trim();
+    return {
+      intent: "revise_chapter",
+      ...(bookId ? { bookId } : {}),
+      chapterNumber: parseInt(reviseMatch[1]!, 10),
+      ...(trailing ? { instruction: trailing } : {}),
+    };
+  }
+
+  if (/(focus|聚焦|主线|旧案线)/i.test(trimmed)) {
+    return {
+      intent: "update_focus",
+      ...(bookId ? { bookId } : {}),
+      instruction: trimmed,
+    };
+  }
+
+  if (/(为什么|why)/i.test(trimmed)) {
+    return {
+      intent: "explain_failure",
+      ...(bookId ? { bookId } : {}),
+      instruction: trimmed,
+    };
+  }
+
+  return {
+    intent: "explain_status",
+    ...(bookId ? { bookId } : {}),
+    instruction: lower,
+  };
+}