Files
writer-work-flow/apps/web/lib/workbench/clarify.ts
Yaojia Wang e30c933c76 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%。
2026-07-08 08:47:28 +02:00

65 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
}