feat(ux): R1+R2 审稿裁决人体学 — 冲突卡内联片段 + 分组/排序/跳下一条/主次按钮

R1: 新 lib/review/snippet.ts 从 where 解析段号取终稿命中段预览,ConflictCard 渲染引用块。
R2: 新 lib/review/grouping.ts 按 type 分组+严重度排序(仅改显示顺序,保留原始 index 守 conflict_index 不变量),组计数徽标、跳到下一条未裁决(环回)、采纳改法主按钮/忽略手改次级。
TDD: snippet 8 测 + grouping 10 测。前端门禁绿: lint/tsc/vitest 201/build。
This commit is contained in:
Yaojia Wang
2026-06-20 15:53:45 +02:00
parent 6f419b35ed
commit d81583d5d1
7 changed files with 329 additions and 22 deletions

View File

@@ -0,0 +1,27 @@
// R1 · 冲突卡内联上下文片段UX §6.4)。
// `where` 是 LLM 给的文字定位(如「第 3 段主角忽然示弱」M2 无精确字符 offset。
// 这里按段级回放:从 where 解析 1-based 段号 → 取终稿对应段 → 截断成预览。
// 纯逻辑,与 ReviewReport.segmentText 同口径(空行切段),便于 node 环境单测。
// 预览窗口最大字符数(命中段超长时截断,避免撑爆卡片)。
const PREVIEW_MAX = 80;
// 从 where 文案解析 1-based 段号(「第 3 段」→ 3范围「第 46 段」取首个)。无则 null。
export function parseParagraphNo(where: string): number | null {
const match = where.match(/第\s*(\d+)/);
if (!match) return null;
const n = Number(match[1]);
return Number.isInteger(n) && n > 0 ? n : null;
}
// 取终稿第 N 段1-based正文预览无段号/越界/空段 → null卡片回退到「跳转」按钮
export function conflictSnippet(finalText: string, where: string): string | null {
const no = parseParagraphNo(where);
if (no === null) return null;
const paragraphs = finalText.split(/\n{2,}/);
const paragraph = paragraphs[no - 1]?.trim();
if (!paragraph) return null;
return paragraph.length <= PREVIEW_MAX
? paragraph
: `${paragraph.slice(0, PREVIEW_MAX)}`;
}