'use client'
import React, { useMemo } from 'react'
import { Node, Handle, Position } from '@xyflow/react'
import { Badge } from '@/components/ui/badge'
import { FileCode, FileX } from 'lucide-react'
import { AdaptiveGraph } from '@/components/features/adaptive-graph'
// 节点数据类型
interface GraphNodeData {
id: string
path: string
fileName: string
fileTypeId: string
fileTypeName: string
summary: string | null
dependencyCount: number
isDeleted: boolean
}
// 组件 Props
interface FileDependencyGraphProps {
nodes: GraphNodeData[]
edges: Array<{ source: string; target: string; label?: string }>
onNodeClick?: (node: GraphNodeData) => void
}
// 自定义节点组件
const CustomNode = ({ data }: { data: GraphNodeData & { isHighlighted?: boolean; isDimmed?: boolean } }) => {
const bgColor = data.isDeleted
? 'bg-red-50 dark:bg-red-950/20'
: data.isHighlighted
? 'bg-blue-50 dark:bg-blue-950/50'
: data.isDimmed
? 'bg-gray-50 dark:bg-gray-900/30'
: 'bg-white dark:bg-gray-800'
const borderColor = data.isDeleted
? 'border-red-300 dark:border-red-700'
: data.isHighlighted
? 'border-blue-500 dark:border-blue-400'
: 'border-gray-200 dark:border-gray-700'
const opacity = data.isDimmed ? 'opacity-40' : 'opacity-100'
return (
{/* Handle 组件用于连接边,没有Handle看不见连边,!opacity-0用来隐藏卡片上用来连接的小点 */}
{data.isDeleted ? (
) : (
)}
{data.fileName}
{data.path}
{data.fileTypeName}
{data.dependencyCount > 0 && (
{data.dependencyCount} 依赖
)}
{data.isDeleted && (
已删除
)}
{/* Handle 组件用于连接边,没有Handle看不见连边,!opacity-0用来隐藏卡片上用来连接的小点 */}
)
}
// 节点类型定义
const nodeTypes = {
custom: CustomNode,
}
export function FileDependencyGraph({ nodes: rawNodes, edges: rawEdges, onNodeClick }: FileDependencyGraphProps) {
// 构建依赖关系映射
const dependencyMap = useMemo(() => {
const map = new Map>()
const reverseDependencyMap = new Map>()
rawEdges.forEach((edge) => {
// 正向依赖:source 依赖 target
if (!map.has(edge.source)) {
map.set(edge.source, new Set())
}
map.get(edge.source)!.add(edge.target)
// 反向依赖:target 被 source 依赖
if (!reverseDependencyMap.has(edge.target)) {
reverseDependencyMap.set(edge.target, new Set())
}
reverseDependencyMap.get(edge.target)!.add(edge.source)
})
return { dependencies: map, reverseDependencies: reverseDependencyMap }
}, [rawEdges])
// 过滤函数:根据搜索查询返回过滤后的节点和高亮节点
const handleFilter = useMemo(
() => (nodes: GraphNodeData[], query: string) => {
if (!query.trim()) {
return {
filteredNodeIds: new Set(nodes.map((n) => n.id)),
highlightedNodeIds: new Set(),
}
}
const lowerQuery = query.toLowerCase()
const matchedNodes = nodes.filter(
(node) =>
node.fileName.toLowerCase().includes(lowerQuery) ||
node.path.toLowerCase().includes(lowerQuery) ||
node.summary?.toLowerCase().includes(lowerQuery)
)
// 包含匹配的节点及其依赖和被依赖的节点
const resultSet = new Set()
matchedNodes.forEach((node) => {
resultSet.add(node.id)
// 添加该节点依赖的节点
const deps = dependencyMap.dependencies.get(node.id)
if (deps) {
deps.forEach((depId) => resultSet.add(depId))
}
// 添加依赖该节点的节点
const reverseDeps = dependencyMap.reverseDependencies.get(node.id)
if (reverseDeps) {
reverseDeps.forEach((depId) => resultSet.add(depId))
}
})
return {
filteredNodeIds: resultSet,
highlightedNodeIds: new Set(matchedNodes.map((n) => n.id)),
}
},
[dependencyMap]
)
// 节点转换函数
const transformNode = (
node: GraphNodeData,
options: { isHighlighted: boolean; isDimmed: boolean }
): Node => ({
id: node.id,
type: 'custom',
data: {
...node,
isHighlighted: options.isHighlighted,
isDimmed: options.isDimmed,
},
position: { x: 0, y: 0 }, // 将由布局算法设置
})
// MiniMap 节点颜色函数
const getNodeColor = (node: Node) => {
const data = node.data as unknown as GraphNodeData & { isHighlighted?: boolean; isDeleted?: boolean }
if (data.isDeleted) return '#fca5a5'
if (data.isHighlighted) return '#60a5fa'
return '#cbd5e1'
}
// 统计信息渲染
const renderStats = (filteredCount: number, totalCount: number, matchedCount: number) => (
显示 {filteredCount} / {totalCount} 个文件
{matchedCount > 0 && ` (${matchedCount} 个匹配)`}
)
return (
)
}