UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

962 lines (837 loc) 32.4 kB
/** * Memory Management Interface * Comprehensive memory management system for Claude Flow */ class MemoryInterface { constructor() { this.container = null; this.currentNamespace = 'global'; this.memoryData = new Map(); this.searchFilters = {}; this.analytics = { usage: new Map(), history: [], patterns: new Map(), }; this.backupManager = new BackupManager(); this.syncManager = new SyncManager(); this.compressionManager = new CompressionManager(); this.visualizer = new MemoryVisualizer(); this.init(); } init() { this.createInterface(); this.setupEventListeners(); this.startMonitoring(); this.loadMemoryData(); } createInterface() { this.container = document.createElement('div'); this.container.className = 'memory-interface'; this.container.innerHTML = ` <div class="memory-header"> <h2>Memory Management Interface</h2> <div class="memory-controls"> <button class="btn-refresh" title="Refresh Data"> <i class="icon-refresh"></i> </button> <button class="btn-backup" title="Create Backup"> <i class="icon-backup"></i> </button> <button class="btn-compress" title="Optimize Memory"> <i class="icon-compress"></i> </button> <button class="btn-sync" title="Sync Status"> <i class="icon-sync"></i> </button> </div> </div> <div class="memory-layout"> <!-- Namespace Browser --> <div class="namespace-panel"> <div class="panel-header"> <h3>Namespace Browser</h3> <button class="btn-add-namespace" title="Add Namespace">+</button> </div> <div class="namespace-tree"> <div class="namespace-search"> <input type="text" placeholder="Search namespaces..." class="namespace-search-input"> </div> <div class="namespace-tree-container"> <!-- Namespace tree will be populated here --> </div> </div> </div> <!-- Key-Value Editor --> <div class="editor-panel"> <div class="panel-header"> <h3>Key-Value Editor</h3> <div class="editor-controls"> <button class="btn-add-key" title="Add Key">+</button> <button class="btn-bulk-edit" title="Bulk Edit">Bulk</button> <button class="btn-export" title="Export">Export</button> </div> </div> <div class="editor-content"> <div class="editor-search"> <input type="text" placeholder="Search keys..." class="key-search-input"> <select class="type-filter"> <option value="">All Types</option> <option value="string">String</option> <option value="number">Number</option> <option value="boolean">Boolean</option> <option value="object">Object</option> <option value="array">Array</option> </select> </div> <div class="key-value-list"> <!-- Key-value pairs will be populated here --> </div> </div> </div> <!-- Analytics Dashboard --> <div class="analytics-panel"> <div class="panel-header"> <h3>Memory Analytics</h3> <select class="analytics-timeframe"> <option value="1h">Last Hour</option> <option value="24h">Last 24 Hours</option> <option value="7d">Last 7 Days</option> <option value="30d">Last 30 Days</option> </select> </div> <div class="analytics-content"> <div class="analytics-stats"> <div class="stat-card"> <h4>Total Keys</h4> <span class="stat-value" id="total-keys">0</span> </div> <div class="stat-card"> <h4>Memory Usage</h4> <span class="stat-value" id="memory-usage">0 KB</span> </div> <div class="stat-card"> <h4>Compression Rate</h4> <span class="stat-value" id="compression-rate">0%</span> </div> <div class="stat-card"> <h4>Access Frequency</h4> <span class="stat-value" id="access-frequency">0/min</span> </div> </div> <div class="analytics-charts"> <div class="chart-container"> <canvas id="usage-chart"></canvas> </div> <div class="chart-container"> <canvas id="pattern-chart"></canvas> </div> </div> </div> </div> </div> <!-- Search Interface --> <div class="search-panel"> <div class="panel-header"> <h3>Advanced Search</h3> <button class="btn-saved-searches" title="Saved Searches"> <i class="icon-bookmark"></i> </button> </div> <div class="search-content"> <div class="search-builder"> <div class="search-row"> <select class="search-field"> <option value="key">Key</option> <option value="value">Value</option> <option value="type">Type</option> <option value="namespace">Namespace</option> <option value="created">Created Date</option> <option value="modified">Modified Date</option> </select> <select class="search-operator"> <option value="contains">Contains</option> <option value="equals">Equals</option> <option value="startsWith">Starts With</option> <option value="endsWith">Ends With</option> <option value="regex">Regex</option> </select> <input type="text" class="search-value" placeholder="Search value..."> <button class="btn-add-filter">+</button> </div> </div> <div class="search-filters"> <!-- Active filters will be shown here --> </div> <div class="search-results"> <!-- Search results will be populated here --> </div> </div> </div> <!-- Backup/Restore Panel --> <div class="backup-panel"> <div class="panel-header"> <h3>Backup & Restore</h3> <button class="btn-schedule-backup" title="Schedule Backup"> <i class="icon-schedule"></i> </button> </div> <div class="backup-content"> <div class="backup-controls"> <button class="btn-create-backup">Create Backup</button> <button class="btn-restore-backup">Restore Backup</button> <button class="btn-import-backup">Import Backup</button> </div> <div class="backup-list"> <!-- Backup list will be populated here --> </div> </div> </div> <!-- Modals --> <div class="modal-overlay" id="modal-overlay"> <div class="modal" id="key-editor-modal"> <div class="modal-header"> <h3>Edit Key-Value Pair</h3> <button class="btn-close-modal">×</button> </div> <div class="modal-content"> <form id="key-editor-form"> <div class="form-group"> <label>Key:</label> <input type="text" id="edit-key" required> </div> <div class="form-group"> <label>Type:</label> <select id="edit-type"> <option value="string">String</option> <option value="number">Number</option> <option value="boolean">Boolean</option> <option value="object">Object</option> <option value="array">Array</option> </select> </div> <div class="form-group"> <label>Value:</label> <textarea id="edit-value" rows="10"></textarea> </div> <div class="form-group"> <label>Namespace:</label> <select id="edit-namespace"> <!-- Namespaces will be populated here --> </select> </div> <div class="form-actions"> <button type="submit" class="btn-save">Save</button> <button type="button" class="btn-cancel">Cancel</button> </div> </form> </div> </div> </div> `; } setupEventListeners() { // Header controls this.container.querySelector('.btn-refresh').addEventListener('click', () => this.refresh()); this.container .querySelector('.btn-backup') .addEventListener('click', () => this.createBackup()); this.container .querySelector('.btn-compress') .addEventListener('click', () => this.optimizeMemory()); this.container .querySelector('.btn-sync') .addEventListener('click', () => this.showSyncStatus()); // Namespace controls this.container .querySelector('.btn-add-namespace') .addEventListener('click', () => this.addNamespace()); this.container .querySelector('.namespace-search-input') .addEventListener('input', (e) => this.searchNamespaces(e.target.value)); // Editor controls this.container.querySelector('.btn-add-key').addEventListener('click', () => this.addKey()); this.container.querySelector('.btn-bulk-edit').addEventListener('click', () => this.bulkEdit()); this.container .querySelector('.btn-export') .addEventListener('click', () => this.exportMemory()); // Search controls this.container .querySelector('.key-search-input') .addEventListener('input', (e) => this.searchKeys(e.target.value)); this.container .querySelector('.type-filter') .addEventListener('change', (e) => this.filterByType(e.target.value)); // Analytics controls this.container .querySelector('.analytics-timeframe') .addEventListener('change', (e) => this.updateAnalytics(e.target.value)); // Search interface this.container .querySelector('.btn-add-filter') .addEventListener('click', () => this.addSearchFilter()); this.container .querySelector('.btn-saved-searches') .addEventListener('click', () => this.showSavedSearches()); // Backup controls this.container .querySelector('.btn-create-backup') .addEventListener('click', () => this.createBackup()); this.container .querySelector('.btn-restore-backup') .addEventListener('click', () => this.restoreBackup()); this.container .querySelector('.btn-import-backup') .addEventListener('click', () => this.importBackup()); // Modal controls this.container .querySelector('.btn-close-modal') .addEventListener('click', () => this.closeModal()); this.container.querySelector('.modal-overlay').addEventListener('click', (e) => { if (e.target === this.container.querySelector('.modal-overlay')) { this.closeModal(); } }); // Form submission this.container .querySelector('#key-editor-form') .addEventListener('submit', (e) => this.saveKeyValue(e)); } async loadMemoryData() { try { // Load memory data from Claude Flow's memory system const response = await fetch('/api/memory/list'); const data = await response.json(); this.memoryData = new Map(Object.entries(data)); this.updateNamespaceTree(); this.updateKeyValueList(); this.updateAnalytics(); } catch (error) { console.error('Failed to load memory data:', error); this.showError('Failed to load memory data'); } } updateNamespaceTree() { const container = this.container.querySelector('.namespace-tree-container'); const namespaces = this.getNamespaces(); container.innerHTML = this.renderNamespaceTree(namespaces); // Add click handlers for namespace items container.querySelectorAll('.namespace-item').forEach((item) => { item.addEventListener('click', (e) => { e.stopPropagation(); this.selectNamespace(item.dataset.namespace); }); }); } renderNamespaceTree(namespaces, parent = '', level = 0) { let html = ''; for (const [namespace, data] of namespaces) { const fullPath = parent ? `${parent}.${namespace}` : namespace; const hasChildren = data.children && data.children.size > 0; html += ` <div class="namespace-item ${this.currentNamespace === fullPath ? 'active' : ''}" data-namespace="${fullPath}" style="padding-left: ${level * 20}px"> <div class="namespace-content"> ${hasChildren ? '<i class="icon-folder"></i>' : '<i class="icon-file"></i>'} <span class="namespace-name">${namespace}</span> <span class="namespace-count">(${data.count})</span> </div> </div> `; if (hasChildren) { html += this.renderNamespaceTree(data.children, fullPath, level + 1); } } return html; } updateKeyValueList() { const container = this.container.querySelector('.key-value-list'); const keys = this.getKeysForNamespace(this.currentNamespace); container.innerHTML = keys.map((key) => this.renderKeyValueItem(key)).join(''); // Add event listeners for key-value items container.querySelectorAll('.key-value-item').forEach((item) => { item .querySelector('.btn-edit') .addEventListener('click', () => this.editKey(item.dataset.key)); item .querySelector('.btn-delete') .addEventListener('click', () => this.deleteKey(item.dataset.key)); item .querySelector('.btn-copy') .addEventListener('click', () => this.copyKey(item.dataset.key)); }); } renderKeyValueItem(key) { const value = this.memoryData.get(key); const type = this.getValueType(value); const displayValue = this.formatValueForDisplay(value, type); return ` <div class="key-value-item" data-key="${key}"> <div class="key-info"> <span class="key-name">${key}</span> <span class="key-type">${type}</span> </div> <div class="value-preview"> <span class="value-content">${displayValue}</span> </div> <div class="item-actions"> <button class="btn-edit" title="Edit"> <i class="icon-edit"></i> </button> <button class="btn-copy" title="Copy"> <i class="icon-copy"></i> </button> <button class="btn-delete" title="Delete"> <i class="icon-delete"></i> </button> </div> </div> `; } getNamespaces() { const namespaces = new Map(); for (const [key, value] of this.memoryData) { const parts = key.split('.'); let current = namespaces; let path = ''; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; path = path ? `${path}.${part}` : part; if (!current.has(part)) { current.set(part, { count: 0, children: new Map(), }); } current = current.get(part).children; } // Count the final key const finalPart = parts[parts.length - 1]; if (!current.has(finalPart)) { current.set(finalPart, { count: 0, children: new Map() }); } current.get(finalPart).count++; } return namespaces; } getKeysForNamespace(namespace) { const keys = []; const prefix = namespace === 'global' ? '' : `${namespace}.`; for (const [key, value] of this.memoryData) { if (namespace === 'global' || key.startsWith(prefix)) { keys.push(key); } } return keys.sort(); } getValueType(value) { if (value === null) return 'null'; if (value === undefined) return 'undefined'; if (Array.isArray(value)) return 'array'; return typeof value; } formatValueForDisplay(value, type) { switch (type) { case 'string': return value.length > 50 ? `${value.substring(0, 50)}...` : value; case 'object': return JSON.stringify(value).substring(0, 100) + '...'; case 'array': return `[${value.length} items]`; default: return String(value); } } updateAnalytics(timeframe = '24h') { const stats = this.calculateStats(timeframe); // Update stat cards this.container.querySelector('#total-keys').textContent = stats.totalKeys; this.container.querySelector('#memory-usage').textContent = stats.memoryUsage; this.container.querySelector('#compression-rate').textContent = stats.compressionRate; this.container.querySelector('#access-frequency').textContent = stats.accessFrequency; // Update charts this.visualizer.updateUsageChart(stats.usageData); this.visualizer.updatePatternChart(stats.patternData); } calculateStats(timeframe) { const now = Date.now(); const timeframes = { '1h': 60 * 60 * 1000, '24h': 24 * 60 * 60 * 1000, '7d': 7 * 24 * 60 * 60 * 1000, '30d': 30 * 24 * 60 * 60 * 1000, }; const cutoff = now - timeframes[timeframe]; return { totalKeys: this.memoryData.size, memoryUsage: this.calculateMemoryUsage(), compressionRate: this.compressionManager.getCompressionRate(), accessFrequency: this.calculateAccessFrequency(cutoff), usageData: this.getUsageData(cutoff), patternData: this.getPatternData(cutoff), }; } calculateMemoryUsage() { let totalSize = 0; for (const [key, value] of this.memoryData) { totalSize += this.getObjectSize(key) + this.getObjectSize(value); } return this.formatBytes(totalSize); } getObjectSize(obj) { let size = 0; if (typeof obj === 'string') { size = obj.length * 2; // UTF-16 } else if (typeof obj === 'number') { size = 8; // 64-bit float } else if (typeof obj === 'boolean') { size = 1; } else if (obj === null || obj === undefined) { size = 0; } else { size = JSON.stringify(obj).length * 2; } return size; } formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } calculateAccessFrequency(cutoff) { const recentAccess = this.analytics.history.filter((entry) => entry.timestamp > cutoff); const frequency = recentAccess.length / ((Date.now() - cutoff) / 60000); // per minute return frequency.toFixed(2); } getUsageData(cutoff) { // Return usage data for charts return this.analytics.history .filter((entry) => entry.timestamp > cutoff) .map((entry) => ({ timestamp: entry.timestamp, operations: entry.operations || 0, memory: entry.memory || 0, })); } getPatternData(cutoff) { // Return pattern data for charts const patterns = new Map(); for (const [key, value] of this.analytics.patterns) { if (value.lastAccess > cutoff) { patterns.set(key, value); } } return Array.from(patterns.entries()); } // Event handlers async selectNamespace(namespace) { this.currentNamespace = namespace; this.updateKeyValueList(); // Update active state this.container.querySelectorAll('.namespace-item').forEach((item) => { item.classList.toggle('active', item.dataset.namespace === namespace); }); await this.notifyCoordination(`Selected namespace: ${namespace}`); } async addNamespace() { const name = prompt('Enter namespace name:'); if (name) { // Create a placeholder key to establish the namespace const key = `${name}.placeholder`; await this.setMemoryValue(key, 'Namespace placeholder'); await this.loadMemoryData(); await this.notifyCoordination(`Added namespace: ${name}`); } } async addKey() { this.openModal('key-editor-modal'); this.container.querySelector('#edit-key').value = ''; this.container.querySelector('#edit-value').value = ''; this.container.querySelector('#edit-type').value = 'string'; this.container.querySelector('#edit-namespace').value = this.currentNamespace; } async editKey(key) { const value = this.memoryData.get(key); const type = this.getValueType(value); this.openModal('key-editor-modal'); this.container.querySelector('#edit-key').value = key; this.container.querySelector('#edit-value').value = JSON.stringify(value, null, 2); this.container.querySelector('#edit-type').value = type; this.container.querySelector('#edit-namespace').value = this.getNamespaceFromKey(key); } async deleteKey(key) { if (confirm(`Are you sure you want to delete "${key}"?`)) { await this.deleteMemoryValue(key); await this.loadMemoryData(); await this.notifyCoordination(`Deleted key: ${key}`); } } async copyKey(key) { const value = this.memoryData.get(key); const text = JSON.stringify({ [key]: value }, null, 2); try { await navigator.clipboard.writeText(text); this.showSuccess('Key copied to clipboard'); } catch (error) { console.error('Failed to copy to clipboard:', error); this.showError('Failed to copy to clipboard'); } } async saveKeyValue(e) { e.preventDefault(); const key = this.container.querySelector('#edit-key').value; const type = this.container.querySelector('#edit-type').value; const valueText = this.container.querySelector('#edit-value').value; try { let value; switch (type) { case 'string': value = valueText; break; case 'number': value = parseFloat(valueText); break; case 'boolean': value = valueText.toLowerCase() === 'true'; break; case 'object': case 'array': value = JSON.parse(valueText); break; default: value = valueText; } await this.setMemoryValue(key, value); await this.loadMemoryData(); this.closeModal(); await this.notifyCoordination(`Saved key: ${key}`); } catch (error) { console.error('Failed to save key-value:', error); this.showError('Failed to save key-value pair'); } } async createBackup() { try { const backup = await this.backupManager.createBackup(this.memoryData); this.showSuccess(`Backup created: ${backup.id}`); await this.notifyCoordination(`Created backup: ${backup.id}`); } catch (error) { console.error('Failed to create backup:', error); this.showError('Failed to create backup'); } } async optimizeMemory() { try { const result = await this.compressionManager.optimize(this.memoryData); this.showSuccess(`Memory optimized: ${result.savedBytes} bytes saved`); await this.loadMemoryData(); await this.notifyCoordination(`Optimized memory: ${result.savedBytes} bytes saved`); } catch (error) { console.error('Failed to optimize memory:', error); this.showError('Failed to optimize memory'); } } async refresh() { await this.loadMemoryData(); this.showSuccess('Memory data refreshed'); } // Helper methods openModal(modalId) { this.container.querySelector('#modal-overlay').style.display = 'flex'; this.container.querySelector(`#${modalId}`).style.display = 'block'; } closeModal() { this.container.querySelector('#modal-overlay').style.display = 'none'; this.container.querySelectorAll('.modal').forEach((modal) => { modal.style.display = 'none'; }); } getNamespaceFromKey(key) { const parts = key.split('.'); return parts.length > 1 ? parts.slice(0, -1).join('.') : 'global'; } async setMemoryValue(key, value) { const response = await fetch('/api/memory/set', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, value }), }); if (!response.ok) { throw new Error('Failed to set memory value'); } } async deleteMemoryValue(key) { const response = await fetch('/api/memory/delete', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key }), }); if (!response.ok) { throw new Error('Failed to delete memory value'); } } showSuccess(message) { // Implementation for success notification console.log('Success:', message); } showError(message) { // Implementation for error notification console.error('Error:', message); } async notifyCoordination(message) { try { await fetch('/api/coordination/notify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, timestamp: Date.now() }), }); } catch (error) { console.error('Failed to notify coordination:', error); } } startMonitoring() { // Start real-time monitoring setInterval(() => { this.updateAnalytics(); }, 30000); // Update every 30 seconds } render() { return this.container; } } // Supporting classes class BackupManager { constructor() { this.backups = new Map(); } async createBackup(memoryData) { const id = `backup_${Date.now()}`; const backup = { id, timestamp: Date.now(), data: new Map(memoryData), size: this.calculateBackupSize(memoryData), }; this.backups.set(id, backup); return backup; } async restoreBackup(backupId) { const backup = this.backups.get(backupId); if (!backup) { throw new Error('Backup not found'); } return backup.data; } calculateBackupSize(memoryData) { return JSON.stringify(Array.from(memoryData.entries())).length; } } class SyncManager { constructor() { this.syncStatus = 'idle'; this.lastSync = null; } async sync() { this.syncStatus = 'syncing'; try { // Implement sync logic here await new Promise((resolve) => setTimeout(resolve, 1000)); this.lastSync = Date.now(); this.syncStatus = 'synchronized'; } catch (error) { this.syncStatus = 'error'; throw error; } } getStatus() { return { status: this.syncStatus, lastSync: this.lastSync, }; } } class CompressionManager { constructor() { this.compressionRate = 0; } async optimize(memoryData) { // Implement compression logic const originalSize = this.calculateSize(memoryData); // Simulate compression await new Promise((resolve) => setTimeout(resolve, 500)); const compressedSize = originalSize * 0.7; // 30% compression const savedBytes = originalSize - compressedSize; this.compressionRate = (savedBytes / originalSize) * 100; return { savedBytes: Math.floor(savedBytes), compressionRate: this.compressionRate, }; } getCompressionRate() { return `${this.compressionRate.toFixed(1)}%`; } calculateSize(memoryData) { return JSON.stringify(Array.from(memoryData.entries())).length; } } class MemoryVisualizer { constructor() { this.usageChart = null; this.patternChart = null; } updateUsageChart(data) { const canvas = document.getElementById('usage-chart'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Simple chart implementation ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw usage data if (data.length > 0) { ctx.beginPath(); ctx.strokeStyle = '#007bff'; ctx.lineWidth = 2; const width = canvas.width; const height = canvas.height; const stepX = width / (data.length - 1); const maxValue = Math.max(...data.map((d) => d.operations)); data.forEach((point, index) => { const x = index * stepX; const y = height - (point.operations / maxValue) * height; if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); } } updatePatternChart(data) { const canvas = document.getElementById('pattern-chart'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Simple pattern chart implementation ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw pattern data as bars if (data.length > 0) { const width = canvas.width; const height = canvas.height; const barWidth = width / data.length; const maxValue = Math.max(...data.map((d) => d[1].count || 0)); data.forEach((pattern, index) => { const barHeight = ((pattern[1].count || 0) / maxValue) * height; const x = index * barWidth; const y = height - barHeight; ctx.fillStyle = '#28a745'; ctx.fillRect(x, y, barWidth - 2, barHeight); }); } } } // Export for use in other modules window.MemoryInterface = MemoryInterface;