feat: Sensing-only UI mode with Gaussian splat visualization and Rust migration ADR
- Add Python WebSocket sensing server (ws_server.py) with ESP32 UDP CSI and Windows RSSI auto-detect collectors on port 8765 - Add Three.js Gaussian splat renderer with custom GLSL shaders for real-time WiFi signal field visualization (blue→green→red gradient) - Add SensingTab component with RSSI sparkline, feature meters, and motion classification badge - Add sensing.service.js WebSocket client with reconnect and simulation fallback - Implement sensing-only mode: suppress all DensePose API calls when FastAPI backend (port 8000) is not running, clean console output - ADR-019: Document sensing-only UI architecture and data flow - ADR-020: Migrate AI/model inference to Rust with RuVector ONNX Runtime, replacing ~2.7GB Python stack with ~50MB static binary - Add ruvnet/ruvector as upstream remote for RuVector crate ecosystem Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
44
ui/app.js
44
ui/app.js
@@ -4,6 +4,7 @@ import { TabManager } from './components/TabManager.js';
|
||||
import { DashboardTab } from './components/DashboardTab.js';
|
||||
import { HardwareTab } from './components/HardwareTab.js';
|
||||
import { LiveDemoTab } from './components/LiveDemoTab.js';
|
||||
import { SensingTab } from './components/SensingTab.js';
|
||||
import { apiService } from './services/api.service.js';
|
||||
import { wsService } from './services/websocket.service.js';
|
||||
import { healthService } from './services/health.service.js';
|
||||
@@ -65,16 +66,17 @@ class WiFiDensePoseApp {
|
||||
this.showBackendStatus('Mock server active - testing mode', 'warning');
|
||||
} else {
|
||||
console.log('🔌 Initializing with real backend');
|
||||
|
||||
|
||||
// Verify backend is actually working
|
||||
try {
|
||||
const health = await healthService.checkLiveness();
|
||||
console.log('✅ Backend is available and responding:', health);
|
||||
this.showBackendStatus('Connected to real backend', 'success');
|
||||
} catch (error) {
|
||||
console.error('❌ Backend check failed:', error);
|
||||
this.showBackendStatus('Backend connection failed', 'error');
|
||||
// Don't throw - let the app continue and retry later
|
||||
// DensePose API backend not running — sensing-only mode
|
||||
backendDetector.sensingOnlyMode = true;
|
||||
console.log('ℹ️ DensePose API not running — sensing-only mode via WebSocket on :8765');
|
||||
this.showBackendStatus('Sensing mode — live WiFi data via WebSocket', 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,33 +103,44 @@ class WiFiDensePoseApp {
|
||||
|
||||
// Initialize individual tab components
|
||||
initializeTabComponents() {
|
||||
// Skip DensePose-dependent tabs in sensing-only mode
|
||||
const sensingOnly = backendDetector.sensingOnlyMode;
|
||||
|
||||
// Dashboard tab
|
||||
const dashboardContainer = document.getElementById('dashboard');
|
||||
if (dashboardContainer) {
|
||||
this.components.dashboard = new DashboardTab(dashboardContainer);
|
||||
this.components.dashboard.init().catch(error => {
|
||||
console.error('Failed to initialize dashboard:', error);
|
||||
});
|
||||
if (!sensingOnly) {
|
||||
this.components.dashboard.init().catch(error => {
|
||||
console.error('Failed to initialize dashboard:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Hardware tab
|
||||
const hardwareContainer = document.getElementById('hardware');
|
||||
if (hardwareContainer) {
|
||||
this.components.hardware = new HardwareTab(hardwareContainer);
|
||||
this.components.hardware.init();
|
||||
if (!sensingOnly) this.components.hardware.init();
|
||||
}
|
||||
|
||||
// Live demo tab
|
||||
const demoContainer = document.getElementById('demo');
|
||||
if (demoContainer) {
|
||||
this.components.demo = new LiveDemoTab(demoContainer);
|
||||
this.components.demo.init();
|
||||
if (!sensingOnly) this.components.demo.init();
|
||||
}
|
||||
|
||||
// Sensing tab
|
||||
const sensingContainer = document.getElementById('sensing');
|
||||
if (sensingContainer) {
|
||||
this.components.sensing = new SensingTab(sensingContainer);
|
||||
}
|
||||
|
||||
// Architecture tab - static content, no component needed
|
||||
|
||||
|
||||
// Performance tab - static content, no component needed
|
||||
|
||||
|
||||
// Applications tab - static content, no component needed
|
||||
}
|
||||
|
||||
@@ -153,6 +166,15 @@ class WiFiDensePoseApp {
|
||||
case 'demo':
|
||||
// Demo starts manually
|
||||
break;
|
||||
|
||||
case 'sensing':
|
||||
// Lazy-init sensing tab on first visit
|
||||
if (this.components.sensing && !this.components.sensing.splatRenderer) {
|
||||
this.components.sensing.init().catch(error => {
|
||||
console.error('Failed to initialize sensing tab:', error);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user