Skip to main content
Glama

documcp

by tosin2013
visualization.ts38.9 kB
/** * Memory Visualization Interface for DocuMCP * Generate visual representations of memory data, patterns, and insights */ import { EventEmitter } from 'events'; import { MemoryEntry, JSONLStorage } from './storage.js'; import { MemoryManager } from './manager.js'; import { IncrementalLearningSystem } from './learning.js'; import { KnowledgeGraph } from './knowledge-graph.js'; import { TemporalMemoryAnalysis } from './temporal-analysis.js'; export interface VisualizationConfig { width: number; height: number; theme: 'light' | 'dark' | 'auto'; colorScheme: string[]; interactive: boolean; exportFormat: 'svg' | 'png' | 'json' | 'html'; responsive: boolean; } export interface ChartData { type: 'line' | 'bar' | 'scatter' | 'heatmap' | 'network' | 'sankey' | 'treemap' | 'timeline'; title: string; description: string; data: any; config: Partial<VisualizationConfig>; metadata: { generated: Date; dataPoints: number; timeRange?: { start: Date; end: Date }; filters?: Record<string, any>; }; } export interface DashboardData { title: string; description: string; charts: ChartData[]; summary: { totalEntries: number; timeRange: { start: Date; end: Date }; keyInsights: string[]; healthScore: number; }; generated: Date; } export interface NetworkVisualization { nodes: Array<{ id: string; label: string; group: string; size: number; color: string; metadata: any; }>; edges: Array<{ source: string; target: string; weight: number; type: string; color: string; metadata: any; }>; layout: 'force' | 'circular' | 'hierarchical' | 'grid'; clustering: boolean; } export interface HeatmapVisualization { data: number[][]; labels: { x: string[]; y: string[]; }; colorScale: { min: number; max: number; colors: string[]; }; title: string; description: string; } export interface TimelineVisualization { events: Array<{ id: string; timestamp: Date; title: string; description: string; type: string; importance: number; color: string; metadata: any; }>; timeRange: { start: Date; end: Date }; granularity: 'hour' | 'day' | 'week' | 'month'; groupBy?: string; } export class MemoryVisualizationSystem extends EventEmitter { private storage: JSONLStorage; private manager: MemoryManager; private learningSystem: IncrementalLearningSystem; private knowledgeGraph: KnowledgeGraph; private temporalAnalysis: TemporalMemoryAnalysis; private defaultConfig: VisualizationConfig; private visualizationCache: Map<string, ChartData>; constructor( storage: JSONLStorage, manager: MemoryManager, learningSystem: IncrementalLearningSystem, knowledgeGraph: KnowledgeGraph, temporalAnalysis: TemporalMemoryAnalysis, ) { super(); this.storage = storage; this.manager = manager; this.learningSystem = learningSystem; this.knowledgeGraph = knowledgeGraph; this.temporalAnalysis = temporalAnalysis; this.visualizationCache = new Map(); this.defaultConfig = { width: 800, height: 600, theme: 'light', colorScheme: [ '#3B82F6', // Blue '#10B981', // Green '#F59E0B', // Yellow '#EF4444', // Red '#8B5CF6', // Purple '#06B6D4', // Cyan '#F97316', // Orange '#84CC16', // Lime ], interactive: true, exportFormat: 'svg', responsive: true, }; } /** * Generate comprehensive dashboard */ async generateDashboard(options?: { timeRange?: { start: Date; end: Date }; includeCharts?: string[]; config?: Partial<VisualizationConfig>; }): Promise<DashboardData> { const timeRange = options?.timeRange || this.getDefaultTimeRange(); const config = { ...this.defaultConfig, ...options?.config }; this.emit('dashboard_generation_started', { timeRange }); try { const charts: ChartData[] = []; // Activity Timeline if (!options?.includeCharts || options.includeCharts.includes('activity')) { charts.push(await this.generateActivityTimeline(timeRange, config)); } // Memory Type Distribution if (!options?.includeCharts || options.includeCharts.includes('distribution')) { charts.push(await this.generateMemoryTypeDistribution(timeRange, config)); } // Success Rate Trends if (!options?.includeCharts || options.includeCharts.includes('success')) { charts.push(await this.generateSuccessRateTrends(timeRange, config)); } // Knowledge Graph Network if (!options?.includeCharts || options.includeCharts.includes('network')) { charts.push(await this.generateKnowledgeGraphVisualization(config)); } // Learning Patterns Heatmap if (!options?.includeCharts || options.includeCharts.includes('learning')) { charts.push(await this.generateLearningPatternsHeatmap(config)); } // Temporal Patterns if (!options?.includeCharts || options.includeCharts.includes('temporal')) { charts.push(await this.generateTemporalPatternsChart(timeRange, config)); } // Project Correlation Matrix if (!options?.includeCharts || options.includeCharts.includes('correlation')) { charts.push(await this.generateProjectCorrelationMatrix(timeRange, config)); } // Get summary data const entries = await this.getEntriesInTimeRange(timeRange); const keyInsights = await this.generateKeyInsights(entries, timeRange); const healthScore = await this.calculateSystemHealthScore(entries); const dashboard: DashboardData = { title: 'DocuMCP Memory System Dashboard', description: `Comprehensive overview of memory system activity from ${timeRange.start.toLocaleDateString()} to ${timeRange.end.toLocaleDateString()}`, charts, summary: { totalEntries: entries.length, timeRange, keyInsights, healthScore, }, generated: new Date(), }; this.emit('dashboard_generated', { charts: charts.length, entries: entries.length, timeRange, }); return dashboard; } catch (error) { this.emit('dashboard_error', { error: error instanceof Error ? error.message : String(error), }); throw error; } } /** * Generate activity timeline chart */ async generateActivityTimeline( timeRange: { start: Date; end: Date }, config: Partial<VisualizationConfig>, ): Promise<ChartData> { const entries = await this.getEntriesInTimeRange(timeRange); // Group entries by day const dailyData = new Map<string, number>(); const successData = new Map<string, number>(); for (const entry of entries) { const day = entry.timestamp.slice(0, 10); // YYYY-MM-DD dailyData.set(day, (dailyData.get(day) || 0) + 1); if (entry.data.outcome === 'success' || entry.data.success === true) { successData.set(day, (successData.get(day) || 0) + 1); } } // Create time series data const datasets = [ { label: 'Total Activity', data: Array.from(dailyData.entries()).map(([date, count]) => ({ x: date, y: count, })), borderColor: config.colorScheme?.[0] || '#3B82F6', backgroundColor: config.colorScheme?.[0] || '#3B82F6', fill: false, }, { label: 'Successful Activities', data: Array.from(successData.entries()).map(([date, count]) => ({ x: date, y: count, })), borderColor: config.colorScheme?.[1] || '#10B981', backgroundColor: config.colorScheme?.[1] || '#10B981', fill: false, }, ]; return { type: 'line', title: 'Memory Activity Timeline', description: 'Daily memory system activity showing total entries and successful outcomes', data: { datasets, options: { responsive: config.responsive, plugins: { title: { display: true, text: 'Memory Activity Over Time', }, legend: { display: true, position: 'top', }, }, scales: { x: { type: 'time', time: { unit: 'day', }, title: { display: true, text: 'Date', }, }, y: { title: { display: true, text: 'Number of Entries', }, }, }, }, }, config, metadata: { generated: new Date(), dataPoints: entries.length, timeRange, filters: { type: 'activity_timeline' }, }, }; } /** * Generate memory type distribution chart */ async generateMemoryTypeDistribution( timeRange: { start: Date; end: Date }, config: Partial<VisualizationConfig>, ): Promise<ChartData> { const entries = await this.getEntriesInTimeRange(timeRange); // Count entries by type const typeCounts = new Map<string, number>(); for (const entry of entries) { typeCounts.set(entry.type, (typeCounts.get(entry.type) || 0) + 1); } // Sort by count const sortedTypes = Array.from(typeCounts.entries()).sort(([, a], [, b]) => b - a); const data = { labels: sortedTypes.map(([type]) => type), datasets: [ { data: sortedTypes.map(([, count]) => count), backgroundColor: config.colorScheme || this.defaultConfig.colorScheme, borderColor: config.colorScheme?.map((c) => this.darkenColor(c)) || this.defaultConfig.colorScheme.map((c) => this.darkenColor(c)), borderWidth: 2, }, ], }; return { type: 'bar', title: 'Memory Type Distribution', description: 'Distribution of memory entries by type', data: { ...data, options: { responsive: config.responsive, plugins: { title: { display: true, text: 'Memory Entry Types', }, legend: { display: false, }, }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Number of Entries', }, }, x: { title: { display: true, text: 'Memory Type', }, }, }, }, }, config, metadata: { generated: new Date(), dataPoints: entries.length, timeRange, filters: { type: 'type_distribution' }, }, }; } /** * Generate success rate trends chart */ async generateSuccessRateTrends( timeRange: { start: Date; end: Date }, config: Partial<VisualizationConfig>, ): Promise<ChartData> { const entries = await this.getEntriesInTimeRange(timeRange); // Group by week and calculate success rates const weeklyData = new Map<string, { total: number; successful: number }>(); for (const entry of entries) { const week = this.getWeekKey(new Date(entry.timestamp)); const current = weeklyData.get(week) || { total: 0, successful: 0 }; current.total++; if (entry.data.outcome === 'success' || entry.data.success === true) { current.successful++; } weeklyData.set(week, current); } // Calculate success rates const data = Array.from(weeklyData.entries()) .map(([week, stats]) => ({ x: week, y: stats.total > 0 ? (stats.successful / stats.total) * 100 : 0, total: stats.total, successful: stats.successful, })) .sort((a, b) => a.x.localeCompare(b.x)); return { type: 'line', title: 'Success Rate Trends', description: 'Weekly success rate trends for memory system operations', data: { datasets: [ { label: 'Success Rate (%)', data: data, borderColor: config.colorScheme?.[1] || '#10B981', backgroundColor: config.colorScheme?.[1] || '#10B981', fill: false, tension: 0.1, }, ], options: { responsive: config.responsive, plugins: { title: { display: true, text: 'Success Rate Over Time', }, tooltip: { callbacks: { afterBody: (context: any) => { const point = data[context[0].dataIndex]; return `Total: ${point.total}, Successful: ${point.successful}`; }, }, }, }, scales: { x: { title: { display: true, text: 'Week', }, }, y: { beginAtZero: true, max: 100, title: { display: true, text: 'Success Rate (%)', }, }, }, }, }, config, metadata: { generated: new Date(), dataPoints: data.length, timeRange, filters: { type: 'success_trends' }, }, }; } /** * Generate knowledge graph network visualization */ async generateKnowledgeGraphVisualization( config: Partial<VisualizationConfig>, ): Promise<ChartData> { const allNodes = await this.knowledgeGraph.getAllNodes(); const allEdges = await this.knowledgeGraph.getAllEdges(); // Prepare network data const networkData: NetworkVisualization = { nodes: allNodes.map((node) => ({ id: node.id, label: node.label || node.id.slice(0, 10), group: node.type, size: Math.max(10, Math.min(30, (node.weight || 1) * 10)), color: this.getColorForNodeType(node.type, config.colorScheme), metadata: node.properties, })), edges: allEdges.map((edge) => ({ source: edge.source, target: edge.target, weight: edge.weight, type: edge.type, color: this.getColorForEdgeType(edge.type, config.colorScheme), metadata: edge.properties, })), layout: 'force', clustering: true, }; return { type: 'network', title: 'Knowledge Graph Network', description: 'Interactive network visualization of memory relationships and connections', data: networkData, config, metadata: { generated: new Date(), dataPoints: allNodes.length + allEdges.length, filters: { type: 'knowledge_graph' }, }, }; } /** * Generate learning patterns heatmap */ async generateLearningPatternsHeatmap(config: Partial<VisualizationConfig>): Promise<ChartData> { const patterns = await this.learningSystem.getPatterns(); // Create correlation matrix between different pattern dimensions const frameworks = [ ...new Set( patterns .flatMap((p) => p.metadata.technologies || []) .filter( (t) => t.includes('framework') || t.includes('js') || t.includes('react') || t.includes('vue'), ), ), ]; const languages = [ ...new Set( patterns .flatMap((p) => p.metadata.technologies || []) .filter((t) => !t.includes('framework')), ), ]; const heatmapData: number[][] = []; const labels = { x: frameworks, y: languages }; for (const language of languages) { const row: number[] = []; for (const framework of frameworks) { // Calculate correlation/co-occurrence const langPatterns = patterns.filter((p) => p.metadata.technologies?.includes(language)); const frameworkPatterns = patterns.filter( (p) => p.metadata.technologies?.includes(framework), ); const bothPatterns = patterns.filter( (p) => p.metadata.technologies?.includes(language) && p.metadata.technologies?.includes(framework), ); const correlation = langPatterns.length > 0 && frameworkPatterns.length > 0 ? bothPatterns.length / Math.min(langPatterns.length, frameworkPatterns.length) : 0; row.push(correlation); } heatmapData.push(row); } const heatmap: HeatmapVisualization = { data: heatmapData, labels, colorScale: { min: 0, max: 1, colors: ['#F3F4F6', '#93C5FD', '#3B82F6', '#1D4ED8', '#1E3A8A'], }, title: 'Language-Framework Learning Patterns', description: 'Correlation matrix showing relationships between programming languages and frameworks in learning patterns', }; return { type: 'heatmap', title: 'Learning Patterns Heatmap', description: 'Visualization of learning pattern correlations across languages and frameworks', data: heatmap, config, metadata: { generated: new Date(), dataPoints: patterns.length, filters: { type: 'learning_patterns' }, }, }; } /** * Generate temporal patterns chart */ async generateTemporalPatternsChart( timeRange: { start: Date; end: Date }, config: Partial<VisualizationConfig>, ): Promise<ChartData> { const patterns = await this.temporalAnalysis.analyzeTemporalPatterns({ granularity: 'day', aggregation: 'count', timeRange: { start: timeRange.start, end: timeRange.end, duration: timeRange.end.getTime() - timeRange.start.getTime(), label: 'Analysis Period', }, }); // Prepare data for different pattern types const patternData = patterns.map((pattern) => ({ type: pattern.type, confidence: pattern.confidence, description: pattern.description, dataPoints: pattern.dataPoints?.length || 0, })); const data = { labels: patternData.map((p) => p.type), datasets: [ { label: 'Pattern Confidence', data: patternData.map((p) => p.confidence * 100), backgroundColor: config.colorScheme || this.defaultConfig.colorScheme, borderColor: config.colorScheme?.map((c) => this.darkenColor(c)) || this.defaultConfig.colorScheme.map((c) => this.darkenColor(c)), borderWidth: 2, }, ], }; return { type: 'bar', title: 'Temporal Patterns Analysis', description: 'Confidence levels of detected temporal patterns in memory activity', data: { ...data, options: { responsive: config.responsive, plugins: { title: { display: true, text: 'Detected Temporal Patterns', }, tooltip: { callbacks: { afterBody: (context: any) => { const pattern = patternData[context[0].dataIndex]; return pattern.description; }, }, }, }, scales: { y: { beginAtZero: true, max: 100, title: { display: true, text: 'Confidence (%)', }, }, x: { title: { display: true, text: 'Pattern Type', }, }, }, }, }, config, metadata: { generated: new Date(), dataPoints: patterns.length, timeRange, filters: { type: 'temporal_patterns' }, }, }; } /** * Generate project correlation matrix */ async generateProjectCorrelationMatrix( timeRange: { start: Date; end: Date }, config: Partial<VisualizationConfig>, ): Promise<ChartData> { const entries = await this.getEntriesInTimeRange(timeRange); // Extract unique projects const projects = [ ...new Set( entries .map((e) => e.data.projectPath || e.data.projectId || 'Unknown') .filter((p) => p !== 'Unknown'), ), ].slice(0, 10); // Limit to top 10 // Calculate correlation matrix const correlationMatrix: number[][] = []; for (const project1 of projects) { const row: number[] = []; for (const project2 of projects) { if (project1 === project2) { row.push(1.0); } else { const correlation = this.calculateProjectCorrelation(entries, project1, project2); row.push(correlation); } } correlationMatrix.push(row); } const heatmap: HeatmapVisualization = { data: correlationMatrix, labels: { x: projects, y: projects }, colorScale: { min: -1, max: 1, colors: ['#EF4444', '#F59E0B', '#F3F4F6', '#10B981', '#059669'], }, title: 'Project Correlation Matrix', description: 'Correlation matrix showing relationships between different projects based on memory patterns', }; return { type: 'heatmap', title: 'Project Correlations', description: 'Visualization of correlations between different projects in the memory system', data: heatmap, config, metadata: { generated: new Date(), dataPoints: projects.length * projects.length, timeRange, filters: { type: 'project_correlation' }, }, }; } /** * Generate custom visualization */ async generateCustomVisualization( type: ChartData['type'], query: { filters?: Record<string, any>; timeRange?: { start: Date; end: Date }; aggregation?: string; groupBy?: string; }, config?: Partial<VisualizationConfig>, ): Promise<ChartData> { const activeConfig = { ...this.defaultConfig, ...config }; const timeRange = query.timeRange || this.getDefaultTimeRange(); let entries = await this.getEntriesInTimeRange(timeRange); // Apply filters if (query.filters) { entries = this.applyFilters(entries, query.filters); } switch (type) { case 'timeline': return this.generateTimelineVisualization(entries, query, activeConfig); case 'scatter': return this.generateScatterPlot(entries, query, activeConfig); case 'treemap': return this.generateTreemapVisualization(entries, query, activeConfig); case 'sankey': return this.generateSankeyDiagram(entries, query, activeConfig); default: throw new Error(`Unsupported visualization type: ${type}`); } } /** * Export visualization to specified format */ async exportVisualization( chartData: ChartData, format: 'svg' | 'png' | 'json' | 'html' = 'json', options?: { filename?: string; quality?: number; width?: number; height?: number; }, ): Promise<string | Buffer> { this.emit('export_started', { type: chartData.type, format }); try { switch (format) { case 'json': return JSON.stringify(chartData, null, 2); case 'html': return this.generateHTMLVisualization(chartData, options); case 'svg': return this.generateSVGVisualization(chartData, options); case 'png': // This would require a rendering library like Puppeteer throw new Error('PNG export requires additional rendering capabilities'); default: throw new Error(`Unsupported export format: ${format}`); } } catch (error) { this.emit('export_error', { error: error instanceof Error ? error.message : String(error) }); throw error; } } /** * Helper methods */ private async getEntriesInTimeRange(timeRange: { start: Date; end: Date; }): Promise<MemoryEntry[]> { const allEntries = await this.storage.getAll(); return allEntries.filter((entry) => { const entryDate = new Date(entry.timestamp); return entryDate >= timeRange.start && entryDate <= timeRange.end; }); } private getDefaultTimeRange(): { start: Date; end: Date } { const end = new Date(); const start = new Date(end.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago return { start, end }; } private getWeekKey(date: Date): string { const year = date.getFullYear(); const week = this.getWeekNumber(date); return `${year}-W${week.toString().padStart(2, '0')}`; } private getWeekNumber(date: Date): number { const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const dayNum = d.getUTCDay() || 7; d.setUTCDate(d.getUTCDate() + 4 - dayNum); const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7); } private getColorForNodeType(type: string, colorScheme?: string[]): string { const colors = colorScheme || this.defaultConfig.colorScheme; const index = type.charCodeAt(0) % colors.length; return colors[index]; } private getColorForEdgeType(type: string, colorScheme?: string[]): string { const colors = colorScheme || this.defaultConfig.colorScheme; const typeColors: Record<string, string> = { similarity: colors[0], dependency: colors[1], temporal: colors[2], causal: colors[3], }; return typeColors[type] || colors[4]; } private darkenColor(color: string): string { // Simple color darkening - in production, use a proper color library if (color.startsWith('#')) { const hex = color.slice(1); const num = parseInt(hex, 16); const r = Math.max(0, (num >> 16) - 40); const g = Math.max(0, ((num >> 8) & 0x00ff) - 40); const b = Math.max(0, (num & 0x0000ff) - 40); return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`; } return color; } private calculateProjectCorrelation( entries: MemoryEntry[], project1: string, project2: string, ): number { const entries1 = entries.filter( (e) => e.data.projectPath?.includes(project1) || e.data.projectId === project1, ); const entries2 = entries.filter( (e) => e.data.projectPath?.includes(project2) || e.data.projectId === project2, ); if (entries1.length === 0 || entries2.length === 0) return 0; // Simple correlation based on shared characteristics let sharedFeatures = 0; let totalFeatures = 0; // Compare languages const lang1 = new Set(entries1.map((e) => e.data.language).filter(Boolean)); const lang2 = new Set(entries2.map((e) => e.data.language).filter(Boolean)); const sharedLangs = new Set([...lang1].filter((l) => lang2.has(l))); sharedFeatures += sharedLangs.size; totalFeatures += new Set([...lang1, ...lang2]).size; // Compare frameworks const fw1 = new Set(entries1.map((e) => e.data.framework).filter(Boolean)); const fw2 = new Set(entries2.map((e) => e.data.framework).filter(Boolean)); const sharedFws = new Set([...fw1].filter((f) => fw2.has(f))); sharedFeatures += sharedFws.size; totalFeatures += new Set([...fw1, ...fw2]).size; return totalFeatures > 0 ? sharedFeatures / totalFeatures : 0; } private applyFilters(entries: MemoryEntry[], filters: Record<string, any>): MemoryEntry[] { return entries.filter((entry) => { for (const [key, value] of Object.entries(filters)) { switch (key) { case 'type': if (Array.isArray(value) && !value.includes(entry.type)) return false; if (typeof value === 'string' && entry.type !== value) return false; break; case 'outcome': if (entry.data.outcome !== value) return false; break; case 'language': if (entry.data.language !== value) return false; break; case 'framework': if (entry.data.framework !== value) return false; break; case 'project': if (!entry.data.projectPath?.includes(value) && entry.data.projectId !== value) { return false; } break; case 'tags': if (Array.isArray(value) && !value.some((tag) => entry.tags?.includes(tag))) { return false; } break; } } return true; }); } private async generateKeyInsights( entries: MemoryEntry[], timeRange: { start: Date; end: Date }, ): Promise<string[]> { const insights: string[] = []; // Activity insight const dailyAverage = entries.length / Math.max( 1, Math.ceil((timeRange.end.getTime() - timeRange.start.getTime()) / (24 * 60 * 60 * 1000)), ); insights.push(`Average ${dailyAverage.toFixed(1)} entries per day`); // Success rate insight const successful = entries.filter( (e) => e.data.outcome === 'success' || e.data.success === true, ).length; const successRate = entries.length > 0 ? (successful / entries.length) * 100 : 0; insights.push(`${successRate.toFixed(1)}% success rate`); // Most common type const typeCounts = new Map<string, number>(); entries.forEach((e) => typeCounts.set(e.type, (typeCounts.get(e.type) || 0) + 1)); const mostCommonType = Array.from(typeCounts.entries()).sort(([, a], [, b]) => b - a)[0]; if (mostCommonType) { insights.push(`Most common activity: ${mostCommonType[0]} (${mostCommonType[1]} entries)`); } // Growth trend const midpoint = new Date((timeRange.start.getTime() + timeRange.end.getTime()) / 2); const firstHalf = entries.filter((e) => new Date(e.timestamp) < midpoint).length; const secondHalf = entries.filter((e) => new Date(e.timestamp) >= midpoint).length; if (firstHalf > 0) { const growthRate = ((secondHalf - firstHalf) / firstHalf) * 100; insights.push( `Activity ${growthRate >= 0 ? 'increased' : 'decreased'} by ${Math.abs(growthRate).toFixed( 1, )}%`, ); } return insights.slice(0, 5); // Return top 5 insights } private async calculateSystemHealthScore(entries: MemoryEntry[]): Promise<number> { let score = 0; // Activity level (0-25 points) const recentEntries = entries.filter( (e) => new Date(e.timestamp) > new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), ); score += Math.min(25, recentEntries.length * 2); // Success rate (0-25 points) const successful = entries.filter( (e) => e.data.outcome === 'success' || e.data.success === true, ).length; const successRate = entries.length > 0 ? successful / entries.length : 0; score += successRate * 25; // Diversity (0-25 points) const uniqueTypes = new Set(entries.map((e) => e.type)).size; score += Math.min(25, uniqueTypes * 3); // Consistency (0-25 points) if (entries.length >= 7) { const dailyActivities = new Map<string, number>(); entries.forEach((e) => { const day = e.timestamp.slice(0, 10); dailyActivities.set(day, (dailyActivities.get(day) || 0) + 1); }); const values = Array.from(dailyActivities.values()); const mean = values.reduce((sum, val) => sum + val, 0) / values.length; const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length; const consistency = mean > 0 ? Math.max(0, 1 - Math.sqrt(variance) / mean) : 0; score += consistency * 25; } return Math.round(Math.min(100, score)); } private generateTimelineVisualization( entries: MemoryEntry[], query: any, config: VisualizationConfig, ): ChartData { const events = entries.map((entry) => ({ id: entry.id, timestamp: new Date(entry.timestamp), title: entry.type, description: entry.data.description || `${entry.type} entry`, type: entry.type, importance: entry.data.outcome === 'success' ? 1 : 0.5, color: this.getColorForNodeType(entry.type, config.colorScheme), metadata: entry.data, })); const timelineData: TimelineVisualization = { events, timeRange: { start: new Date(Math.min(...events.map((e) => e.timestamp.getTime()))), end: new Date(Math.max(...events.map((e) => e.timestamp.getTime()))), }, granularity: 'day', groupBy: query.groupBy, }; return { type: 'timeline', title: 'Memory Activity Timeline', description: 'Chronological timeline of memory system activities', data: timelineData, config, metadata: { generated: new Date(), dataPoints: events.length, filters: query.filters, }, }; } private generateScatterPlot( entries: MemoryEntry[], query: any, config: VisualizationConfig, ): ChartData { // Create scatter plot data based on timestamp vs some metric const data = entries.map((entry) => ({ x: new Date(entry.timestamp).getTime(), y: entry.data.duration || entry.data.complexity || Math.random(), // Use available metric color: this.getColorForNodeType(entry.type, config.colorScheme), metadata: entry, })); return { type: 'scatter', title: 'Memory Activity Scatter Plot', description: 'Scatter plot visualization of memory activities', data: { datasets: [ { label: 'Activities', data: data, backgroundColor: data.map((d) => d.color), }, ], }, config, metadata: { generated: new Date(), dataPoints: data.length, filters: query.filters, }, }; } private generateTreemapVisualization( entries: MemoryEntry[], query: any, config: VisualizationConfig, ): ChartData { // Group entries by type and project for treemap const hierarchy = new Map<string, Map<string, number>>(); for (const entry of entries) { const type = entry.type; const project = entry.data.projectPath || entry.data.projectId || 'Unknown'; if (!hierarchy.has(type)) { hierarchy.set(type, new Map()); } hierarchy.get(type)!.set(project, (hierarchy.get(type)!.get(project) || 0) + 1); } // Convert to treemap format const treemapData = Array.from(hierarchy.entries()).map(([type, projects]) => ({ name: type, value: Array.from(projects.values()).reduce((sum, val) => sum + val, 0), children: Array.from(projects.entries()).map(([project, count]) => ({ name: project, value: count, })), })); return { type: 'treemap', title: 'Memory Type Hierarchy', description: 'Hierarchical treemap of memory entries by type and project', data: treemapData, config, metadata: { generated: new Date(), dataPoints: entries.length, filters: query.filters, }, }; } private generateSankeyDiagram( entries: MemoryEntry[], query: any, config: VisualizationConfig, ): ChartData { // Create flow data from entry types to outcomes const flows = new Map<string, Map<string, number>>(); for (const entry of entries) { const source = entry.type; const target = entry.data.outcome || (entry.data.success ? 'success' : 'unknown'); if (!flows.has(source)) { flows.set(source, new Map()); } flows.get(source)!.set(target, (flows.get(source)!.get(target) || 0) + 1); } // Convert to Sankey format const nodes: string[] = []; const links: Array<{ source: number; target: number; value: number }> = []; // Collect all unique nodes const sources = Array.from(flows.keys()); const targets = new Set<string>(); flows.forEach((targetMap) => { targetMap.forEach((_, target) => targets.add(target)); }); nodes.push(...sources, ...Array.from(targets).filter((t) => !sources.includes(t))); // Create links flows.forEach((targetMap, source) => { targetMap.forEach((value, target) => { const sourceIndex = nodes.indexOf(source); const targetIndex = nodes.indexOf(target); if (sourceIndex !== -1 && targetIndex !== -1) { links.push({ source: sourceIndex, target: targetIndex, value }); } }); }); return { type: 'sankey', title: 'Memory Flow Diagram', description: 'Sankey diagram showing flow from memory types to outcomes', data: { nodes, links }, config, metadata: { generated: new Date(), dataPoints: links.length, filters: query.filters, }, }; } private generateHTMLVisualization(chartData: ChartData, _options?: any): string { // Generate basic HTML with embedded Chart.js or D3.js return ` <!DOCTYPE html> <html> <head> <title>${chartData.title}</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style> body { font-family: Arial, sans-serif; margin: 20px; } .chart-container { width: 100%; height: 400px; } .description { margin-bottom: 20px; color: #666; } </style> </head> <body> <h1>${chartData.title}</h1> <p class="description">${chartData.description}</p> <div class="chart-container"> <canvas id="chart"></canvas> </div> <script> const ctx = document.getElementById('chart').getContext('2d'); new Chart(ctx, ${JSON.stringify(chartData.data)}); </script> </body> </html>`; } private generateSVGVisualization(chartData: ChartData, options?: any): string { // Generate basic SVG - in production, use a proper chart library const width = options?.width || 800; const height = options?.height || 600; return ` <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="white"/> <text x="50%" y="30" text-anchor="middle" font-size="18" font-weight="bold"> ${chartData.title} </text> <text x="50%" y="50" text-anchor="middle" font-size="14" fill="#666"> ${chartData.description} </text> <!-- Chart data would be rendered here --> <text x="50%" y="${height / 2}" text-anchor="middle" font-size="12" fill="#999"> Chart visualization (${chartData.metadata.dataPoints} data points) </text> </svg>`; } }

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/tosin2013/documcp'

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