nanobot/webui/src/lib/file-diff.ts
chengyongru 7768672c5b feat: add file edit diff progress view
Capture file edit snapshots through runner tool lifecycle hooks and render unified diffs in the WebUI with folding and truncation controls.
2026-07-09 10:42:43 +08:00

95 lines
2.4 KiB
TypeScript

import { parsePatch } from "diff";
import type { UIFileDiff } from "@/lib/types";
export interface RenderableFileDiffLine {
kind: "context" | "add" | "delete";
old_lineno?: number | null;
new_lineno?: number | null;
content: string;
}
export interface RenderableFileDiffHunk {
old_start: number;
old_lines: number;
new_start: number;
new_lines: number;
lines: RenderableFileDiffLine[];
}
export interface RenderableFileDiff {
hunks: RenderableFileDiffHunk[];
}
export function hasRenderableFileDiff(diff?: UIFileDiff): boolean {
if (!diff) return false;
return typeof diff.text === "string" && diff.text.trim().length > 0;
}
export function parseRenderableFileDiff(diff: UIFileDiff): RenderableFileDiff {
if (typeof diff.text === "string" && diff.text.trim().length > 0) {
return parseUnifiedDiffText(diff.text);
}
return { hunks: [] };
}
function parseUnifiedDiffText(text: string): RenderableFileDiff {
let files: ReturnType<typeof parsePatch>;
try {
files = parsePatch(text);
} catch {
return { hunks: [] };
}
return {
hunks: files.flatMap((file) =>
file.hunks.map((hunk) => {
let oldLineno = hunk.oldStart;
let newLineno = hunk.newStart;
const lines: RenderableFileDiffLine[] = [];
for (const rawLine of hunk.lines) {
if (rawLine.startsWith("\\")) continue;
const marker = rawLine[0];
const content = rawLine.slice(1);
if (marker === "+") {
lines.push({
kind: "add",
old_lineno: null,
new_lineno: newLineno,
content,
});
newLineno += 1;
continue;
}
if (marker === "-") {
lines.push({
kind: "delete",
old_lineno: oldLineno,
new_lineno: null,
content,
});
oldLineno += 1;
continue;
}
lines.push({
kind: "context",
old_lineno: oldLineno,
new_lineno: newLineno,
content: marker === " " ? content : rawLine,
});
oldLineno += 1;
newLineno += 1;
}
return {
old_start: hunk.oldStart,
old_lines: hunk.oldLines,
new_start: hunk.newStart,
new_lines: hunk.newLines,
lines,
};
}),
),
};
}