feat(web): 审稿改稿闭环——一键采纳/AI改写/正文高亮定位编辑/伏笔确认/草稿自动保存/章节目录
- 冲突「采纳改法」:有补丁瞬时改入终稿并选中;无补丁但可定位则采纳时调 AI(refiner)重写该段套入;定位不到提示手改 - 正文改为行内高亮叠层编辑器(与页面同底色、随内容增高、无滚动条),点冲突卡/锚点整页滚到并选中+闪烁 - 仅对可在正文定位的冲突显示「跳转」/锚点;locate 支持从 where 引号抽原文,杜绝定位失败刷屏 - 伏笔建议卡:新埋一键登记(预填表单)/ 疑似回收一键标记 CLOSED(复用现有端点) - 审稿页草稿自动保存(防抖 PUT /draft),与「验收本章」解耦,刷新不丢 - 写作路由带章号 ?chapter=N + 验收成功面板/大纲页「写下一章」入口;写作目录从大纲列出全部章节 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
54
apps/web/lib/review/locate.test.ts
Normal file
54
apps/web/lib/review/locate.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { extractDraftQuotes, locateInDraft } from "./locate";
|
||||
|
||||
const TEXT = "第一段没问题。\n\n他乘坐迈巴赫扬长而去,留下一地尘土。\n\n结尾。";
|
||||
|
||||
describe("locateInDraft", () => {
|
||||
it("locates the exact original fragment", () => {
|
||||
const loc = locateInDraft(TEXT, { original: "迈巴赫", where: "第2段" });
|
||||
expect(loc).not.toBeNull();
|
||||
expect(TEXT.slice(loc!.start, loc!.end)).toBe("迈巴赫");
|
||||
});
|
||||
|
||||
it("falls back to the paragraph snippet when no original", () => {
|
||||
const loc = locateInDraft(TEXT, { original: null, where: "第2段" });
|
||||
expect(loc).not.toBeNull();
|
||||
expect(TEXT.slice(loc!.start, loc!.end)).toBe(
|
||||
"他乘坐迈巴赫扬长而去,留下一地尘土。",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns null when original is absent from text and no paragraph hint", () => {
|
||||
expect(
|
||||
locateInDraft(TEXT, { original: "保时捷", where: "无段号" }),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when the snippet paragraph is out of range", () => {
|
||||
expect(locateInDraft(TEXT, { original: null, where: "第9段" })).toBeNull();
|
||||
});
|
||||
|
||||
it("locates via quoted draft text inside where (no original, no 段号)", () => {
|
||||
// 真实形态:where = 草稿中写"<草稿原文>",引号内即正文精确子串。
|
||||
const loc = locateInDraft(TEXT, {
|
||||
original: null,
|
||||
where: '草稿中写"迈巴赫扬长而去"',
|
||||
});
|
||||
expect(loc).not.toBeNull();
|
||||
expect(TEXT.slice(loc!.start, loc!.end)).toBe("迈巴赫扬长而去");
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractDraftQuotes", () => {
|
||||
it("extracts quoted spans (multiple quote styles) longest-first, deduped", () => {
|
||||
expect(extractDraftQuotes('草稿中写"长一些的原文",又如「短的」')).toEqual([
|
||||
"长一些的原文",
|
||||
"短的",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns empty when there are no quotes", () => {
|
||||
expect(extractDraftQuotes("第3段,主角忽然示弱")).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user