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

@@ -60,6 +60,9 @@ export function buildGenerateRequest(values: FieldValues): ToolGenerateRequest {
}
const kind = values["kind"]?.trim();
if (kind) body.kind = kind;
// 原文输入(扩写/降AI/拆书样本text_input 策略):非空才带(空白省略)。
const text = values["text"]?.trim();
if (text) body.text = text;
return body;
}
@@ -142,16 +145,61 @@ export function previewRows(
return asRecordArray(firstArrayEntry(preview));
}
// 单对象产物(非列表)的 output_kind续写/扩写/降AI纯正文 {text}+ 拆书(结构化对象)。
// 这些 preview 本身就是一个对象(无顶层数组),故不走 firstArrayEntry 的「记录列表」假设,
// 而归一为「单张卡」Scope B 竞品快赢生成器)。
const SINGLE_OBJECT_KINDS = new Set([
"ContinuationResult",
"PolishResult",
"DeAiResult",
"BookTeardownResult",
]);
// output_kind → 行渲染策略。未知 kind 走兜底heading=首个字符串字段,其余键值列出)。
export function mapPreview(
outputKind: string,
preview: Record<string, unknown> | undefined,
): PreviewModel {
if (SINGLE_OBJECT_KINDS.has(outputKind)) {
const item = singleObjectItem(outputKind, preview);
return { kind: outputKind, items: item ? [item] : [] };
}
const rows = asRecordArray(firstArrayEntry(preview));
const items = rows.map((row) => previewItem(outputKind, row));
return { kind: outputKind, items };
}
// 把单对象产物归一为一张预览卡:纯正文类 → body拆书 → structure 作 heading + 列表字段。
// preview 缺失/正文为空 → 返 nullmapPreview 据此给空 items不渲空卡
function singleObjectItem(
outputKind: string,
preview: Record<string, unknown> | undefined,
): PreviewItem | null {
if (!preview) return null;
if (outputKind === "BookTeardownResult") {
return teardownItem(preview);
}
const text = asString(preview["text"]);
return text ? { heading: null, fields: [], body: text } : null;
}
// 拆书结构化产物 → 一张卡structure 作标题themes/archetypes/hooks 各拼成一行字段。
function teardownItem(preview: Record<string, unknown>): PreviewItem {
const listField = (key: string, label: string): PreviewField[] => {
const joined = asStringArray(preview[key]).join("");
return joined ? [{ label, value: joined }] : [];
};
return {
heading: asString(preview["structure"]) || null,
fields: [
...listField("themes", "主题"),
...listField("archetypes", "人物原型"),
...listField("hooks", "钩子套路"),
],
body: null,
};
}
function previewItem(
outputKind: string,
row: Record<string, unknown>,