agent-ecosystem/src/renderer/components/team/review/ReviewDiffContent.tsx
iliya a73ba551db fix: render new files via CodeMirror and fix root drop refresh
- FileSectionDiff: show CodeMirror for new files even when contentSource
  is 'unavailable' — write-new snippets provide the content
- ReviewDiffContent: remove duplicate file header (already in FileSectionHeader)
- editorSlice: fix refreshDirectory for project root — tree is the root's
  children array, not a nested entry, so merge must replace top-level
2026-03-01 13:47:30 +02:00

94 lines
3.1 KiB
TypeScript

import { useMemo } from 'react';
import { diffLines } from 'diff';
import type { FileChangeSummary, SnippetDiff } from '@shared/types/review';
interface ReviewDiffContentProps {
file: FileChangeSummary;
}
const SnippetDiffView = ({ snippet, index }: { snippet: SnippetDiff; index: number }) => {
const diffResult = useMemo(() => {
if (snippet.type === 'write-new') {
// Весь файл — новый
return diffLines('', snippet.newString);
}
if (snippet.type === 'write-update') {
// Полная перезапись
return diffLines('', snippet.newString);
}
return diffLines(snippet.oldString, snippet.newString);
}, [snippet]);
const toolLabel =
snippet.type === 'write-new'
? 'New file'
: snippet.type === 'write-update'
? 'Full rewrite'
: snippet.type === 'multi-edit'
? 'Multi-edit'
: 'Edit';
return (
<div className="overflow-hidden rounded-lg border border-border">
{/* Заголовок snippet */}
<div className="flex items-center justify-between border-b border-border bg-surface-raised px-3 py-1.5">
<span className="text-xs text-text-muted">
#{index + 1} {toolLabel}
</span>
<span className="text-xs text-text-muted">
{snippet.timestamp ? new Date(snippet.timestamp).toLocaleTimeString() : ''}
</span>
</div>
{/* Строки диффа */}
<div className="overflow-x-auto font-mono text-xs leading-5">
{diffResult.map((part, i) => {
const lines = part.value.replace(/\n$/, '').split('\n');
return lines.map((line, j) => {
let bgClass = '';
let prefix = ' ';
let textClass = 'text-text-secondary';
if (part.added) {
bgClass = 'bg-[var(--diff-added-bg,rgba(46,160,67,0.15))]';
prefix = '+';
textClass = 'text-[var(--diff-added-text,#3fb950)]';
} else if (part.removed) {
bgClass = 'bg-[var(--diff-removed-bg,rgba(248,81,73,0.15))]';
prefix = '-';
textClass = 'text-[var(--diff-removed-text,#f85149)]';
}
return (
<div key={`${i}-${j}`} className={`px-3 ${bgClass} ${textClass}`}>
<span className="inline-block w-4 select-none text-text-muted opacity-50">
{prefix}
</span>
<span className="whitespace-pre">{line}</span>
</div>
);
});
})}
</div>
</div>
);
};
export const ReviewDiffContent = ({ file }: ReviewDiffContentProps) => {
const nonErrorSnippets = useMemo(() => file.snippets.filter((s) => !s.isError), [file.snippets]);
return (
<div className="space-y-4 p-4">
{/* Snippets */}
{nonErrorSnippets.map((snippet, index) => (
<SnippetDiffView key={snippet.toolUseId} snippet={snippet} index={index} />
))}
{nonErrorSnippets.length === 0 && (
<div className="py-8 text-center text-sm text-text-muted">No changes to display</div>
)}
</div>
);
};