wifi-densepose/vendor/midstream/npm/src/dashboard.ts

443 lines
12 KiB
TypeScript

/**
* MidStream Real-Time Dashboard
*
* Minimal console-based dashboard with WASM and streaming support
* for real-time display of MidStream introspection capabilities.
*
* Created by rUv
*/
import { MidStreamAgent, AnalysisResult } from './agent.js';
import { OpenAIRealtimeClient } from './openai-realtime.js';
import chalk from 'chalk';
import * as readline from 'readline';
// ============================================================================
// Dashboard State Management
// ============================================================================
interface DashboardState {
messageCount: number;
totalTokens: number;
patternsDetected: string[];
attractorType: string;
lyapunovExponent: number;
isStable: boolean;
isChaotic: boolean;
avgReward: number;
recentMessages: string[];
audioStreaming: boolean;
videoStreaming: boolean;
lastUpdate: Date;
fps: number;
latency: number;
}
interface StreamMetrics {
type: 'audio' | 'video' | 'text';
bytesProcessed: number;
chunksReceived: number;
avgChunkSize: number;
startTime: number;
lastChunkTime: number;
}
// ============================================================================
// Dashboard Class
// ============================================================================
export class MidStreamDashboard {
private agent: MidStreamAgent;
private state: DashboardState;
private streamMetrics: Map<string, StreamMetrics>;
private updateInterval: NodeJS.Timeout | null = null;
private startTime: number;
private frameCount: number = 0;
constructor() {
this.agent = new MidStreamAgent({
maxHistory: 1000,
embeddingDim: 3,
});
this.state = {
messageCount: 0,
totalTokens: 0,
patternsDetected: [],
attractorType: 'unknown',
lyapunovExponent: 0,
isStable: true,
isChaotic: false,
avgReward: 0,
recentMessages: [],
audioStreaming: false,
videoStreaming: false,
lastUpdate: new Date(),
fps: 0,
latency: 0,
};
this.streamMetrics = new Map();
this.startTime = Date.now();
}
// ==========================================================================
// Core Dashboard Methods
// ==========================================================================
/**
* Start the dashboard with real-time updates
*/
start(refreshRate: number = 100): void {
this.clearScreen();
this.render();
this.updateInterval = setInterval(() => {
this.frameCount++;
this.state.fps = Math.round(this.frameCount / ((Date.now() - this.startTime) / 1000));
this.clearScreen();
this.render();
}, refreshRate);
}
/**
* Stop the dashboard
*/
stop(): void {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
/**
* Process a message and update dashboard state
*/
processMessage(message: string, tokens: number = 0): void {
const startTime = Date.now();
// Process with agent
this.agent.processMessage(message);
// Update state
this.state.messageCount++;
this.state.totalTokens += tokens;
this.state.recentMessages.unshift(message.substring(0, 50) + '...');
if (this.state.recentMessages.length > 5) {
this.state.recentMessages.pop();
}
// Get analysis
const analysis = this.agent.getStatus();
this.updateFromAnalysis(analysis);
// Calculate latency
this.state.latency = Date.now() - startTime;
this.state.lastUpdate = new Date();
}
/**
* Process streaming data (audio/video/text)
*/
processStream(streamId: string, data: Buffer, type: 'audio' | 'video' | 'text'): void {
let metrics = this.streamMetrics.get(streamId);
if (!metrics) {
metrics = {
type,
bytesProcessed: 0,
chunksReceived: 0,
avgChunkSize: 0,
startTime: Date.now(),
lastChunkTime: Date.now(),
};
this.streamMetrics.set(streamId, metrics);
}
// Update metrics
metrics.bytesProcessed += data.length;
metrics.chunksReceived++;
metrics.avgChunkSize = Math.round(metrics.bytesProcessed / metrics.chunksReceived);
metrics.lastChunkTime = Date.now();
// Update state
if (type === 'audio') {
this.state.audioStreaming = true;
} else if (type === 'video') {
this.state.videoStreaming = true;
}
}
/**
* Update dashboard from analysis results
*/
private updateFromAnalysis(analysis: AnalysisResult): void {
this.state.patternsDetected = analysis.patterns
.slice(0, 5)
.map((p: any) => `${p.type || 'pattern'} (${p.confidence || 0}%)`);
if (analysis.temporalAnalysis) {
this.state.attractorType = analysis.temporalAnalysis.attractorType || 'unknown';
this.state.lyapunovExponent = analysis.temporalAnalysis.lyapunovExponent || 0;
this.state.isStable = analysis.temporalAnalysis.isStable || false;
this.state.isChaotic = analysis.temporalAnalysis.isChaotic || false;
}
if (analysis.metaLearning) {
this.state.avgReward = analysis.metaLearning.avgReward || 0;
}
}
// ==========================================================================
// Rendering Methods
// ==========================================================================
/**
* Clear the console screen
*/
private clearScreen(): void {
process.stdout.write('\x1b[2J\x1b[0f');
}
/**
* Render the dashboard
*/
private render(): void {
const width = process.stdout.columns || 80;
const separator = '─'.repeat(width);
console.log(chalk.bold.cyan('╔' + '═'.repeat(width - 2) + '╗'));
console.log(
chalk.bold.cyan('║') +
this.centerText('MidStream Real-Time Dashboard', width - 2) +
chalk.bold.cyan('║')
);
console.log(
chalk.bold.cyan('║') +
this.centerText('Created by rUv', width - 2) +
chalk.bold.cyan('║')
);
console.log(chalk.bold.cyan('╚' + '═'.repeat(width - 2) + '╝'));
// System Metrics
this.renderSection('System Metrics', [
`Messages Processed: ${chalk.green(this.state.messageCount)}`,
`Total Tokens: ${chalk.green(this.state.totalTokens)}`,
`FPS: ${chalk.yellow(this.state.fps)}`,
`Latency: ${chalk.yellow(this.state.latency + 'ms')}`,
`Uptime: ${chalk.cyan(this.formatUptime())}`,
]);
// Temporal Analysis
this.renderSection('Temporal Analysis', [
`Attractor Type: ${this.colorizeAttractor(this.state.attractorType)}`,
`Lyapunov Exp: ${this.colorizeLyapunov(this.state.lyapunovExponent)}`,
`Stability: ${this.state.isStable ? chalk.green('STABLE') : chalk.red('UNSTABLE')}`,
`Chaos: ${this.state.isChaotic ? chalk.red('CHAOTIC') : chalk.green('ORDERED')}`,
`Avg Reward: ${chalk.cyan(this.state.avgReward.toFixed(3))}`,
]);
// Pattern Detection
this.renderSection(
'Pattern Detection',
this.state.patternsDetected.length > 0
? this.state.patternsDetected.map((p) => chalk.magenta('• ' + p))
: [chalk.gray('No patterns detected yet')]
);
// Streaming Status
this.renderStreamingStatus();
// Recent Messages
this.renderSection(
'Recent Messages',
this.state.recentMessages.length > 0
? this.state.recentMessages.map((m) => chalk.gray('• ' + m))
: [chalk.gray('No messages yet')]
);
// Stream Metrics
this.renderStreamMetrics();
// Footer
console.log(chalk.gray(separator));
console.log(
chalk.gray(
`Last Update: ${this.state.lastUpdate.toLocaleTimeString()} | Press Ctrl+C to exit`
)
);
}
/**
* Render a section with title and content
*/
private renderSection(title: string, lines: string[]): void {
const width = process.stdout.columns || 80;
console.log('\n' + chalk.bold.white(title));
console.log(chalk.gray('─'.repeat(width)));
lines.forEach((line) => console.log(line));
}
/**
* Render streaming status
*/
private renderStreamingStatus(): void {
const audioStatus = this.state.audioStreaming
? chalk.green('● ACTIVE')
: chalk.gray('○ INACTIVE');
const videoStatus = this.state.videoStreaming
? chalk.green('● ACTIVE')
: chalk.gray('○ INACTIVE');
this.renderSection('Streaming Status', [
`Audio: ${audioStatus}`,
`Video: ${videoStatus}`,
`Streams: ${chalk.cyan(this.streamMetrics.size)} active`,
]);
}
/**
* Render stream metrics
*/
private renderStreamMetrics(): void {
if (this.streamMetrics.size === 0) {
return;
}
const metrics: string[] = [];
this.streamMetrics.forEach((metric, streamId) => {
const duration = (Date.now() - metric.startTime) / 1000;
const rate = (metric.bytesProcessed / duration / 1024).toFixed(2);
metrics.push(
chalk.cyan(`${streamId} (${metric.type}):`) +
` ${chalk.yellow(metric.chunksReceived)} chunks, ` +
`${chalk.yellow(this.formatBytes(metric.bytesProcessed))}, ` +
`${chalk.yellow(rate)} KB/s`
);
});
this.renderSection('Stream Metrics', metrics);
}
// ==========================================================================
// Helper Methods
// ==========================================================================
/**
* Center text within a given width
*/
private centerText(text: string, width: number): string {
const padding = Math.max(0, width - text.length);
const leftPad = Math.floor(padding / 2);
const rightPad = padding - leftPad;
return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
}
/**
* Format uptime
*/
private formatUptime(): string {
const seconds = Math.floor((Date.now() - this.startTime) / 1000);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${hours}h ${minutes}m ${secs}s`;
}
/**
* Format bytes
*/
private formatBytes(bytes: number): string {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB';
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
}
/**
* Colorize attractor type
*/
private colorizeAttractor(type: string): string {
const colors: Record<string, any> = {
fixed: chalk.green,
periodic: chalk.blue,
chaotic: chalk.red,
unknown: chalk.gray,
};
const color = colors[type] || chalk.white;
return color(type.toUpperCase());
}
/**
* Colorize Lyapunov exponent
*/
private colorizeLyapunov(value: number): string {
const str = value.toFixed(4);
if (value < 0) return chalk.green(str);
if (value > 0) return chalk.red(str);
return chalk.yellow(str);
}
/**
* Get current agent
*/
getAgent(): MidStreamAgent {
return this.agent;
}
/**
* Get current state
*/
getState(): DashboardState {
return { ...this.state };
}
}
// ============================================================================
// Interactive Dashboard
// ============================================================================
export class InteractiveDashboard extends MidStreamDashboard {
private rl: readline.Interface | null = null;
/**
* Start interactive mode with user input
*/
startInteractive(): void {
this.start(100);
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// Setup readline to capture input without blocking display
process.stdin.on('keypress', (str, key) => {
if (key.ctrl && key.name === 'c') {
this.stop();
if (this.rl) {
this.rl.close();
}
process.exit(0);
}
});
}
/**
* Stop interactive mode
*/
stopInteractive(): void {
this.stop();
if (this.rl) {
this.rl.close();
this.rl = null;
}
}
}
// ============================================================================
// Exports
// ============================================================================
export { DashboardState, StreamMetrics };