feat(frontend): 润色再沟通接入 AI 反问澄清——选项芯片 + 折答案回炉(WFW-9 M1)

RefinePanel「再沟通」升级为对话式:意见含糊/极短(门控 <10 字,或点「帮我理清方向」)时先调
clarify 预检端点让 AI 反问,渲染 ChoiceChips 选项芯片(+自由输入兜底);作者选/答后
foldClarifications 把答案折进 instruction,再走既有 refine 回炉(零迁移)。清晰意见直接回炉、
不交往返税;预检失败=放行(useClarify 视 error/异常为 needClarification=false,不阻塞润色)。
新增:lib/workbench/clarify.ts(VM 类型+foldClarifications+needsClarifyGate 纯逻辑) +
useClarify(SSE 外调映射 snake→VM) + ChoiceChips.tsx;gen:api 重生成客户端。
门禁绿:tsc/lint/vitest 654(+clarify/useClarify 18 例)/build/coverage 95.36%。
This commit is contained in:
Yaojia Wang
2026-07-08 08:47:28 +02:00
parent 1652ad9d20
commit e30c933c76
7 changed files with 714 additions and 10 deletions

View File

@@ -0,0 +1,64 @@
// AI 反问澄清(纯逻辑 + 前端 view-model 类型WFW-9 M1路线A 两阶段)。
// 「润色/再沟通」意见不清晰时,先走独立非流式 JSON 预检端点让 AI 反问给选项,
// 作者点选/自由输入后,把答案「折进」既有 refine 的 instruction 字符串(零迁移、不动 refine 端点)。
// 这里只放确定性纯函数 + 本地 VM 类型camelCase与后端 snake_case ClarifyDecision 的映射由主线接线时完成。
// 澄清选项(锚定本段/本章的一种具体走法)。
export interface ClarifyOptionVM {
label: string;
value: string;
}
// 一个澄清问题:反问 + 24 个具体选项(可为空退化为纯自由输入)+ 常驻自由输入兜底。
export interface ClarifyQuestionVM {
question: string;
options: ClarifyOptionVM[];
allowFreeText: boolean;
}
// 一回合预检结论(对应后端 ClarifyDecision
export interface ClarifyDecisionVM {
needClarification: boolean;
// v1 硬上限 1 问needClarification=false 时为空。
questions: ClarifyQuestionVM[];
// needClarification=false 时给一句「我这样理解对吗」确认语,可空。
verification?: string;
}
// 作者对某个澄清问题的回答(点选回填 value 或自由输入文本)。
export interface ClarifyAnswer {
question: string;
answer: string;
}
const CLARIFY_PREFIX = "已澄清:";
const CLARIFY_ARROW = " → ";
const LINE_SEPARATOR = "\n";
// 把作者对各澄清问题的回答逐条折进 instruction原意见在前每条澄清另起一行供既有 refine 端点直接使用。
// 纯函数、确定性、不可变:不改动入参;跳过空回答;保留传入顺序。
export function foldClarifications(
instruction: string,
answers: readonly ClarifyAnswer[],
): string {
const lines = answers
.map((entry) => ({
question: entry.question.trim(),
answer: entry.answer.trim(),
}))
.filter((entry) => entry.answer.length > 0)
.map((entry) =>
entry.question
? `${CLARIFY_PREFIX}${entry.question}${CLARIFY_ARROW}${entry.answer}`
: `${CLARIFY_PREFIX}${entry.answer}`,
);
const trimmedInstruction = instruction.trim();
const parts = trimmedInstruction ? [trimmedInstruction, ...lines] : lines;
return parts.join(LINE_SEPARATOR);
}
// 门控instruction 去空白后长度不足 minChars意见太空/太短)才需要预检反问;清晰意见直接走 refine 不发问。
export function needsClarifyGate(instruction: string, minChars: number): boolean {
return instruction.trim().length < minChars;
}