Skip to main content
Glama
KnowledgeGraphService.ts40.2 kB
import { Graph, GraphStatus, Node, Edge, Resource, NodeType, EdgeType, GraphModel, GraphType, SimpleGraph } from '../models/Graph'; import { FileService } from './FileService'; import { KnowledgeGraphConfig } from '../config/types'; import { SvgService } from './SvgService'; import { formatLocalDateTime } from '../utils/datetime'; import { z } from 'zod'; import * as path from 'path'; import * as fs from 'fs/promises'; import * as fsSync from 'node:fs'; export class KnowledgeGraphError extends Error { constructor(message: string) { super(message); this.name = 'KnowledgeGraphError'; } } export class KnowledgeGraphService { private graphs: Map<string, Graph> = new Map(); private fileService?: FileService; private svgService: SvgService; private initialized = false; private config: KnowledgeGraphConfig; constructor(config?: KnowledgeGraphConfig) { this.config = config || { autoCreate: true, autoPersist: true, generateSvg: true, svgSettings: { width: 1200, height: 800, nodeRadius: 20, fontSize: 12, fontFamily: 'Arial', colors: { component: '#4285F4', module: '#EA4335', service: '#FBBC05', data: '#34A853', api: '#8E44AD', concept: '#E67E22', resource: '#16A085', default: '#95A5A6' } } }; if (config?.graphDir) { this.fileService = new FileService(config.graphDir); } this.svgService = new SvgService(config?.svgSettings); } async init(): Promise<void> { console.log('初始化KnowledgeGraphService...'); if (this.fileService) { console.log(`使用图谱目录: ${this.fileService.getGraphDir()}`); await this.fileService.init(); await this.reloadData(); this.initialized = true; console.log('KnowledgeGraphService初始化完成'); } else { console.warn('未配置文件服务,将使用内存模式运行'); this.initialized = true; } } private async reloadData(): Promise<void> { if (!this.fileService) return; try { console.log('开始重新加载图谱数据...'); // 使用fileService.loadGraphs方法而不是readGraphFile const data = await this.fileService.loadGraphs(); console.log(`加载到${data.graphs?.length || 0}个图谱数据`); if (data.graphs && Array.isArray(data.graphs)) { this.graphs.clear(); data.graphs.forEach((graph: any) => { // 转换日期字符串为Date对象 graph.createdAt = new Date(graph.createdAt); graph.updatedAt = new Date(graph.updatedAt); if (graph.publishedAt) graph.publishedAt = new Date(graph.publishedAt); if (graph.archivedAt) graph.archivedAt = new Date(graph.archivedAt); // 处理节点的日期 graph.nodes?.forEach((node: any) => { node.createdAt = new Date(node.createdAt); node.updatedAt = new Date(node.updatedAt); }); // 处理边的日期 graph.edges?.forEach((edge: any) => { edge.createdAt = new Date(edge.createdAt); edge.updatedAt = new Date(edge.updatedAt); }); // 处理资源的日期 graph.resources?.forEach((resource: any) => { resource.createdAt = new Date(resource.createdAt); resource.updatedAt = new Date(resource.updatedAt); }); console.log(`加载图谱: ${graph.id} - ${graph.name}`); this.graphs.set(graph.id, graph); }); console.log(`共加载了${this.graphs.size}个图谱到内存`); } else { console.log('没有找到有效的图谱数据'); } } catch (error) { console.error('重新加载图谱数据失败:', error); } } private async ensureInitialized(): Promise<void> { if (!this.initialized) { await this.init(); } else if (this.graphs.size === 0 && this.fileService) { // 如果 map 为空但文件服务存在,尝试重新加载数据 await this.reloadData(); } } // 创建新的知识图谱 async createGraph(name: string, type?: typeof GraphType[keyof typeof GraphType], description?: string): Promise<Graph> { try { if (!name || typeof name !== 'string') { throw new KnowledgeGraphError(`创建图谱失败: name必须提供且类型为字符串,当前类型: ${typeof name}, 值: ${JSON.stringify(name)}`); } // 检查type参数,如果提供了但不是有效类型则抛出错误 if (type && !Object.values(GraphType).includes(type as any)) { throw new KnowledgeGraphError(`创建图谱失败: 无效的图谱类型 "${type}"。有效类型: ${Object.values(GraphType).join(', ')}`); } // 检查description参数 if (description && typeof description !== 'string') { throw new KnowledgeGraphError(`创建图谱失败: description必须为字符串类型,当前类型: ${typeof description}`); } await this.ensureInitialized(); const graph = GraphModel.create(name, type, description); this.graphs.set(graph.id, graph); await this.persistChanges(); return graph; } catch (error) { console.error('创建图谱错误:', error); if (error instanceof KnowledgeGraphError) { throw error; } else { const errorMsg = error instanceof Error ? error.message : String(error); throw new KnowledgeGraphError(`创建图谱时发生未知错误: ${errorMsg}`); } } } // 获取知识图谱 async getGraph(id: string): Promise<Graph> { await this.ensureInitialized(); const graph = this.graphs.get(id); if (!graph) { throw new KnowledgeGraphError(`知识图谱 ${id} 不存在`); } return graph; } // 更新知识图谱基本信息 async updateGraph(id: string, updates: { name?: string; description?: string }): Promise<Graph> { await this.ensureInitialized(); const graph = await this.getGraph(id); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能修改'); } if (updates.name) { graph.name = updates.name; } if (updates.description !== undefined) { graph.description = updates.description; } graph.updatedAt = new Date(); this.graphs.set(id, graph); await this.persistChanges(); return graph; } // 发布知识图谱 async publishGraph(id: string): Promise<Graph> { await this.ensureInitialized(); const graph = await this.getGraph(id); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能发布'); } graph.status = GraphStatus.PUBLISHED; graph.publishedAt = new Date(); graph.updatedAt = new Date(); // 生成SVG图表 if (this.config.generateSvg && this.fileService) { const svgContent = this.svgService.generateGraphSvg(graph); // 生成基础文件名和后缀 const baseName = graph.name.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]+/g, '_'); const suffix = `graph_${id.slice(0, 8)}`; // 生成带时间的文件名 const fileName = `${this.generateFileNameWithTime(baseName, suffix)}.svg`; const svgPath = await this.fileService.saveSvg(id, fileName, svgContent); // 添加SVG资源 const resource = GraphModel.createResource( `${graph.name} 知识图谱`, 'svg', `resources/svg/${svgPath}`, '自动生成的知识图谱可视化图' ); graph.resources.push(resource); } this.graphs.set(id, graph); await this.persistChanges(); return graph; } // 归档知识图谱 async archiveGraph(id: string): Promise<Graph> { await this.ensureInitialized(); const graph = await this.getGraph(id); graph.status = GraphStatus.ARCHIVED; graph.archivedAt = new Date(); graph.updatedAt = new Date(); this.graphs.set(id, graph); await this.persistChanges(); return graph; } // 删除知识图谱 async deleteGraph(id: string): Promise<void> { await this.ensureInitialized(); // 先检查图谱是否存在 const graph = await this.getGraph(id); // 删除图谱相关的所有资源文件 if (this.fileService) { // 删除相关的SVG文件 const svgResources = graph.resources.filter(r => r.type === 'svg'); for (const resource of svgResources) { try { // 从路径中提取文件名 const fileName = resource.path.split('/').pop(); if (fileName) { await this.fileService.deleteSvg(fileName); } } catch (error) { console.error(`删除SVG资源失败: ${error}`); // 继续删除其他资源 } } // 删除相关的MD文件 const mdResources = graph.resources.filter(r => r.type === 'md'); for (const resource of mdResources) { try { // 从路径中提取文件名 const fileName = resource.path.split('/').pop(); if (fileName) { await this.fileService.deleteMd(fileName); } } catch (error) { console.error(`删除MD资源失败: ${error}`); // 继续删除其他资源 } } } // 从内存中删除图谱 this.graphs.delete(id); // 持久化更改 await this.persistChanges(); console.log(`已删除知识图谱: ${graph.name} (${id})`); } // 添加节点 async addNode(graphId: string, nodeType: typeof NodeType[keyof typeof NodeType], name: string, description?: string, filePath?: string, metadata?: Record<string, any>): Promise<Node> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); // 验证节点类型与图谱类型是否匹配 this.validateNodeTypeForGraphType(graph.type, nodeType); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能添加节点'); } // 检查节点名称是否重复 if (graph.nodes.some(n => n.name === name)) { throw new KnowledgeGraphError(`节点名称 "${name}" 已存在`); } const node = GraphModel.createNode(nodeType, name, description, filePath, metadata); graph.nodes.push(node); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); return node; } // 更新节点 async updateNode(graphId: string, nodeId: string, updates: { name?: string; description?: string; filePath?: string; metadata?: Record<string, any> }): Promise<Node> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能修改节点'); } const nodeIndex = graph.nodes.findIndex(n => n.id === nodeId); if (nodeIndex === -1) { throw new KnowledgeGraphError(`节点 ${nodeId} 不存在`); } const node = graph.nodes[nodeIndex]; if (updates.name && updates.name !== node.name) { // 检查新名称是否重复 if (graph.nodes.some(n => n.name === updates.name && n.id !== nodeId)) { throw new KnowledgeGraphError(`节点名称 "${updates.name}" 已存在`); } node.name = updates.name; } if (updates.description !== undefined) { node.description = updates.description; } if (updates.filePath !== undefined) { node.filePath = updates.filePath; } if (updates.metadata !== undefined) { node.metadata = updates.metadata; } node.updatedAt = new Date(); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); return node; } // 删除节点 async deleteNode(graphId: string, nodeId: string): Promise<void> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能删除节点'); } const nodeIndex = graph.nodes.findIndex(n => n.id === nodeId); if (nodeIndex === -1) { throw new KnowledgeGraphError(`节点 ${nodeId} 不存在`); } // 检查是否是根节点 if (graph.rootNodeId === nodeId) { throw new KnowledgeGraphError('不能删除根节点'); } // 删除与节点相关的边 graph.edges = graph.edges.filter(e => e.source !== nodeId && e.target !== nodeId); // 删除节点 graph.nodes.splice(nodeIndex, 1); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); } // 添加边 async addEdge(graphId: string, edgeType: typeof EdgeType[keyof typeof EdgeType], source: string, target: string, label?: string, weight?: number, metadata?: Record<string, any>): Promise<Edge> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); // 验证边类型与图谱类型是否匹配 this.validateEdgeTypeForGraphType(graph.type, edgeType); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能添加边'); } // 验证源节点和目标节点是否存在 const sourceNode = graph.nodes.find(n => n.id === source); const targetNode = graph.nodes.find(n => n.id === target); if (!sourceNode) { throw new KnowledgeGraphError(`源节点 ${source} 不存在`); } if (!targetNode) { throw new KnowledgeGraphError(`目标节点 ${target} 不存在`); } // 检查是否已存在相同的边 if (graph.edges.some(e => e.source === source && e.target === target && e.type === edgeType)) { throw new KnowledgeGraphError(`从 ${source} 到 ${target} 的 ${edgeType} 类型边已存在`); } const edge = GraphModel.createEdge(edgeType, source, target, label, weight, metadata); graph.edges.push(edge); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); return edge; } // 更新边 async updateEdge(graphId: string, edgeId: string, updates: { label?: string; weight?: number; metadata?: Record<string, any> }): Promise<Edge> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能修改边'); } const edgeIndex = graph.edges.findIndex(e => e.id === edgeId); if (edgeIndex === -1) { throw new KnowledgeGraphError(`边 ${edgeId} 不存在`); } const edge = graph.edges[edgeIndex]; if (updates.label !== undefined) { edge.label = updates.label; } if (updates.weight !== undefined) { edge.weight = updates.weight; } if (updates.metadata !== undefined) { edge.metadata = updates.metadata; } edge.updatedAt = new Date(); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); return edge; } // 删除边 async deleteEdge(graphId: string, edgeId: string): Promise<void> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能删除边'); } const edgeIndex = graph.edges.findIndex(e => e.id === edgeId); if (edgeIndex === -1) { throw new KnowledgeGraphError(`边 ${edgeId} 不存在`); } graph.edges.splice(edgeIndex, 1); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); } // 添加资源 async addResource(graphId: string, name: string, type: string, content: Buffer | string, description?: string): Promise<Resource> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能添加资源'); } // 检查资源名称是否重复 if (graph.resources.some(r => r.name === name)) { throw new KnowledgeGraphError(`资源名称 "${name}" 已存在`); } const resource = GraphModel.createResource(name, type, '', description); if (this.fileService) { // 保存资源文件 const relativePath = await this.fileService.saveResource(resource.id, type, content); // 更新资源路径 resource.path = `resources/${resource.id}.${type}`; } else { throw new KnowledgeGraphError('无法保存资源文件:文件服务未初始化'); } graph.resources.push(resource); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); return resource; } // 将资源关联到节点 async linkResourceToNode(graphId: string, nodeId: string, resourceId: string): Promise<Node> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能修改节点'); } // 检查节点是否存在 const nodeIndex = graph.nodes.findIndex(n => n.id === nodeId); if (nodeIndex === -1) { throw new KnowledgeGraphError(`节点 ${nodeId} 不存在`); } // 检查资源是否存在 if (!graph.resources.some(r => r.id === resourceId)) { throw new KnowledgeGraphError(`资源 ${resourceId} 不存在`); } const node = graph.nodes[nodeIndex]; // 确保resources数组存在 if (!node.resources) { node.resources = []; } // 检查是否已关联 if (node.resources.includes(resourceId)) { throw new KnowledgeGraphError(`资源 ${resourceId} 已关联到节点 ${nodeId}`); } node.resources.push(resourceId); node.updatedAt = new Date(); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); return node; } // 从节点移除资源关联 async unlinkResourceFromNode(graphId: string, nodeId: string, resourceId: string): Promise<void> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能修改节点'); } // 检查节点是否存在 const nodeIndex = graph.nodes.findIndex(n => n.id === nodeId); if (nodeIndex === -1) { throw new KnowledgeGraphError(`节点 ${nodeId} 不存在`); } const node = graph.nodes[nodeIndex]; // 确保resources数组存在 if (!node.resources || !node.resources.includes(resourceId)) { throw new KnowledgeGraphError(`资源 ${resourceId} 未关联到节点 ${nodeId}`); } node.resources = node.resources.filter(id => id !== resourceId); node.updatedAt = new Date(); graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); } // 列出所有知识图谱 async listGraphs(status?: string): Promise<SimpleGraph[]> { await this.ensureInitialized(); let graphs = Array.from(this.graphs.values()); if (status) { graphs = graphs.filter(g => g.status === status); } // 为每个图谱添加简化的节点列表信息 return graphs.map(graph => ({ ...graph, nodes: graph.nodes?.map(node => ({ id: node.id, name: node.name, type: node.type })) || [] })); } // 获取节点详情 async getNodeDetails(graphId: string, nodeId: string): Promise<{ node: Node; incomingEdges: Edge[]; outgoingEdges: Edge[]; resources: Resource[]; }> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); const node = graph.nodes.find(n => n.id === nodeId); if (!node) { throw new KnowledgeGraphError(`节点 ${nodeId} 不存在`); } // 获取与节点相关的边 const incomingEdges = graph.edges.filter(e => e.target === nodeId); const outgoingEdges = graph.edges.filter(e => e.source === nodeId); // 获取节点关联的资源 const resources: Resource[] = []; if (node.resources && node.resources.length > 0) { for (const resourceId of node.resources) { const resource = graph.resources.find(r => r.id === resourceId); if (resource) { resources.push(resource); } } } return { node, incomingEdges, outgoingEdges, resources }; } // 生成带有可读时间格式的文件名 private generateFileNameWithTime(baseName: string, suffix: string): string { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); // 格式: 基础名称_后缀_年月日_时分.扩展名 return `${baseName}_${suffix}_${year}${month}${day}_${hours}${minutes}`; } // 保存Markdown文档到知识图谱 async saveMdToGraph(id: string, nodeId: string | null, title: string, content: string, description: string = ''): Promise<void> { await this.ensureInitialized(); const graph = await this.getGraph(id); if (!this.fileService) { throw new KnowledgeGraphError('文件服务未配置,无法保存Markdown文档'); } // 生成基础文件名和后缀 const baseName = title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]+/g, '_'); const suffix = nodeId ? `node_${nodeId.slice(0, 8)}` : `graph_${id.slice(0, 8)}`; // 生成带时间的文件名 const fileName = `${this.generateFileNameWithTime(baseName, suffix)}.md`; // 保存文档 await this.fileService.saveMd(id, fileName, content); // 创建资源记录 if (!graph.resources) { graph.resources = []; } const resourceData = { id: `res_${Date.now()}`, name: title, type: 'markdown', title, description: description || title, path: `resources/md/${fileName}`, createdAt: new Date(), updatedAt: new Date() }; graph.resources.push(resourceData); // 如果指定了节点,将资源关联到该节点 if (nodeId) { const node = graph.nodes?.find(n => n.id === nodeId); if (!node) { throw new KnowledgeGraphError(`节点 ${nodeId} 不存在`); } if (!node.resources) { node.resources = []; } node.resources.push(resourceData.id); node.updatedAt = new Date(); } graph.updatedAt = new Date(); await this.persistChanges(); } // 关联Markdown文档到图谱 (为向后兼容保留的别名方法) async linkMdToGraph(id: string, nodeId: string | null, title: string, content: string, description: string = ''): Promise<void> { return this.saveMdToGraph(id, nodeId, title, content, description); } // 保存SVG到节点 async saveSvgToNode(id: string, nodeId: string, title: string, description: string = '', svgContent: string): Promise<void> { await this.ensureInitialized(); const graph = await this.getGraph(id); const node = graph.nodes?.find(n => n.id === nodeId); if (!node) { throw new KnowledgeGraphError(`节点 ${nodeId} 不存在`); } if (!this.fileService) { throw new KnowledgeGraphError('文件服务未配置,无法保存SVG'); } // 确保SVG内容有效 if (!svgContent || typeof svgContent !== 'string' || !svgContent.trim().startsWith('<svg')) { throw new KnowledgeGraphError('无效的SVG内容'); } // 生成基础文件名和后缀 const baseName = title.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]+/g, '_'); const suffix = `node_${nodeId.slice(0, 8)}`; // 生成带时间的文件名 const fileName = `${this.generateFileNameWithTime(baseName, suffix)}.svg`; // 保存SVG await this.fileService.saveSvg(id, fileName, svgContent); // 创建资源记录 if (!graph.resources) { graph.resources = []; } const resourceData = { id: `res_${Date.now()}`, name: title, type: 'svg', title, description: description || title, path: `resources/svg/${fileName}`, createdAt: new Date(), updatedAt: new Date() }; graph.resources.push(resourceData); // 将资源关联到节点 if (!node.resources) { node.resources = []; } node.resources.push(resourceData.id); node.updatedAt = new Date(); graph.updatedAt = new Date(); await this.persistChanges(); } // 关联SVG到节点 (为向后兼容保留的别名方法) async linkSvgToNode(id: string, nodeId: string, title: string, description: string = '', useExistingSvg: boolean = false, svgPath: string = ''): Promise<void> { // 此方法只是向后兼容的桥接方法,实际使用saveSvgToNode if (useExistingSvg) { throw new KnowledgeGraphError('新API不再支持使用现有SVG,请提供SVG内容'); } if (!svgPath) { throw new KnowledgeGraphError('必须提供SVG路径'); } // 读取SVG内容 try { const svgContent = await fs.readFile(svgPath, 'utf-8'); return this.saveSvgToNode(id, nodeId, title, description, svgContent); } catch (error) { throw new KnowledgeGraphError(`无法读取SVG文件: ${error}`); } } // 生成图谱SVG - 保留向后兼容 async generateGraphSvg(id: string): Promise<string> { const data = await this.generateGraphDataForSvg(id); // 生成一个非常简单的SVG占位符,实际项目中应由AI或用户根据数据生成完整SVG return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800"> <text x="50%" y="50%" text-anchor="middle" font-size="24"> ${data.name} (请根据规范和数据手动创建完整SVG) </text> </svg>`; } // 为了支持新的API命名,添加与现有方法功能相同的别名方法 async saveGraphSvg(id: string, svgContent: string): Promise<void> { return this.updateGraphSvg(id, svgContent); } // 更新知识图谱的SVG可视化 async updateGraphSvg(id: string, svgContent: string): Promise<void> { await this.ensureInitialized(); const graph = await this.getGraph(id); if (!this.fileService) { throw new KnowledgeGraphError('文件服务未配置,无法保存SVG'); } // 确保SVG内容有效 if (!svgContent || typeof svgContent !== 'string' || !svgContent.trim().startsWith('<svg')) { throw new KnowledgeGraphError('无效的SVG内容'); } // 生成基础文件名和后缀 const baseName = graph.name.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]+/g, '_'); const suffix = `graph_${id.slice(0, 8)}`; // 生成带时间的文件名 const fileName = `${this.generateFileNameWithTime(baseName, suffix)}.svg`; await this.fileService.saveSvg(id, fileName, svgContent); // 添加资源记录 if (!graph.resources) { graph.resources = []; } // 检查是否已存在此SVG资源 const existingIndex = graph.resources.findIndex(r => r.type === 'svg' && r.path.includes(`graph_${id.slice(0, 8)}`) ); const resourceData = { id: existingIndex >= 0 ? graph.resources[existingIndex].id : `res_${Date.now()}`, name: `${graph.name}图谱可视化`, type: 'svg', title: '图谱可视化', description: `${graph.name} 的SVG可视化表示`, path: `resources/svg/${fileName}`, createdAt: existingIndex >= 0 ? graph.resources[existingIndex].createdAt : new Date(), updatedAt: new Date() }; if (existingIndex >= 0) { graph.resources[existingIndex] = resourceData; } else { graph.resources.push(resourceData); } graph.updatedAt = new Date(); await this.persistChanges(); } // 获取知识图谱数据用于创建SVG async generateGraphDataForSvg(id: string): Promise<any> { await this.ensureInitialized(); const graph = await this.getGraph(id); // 处理节点和边的数据,适合SVG绘制 const nodes = graph.nodes?.map(node => ({ id: node.id, type: node.type, name: node.name, description: node.description, metadata: node.metadata })) || []; const edges = graph.edges?.map(edge => ({ id: edge.id, type: edge.type, source: edge.source, target: edge.target, label: edge.label, weight: edge.weight })) || []; // 提供基本布局建议 const layoutSuggestion = { width: 1200, height: 800, nodeTypes: { component: { color: '#4285F4', shape: 'rect', radius: 5 }, module: { color: '#EA4335', shape: 'rect', radius: 10 }, service: { color: '#FBBC05', shape: 'hexagon' }, data: { color: '#34A853', shape: 'cylinder' }, api: { color: '#8E44AD', shape: 'rect', isRounded: true }, concept: { color: '#E67E22', shape: 'circle' }, resource: { color: '#16A085', shape: 'document' }, event: { color: '#3498DB', shape: 'circle' }, decision: { color: '#F1C40F', shape: 'diamond' }, person: { color: '#1ABC9C', shape: 'person' }, default: { color: '#95A5A6', shape: 'rect' } }, edgeTypes: { depends_on: { style: 'dashed', arrowhead: true, color: '#7F8C8D' }, imports: { style: 'solid', arrowhead: true, color: '#2C3E50' }, extends: { style: 'solid', arrowhead: true, color: '#E74C3C' }, implements: { style: 'dotted', arrowhead: true, color: '#9B59B6' }, contains: { style: 'solid', arrowhead: false, color: '#27AE60' }, precedes: { style: 'solid', arrowhead: true, color: '#3498DB' }, default: { style: 'solid', arrowhead: true, color: '#7F8C8D' } } }; return { id: graph.id, name: graph.name, description: graph.description, type: graph.type, nodes, edges, layout: layoutSuggestion }; } // 验证节点类型是否与图谱类型匹配 private validateNodeTypeForGraphType(graphType: typeof GraphType[keyof typeof GraphType], nodeType: typeof NodeType[keyof typeof NodeType]): void { // 根据图谱类型,验证节点类型是否合法 switch (graphType) { case GraphType.TOPOLOGY: // 拓扑结构图允许的节点类型 const topologyAllowedTypes = [ NodeType.COMPONENT, NodeType.MODULE, NodeType.SERVICE, NodeType.DATA, NodeType.API, NodeType.RESOURCE, NodeType.CONCEPT ]; if (!topologyAllowedTypes.includes(nodeType as any)) { throw new KnowledgeGraphError(`节点类型 ${nodeType} 不适用于拓扑结构图`); } break; case GraphType.TIMELINE: // 时间线图谱允许的节点类型 const timelineAllowedTypes = [ NodeType.EVENT, NodeType.DECISION, NodeType.ITERATION, NodeType.PERSON ]; if (!timelineAllowedTypes.includes(nodeType as any)) { throw new KnowledgeGraphError(`节点类型 ${nodeType} 不适用于时间线图谱`); } break; case GraphType.CHANGELOG: // 变更日志图谱允许的节点类型 const changelogAllowedTypes = [ NodeType.CHANGE, NodeType.FEATURE, NodeType.COMPONENT, NodeType.ITERATION, NodeType.PERSON ]; if (!changelogAllowedTypes.includes(nodeType as any)) { throw new KnowledgeGraphError(`节点类型 ${nodeType} 不适用于变更日志图谱`); } break; case GraphType.REQUIREMENT: // 需求文档图谱允许的节点类型 const requirementAllowedTypes = [ NodeType.REQUIREMENT, NodeType.FEATURE, NodeType.COMPONENT, NodeType.ITERATION, NodeType.PERSON, NodeType.DECISION ]; if (!requirementAllowedTypes.includes(nodeType as any)) { throw new KnowledgeGraphError(`节点类型 ${nodeType} 不适用于需求文档图谱`); } break; case GraphType.KNOWLEDGE_BASE: // 知识库图谱允许所有节点类型 break; case GraphType.ONTOLOGY: // 本体论图谱允许的节点类型 const ontologyAllowedTypes = [ NodeType.CONCEPT, NodeType.RESOURCE, NodeType.DATA ]; if (!ontologyAllowedTypes.includes(nodeType as any)) { throw new KnowledgeGraphError(`节点类型 ${nodeType} 不适用于本体论图谱`); } break; default: // 默认不进行类型限制 break; } } // 验证边类型是否与图谱类型匹配 private validateEdgeTypeForGraphType(graphType: typeof GraphType[keyof typeof GraphType], edgeType: typeof EdgeType[keyof typeof EdgeType]): void { // 根据图谱类型,验证边类型是否合法 switch (graphType) { case GraphType.TOPOLOGY: // 拓扑结构图允许的边类型 const topologyAllowedTypes = [ EdgeType.DEPENDS_ON, EdgeType.IMPORTS, EdgeType.EXTENDS, EdgeType.IMPLEMENTS, EdgeType.CALLS, EdgeType.REFERENCES, EdgeType.CONTAINS, EdgeType.ASSOCIATED_WITH ]; if (!topologyAllowedTypes.includes(edgeType as any)) { throw new KnowledgeGraphError(`边类型 ${edgeType} 不适用于拓扑结构图`); } break; case GraphType.TIMELINE: // 时间线图谱允许的边类型 const timelineAllowedTypes = [ EdgeType.PRECEDES, EdgeType.LEADS_TO, EdgeType.CREATED_BY, EdgeType.MODIFIED_BY ]; if (!timelineAllowedTypes.includes(edgeType as any)) { throw new KnowledgeGraphError(`边类型 ${edgeType} 不适用于时间线图谱`); } break; case GraphType.CHANGELOG: // 变更日志图谱允许的边类型 const changelogAllowedTypes = [ EdgeType.PRECEDES, EdgeType.TRANSFORMS_TO, EdgeType.CREATED_BY, EdgeType.MODIFIED_BY, EdgeType.PART_OF ]; if (!changelogAllowedTypes.includes(edgeType as any)) { throw new KnowledgeGraphError(`边类型 ${edgeType} 不适用于变更日志图谱`); } break; case GraphType.REQUIREMENT: // 需求文档图谱允许的边类型 const requirementAllowedTypes = [ EdgeType.IMPLEMENTS_REQ, EdgeType.DEPENDS_ON, EdgeType.PART_OF, EdgeType.CREATED_BY, EdgeType.MODIFIED_BY ]; if (!requirementAllowedTypes.includes(edgeType as any)) { throw new KnowledgeGraphError(`边类型 ${edgeType} 不适用于需求文档图谱`); } break; case GraphType.KNOWLEDGE_BASE: // 知识库图谱允许所有边类型 break; case GraphType.ONTOLOGY: // 本体论图谱允许的边类型 const ontologyAllowedTypes = [ EdgeType.EXTENDS, EdgeType.CONTAINS, EdgeType.PART_OF, EdgeType.ASSOCIATED_WITH ]; if (!ontologyAllowedTypes.includes(edgeType as any)) { throw new KnowledgeGraphError(`边类型 ${edgeType} 不适用于本体论图谱`); } break; default: // 默认不进行类型限制 break; } } // 获取特定类型的图谱列表 async listGraphsByType(type?: typeof GraphType[keyof typeof GraphType], status?: string): Promise<SimpleGraph[]> { await this.ensureInitialized(); let graphs = Array.from(this.graphs.values()); if (type) { graphs = graphs.filter(g => g.type === type); } if (status) { graphs = graphs.filter(g => g.status === status); } // 为每个图谱添加简化的节点列表信息 return graphs.map(graph => ({ ...graph, nodes: graph.nodes?.map(node => ({ id: node.id, name: node.name, type: node.type })) || [] })); } // 从组件分析中导入数据 async importFromComponentAnalysis(graphId: string, analysisData: any): Promise<Graph> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); if (graph.status === GraphStatus.ARCHIVED) { throw new KnowledgeGraphError('已归档的知识图谱不能导入数据'); } // 实现导入逻辑... // 这里根据分析数据创建节点和边 graph.updatedAt = new Date(); this.graphs.set(graphId, graph); await this.persistChanges(); return graph; } // 导出知识图谱为JSON格式 async exportGraphToJson(graphId: string): Promise<string> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); return JSON.stringify(graph, null, 2); } // 持久化更改 private async persistChanges(): Promise<void> { if (this.fileService && this.config.autoPersist !== false) { await this.fileService.updateGraphFile(this.graphs); } } // 更新资源信息 async updateResource(graphId: string, resourceId: string, updateData: { name?: string; description?: string; title?: string; }): Promise<Resource> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); // 查找资源 const resourceIndex = graph.resources.findIndex(r => r.id === resourceId); if (resourceIndex === -1) { throw new KnowledgeGraphError(`资源 ${resourceId} 不存在于图谱 ${graphId} 中`); } const resource = graph.resources[resourceIndex]; // 更新资源信息 if (updateData.name) { resource.name = updateData.name; } if (updateData.description) { resource.description = updateData.description; } if (updateData.title) { resource.title = updateData.title; } resource.updatedAt = new Date(); graph.updatedAt = new Date(); // 保存更改 graph.resources[resourceIndex] = resource; await this.persistChanges(); return resource; } // 删除资源 async deleteResource(graphId: string, resourceId: string): Promise<void> { await this.ensureInitialized(); const graph = await this.getGraph(graphId); // 查找资源 const resourceIndex = graph.resources.findIndex(r => r.id === resourceId); if (resourceIndex === -1) { throw new KnowledgeGraphError(`资源 ${resourceId} 不存在于图谱 ${graphId} 中`); } const resource = graph.resources[resourceIndex]; // 删除物理文件 if (this.fileService) { try { // 从路径中提取文件名 const fileName = resource.path.split('/').pop(); if (fileName) { if (resource.type === 'svg') { await this.fileService.deleteSvg(fileName); } else if (resource.type === 'markdown' || resource.type === 'md') { await this.fileService.deleteMd(fileName); } else { console.warn(`未知的资源类型: ${resource.type}, 无法删除文件`); } } } catch (error) { console.error(`删除资源文件失败: ${error}`); // 继续删除资源记录 } } // 从所有节点的资源引用中移除该资源ID for (const node of graph.nodes) { if (node.resources && node.resources.includes(resourceId)) { node.resources = node.resources.filter(id => id !== resourceId); node.updatedAt = new Date(); } } // 从图谱的资源列表中移除 graph.resources.splice(resourceIndex, 1); graph.updatedAt = new Date(); // 保存更改 await this.persistChanges(); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aiuluna/knowledge-graph-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server