Skip to main content
Glama

Open Search MCP

by flyanima
MIT License
2
  • Apple
  • Linux
interactive-thought-map.ts12.4 kB
/** * InteractiveThoughtMap - 交互式思维导图核心类 * * 核心功能: * - 思维节点管理(增删改查) * - 层级关系维护 * - 状态同步和事件通知 * - 思维导图序列化/反序列化 * - 实时更新和协作支持 */ import { ThoughtNode, ThoughtNodeType, ThoughtNodeStatus, ThoughtNodePriority } from './thought-node.js'; import { EventEmitter } from 'events'; import { Logger } from '../utils/logger.js'; /** * 思维导图事件类型 */ export enum ThoughtMapEvent { NODE_ADDED = 'node_added', NODE_REMOVED = 'node_removed', NODE_UPDATED = 'node_updated', NODE_STATUS_CHANGED = 'node_status_changed', RELATIONSHIP_ADDED = 'relationship_added', RELATIONSHIP_REMOVED = 'relationship_removed', MAP_CLEARED = 'map_cleared', MAP_LOADED = 'map_loaded' } /** * 思维导图统计信息 */ export interface ThoughtMapStats { totalNodes: number; nodesByType: Record<ThoughtNodeType, number>; nodesByStatus: Record<ThoughtNodeStatus, number>; maxDepth: number; avgExecutionTime: number; completionRate: number; } /** * 思维导图搜索选项 */ export interface ThoughtMapSearchOptions { type?: ThoughtNodeType; status?: ThoughtNodeStatus; priority?: ThoughtNodePriority; tags?: string[]; content?: string; depth?: number; dateRange?: { start: Date; end: Date; }; } /** * 交互式思维导图类 */ export class InteractiveThoughtMap extends EventEmitter { private nodes: Map<string, ThoughtNode> = new Map(); private rootNodeId?: string; private logger: Logger; private version: number = 1; private lastModified: Date; constructor() { super(); this.logger = new Logger('InteractiveThoughtMap'); this.lastModified = new Date(); } /** * 添加节点 */ addNode( title: string, content: string, type: ThoughtNodeType = ThoughtNodeType.ANALYSIS, priority: ThoughtNodePriority = ThoughtNodePriority.MEDIUM, parentId?: string ): ThoughtNode { const node = new ThoughtNode(title, content, type, priority, parentId); // 设置根节点 if (type === ThoughtNodeType.ROOT || !parentId) { if (!this.rootNodeId) { this.rootNodeId = node.id; node.setDepth(0); } } // 建立父子关系 if (parentId) { const parentNode = this.nodes.get(parentId); if (parentNode) { parentNode.addChild(node.id); node.setDepth(parentNode.depth + 1); } else { throw new Error(`Parent node ${parentId} not found`); } } this.nodes.set(node.id, node); this.updateVersion(); this.logger.debug(`Added node: ${node.getBrief()}`); this.emit(ThoughtMapEvent.NODE_ADDED, node); return node; } /** * 移除节点(递归移除子节点) */ removeNode(nodeId: string): boolean { const node = this.nodes.get(nodeId); if (!node) { return false; } // 递归移除所有子节点 const childIds = [...node.childIds]; for (const childId of childIds) { this.removeNode(childId); } // 从父节点中移除引用 if (node.parentId) { const parentNode = this.nodes.get(node.parentId); if (parentNode) { parentNode.removeChild(nodeId); } } // 如果是根节点,清空根节点引用 if (this.rootNodeId === nodeId) { this.rootNodeId = undefined; } this.nodes.delete(nodeId); this.updateVersion(); this.logger.debug(`Removed node: ${node.getBrief()}`); this.emit(ThoughtMapEvent.NODE_REMOVED, node); return true; } /** * 获取节点 */ getNode(nodeId: string): ThoughtNode | undefined { return this.nodes.get(nodeId); } /** * 获取根节点 */ getRootNode(): ThoughtNode | undefined { return this.rootNodeId ? this.nodes.get(this.rootNodeId) : undefined; } /** * 获取所有节点 */ getAllNodes(): ThoughtNode[] { return Array.from(this.nodes.values()); } /** * 获取子节点 */ getChildren(nodeId: string): ThoughtNode[] { const node = this.nodes.get(nodeId); if (!node) { return []; } return node.childIds .map(childId => this.nodes.get(childId)) .filter(child => child !== undefined) as ThoughtNode[]; } /** * 获取父节点 */ getParent(nodeId: string): ThoughtNode | undefined { const node = this.nodes.get(nodeId); return node?.parentId ? this.nodes.get(node.parentId) : undefined; } /** * 获取节点路径(从根到指定节点) */ getNodePath(nodeId: string): ThoughtNode[] { const path: ThoughtNode[] = []; let currentNode = this.nodes.get(nodeId); while (currentNode) { path.unshift(currentNode); currentNode = currentNode.parentId ? this.nodes.get(currentNode.parentId) : undefined; } return path; } /** * 获取叶子节点 */ getLeafNodes(): ThoughtNode[] { return Array.from(this.nodes.values()).filter(node => node.childIds.length === 0); } /** * 更新节点状态 */ updateNodeStatus(nodeId: string, status: ThoughtNodeStatus): boolean { const node = this.nodes.get(nodeId); if (!node) { return false; } const oldStatus = node.status; node.updateStatus(status); this.updateVersion(); this.logger.debug(`Updated node status: ${nodeId} ${oldStatus} -> ${status}`); this.emit(ThoughtMapEvent.NODE_STATUS_CHANGED, node, oldStatus, status); this.emit(ThoughtMapEvent.NODE_UPDATED, node); return true; } /** * 更新节点内容 */ updateNodeContent(nodeId: string, content: string, summary?: string): boolean { const node = this.nodes.get(nodeId); if (!node) { return false; } node.updateContent(content, summary); this.updateVersion(); this.logger.debug(`Updated node content: ${nodeId}`); this.emit(ThoughtMapEvent.NODE_UPDATED, node); return true; } /** * 移动节点到新父节点 */ moveNode(nodeId: string, newParentId?: string): boolean { const node = this.nodes.get(nodeId); if (!node) { return false; } // 移除旧的父子关系 if (node.parentId) { const oldParent = this.nodes.get(node.parentId); if (oldParent) { oldParent.removeChild(nodeId); } } // 建立新的父子关系 if (newParentId) { const newParent = this.nodes.get(newParentId); if (!newParent) { return false; } // 检查是否会形成循环 if (this.wouldCreateCycle(nodeId, newParentId)) { return false; } newParent.addChild(nodeId); node.parentId = newParentId; node.setDepth(newParent.depth + 1); } else { node.parentId = undefined; node.setDepth(0); } // 更新所有子节点的深度 this.updateChildrenDepth(nodeId); this.updateVersion(); this.logger.debug(`Moved node: ${nodeId} to parent: ${newParentId || 'root'}`); this.emit(ThoughtMapEvent.RELATIONSHIP_ADDED, node, newParentId); return true; } /** * 搜索节点 */ searchNodes(options: ThoughtMapSearchOptions): ThoughtNode[] { const results: ThoughtNode[] = []; for (const node of this.nodes.values()) { let matches = true; // 类型过滤 if (options.type && node.type !== options.type) { matches = false; } // 状态过滤 if (options.status && node.status !== options.status) { matches = false; } // 优先级过滤 if (options.priority && node.priority !== options.priority) { matches = false; } // 深度过滤 if (options.depth !== undefined && node.depth !== options.depth) { matches = false; } // 标签过滤 if (options.tags && options.tags.length > 0) { const nodeTags = node.metadata.tags || []; const hasAllTags = options.tags.every(tag => nodeTags.includes(tag)); if (!hasAllTags) { matches = false; } } // 内容过滤 if (options.content) { const searchText = options.content.toLowerCase(); const nodeText = `${node.title} ${node.content}`.toLowerCase(); if (!nodeText.includes(searchText)) { matches = false; } } // 日期范围过滤 if (options.dateRange) { const nodeDate = node.metadata.createdAt; if (nodeDate < options.dateRange.start || nodeDate > options.dateRange.end) { matches = false; } } if (matches) { results.push(node); } } return results; } /** * 获取统计信息 */ getStats(): ThoughtMapStats { const nodes = Array.from(this.nodes.values()); const nodesByType: Record<ThoughtNodeType, number> = {} as any; const nodesByStatus: Record<ThoughtNodeStatus, number> = {} as any; // 初始化计数器 Object.values(ThoughtNodeType).forEach(type => { nodesByType[type] = 0; }); Object.values(ThoughtNodeStatus).forEach(status => { nodesByStatus[status] = 0; }); let maxDepth = 0; let totalExecutionTime = 0; let executionCount = 0; let completedCount = 0; for (const node of nodes) { nodesByType[node.type]++; nodesByStatus[node.status]++; maxDepth = Math.max(maxDepth, node.depth); if (node.metadata.executionTime) { totalExecutionTime += node.metadata.executionTime; executionCount++; } if (node.isCompleted()) { completedCount++; } } return { totalNodes: nodes.length, nodesByType, nodesByStatus, maxDepth, avgExecutionTime: executionCount > 0 ? totalExecutionTime / executionCount : 0, completionRate: nodes.length > 0 ? completedCount / nodes.length : 0 }; } /** * 清空思维导图 */ clear(): void { this.nodes.clear(); this.rootNodeId = undefined; this.updateVersion(); this.logger.debug('Cleared thought map'); this.emit(ThoughtMapEvent.MAP_CLEARED); } /** * 序列化为JSON */ toJSON(): any { return { version: this.version, lastModified: this.lastModified.toISOString(), rootNodeId: this.rootNodeId, nodes: Array.from(this.nodes.values()).map(node => node.toJSON()) }; } /** * 从JSON反序列化 */ static fromJSON(data: any): InteractiveThoughtMap { const map = new InteractiveThoughtMap(); map.version = data.version || 1; map.lastModified = new Date(data.lastModified); map.rootNodeId = data.rootNodeId; // 重建节点 for (const nodeData of data.nodes) { const node = ThoughtNode.fromJSON(nodeData); map.nodes.set(node.id, node); } map.emit(ThoughtMapEvent.MAP_LOADED, map); return map; } /** * 检查是否会形成循环 */ private wouldCreateCycle(nodeId: string, potentialParentId: string): boolean { let currentId = potentialParentId; while (currentId) { if (currentId === nodeId) { return true; } const currentNode = this.nodes.get(currentId); currentId = currentNode?.parentId || ''; } return false; } /** * 更新子节点深度 */ private updateChildrenDepth(nodeId: string): void { const node = this.nodes.get(nodeId); if (!node) { return; } for (const childId of node.childIds) { const child = this.nodes.get(childId); if (child) { child.setDepth(node.depth + 1); this.updateChildrenDepth(childId); } } } /** * 更新版本号 */ private updateVersion(): void { this.version++; this.lastModified = new Date(); } /** * 获取版本信息 */ getVersion(): { version: number; lastModified: Date } { return { version: this.version, lastModified: this.lastModified }; } /** * 获取节点数量 */ getNodeCount(): number { return this.nodes.size; } /** * 检查节点是否存在 */ hasNode(nodeId: string): boolean { return this.nodes.has(nodeId); } }

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/flyanima/open-search-mcp'

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