feat(frontend): 编辑器内「润色 / 再沟通」——选段回炉 + 追加意见迭代出版本栈,接受回填
Phase 1(写作工作台重构):编辑器选中一段即可「润色选段」,复用同步 /refine 回炉 (只读、不写库,守不变量 #3);不满意可「再沟通」——以上一版产出为输入 + 作者新意见 再改一版,形成版本栈(useRefine,已单测)。接受回填按内容重锚落回草稿,原文已变则 提示重选、绝不盲替换(applyRefinement,已单测)。Editor 上报选区,结果以内联卡片展示。
This commit is contained in:
24
apps/web/lib/workbench/refineApply.ts
Normal file
24
apps/web/lib/workbench/refineApply.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// 润色/再沟通 accept 回填:把选中原文替换为产出。选区偏移可能因异步/流式漂移,
|
||||
// 故先按 range 校验,未命中则按原文内容重锚(indexOf)——原文已不存在则返回 null,
|
||||
// 由调用方提示作者重新选择,绝不盲替换(plan §3.3 mustFix)。
|
||||
|
||||
export interface SelectionRange {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export function applyRefinement(
|
||||
fullText: string,
|
||||
original: string,
|
||||
refined: string,
|
||||
range: SelectionRange,
|
||||
): string | null {
|
||||
// 选区未漂移:range 处正好是原文 → 原地替换。
|
||||
if (fullText.slice(range.start, range.end) === original) {
|
||||
return fullText.slice(0, range.start) + refined + fullText.slice(range.end);
|
||||
}
|
||||
// 漂移:按内容重锚,保守取首个匹配。
|
||||
const idx = fullText.indexOf(original);
|
||||
if (idx === -1) return null;
|
||||
return fullText.slice(0, idx) + refined + fullText.slice(idx + original.length);
|
||||
}
|
||||
Reference in New Issue
Block a user