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))
}