Skip to main content
Glama

documcp

by tosin2013
visualization.ts39.7 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