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。
28 lines
1.3 KiB
TypeScript
28 lines
1.3 KiB
TypeScript
// R1 · 冲突卡内联上下文片段(UX §6.4)。
|
||
// `where` 是 LLM 给的文字定位(如「第 3 段,主角忽然示弱」),M2 无精确字符 offset。
|
||
// 这里按段级回放:从 where 解析 1-based 段号 → 取终稿对应段 → 截断成预览。
|
||
// 纯逻辑,与 ReviewReport.segmentText 同口径(空行切段),便于 node 环境单测。
|
||
|
||
// 预览窗口最大字符数(命中段超长时截断,避免撑爆卡片)。
|
||
const PREVIEW_MAX = 80;
|
||
|
||
// 从 where 文案解析 1-based 段号(「第 3 段」→ 3;范围「第 4–6 段」取首个)。无则 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)}…`;
|
||
}
|