claude-flow-multilang
Version:
Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture
516 lines (437 loc) • 12.4 kB
JavaScript
/**
* StateManager - Global state persistence and management
* Handles UI state, user preferences, and data persistence
*/
import { EventBus } from './EventBus.js';
export class StateManager {
constructor(eventBus) {
this.eventBus = eventBus;
this.state = new Map();
this.preferences = new Map();
this.toolResults = new Map();
this.viewStates = new Map();
this.sessionData = new Map();
this.storageKey = 'claude-flow-ui-state';
this.isInitialized = false;
this.autoSaveInterval = 30000; // 30 seconds
this.autoSaveTimer = null;
}
/**
* Initialize state manager
*/
async initialize() {
try {
// Load persisted state
await this.loadPersistedState();
// Setup auto-save
this.setupAutoSave();
// Setup event handlers
this.setupEventHandlers();
this.isInitialized = true;
this.eventBus.emit('state-manager:initialized');
console.log('💾 State Manager initialized');
} catch (error) {
console.error('❌ Failed to initialize State Manager:', error);
throw error;
}
}
/**
* Load persisted state from storage
*/
async loadPersistedState() {
try {
let persistedData = null;
// Try to load from localStorage in browser
if (typeof window !== 'undefined' && window.localStorage) {
const stored = localStorage.getItem(this.storageKey);
if (stored) {
persistedData = JSON.parse(stored);
}
}
// Try to load from file system in Node.js
if (!persistedData && typeof process !== 'undefined') {
try {
const fs = await import('fs');
const path = await import('path');
const stateFile = path.join(process.cwd(), '.claude-flow-state.json');
if (fs.existsSync(stateFile)) {
const data = fs.readFileSync(stateFile, 'utf8');
persistedData = JSON.parse(data);
}
} catch (error) {
console.warn('Could not load state from file system:', error.message);
}
}
if (persistedData) {
this.restoreFromData(persistedData);
console.log('💾 Loaded persisted state');
} else {
this.initializeDefaultState();
}
} catch (error) {
console.warn('Could not load persisted state:', error);
this.initializeDefaultState();
}
}
/**
* Restore state from persisted data
*/
restoreFromData(data) {
if (data.preferences) {
this.preferences = new Map(Object.entries(data.preferences));
}
if (data.viewStates) {
this.viewStates = new Map(Object.entries(data.viewStates));
}
if (data.toolResults) {
this.toolResults = new Map(Object.entries(data.toolResults));
}
if (data.sessionData) {
this.sessionData = new Map(Object.entries(data.sessionData));
}
if (data.state) {
this.state = new Map(Object.entries(data.state));
}
}
/**
* Initialize default state
*/
initializeDefaultState() {
this.preferences.set('theme', 'dark');
this.preferences.set('autoSave', true);
this.preferences.set('showTooltips', true);
this.preferences.set('animationSpeed', 'normal');
this.preferences.set('defaultView', 'overview');
this.preferences.set('keyboardShortcuts', true);
this.preferences.set('realTimeUpdates', true);
this.preferences.set('logLevel', 'info');
this.state.set('initialized', true);
this.state.set('version', '2.0.0');
this.state.set('installDate', Date.now());
console.log('💾 Initialized default state');
}
/**
* Setup auto-save functionality
*/
setupAutoSave() {
if (!this.getPreference('autoSave', true)) {
return;
}
this.autoSaveTimer = setInterval(() => {
this.persistState();
}, this.autoSaveInterval);
}
/**
* Persist current state
*/
async persistState() {
try {
const stateData = {
timestamp: Date.now(),
version: '2.0.0',
preferences: Object.fromEntries(this.preferences),
viewStates: Object.fromEntries(this.viewStates),
toolResults: Object.fromEntries(this.toolResults),
sessionData: Object.fromEntries(this.sessionData),
state: Object.fromEntries(this.state),
};
// Save to localStorage in browser
if (typeof window !== 'undefined' && window.localStorage) {
localStorage.setItem(this.storageKey, JSON.stringify(stateData));
}
// Save to file system in Node.js
if (typeof process !== 'undefined') {
try {
const fs = await import('fs');
const path = await import('path');
const stateFile = path.join(process.cwd(), '.claude-flow-state.json');
fs.writeFileSync(stateFile, JSON.stringify(stateData, null, 2));
} catch (error) {
console.warn('Could not save state to file system:', error.message);
}
}
this.eventBus.emit('state:persisted', { timestamp: Date.now() });
} catch (error) {
console.error('Failed to persist state:', error);
this.eventBus.emit('state:error', { error: error.message });
}
}
/**
* Get preference value
*/
getPreference(key, defaultValue = null) {
return this.preferences.get(key) ?? defaultValue;
}
/**
* Set preference value
*/
setPreference(key, value) {
this.preferences.set(key, value);
this.eventBus.emit('preference:changed', { key, value });
// Auto-save if enabled
if (this.getPreference('autoSave', true)) {
this.debouncedSave();
}
}
/**
* Get multiple preferences
*/
getPreferences(keys) {
const result = {};
for (const key of keys) {
result[key] = this.preferences.get(key);
}
return result;
}
/**
* Set multiple preferences
*/
setPreferences(preferences) {
for (const [key, value] of Object.entries(preferences)) {
this.preferences.set(key, value);
}
this.eventBus.emit('preferences:changed', preferences);
if (this.getPreference('autoSave', true)) {
this.debouncedSave();
}
}
/**
* Get all user preferences
*/
getUserPreferences() {
return Object.fromEntries(this.preferences);
}
/**
* Get state value
*/
getState(key, defaultValue = null) {
return this.state.get(key) ?? defaultValue;
}
/**
* Set state value
*/
setState(key, value) {
this.state.set(key, value);
this.eventBus.emit('state:changed', { key, value });
if (this.getPreference('autoSave', true)) {
this.debouncedSave();
}
}
/**
* Get view state
*/
getViewState(viewId) {
return this.viewStates.get(viewId);
}
/**
* Set view state
*/
setViewState(viewId, state) {
const existing = this.viewStates.get(viewId) || {};
const newState = { ...existing, ...state, lastUpdate: Date.now() };
this.viewStates.set(viewId, newState);
this.eventBus.emit('view-state:changed', { viewId, state: newState });
if (this.getPreference('autoSave', true)) {
this.debouncedSave();
}
}
/**
* Clear view state
*/
clearViewState(viewId) {
this.viewStates.delete(viewId);
this.eventBus.emit('view-state:cleared', { viewId });
}
/**
* Get tool result
*/
getToolResult(toolName) {
return this.toolResults.get(toolName);
}
/**
* Set tool result
*/
setToolResult(toolName, result) {
const resultData = {
result,
timestamp: Date.now(),
tool: toolName,
};
this.toolResults.set(toolName, resultData);
this.eventBus.emit('tool-result:stored', { toolName, result });
// Keep only last 100 results to manage memory
if (this.toolResults.size > 100) {
const entries = Array.from(this.toolResults.entries());
entries.sort((a, b) => b[1].timestamp - a[1].timestamp);
this.toolResults.clear();
for (const [key, value] of entries.slice(0, 100)) {
this.toolResults.set(key, value);
}
}
if (this.getPreference('autoSave', true)) {
this.debouncedSave();
}
}
/**
* Get recent tool results
*/
getRecentToolResults(limit = 10) {
const results = Array.from(this.toolResults.entries())
.sort((a, b) => b[1].timestamp - a[1].timestamp)
.slice(0, limit);
return results.map(([tool, data]) => ({
tool,
...data,
}));
}
/**
* Get session data
*/
getSessionData(key) {
return this.sessionData.get(key);
}
/**
* Set session data
*/
setSessionData(key, value) {
this.sessionData.set(key, value);
this.eventBus.emit('session-data:changed', { key, value });
}
/**
* Clear session data
*/
clearSessionData() {
this.sessionData.clear();
this.eventBus.emit('session-data:cleared');
}
/**
* Export state data
*/
exportState() {
return {
timestamp: Date.now(),
version: '2.0.0',
preferences: Object.fromEntries(this.preferences),
viewStates: Object.fromEntries(this.viewStates),
toolResults: Object.fromEntries(this.toolResults),
sessionData: Object.fromEntries(this.sessionData),
state: Object.fromEntries(this.state),
};
}
/**
* Import state data
*/
importState(stateData) {
try {
this.restoreFromData(stateData);
this.eventBus.emit('state:imported', { timestamp: Date.now() });
console.log('💾 State imported successfully');
} catch (error) {
console.error('Failed to import state:', error);
throw error;
}
}
/**
* Reset to default state
*/
resetState() {
this.state.clear();
this.preferences.clear();
this.viewStates.clear();
this.toolResults.clear();
this.sessionData.clear();
this.initializeDefaultState();
this.persistState();
this.eventBus.emit('state:reset', { timestamp: Date.now() });
console.log('💾 State reset to defaults');
}
/**
* Clear specific data types
*/
clearData(types = []) {
if (types.includes('preferences') || types.length === 0) {
this.preferences.clear();
}
if (types.includes('viewStates') || types.length === 0) {
this.viewStates.clear();
}
if (types.includes('toolResults') || types.length === 0) {
this.toolResults.clear();
}
if (types.includes('sessionData') || types.length === 0) {
this.sessionData.clear();
}
this.eventBus.emit('data:cleared', { types, timestamp: Date.now() });
}
/**
* Get state statistics
*/
getStateStats() {
return {
preferences: this.preferences.size,
viewStates: this.viewStates.size,
toolResults: this.toolResults.size,
sessionData: this.sessionData.size,
generalState: this.state.size,
lastSave: this.state.get('lastSave'),
autoSaveEnabled: this.getPreference('autoSave', true),
};
}
/**
* Setup debounced save to avoid excessive writes
*/
debouncedSave() {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = setTimeout(() => {
this.persistState();
}, 1000); // Save after 1 second of inactivity
}
/**
* Persist all state immediately
*/
async persistAllState() {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
await this.persistState();
}
/**
* Setup event handlers
*/
setupEventHandlers() {
// Listen for shutdown events
this.eventBus.on('ui:shutdown', async () => {
await this.persistAllState();
});
// Listen for preference changes from UI
this.eventBus.on('ui:preference:set', (data) => {
this.setPreference(data.key, data.value);
});
// Listen for state changes from UI
this.eventBus.on('ui:state:set', (data) => {
this.setState(data.key, data.value);
});
// Listen for tool results
this.eventBus.on('tool:executed', (data) => {
this.setToolResult(data.tool, data.result);
});
}
/**
* Cleanup and shutdown
*/
async shutdown() {
// Clear auto-save timer
if (this.autoSaveTimer) {
clearInterval(this.autoSaveTimer);
}
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
// Final save
await this.persistAllState();
this.eventBus.emit('state-manager:shutdown');
}
}
export default StateManager;