This commit is contained in:
rUv
2025-06-07 13:55:28 +00:00
parent 6dd89f2ada
commit 7b5df5c077
11 changed files with 713 additions and 141 deletions

View File

@@ -34,6 +34,12 @@ ui/
## 🚀 Features
### Smart Backend Detection
- **Automatic Detection**: Automatically detects if your FastAPI backend is running
- **Real Backend Priority**: Always uses the real backend when available
- **Mock Fallback**: Falls back to mock server only when backend is unavailable
- **Testing Mode**: Can force mock mode for testing and development
### Real-time Dashboard
- Live system health monitoring
- Real-time pose detection statistics
@@ -281,6 +287,14 @@ apiService.addResponseInterceptor(async (response, url) => {
## 🚀 Deployment
### Development
**Option 1: Use the startup script**
```bash
cd /workspaces/wifi-densepose/ui
./start-ui.sh
```
**Option 2: Manual setup**
```bash
# First, start your FastAPI backend (runs on port 8000)
wifi-densepose start
@@ -294,9 +308,14 @@ python -m http.server 3000
npx http-server . -p 3000
# Open the UI at http://localhost:3000
# The UI will connect to the FastAPI backend at http://localhost:8000
# The UI will automatically detect and connect to your backend
```
### Backend Detection Behavior
- **Real Backend Available**: UI connects to `http://localhost:8000` and shows ✅ "Connected to real backend"
- **Backend Unavailable**: UI automatically uses mock server and shows ⚠️ "Mock server active - testing mode"
- **Force Mock Mode**: Set `API_CONFIG.MOCK_SERVER.ENABLED = true` for testing
### Production
1. Configure `API_CONFIG.BASE_URL` for your backend
2. Set up HTTPS for WebSocket connections

View File

@@ -7,6 +7,7 @@ import { LiveDemoTab } from './components/LiveDemoTab.js';
import { apiService } from './services/api.service.js';
import { wsService } from './services/websocket.service.js';
import { healthService } from './services/health.service.js';
import { backendDetector } from './utils/backend-detector.js';
class WiFiDensePoseApp {
constructor() {
@@ -51,13 +52,30 @@ class WiFiDensePoseApp {
return response;
});
// Check API availability
try {
const health = await healthService.checkLiveness();
console.log('API is available:', health);
} catch (error) {
console.error('API is not available:', error);
throw new Error('API is not available. Please ensure the backend is running.');
// Detect backend availability and initialize accordingly
const useMock = await backendDetector.shouldUseMockServer();
if (useMock) {
console.log('🧪 Initializing with mock server for testing');
// Import and start mock server only when needed
const { mockServer } = await import('./utils/mock-server.js');
mockServer.start();
// Show notification to user
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
}
}
}
@@ -195,6 +213,28 @@ class WiFiDensePoseApp {
});
}
// Show backend status notification
showBackendStatus(message, type) {
// Create status notification if it doesn't exist
let statusToast = document.getElementById('backendStatusToast');
if (!statusToast) {
statusToast = document.createElement('div');
statusToast.id = 'backendStatusToast';
statusToast.className = 'backend-status-toast';
document.body.appendChild(statusToast);
}
statusToast.textContent = message;
statusToast.className = `backend-status-toast ${type}`;
statusToast.classList.add('show');
// Auto-hide success messages, keep warnings and errors longer
const timeout = type === 'success' ? 3000 : 8000;
setTimeout(() => {
statusToast.classList.remove('show');
}, timeout);
}
// Show global error message
showGlobalError(message) {
// Create error toast if it doesn't exist

View File

@@ -6,6 +6,12 @@ export const API_CONFIG = {
WS_PREFIX: 'ws://',
WSS_PREFIX: 'wss://',
// Mock server configuration (only for testing)
MOCK_SERVER: {
ENABLED: false, // Set to true only for testing without backend
AUTO_DETECT: true, // Automatically detect if backend is available
},
// API Endpoints
ENDPOINTS: {
// Root & Info

View File

@@ -1,6 +1,7 @@
// API Service for WiFi-DensePose UI
import { API_CONFIG, buildApiUrl } from '../config/api.config.js';
import { backendDetector } from '../utils/backend-detector.js';
export class ApiService {
constructor() {
@@ -69,8 +70,15 @@ export class ApiService {
// Process request through interceptors
const processed = await this.processRequest(url, options);
// Determine the correct base URL (real backend vs mock)
let finalUrl = processed.url;
if (processed.url.startsWith(API_CONFIG.BASE_URL)) {
const baseUrl = await backendDetector.getBaseUrl();
finalUrl = processed.url.replace(API_CONFIG.BASE_URL, baseUrl);
}
// Make the request
const response = await fetch(processed.url, {
const response = await fetch(finalUrl, {
...processed.options,
headers: this.getHeaders(processed.options.headers)
});

View File

@@ -1,6 +1,7 @@
// WebSocket Service for WiFi-DensePose UI
import { API_CONFIG, buildWsUrl } from '../config/api.config.js';
import { backendDetector } from '../utils/backend-detector.js';
export class WebSocketService {
constructor() {
@@ -10,8 +11,18 @@ export class WebSocketService {
}
// Connect to WebSocket endpoint
connect(endpoint, params = {}, handlers = {}) {
const url = buildWsUrl(endpoint, params);
async connect(endpoint, params = {}, handlers = {}) {
// Determine if we should use mock WebSockets
const useMock = await backendDetector.shouldUseMockServer();
let url;
if (useMock) {
// Use mock WebSocket URL (served from same origin as UI)
url = buildWsUrl(endpoint, params).replace('localhost:8000', window.location.host);
} else {
// Use real backend WebSocket URL
url = buildWsUrl(endpoint, params);
}
// Check if already connected
if (this.connections.has(url)) {

View File

@@ -1573,6 +1573,42 @@ canvas {
opacity: 1;
}
/* Backend status toast */
.backend-status-toast {
position: fixed;
top: var(--space-24);
right: var(--space-24);
padding: var(--space-12) var(--space-20);
border-radius: var(--radius-base);
box-shadow: var(--shadow-lg);
transform: translateY(-100%);
opacity: 0;
transition: all 0.3s ease;
z-index: 1001;
font-weight: var(--font-weight-medium);
font-size: var(--font-size-sm);
}
.backend-status-toast.show {
transform: translateY(0);
opacity: 1;
}
.backend-status-toast.success {
background: var(--color-success);
color: white;
}
.backend-status-toast.warning {
background: var(--color-warning);
color: white;
}
.backend-status-toast.error {
background: var(--color-error);
color: white;
}
/* Tab badge */
.tab-badge {
display: inline-block;

View File

@@ -95,8 +95,8 @@
<!-- Test Controls Panel -->
<div class="test-controls">
<h3>Integration Tests</h3>
<button class="test-button" onclick="startMockServer()">Start Mock Server</button>
<button class="test-button danger" onclick="stopMockServer()">Stop Mock Server</button>
<button class="test-button" onclick="toggleMockMode()">Toggle Mock Mode</button>
<button class="test-button" onclick="checkBackendStatus()">Check Backend Status</button>
<button class="test-button" onclick="testHealthAPI()">Test Health API</button>
<button class="test-button" onclick="testPoseAPI()">Test Pose API</button>
<button class="test-button" onclick="testWebSocketStream()">Test WebSocket</button>
@@ -376,34 +376,60 @@
<script type="module">
import { mockServer } from '../utils/mock-server.js';
import { WiFiDensePoseApp } from '../app.js';
import { API_CONFIG } from '../config/api.config.js';
import { backendDetector } from '../utils/backend-detector.js';
// Global test functions
window.mockServer = mockServer;
window.app = null;
window.startMockServer = () => {
window.toggleMockMode = async () => {
try {
mockServer.start();
updateMockIndicator(true);
showTestStatus('Mock server started successfully', 'success');
// Toggle mock mode
API_CONFIG.MOCK_SERVER.ENABLED = !API_CONFIG.MOCK_SERVER.ENABLED;
// Initialize app if not already done
// Force backend detector to recheck
backendDetector.forceCheck();
if (API_CONFIG.MOCK_SERVER.ENABLED) {
mockServer.start();
updateMockIndicator(true);
showTestStatus('Mock mode enabled - using test data', 'success');
} else {
mockServer.stop();
updateMockIndicator(false);
showTestStatus('Mock mode disabled - using real backend', 'info');
}
// Reinitialize app with new configuration
if (!window.app) {
window.app = new WiFiDensePoseApp();
window.app.init();
await window.app.init();
}
} catch (error) {
showTestStatus(`Failed to start mock server: ${error.message}`, 'error');
showTestStatus(`Failed to toggle mock mode: ${error.message}`, 'error');
}
};
window.stopMockServer = () => {
window.checkBackendStatus = async () => {
try {
mockServer.stop();
updateMockIndicator(false);
showTestStatus('Mock server stopped', 'info');
showTestStatus('Checking backend status...', 'info');
const isAvailable = await backendDetector.checkBackendAvailability();
const useMock = await backendDetector.shouldUseMockServer();
if (isAvailable && !useMock) {
showTestStatus('✅ Real backend is available and being used', 'success');
updateMockIndicator(false);
} else if (useMock) {
showTestStatus('🧪 Using mock server (testing mode)', 'success');
updateMockIndicator(true);
} else {
showTestStatus('❌ Backend unavailable, mock server available', 'error');
updateMockIndicator(false);
}
} catch (error) {
showTestStatus(`Failed to stop mock server: ${error.message}`, 'error');
showTestStatus(`Backend check failed: ${error.message}`, 'error');
}
};
@@ -537,9 +563,15 @@
}
}
// Auto-start mock server on load
document.addEventListener('DOMContentLoaded', () => {
startMockServer();
// Auto-check backend status on load
document.addEventListener('DOMContentLoaded', async () => {
await checkBackendStatus();
// Initialize app
if (!window.app) {
window.app = new WiFiDensePoseApp();
await window.app.init();
}
});
</script>
</body>

View File

@@ -0,0 +1,101 @@
// 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
}
// 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();