Files
wifi-densepose/ui/utils/backend-detector.js
ruv b7e0f07e6e 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>
2026-02-28 14:37:29 -05:00

102 lines
2.9 KiB
JavaScript

// Backend Detection Utility
import { API_CONFIG } from '../config/api.config.js';
export class BackendDetector {
constructor() {
this.isBackendAvailable = null;
this.lastCheck = 0;
this.checkInterval = 30000; // Check every 30 seconds
this.sensingOnlyMode = false; // True when DensePose API is down, sensing WS is the only backend
}
// Check if the real backend is available
async checkBackendAvailability() {
const now = Date.now();
// Use cached result if recent
if (this.isBackendAvailable !== null && (now - this.lastCheck) < this.checkInterval) {
return this.isBackendAvailable;
}
try {
console.log('🔍 Checking backend availability...');
// Try to connect to the health endpoint with a short timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
const response = await fetch(`${API_CONFIG.BASE_URL}/health/live`, {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json'
}
});
clearTimeout(timeoutId);
if (response.ok) {
this.isBackendAvailable = true;
this.lastCheck = now;
console.log('✅ Real backend is available');
return true;
} else {
throw new Error(`Backend responded with status ${response.status}`);
}
} catch (error) {
this.isBackendAvailable = false;
this.lastCheck = now;
if (error.name === 'AbortError') {
console.log('⏱️ Backend check timed out - assuming unavailable');
} else {
console.log(`❌ Backend unavailable: ${error.message}`);
}
return false;
}
}
// Determine if mock server should be used
async shouldUseMockServer() {
// If mock is explicitly enabled, always use it
if (API_CONFIG.MOCK_SERVER.ENABLED) {
console.log('🧪 Using mock server (explicitly enabled)');
return true;
}
// If auto-detection is disabled, never use mock
if (!API_CONFIG.MOCK_SERVER.AUTO_DETECT) {
console.log('🔌 Using real backend (auto-detection disabled)');
return false;
}
// Check if backend is available
const backendAvailable = await this.checkBackendAvailability();
if (backendAvailable) {
console.log('🔌 Using real backend (detected and available)');
return false;
} else {
console.log('🧪 Using mock server (backend unavailable)');
return true;
}
}
// Get the appropriate base URL
async getBaseUrl() {
const useMock = await this.shouldUseMockServer();
return useMock ? window.location.origin : API_CONFIG.BASE_URL;
}
// Force a fresh check
forceCheck() {
this.isBackendAvailable = null;
this.lastCheck = 0;
}
}
// Create singleton instance
export const backendDetector = new BackendDetector();