/** * API Adapter for Flask Backend (android.py) * * This file provides a pywebview.api compatible interface that works with * the Flask REST API instead of the pywebview Python bridge. * * Include this BEFORE app.js when running on Android/Flask: * * */ (function() { 'use strict'; // API base URL - will be automatically set to current origin const API_BASE = window.location.origin; /** * Make API call to Flask backend */ async function apiCall(endpoint, data = null) { const url = `${API_BASE}/api/${endpoint}`; const options = { method: data ? 'POST' : 'GET', headers: { 'Content-Type': 'application/json', } }; if (data) { options.body = JSON.stringify(data); } try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); return result; } catch (error) { console.error(`API call failed: ${endpoint}`, error); throw error; } } /** * Create pywebview.api compatible interface */ window.pywebview = { api: { // ==================== Synthetic Data Generation ==================== generate_simplex_points: async function(n, d, k, seed) { return await apiCall('generate_simplex_points', { n, d, k, seed }); }, save_synthetic_dataset: async function(points) { return await apiCall('save_synthetic_dataset', { points }); }, // ==================== MNIST Dataset ==================== load_mnist: async function(max_samples, subset) { return await apiCall('load_mnist', { max_samples, subset }); }, // ==================== Upload Handling ==================== upload_csv: async function(name, content, delimiter) { return await apiCall('upload_csv', { name, content, delimiter }); }, upload_images: async function(files) { return await apiCall('upload_images', { files }); }, list_datasets: async function() { return await apiCall('list_datasets'); }, prepare_csv_dataset: async function(dataset_id, selected_columns, handle_missing) { return await apiCall('prepare_csv_dataset', { dataset_id, selected_columns, handle_missing }); }, // ==================== Embeddings ==================== compute_embeddings: async function(dataset_id, method) { return await apiCall('compute_embeddings', { dataset_id, method }); }, // ==================== t-SNE ==================== run_tsne: async function(dataset_id, perplexity, learning_rate, n_iter, early_exaggeration, momentum, init_method, init_data, seed) { return await apiCall('run_tsne', { dataset_id, perplexity, learning_rate, n_iter, early_exaggeration, momentum, init_method, init_data, seed }); }, stop_tsne: async function() { // Note: stop functionality needs to be implemented in Flask backend return { success: true }; }, // ==================== Clustering ==================== run_clustering: async function(dataset_id, method, k, eps, min_samples) { return await apiCall('run_clustering', { dataset_id, method, k, eps, min_samples }); }, // ==================== Export ==================== export_results: async function(dataset_id) { return await apiCall('export_results', { dataset_id }); }, get_image_at_index: async function(dataset_id, index) { return await apiCall('get_image_at_index', { dataset_id, index }); } }, // ==================== API Status ==================== /** * Check if we're running in Flask/Android mode */ isFlaskMode: function() { return true; }, /** * Check MCP connection status */ checkMCPStatus: async function() { try { const status = await apiCall('mcp_status'); return status; } catch (error) { return { connected: false, available: false }; } } }; // ==================== Progress Updates ==================== /** * Progress updates for t-SNE * In Flask mode, we use polling instead of callbacks */ let progressInterval = null; window.startProgressPolling = function() { if (progressInterval) { clearInterval(progressInterval); } // Poll for progress updates every 500ms progressInterval = setInterval(async () => { try { const progress = await apiCall('tsne_progress'); if (progress && progress.current !== undefined) { window.updateProgress(progress.current, progress.total, progress.message); // Stop polling when complete if (progress.current >= progress.total) { clearInterval(progressInterval); progressInterval = null; } } } catch (error) { // Silently fail if progress endpoint not available } }, 500); }; window.stopProgressPolling = function() { if (progressInterval) { clearInterval(progressInterval); progressInterval = null; } }; // ==================== Connection Status Indicator ==================== /** * Show connection status in UI */ async function showConnectionStatus() { try { const health = await apiCall('health'); const statusDiv = document.createElement('div'); statusDiv.id = 'connection-status'; statusDiv.style.cssText = ` position: fixed; top: 10px; right: 10px; padding: 8px 12px; background: ${health.mcp_connected ? '#10b981' : '#f59e0b'}; color: white; border-radius: 6px; font-size: 0.85em; font-weight: 600; z-index: 9999; box-shadow: 0 2px 8px rgba(0,0,0,0.2); `; statusDiv.innerHTML = health.mcp_connected ? '🟢 MCP Connected' : '🟡 Local Mode'; document.body.appendChild(statusDiv); // Add tooltip statusDiv.title = health.mcp_connected ? 'Connected to MCP server for heavy computations' : 'Using local fallback (computations may be slower)'; } catch (error) { console.error('Failed to check connection status:', error); } } // ==================== Initialization ==================== /** * Initialize Flask API adapter */ function initAdapter() { console.log('Flask API Adapter initialized'); console.log(`API Base URL: ${API_BASE}`); // Show connection status showConnectionStatus(); // Dispatch ready event window.dispatchEvent(new Event('pywebviewready')); } // Wait for DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAdapter); } else { initAdapter(); } // ==================== Helper Functions ==================== /** * Download file helper (for CSV export) */ window.downloadFile = function(content, filename, type) { const blob = new Blob([content], { type: type }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); }; })();