feat(web): Scope B B2 — 工具箱原文输入 + 4 新生成器预览渲染

buildGenerateRequest 转发 text(扩写/降AI/拆书原文,text_input 策略;空白省略)。
mapPreview 处理单对象产物:续写/扩写/降AI({text}→单 body 卡)、拆书
BookTeardownResult(structure 作标题 + themes/archetypes/hooks 列表字段)——
绕过 firstArrayEntry 的「记录列表」假设,使 4 新生成器声明驱动表单/预览自动可渲染。
原文 textarea 字段(name=text)由既有 FormField 渲染,无需改组件。
This commit is contained in:
Yaojia Wang
2026-06-23 19:21:10 +02:00
parent a2dcd89881
commit e2d798627a
3 changed files with 92 additions and 1 deletions

View File

@@ -74,6 +74,18 @@ describe("buildGenerateRequest", () => {
it("defaults brief to empty string", () => {
expect(buildGenerateRequest({})).toEqual({ brief: "" });
});
it("forwards trimmed text (扩写/降AI/拆书 原文输入)", () => {
expect(
buildGenerateRequest({ brief: "扩写", text: " 原始正文片段 " }),
).toEqual({ brief: "扩写", text: "原始正文片段" });
});
it("omits blank/whitespace-only text", () => {
expect(buildGenerateRequest({ brief: "x", text: " " })).toEqual({
brief: "x",
});
});
});
describe("previewRows", () => {
@@ -137,6 +149,37 @@ describe("mapPreview", () => {
it("yields no items for empty preview", () => {
expect(mapPreview("IdeaListResult", undefined).items).toEqual([]);
});
it("maps a text-only result (续写/扩写/降AI) into a single body item", () => {
for (const kind of ["ContinuationResult", "PolishResult", "DeAiResult"]) {
const model = mapPreview(kind, { text: "一段续写/改写后的正文" });
expect(model.items).toHaveLength(1);
expect(model.items[0]?.heading).toBeNull();
expect(model.items[0]?.body).toBe("一段续写/改写后的正文");
}
});
it("maps BookTeardownResult structured fields into one card", () => {
const model = mapPreview("BookTeardownResult", {
themes: ["逆袭", "复仇"],
archetypes: ["废柴主角"],
structure: "三幕递进",
hooks: ["开局打脸", "扮猪吃虎"],
});
expect(model.items).toHaveLength(1);
const item = model.items[0];
expect(item?.heading).toBe("三幕递进");
const byLabel = (label: string) =>
item?.fields.find((f) => f.label === label)?.value;
expect(byLabel("主题")).toBe("逆袭;复仇");
expect(byLabel("人物原型")).toBe("废柴主角");
expect(byLabel("钩子套路")).toBe("开局打脸;扮猪吃虎");
});
it("yields no items for an empty text result", () => {
expect(mapPreview("ContinuationResult", { text: "" }).items).toEqual([]);
expect(mapPreview("ContinuationResult", undefined).items).toEqual([]);
});
});
describe("resolveLegacyRoute", () => {