visualization.ts•38.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>`;
}
}