import { useState, useMemo } from 'react' import { ArrowLeftRight, Check, X } from 'lucide-react' import { useTranslation } from 'react-i18next' interface DiffViewerProps { /** 原始文本 */ original: string /** 修改后文本 */ modified: string /** 原始文本标签 */ originalLabel?: string /** 修改后文本标签 */ modifiedLabel?: string /** 接受修改回调 */ onAccept?: (text: string) => void /** 拒绝修改回调 */ onReject?: () => void } interface DiffLine { type: 'same' | 'add' | 'remove' content: string lineOld?: number lineNew?: number } /** * 文本对比查看器 — 并排显示原文和修改后的差异 * 类似 VS Code 的 diff 视图 */ export default function DiffViewer({ original, modified, originalLabel, modifiedLabel, onAccept, onReject, }: DiffViewerProps) { const { t } = useTranslation('editors') const resolvedOriginalLabel = originalLabel ?? t('diffViewer.original') const resolvedModifiedLabel = modifiedLabel ?? t('diffViewer.revised') const [mode, setMode] = useState<'side' | 'inline'>('side') // 计算差异 const diffLines = useMemo(() => computeDiff(original, modified), [original, modified]) // 统计 const addCount = diffLines.filter((d) => d.type === 'add').length const removeCount = diffLines.filter((d) => d.type === 'remove').length return (
{/* 头部工具栏 */}
{t('diffViewer.title')} +{addCount} {' '} -{removeCount}
{/* 视图切换 */} {/* 操作按钮 */} {onReject && ( )} {onAccept && ( )}
{/* Diff 内容 */} {mode === 'side' ? ( ) : ( )}
) } /** 并排视图 */ function SideBySideView({ diffLines, originalLabel, modifiedLabel }: { diffLines: DiffLine[]; originalLabel: string; modifiedLabel: string }) { return (
{/* 左侧:原文 */}
{originalLabel}
{diffLines .filter((d) => d.type !== 'add') .map((d, i) => (
{d.lineOld ?? ''} {d.type === 'remove' && -} {d.content}
))}
{/* 右侧:修改后 */}
{modifiedLabel}
{diffLines .filter((d) => d.type !== 'remove') .map((d, i) => (
{d.lineNew ?? ''} {d.type === 'add' && +} {d.content}
))}
) } /** 内联视图 */ function InlineView({ diffLines }: { diffLines: DiffLine[] }) { return (
{diffLines.map((d, i) => (
{d.lineOld ?? ''} {d.lineNew ?? ''} {d.type === 'add' ? '+' : d.type === 'remove' ? '-' : ' '} {d.content}
))}
) } /** * 简单行级 diff 算法(LCS-based) * 生产环境建议替换为 diff-match-patch 或类似库 */ function computeDiff(original: string, modified: string): DiffLine[] { const oldLines = original.split('\n') const newLines = modified.split('\n') // 简化实现:逐行对比,使用 LCS 找出公共子序列 const lcs = longestCommonSubsequence(oldLines, newLines) const result: DiffLine[] = [] let oldIdx = 0 let newIdx = 0 let oldLineNum = 1 let newLineNum = 1 for (const common of lcs) { // 输出 old 中在 common 之前的删除行 while (oldIdx < oldLines.length && oldLines[oldIdx] !== common) { result.push({ type: 'remove', content: oldLines[oldIdx], lineOld: oldLineNum++ }) oldIdx++ } // 输出 new 中在 common 之前的新增行 while (newIdx < newLines.length && newLines[newIdx] !== common) { result.push({ type: 'add', content: newLines[newIdx], lineNew: newLineNum++ }) newIdx++ } // 公共行 result.push({ type: 'same', content: common, lineOld: oldLineNum++, lineNew: newLineNum++ }) oldIdx++ newIdx++ } // 处理尾部 while (oldIdx < oldLines.length) { result.push({ type: 'remove', content: oldLines[oldIdx++], lineOld: oldLineNum++ }) } while (newIdx < newLines.length) { result.push({ type: 'add', content: newLines[newIdx++], lineNew: newLineNum++ }) } return result } /** 最长公共子序列(行级) */ function longestCommonSubsequence(a: string[], b: string[]): string[] { const m = a.length const n = b.length // 优化:对于大文本只取前后各 500 行 if (m > 1000 || n > 1000) { return simpleFallback(a, b) } const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0)) for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (a[i - 1] === b[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1 } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) } } } // 回溯 const result: string[] = [] let i = m, j = n while (i > 0 && j > 0) { if (a[i - 1] === b[j - 1]) { result.unshift(a[i - 1]) i-- j-- } else if (dp[i - 1][j] > dp[i][j - 1]) { i-- } else { j-- } } return result } /** 大文本降级:只保留完全相同的行 */ function simpleFallback(a: string[], b: string[]): string[] { const bSet = new Set(b) return a.filter((line) => bSet.has(line)) }