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%。
65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
// AI 反问澄清(纯逻辑 + 前端 view-model 类型;WFW-9 M1,路线A 两阶段)。
|
||
// 「润色/再沟通」意见不清晰时,先走独立非流式 JSON 预检端点让 AI 反问给选项,
|
||
// 作者点选/自由输入后,把答案「折进」既有 refine 的 instruction 字符串(零迁移、不动 refine 端点)。
|
||
// 这里只放确定性纯函数 + 本地 VM 类型(camelCase);与后端 snake_case ClarifyDecision 的映射由主线接线时完成。
|
||
|
||
// 澄清选项(锚定本段/本章的一种具体走法)。
|
||
export interface ClarifyOptionVM {
|
||
label: string;
|
||
value: string;
|
||
}
|
||
|
||
// 一个澄清问题:反问 + 2–4 个具体选项(可为空退化为纯自由输入)+ 常驻自由输入兜底。
|
||
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;
|
||
}
|