Files
discord-fishbowl/admin-frontend/src/pages/SystemStatus.tsx
matt 1b586582d4 Implement comprehensive collaborative creative system with cross-character memory sharing
Major Features Added:
• Cross-character memory sharing with trust-based permissions (Basic 30%, Personal 50%, Intimate 70%, Full 90%)
• Complete collaborative creative projects system with MCP integration
• Database persistence for all creative project data with proper migrations
• Trust evolution system based on interaction quality and relationship development
• Memory sharing MCP server with 6 autonomous tools for character decision-making
• Creative projects MCP server with 8 tools for autonomous project management
• Enhanced character integration with all RAG and MCP capabilities
• Demo scripts showcasing memory sharing and creative collaboration workflows

System Integration:
• Main application now initializes memory sharing and creative managers
• Conversation engine upgraded to use EnhancedCharacter objects with full RAG access
• Database models added for creative projects, collaborators, contributions, and invitations
• Complete prompt construction pipeline enriched with RAG insights and trust data
• Characters can now autonomously propose projects, share memories, and collaborate creatively
2025-07-04 23:07:08 -07:00

671 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import {
Monitor,
Cpu,
HardDrive,
Database,
Wifi,
Play,
Pause,
Settings,
RefreshCw,
AlertTriangle,
CheckCircle,
XCircle,
Clock,
Activity,
Server,
BarChart3,
Sliders
} from 'lucide-react';
import { apiClient } from '../services/api';
import { useWebSocket } from '../contexts/WebSocketContext';
import LoadingSpinner from '../components/Common/LoadingSpinner';
import toast from 'react-hot-toast';
interface SystemStatus {
status: string;
uptime: string;
version: string;
database_status: string;
redis_status: string;
llm_service_status: string;
discord_bot_status: string;
active_processes: string[];
error_count: number;
warnings_count: number;
performance_metrics: {
avg_response_time: number;
requests_per_minute: number;
database_query_time: number;
};
resource_usage: {
cpu_percent: number;
memory_total_mb: number;
memory_used_mb: number;
memory_percent: number;
};
}
interface SystemConfig {
conversation_frequency: number;
response_delay_min: number;
response_delay_max: number;
personality_change_rate: number;
memory_retention_days: number;
max_conversation_length: number;
creativity_boost: boolean;
conflict_resolution_enabled: boolean;
safety_monitoring: boolean;
auto_moderation: boolean;
backup_frequency_hours: number;
}
interface LogEntry {
timestamp: string;
level: string;
component: string;
message: string;
metadata?: any;
}
const SystemStatus: React.FC = () => {
const [systemStatus, setSystemStatus] = useState<SystemStatus | null>(null);
const [systemConfig, setSystemConfig] = useState<SystemConfig | null>(null);
const [logs, setLogs] = useState<LogEntry[]>([]);
const [loading, setLoading] = useState(true);
const [configLoading, setConfigLoading] = useState(false);
const [showConfig, setShowConfig] = useState(false);
const [showLogs, setShowLogs] = useState(false);
const { connected } = useWebSocket();
useEffect(() => {
loadSystemData();
const interval = setInterval(loadSystemStatus, 30000); // Refresh every 30s
return () => clearInterval(interval);
}, []);
const loadSystemData = async () => {
await Promise.all([
loadSystemStatus(),
loadSystemConfig(),
loadSystemLogs()
]);
setLoading(false);
};
const loadSystemStatus = async () => {
try {
const response = await apiClient.getSystemStatus();
setSystemStatus(response.data);
} catch (error) {
console.error('Failed to load system status:', error);
// Fallback demo data
setSystemStatus({
status: 'running',
uptime: '2d 14h 32m',
version: '1.0.0',
database_status: 'healthy',
redis_status: 'healthy',
llm_service_status: 'healthy',
discord_bot_status: 'connected',
active_processes: ['main', 'conversation_engine', 'scheduler', 'admin_interface'],
error_count: 0,
warnings_count: 2,
performance_metrics: {
avg_response_time: 2.5,
requests_per_minute: 30,
database_query_time: 0.05
},
resource_usage: {
cpu_percent: 15.3,
memory_total_mb: 8192,
memory_used_mb: 3420,
memory_percent: 41.7
}
});
}
};
const loadSystemConfig = async () => {
try {
const response = await apiClient.getSystemConfig();
setSystemConfig(response.data);
} catch (error) {
console.error('Failed to load system config:', error);
// Fallback demo data
setSystemConfig({
conversation_frequency: 0.5,
response_delay_min: 1.0,
response_delay_max: 5.0,
personality_change_rate: 0.1,
memory_retention_days: 90,
max_conversation_length: 50,
creativity_boost: true,
conflict_resolution_enabled: true,
safety_monitoring: true,
auto_moderation: false,
backup_frequency_hours: 24
});
}
};
const loadSystemLogs = async () => {
try {
const response = await apiClient.getSystemLogs(50);
setLogs(response.data);
} catch (error) {
console.error('Failed to load system logs:', error);
// Fallback demo data
setLogs([
{
timestamp: new Date().toISOString(),
level: 'INFO',
component: 'conversation_engine',
message: 'Character Alex initiated conversation with Sage'
},
{
timestamp: new Date(Date.now() - 60000).toISOString(),
level: 'DEBUG',
component: 'memory_system',
message: 'Memory consolidation completed for Luna'
},
{
timestamp: new Date(Date.now() - 120000).toISOString(),
level: 'WARN',
component: 'scheduler',
message: 'High memory usage detected'
}
]);
}
};
const handleSystemAction = async (action: 'pause' | 'resume') => {
try {
if (action === 'pause') {
await apiClient.pauseSystem();
toast.success('System paused successfully');
} else {
await apiClient.resumeSystem();
toast.success('System resumed successfully');
}
await loadSystemStatus();
} catch (error) {
toast.error(`Failed to ${action} system`);
}
};
const handleConfigUpdate = async (updatedConfig: Partial<SystemConfig>) => {
try {
setConfigLoading(true);
await apiClient.updateSystemConfig(updatedConfig);
setSystemConfig(prev => prev ? { ...prev, ...updatedConfig } : null);
toast.success('Configuration updated successfully');
} catch (error) {
toast.error('Failed to update configuration');
} finally {
setConfigLoading(false);
}
};
const getStatusIcon = (status: string) => {
switch (status.toLowerCase()) {
case 'healthy':
case 'connected':
case 'running':
return <CheckCircle className="w-5 h-5 text-green-500" />;
case 'warning':
case 'paused':
return <AlertTriangle className="w-5 h-5 text-yellow-500" />;
case 'error':
case 'disconnected':
return <XCircle className="w-5 h-5 text-red-500" />;
default:
return <Monitor className="w-5 h-5 text-gray-500" />;
}
};
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'healthy':
case 'connected':
case 'running':
return 'text-green-600';
case 'warning':
case 'paused':
return 'text-yellow-600';
case 'error':
case 'disconnected':
return 'text-red-600';
default:
return 'text-gray-600';
}
};
const getLevelColor = (level: string) => {
switch (level.toUpperCase()) {
case 'ERROR':
return 'text-red-600 bg-red-50';
case 'WARN':
return 'text-yellow-600 bg-yellow-50';
case 'INFO':
return 'text-blue-600 bg-blue-50';
case 'DEBUG':
return 'text-gray-600 bg-gray-50';
default:
return 'text-gray-600 bg-gray-50';
}
};
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<LoadingSpinner size="lg" text="Loading system status..." />
</div>
);
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">System Status</h1>
<p className="text-gray-600">Monitor system health and performance</p>
</div>
<div className="flex items-center space-x-2">
<div className={`flex items-center space-x-2 px-3 py-1 rounded-full ${connected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
<div className={`w-2 h-2 rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`}></div>
<span className="text-sm font-medium">
{connected ? 'Real-time Connected' : 'Disconnected'}
</span>
</div>
<button
onClick={loadSystemStatus}
className="btn-secondary"
title="Refresh status"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
</div>
{/* System Overview */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="metric-card">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">System Status</p>
<div className="flex items-center space-x-2 mt-1">
{getStatusIcon(systemStatus?.status || 'unknown')}
<p className={`text-lg font-bold capitalize ${getStatusColor(systemStatus?.status || 'unknown')}`}>
{systemStatus?.status || 'Unknown'}
</p>
</div>
</div>
<Monitor className="w-8 h-8 text-blue-500" />
</div>
</div>
<div className="metric-card">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Uptime</p>
<p className="text-lg font-bold text-gray-900">{systemStatus?.uptime || 'Unknown'}</p>
</div>
<Clock className="w-8 h-8 text-green-500" />
</div>
</div>
<div className="metric-card">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Errors</p>
<p className="text-lg font-bold text-red-600">{systemStatus?.error_count || 0}</p>
</div>
<XCircle className="w-8 h-8 text-red-500" />
</div>
</div>
<div className="metric-card">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Warnings</p>
<p className="text-lg font-bold text-yellow-600">{systemStatus?.warnings_count || 0}</p>
</div>
<AlertTriangle className="w-8 h-8 text-yellow-500" />
</div>
</div>
</div>
{/* System Controls */}
<div className="card">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900">System Controls</h3>
<div className="flex space-x-2">
<button
onClick={() => setShowConfig(!showConfig)}
className="btn-secondary"
>
<Settings className="w-4 h-4 mr-2" />
Configuration
</button>
<button
onClick={() => handleSystemAction(systemStatus?.status === 'paused' ? 'resume' : 'pause')}
className={systemStatus?.status === 'paused' ? 'btn-primary' : 'btn-secondary'}
>
{systemStatus?.status === 'paused' ? (
<>
<Play className="w-4 h-4 mr-2" />
Resume System
</>
) : (
<>
<Pause className="w-4 h-4 mr-2" />
Pause System
</>
)}
</button>
</div>
</div>
{/* Service Status */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-2">
<Database className="w-4 h-4 text-gray-600" />
<span className="text-sm font-medium">Database</span>
</div>
<div className="flex items-center space-x-1">
{getStatusIcon(systemStatus?.database_status || 'unknown')}
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.database_status || 'unknown')}`}>
{systemStatus?.database_status || 'Unknown'}
</span>
</div>
</div>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-2">
<Server className="w-4 h-4 text-gray-600" />
<span className="text-sm font-medium">Redis</span>
</div>
<div className="flex items-center space-x-1">
{getStatusIcon(systemStatus?.redis_status || 'unknown')}
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.redis_status || 'unknown')}`}>
{systemStatus?.redis_status || 'Unknown'}
</span>
</div>
</div>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-2">
<Activity className="w-4 h-4 text-gray-600" />
<span className="text-sm font-medium">LLM Service</span>
</div>
<div className="flex items-center space-x-1">
{getStatusIcon(systemStatus?.llm_service_status || 'unknown')}
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.llm_service_status || 'unknown')}`}>
{systemStatus?.llm_service_status || 'Unknown'}
</span>
</div>
</div>
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center space-x-2">
<Wifi className="w-4 h-4 text-gray-600" />
<span className="text-sm font-medium">Discord Bot</span>
</div>
<div className="flex items-center space-x-1">
{getStatusIcon(systemStatus?.discord_bot_status || 'unknown')}
<span className={`text-sm capitalize ${getStatusColor(systemStatus?.discord_bot_status || 'unknown')}`}>
{systemStatus?.discord_bot_status || 'Unknown'}
</span>
</div>
</div>
</div>
</div>
{/* Resource Usage */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="card">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Resource Usage</h3>
<div className="space-y-4">
<div>
<div className="flex items-center justify-between text-sm mb-2">
<div className="flex items-center space-x-2">
<Cpu className="w-4 h-4 text-gray-600" />
<span className="text-gray-600">CPU Usage</span>
</div>
<span className="font-medium">{systemStatus?.resource_usage?.cpu_percent?.toFixed(1) || 0}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: `${systemStatus?.resource_usage?.cpu_percent || 0}%` }}
></div>
</div>
</div>
<div>
<div className="flex items-center justify-between text-sm mb-2">
<div className="flex items-center space-x-2">
<HardDrive className="w-4 h-4 text-gray-600" />
<span className="text-gray-600">Memory Usage</span>
</div>
<span className="font-medium">
{systemStatus?.resource_usage?.memory_used_mb || 0}MB / {systemStatus?.resource_usage?.memory_total_mb || 0}MB
({systemStatus?.resource_usage?.memory_percent?.toFixed(1) || 0}%)
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-green-500 h-2 rounded-full"
style={{ width: `${systemStatus?.resource_usage?.memory_percent || 0}%` }}
></div>
</div>
</div>
</div>
</div>
<div className="card">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Performance Metrics</h3>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">Avg Response Time</span>
<span className="font-medium">{systemStatus?.performance_metrics?.avg_response_time?.toFixed(1) || 0}s</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">Requests/Min</span>
<span className="font-medium">{systemStatus?.performance_metrics?.requests_per_minute || 0}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">DB Query Time</span>
<span className="font-medium">{systemStatus?.performance_metrics?.database_query_time?.toFixed(3) || 0}s</span>
</div>
</div>
</div>
</div>
{/* Configuration Panel */}
{showConfig && systemConfig && (
<div className="card">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900">System Configuration</h3>
<button
onClick={() => setShowConfig(false)}
className="text-gray-400 hover:text-gray-600"
>
×
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Conversation Frequency
</label>
<input
type="range"
min="0.1"
max="1.0"
step="0.1"
value={systemConfig.conversation_frequency}
onChange={(e) => handleConfigUpdate({ conversation_frequency: parseFloat(e.target.value) })}
className="w-full"
disabled={configLoading}
/>
<span className="text-xs text-gray-500">{systemConfig.conversation_frequency}</span>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Memory Retention (Days)
</label>
<input
type="number"
min="1"
max="365"
value={systemConfig.memory_retention_days}
onChange={(e) => handleConfigUpdate({ memory_retention_days: parseInt(e.target.value) })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-primary-500"
disabled={configLoading}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Max Conversation Length
</label>
<input
type="number"
min="10"
max="200"
value={systemConfig.max_conversation_length}
onChange={(e) => handleConfigUpdate({ max_conversation_length: parseInt(e.target.value) })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-1 focus:ring-primary-500"
disabled={configLoading}
/>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-gray-700">Creativity Boost</label>
<button
onClick={() => handleConfigUpdate({ creativity_boost: !systemConfig.creativity_boost })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
systemConfig.creativity_boost ? 'bg-primary-600' : 'bg-gray-200'
}`}
disabled={configLoading}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
systemConfig.creativity_boost ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-gray-700">Conflict Resolution</label>
<button
onClick={() => handleConfigUpdate({ conflict_resolution_enabled: !systemConfig.conflict_resolution_enabled })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
systemConfig.conflict_resolution_enabled ? 'bg-primary-600' : 'bg-gray-200'
}`}
disabled={configLoading}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
systemConfig.conflict_resolution_enabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-gray-700">Safety Monitoring</label>
<button
onClick={() => handleConfigUpdate({ safety_monitoring: !systemConfig.safety_monitoring })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
systemConfig.safety_monitoring ? 'bg-primary-600' : 'bg-gray-200'
}`}
disabled={configLoading}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
systemConfig.safety_monitoring ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-gray-700">Auto Moderation</label>
<button
onClick={() => handleConfigUpdate({ auto_moderation: !systemConfig.auto_moderation })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
systemConfig.auto_moderation ? 'bg-primary-600' : 'bg-gray-200'
}`}
disabled={configLoading}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
systemConfig.auto_moderation ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
</div>
</div>
</div>
)}
{/* System Logs */}
<div className="card">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900">System Logs</h3>
<div className="flex space-x-2">
<button
onClick={() => setShowLogs(!showLogs)}
className="btn-secondary"
>
<BarChart3 className="w-4 h-4 mr-2" />
{showLogs ? 'Hide Logs' : 'Show Logs'}
</button>
<button
onClick={loadSystemLogs}
className="btn-secondary"
>
<RefreshCw className="w-4 h-4" />
</button>
</div>
</div>
{showLogs && (
<div className="space-y-2 max-h-64 overflow-y-auto">
{logs.map((log, index) => (
<div key={index} className="flex items-start space-x-3 p-2 hover:bg-gray-50 rounded">
<span className={`px-2 py-1 text-xs font-medium rounded ${getLevelColor(log.level)}`}>
{log.level}
</span>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2 text-sm">
<span className="font-medium text-gray-900">{log.component}</span>
<span className="text-gray-500">
{new Date(log.timestamp).toLocaleTimeString()}
</span>
</div>
<p className="text-sm text-gray-600 mt-1">{log.message}</p>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
};
export default SystemStatus;