fix: consistent diff stats between preview badge and DiffViewer
- Export computeDiffLineStats from DiffViewer — uses the same LCS/semantic diff algorithm so stats always match the rendered diff - Remove custom computeDiffStats (set-based) that produced different counts - Add quickStats from toolInput.content for Write tool — shows +N immediately before IPC completes (no need to expand first) - diffStats (accurate, from IPC) replaces quickStats once available
This commit is contained in:
parent
987ad96f4a
commit
4d65bad24f
2 changed files with 36 additions and 45 deletions
|
|
@ -149,6 +149,19 @@ function generateDiff(oldLines: string[], newLines: string[]): DiffLine[] {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes diff statistics from old/new strings using the same algorithm as DiffViewer.
|
||||
*/
|
||||
export function computeDiffLineStats(
|
||||
oldString: string,
|
||||
newString: string
|
||||
): { added: number; removed: number } {
|
||||
const oldLines = oldString.split(/\r?\n/);
|
||||
const newLines = newString.split(/\r?\n/);
|
||||
const diffLines = generateDiff(oldLines, newLines);
|
||||
return computeStats(diffLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes diff statistics.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { DiffViewer } from '@renderer/components/chat/viewers/DiffViewer';
|
||||
import { computeDiffLineStats, DiffViewer } from '@renderer/components/chat/viewers/DiffViewer';
|
||||
import { useToolApprovalDiff } from '@renderer/hooks/useToolApprovalDiff';
|
||||
import { AlertTriangle, ChevronDown, ChevronRight, FileDiff, Loader2 } from 'lucide-react';
|
||||
|
||||
|
|
@ -35,51 +35,18 @@ function saveExpandedPref(value: boolean): void {
|
|||
}
|
||||
|
||||
// =============================================================================
|
||||
// Diff stats helper
|
||||
// Quick stats from toolInput (before IPC, for Write tool)
|
||||
// =============================================================================
|
||||
|
||||
function computeDiffStats(
|
||||
oldString: string,
|
||||
newString: string
|
||||
): { added: number; removed: number } {
|
||||
const oldLines = oldString.split(/\r?\n/);
|
||||
const newLines = newString.split(/\r?\n/);
|
||||
// Simple line-count based stats (matches DiffViewer's own count)
|
||||
const maxLen = Math.max(oldLines.length, newLines.length);
|
||||
let added = 0;
|
||||
let removed = 0;
|
||||
// Count lines that differ
|
||||
if (oldString === '' && newString !== '') {
|
||||
added = newLines.length;
|
||||
} else if (newString === '' && oldString !== '') {
|
||||
removed = oldLines.length;
|
||||
} else {
|
||||
// Diff-based: count added/removed from line diff
|
||||
const oldSet = new Map<string, number>();
|
||||
for (const line of oldLines) {
|
||||
oldSet.set(line, (oldSet.get(line) ?? 0) + 1);
|
||||
}
|
||||
const newSet = new Map<string, number>();
|
||||
for (const line of newLines) {
|
||||
newSet.set(line, (newSet.get(line) ?? 0) + 1);
|
||||
}
|
||||
// Lines in new but not in old
|
||||
for (const [line, count] of newSet) {
|
||||
const oldCount = oldSet.get(line) ?? 0;
|
||||
if (count > oldCount) added += count - oldCount;
|
||||
}
|
||||
// Lines in old but not in new
|
||||
for (const [line, count] of oldSet) {
|
||||
const newCount = newSet.get(line) ?? 0;
|
||||
if (count > newCount) removed += count - newCount;
|
||||
}
|
||||
// Ensure at least something shows if strings differ but stats are 0
|
||||
if (added === 0 && removed === 0 && oldString !== newString) {
|
||||
added = Math.max(0, newLines.length - maxLen);
|
||||
removed = Math.max(0, oldLines.length - maxLen);
|
||||
}
|
||||
function computeQuickStats(
|
||||
toolName: string,
|
||||
toolInput: Record<string, unknown>
|
||||
): { added: number; removed: number } | null {
|
||||
if (toolName === 'Write' && typeof toolInput.content === 'string') {
|
||||
const lines = toolInput.content.split(/\r?\n/).length;
|
||||
return { added: lines, removed: 0 };
|
||||
}
|
||||
return { added, removed };
|
||||
return null;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -95,12 +62,23 @@ export const ToolApprovalDiffPreview: React.FC<ToolApprovalDiffPreviewProps> = (
|
|||
const [expanded, setExpanded] = useState(loadExpandedPref);
|
||||
const diff = useToolApprovalDiff(toolName, toolInput, requestId, expanded);
|
||||
|
||||
const stats = useMemo(() => {
|
||||
// Stats from actual diff data (after IPC), using the same algorithm as DiffViewer
|
||||
const diffStats = useMemo(() => {
|
||||
if (!diff.hasDiff || diff.loading || diff.isBinary || diff.error) return null;
|
||||
if (!diff.oldString && !diff.newString) return null;
|
||||
return computeDiffStats(diff.oldString, diff.newString);
|
||||
return computeDiffLineStats(diff.oldString, diff.newString);
|
||||
}, [diff.hasDiff, diff.loading, diff.isBinary, diff.error, diff.oldString, diff.newString]);
|
||||
|
||||
// Quick stats from toolInput (available immediately, before diff loads)
|
||||
const quickStats = useMemo(
|
||||
() => computeQuickStats(toolName, toolInput),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- toolInput identity tied to requestId
|
||||
[toolName, requestId]
|
||||
);
|
||||
|
||||
// Prefer accurate diffStats when available, fall back to quickStats
|
||||
const stats = diffStats ?? quickStats;
|
||||
|
||||
if (!DIFF_TOOLS.has(toolName)) return null;
|
||||
|
||||
const toggleExpanded = (): void => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue