Download brain/frontend/api.js from MouleeswaranM/FairRelay: direct link, hf CLI and curl.
- Browser
- Download file 21.9 kB
-
https://huggingface.co/MouleeswaranM/FairRelay/resolve/refs%2Fpr%2F21/brain/frontend/api.js
- Command line
-
hf download hf://MouleeswaranM/FairRelay@refs/pr/21/brain/frontend/api.js
-
curl -L -o api.js https://huggingface.co/MouleeswaranM/FairRelay/resolve/refs%2Fpr%2F21/brain/frontend/api.js
21.9 kB
| /** | |
| * API Integration Module | |
| * Handles communication with the Fair Dispatch backend | |
| * Real-time data fetching with polling support | |
| */ | |
| const API = { | |
| BASE_URL: 'http://localhost:8000', | |
| API_PREFIX: '/api/v1', | |
| // Polling interval in milliseconds - increased to reduce server load | |
| POLL_INTERVAL: 10000, // 10 seconds for routine checks | |
| FAST_POLL_INTERVAL: 3000, // 3 seconds when actively monitoring | |
| // Current polling timer | |
| _pollTimer: null, | |
| // Cache for current allocation run | |
| _currentAllocationRun: null, | |
| _lastAgentTimeline: null, | |
| // Track last known allocation run ID to avoid redundant fetches | |
| _lastKnownRunId: null, | |
| _lastPollTime: 0, | |
| _lastRunCount: 0, // Track number of runs to detect new allocations | |
| // Selected date for dashboard (initialized to today on first access) | |
| _selectedDate: null, | |
| // Get or initialize the selected date | |
| getSelectedDate() { | |
| if (!this._selectedDate) { | |
| this._selectedDate = this.getTodayDate(); | |
| } | |
| return this._selectedDate; | |
| }, | |
| // Event callbacks | |
| _callbacks: { | |
| onStatusUpdate: null, | |
| onWorkflowUpdate: null, | |
| onError: null | |
| }, | |
| /** | |
| * Set the base URL for API calls | |
| */ | |
| setBaseUrl(url) { | |
| this.BASE_URL = url; | |
| }, | |
| /** | |
| * Make an API request | |
| */ | |
| async request(endpoint, options = {}) { | |
| const url = `${this.BASE_URL}${this.API_PREFIX}${endpoint}`; | |
| // Add cache busting for GET requests to ensure real-time data | |
| const cacheBuster = options.method === 'GET' || !options.method | |
| ? (endpoint.includes('?') ? '&' : '?') + `_t=${new Date().getTime()}` | |
| : ''; | |
| try { | |
| const response = await fetch(url + cacheBuster, { | |
| cache: 'no-store', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Pragma': 'no-cache', | |
| 'Cache-Control': 'no-cache, no-store, must-revalidate', | |
| ...options.headers | |
| }, | |
| ...options | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
| } | |
| return await response.json(); | |
| } catch (error) { | |
| console.error(`API Error (${endpoint}):`, error); | |
| if (this._callbacks.onError) { | |
| this._callbacks.onError(error); | |
| } | |
| throw error; | |
| } | |
| }, | |
| /** | |
| * Check system health | |
| */ | |
| async checkHealth() { | |
| try { | |
| const response = await fetch(`${this.BASE_URL}/health`); | |
| return await response.json(); | |
| } catch (error) { | |
| return { status: 'disconnected', error: error.message }; | |
| } | |
| }, | |
| /** | |
| * Get admin system health with more details | |
| */ | |
| async getAdminHealth() { | |
| try { | |
| return await this.request('/admin/health'); | |
| } catch (error) { | |
| return null; | |
| } | |
| }, | |
| /** | |
| * Get allocation runs for a specific date | |
| */ | |
| async getAllocationRuns(date) { | |
| return this.request(`/admin/allocation_runs?date=${date}`); | |
| }, | |
| /** | |
| * Get agent timeline for an allocation run | |
| */ | |
| async getAgentTimeline(allocationRunId) { | |
| return this.request(`/admin/agent_timeline?allocation_run_id=${allocationRunId}`); | |
| }, | |
| /** | |
| * Get fairness metrics | |
| */ | |
| async getFairnessMetrics(startDate, endDate) { | |
| return this.request(`/admin/metrics/fairness?start_date=${startDate}&end_date=${endDate}`); | |
| }, | |
| /** | |
| * Trigger a new allocation (for testing) | |
| */ | |
| async triggerAllocation(data) { | |
| return this.request('/allocate', { | |
| method: 'POST', | |
| body: JSON.stringify(data) | |
| }); | |
| }, | |
| /** | |
| * Get driver details | |
| */ | |
| async getDriver(driverId) { | |
| return this.request(`/drivers/${driverId}`); | |
| }, | |
| /** | |
| * Get route details | |
| */ | |
| async getRoute(routeId) { | |
| return this.request(`/routes/${routeId}`); | |
| }, | |
| /** | |
| * Get assignments for a date (needed for map visualization) | |
| */ | |
| async getAssignments(date) { | |
| return this.request(`/admin/assignments?date=${date}`); | |
| }, | |
| /** | |
| * Register callback for status updates | |
| */ | |
| onStatusUpdate(callback) { | |
| this._callbacks.onStatusUpdate = callback; | |
| }, | |
| /** | |
| * Register callback for workflow updates | |
| */ | |
| onWorkflowUpdate(callback) { | |
| this._callbacks.onWorkflowUpdate = callback; | |
| }, | |
| /** | |
| * Register callback for errors | |
| */ | |
| onError(callback) { | |
| this._callbacks.onError = callback; | |
| }, | |
| /** | |
| * Start polling for updates | |
| */ | |
| startPolling() { | |
| if (this._pollTimer) { | |
| clearInterval(this._pollTimer); | |
| } | |
| // Use slower polling interval by default | |
| this._pollTimer = setInterval(async () => { | |
| await this._pollStatus(); | |
| }, this.POLL_INTERVAL); | |
| // Initial poll | |
| this._pollStatus(); | |
| }, | |
| /** | |
| * Stop polling | |
| */ | |
| stopPolling() { | |
| if (this._pollTimer) { | |
| clearInterval(this._pollTimer); | |
| this._pollTimer = null; | |
| } | |
| }, | |
| /** | |
| * Set the selected date for dashboard queries | |
| */ | |
| setSelectedDate(date) { | |
| this._selectedDate = date; | |
| // Reset cache when date changes | |
| this._lastKnownRunId = null; | |
| this._lastRunCount = 0; | |
| }, | |
| /** | |
| * Internal polling function - fetches real data from backend | |
| * Optimized to minimize redundant API calls | |
| */ | |
| async _pollStatus() { | |
| try { | |
| const health = await this.checkHealth(); | |
| const connected = health.status === 'healthy'; | |
| if (this._callbacks.onStatusUpdate) { | |
| this._callbacks.onStatusUpdate({ | |
| connected, | |
| health | |
| }); | |
| } | |
| // If connected, check for new data (but minimize API calls) | |
| if (connected && this._callbacks.onWorkflowUpdate) { | |
| const queryDate = this.getSelectedDate(); | |
| // Quick check: just get allocation runs count/latest ID | |
| const runsResponse = await this.getAllocationRuns(queryDate); | |
| const runs = runsResponse.runs || []; | |
| const runCount = runs.length; | |
| const latestRunId = runs.length > 0 ? runs[runs.length - 1].id : null; | |
| // Only fetch full data if: | |
| // 1. We have no cached data, OR | |
| // 2. Run count changed (new allocation added), OR | |
| // 3. Latest run ID changed | |
| const needsFullFetch = !this._currentAllocationRun || | |
| runCount !== this._lastRunCount || | |
| latestRunId !== this._lastKnownRunId; | |
| if (needsFullFetch && runs.length > 0) { | |
| console.log(`[Poll] New data detected, fetching full workflow state...`); | |
| const workflowState = await this.getRealWorkflowState(queryDate); | |
| this._lastRunCount = runCount; | |
| this._callbacks.onWorkflowUpdate(workflowState); | |
| } | |
| // Otherwise, no API call needed - data hasn't changed | |
| } | |
| } catch (error) { | |
| if (this._callbacks.onStatusUpdate) { | |
| this._callbacks.onStatusUpdate({ | |
| connected: false, | |
| error: error.message | |
| }); | |
| } | |
| } | |
| }, | |
| /** | |
| * Get today's date in local timezone (YYYY-MM-DD format) | |
| */ | |
| getTodayDate() { | |
| const now = new Date(); | |
| const year = now.getFullYear(); | |
| const month = String(now.getMonth() + 1).padStart(2, '0'); | |
| const day = String(now.getDate()).padStart(2, '0'); | |
| return `${year}-${month}-${day}`; | |
| }, | |
| /** | |
| * Fetch real workflow state from backend | |
| * @param {string} date - Optional date to fetch, defaults to selected date or today | |
| * @param {string} runId - Optional specific run ID to fetch | |
| */ | |
| async getRealWorkflowState(date = null, runId = null) { | |
| try { | |
| // If a specific run_id is provided, fetch that run directly | |
| if (runId) { | |
| return await this.getWorkflowStateForRun(runId); | |
| } | |
| // Use provided date, or get the selected date | |
| const queryDate = date || this.getSelectedDate(); | |
| const runsResponse = await this.getAllocationRuns(queryDate); | |
| if (runsResponse.runs && runsResponse.runs.length > 0) { | |
| // Get the latest allocation run | |
| const latestRun = runsResponse.runs[runsResponse.runs.length - 1]; | |
| this._currentAllocationRun = latestRun; | |
| // Track the run ID to avoid redundant fetches | |
| this._lastKnownRunId = latestRun.id; | |
| // Get agent timeline for this run | |
| const timeline = await this.getAgentTimeline(latestRun.id); | |
| this._lastAgentTimeline = timeline; | |
| // Transform timeline into workflow state | |
| return this.transformTimelineToWorkflowState(timeline, latestRun); | |
| } | |
| } catch (error) { | |
| console.log('Using mock data - no allocations found:', error.message); | |
| } | |
| // Fallback to mock state | |
| return this.getMockWorkflowState(); | |
| }, | |
| /** | |
| * Fetch workflow state for a specific run ID | |
| * @param {string} runId - The allocation run ID to fetch | |
| */ | |
| async getWorkflowStateForRun(runId) { | |
| try { | |
| // Fetch run summary to get run metadata | |
| const summaryResponse = await fetch(`${this.BASE_URL}${this.API_PREFIX}/runs/${runId}/summary`); | |
| if (!summaryResponse.ok) { | |
| throw new Error(`Failed to fetch run summary: ${summaryResponse.status}`); | |
| } | |
| const summary = await summaryResponse.json(); | |
| // Build allocation run object from summary | |
| const allocationRun = { | |
| id: summary.allocation_run_id, | |
| date: summary.date, | |
| num_drivers: summary.num_drivers, | |
| num_routes: summary.num_routes, | |
| num_packages: summary.num_packages, | |
| gini_index: summary.global_gini_index, | |
| std_dev: summary.global_std_dev, | |
| status: summary.status, | |
| }; | |
| this._currentAllocationRun = allocationRun; | |
| this._lastKnownRunId = runId; | |
| // Get agent timeline for this specific run | |
| const timeline = await this.getAgentTimeline(runId); | |
| this._lastAgentTimeline = timeline; | |
| // Transform timeline into workflow state | |
| return this.transformTimelineToWorkflowState(timeline, allocationRun); | |
| } catch (error) { | |
| console.error('Error fetching workflow state for run:', runId, error); | |
| throw error; | |
| } | |
| }, | |
| /** | |
| * Transform agent timeline data to workflow state format | |
| */ | |
| transformTimelineToWorkflowState(timeline, allocationRun) { | |
| // Map backend agent names to frontend node IDs | |
| // Support both UPPERCASE (from LangGraph) and PascalCase formats | |
| const agentNameMap = { | |
| // LangGraph format (UPPERCASE) | |
| 'ML_EFFORT': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' }, | |
| 'ML_EFFORT_AGENT': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' }, | |
| 'ROUTE_PLANNER': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' }, | |
| 'FAIRNESS_MANAGER': { id: 'fairness_manager', name: 'Fairness Manager Agent', type: 'agent' }, | |
| 'DRIVER_LIAISON': { id: 'driver_liaison', name: 'Driver Liaison Agent', type: 'agent' }, | |
| 'EXPLAINABILITY': { id: 'explainability', name: 'Explainability Agent', type: 'agent' }, | |
| 'LEARNING': { id: 'learning', name: 'Learning Agent', type: 'agent' }, | |
| // PascalCase format (legacy) | |
| 'MLEffortAgent': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' }, | |
| 'RoutePlannerAgent': { id: 'route_planner', name: 'Route Planner Agent', type: 'agent' }, | |
| 'FairnessManagerAgent': { id: 'fairness_manager', name: 'Fairness Manager Agent', type: 'agent' }, | |
| 'DriverLiaisonAgent': { id: 'driver_liaison', name: 'Driver Liaison Agent', type: 'agent' }, | |
| 'ExplainabilityAgent': { id: 'explainability', name: 'Explainability Agent', type: 'agent' }, | |
| 'LearningAgent': { id: 'learning', name: 'Learning Agent', type: 'agent' }, | |
| }; | |
| // Build agent status map from timeline | |
| // Handle both new format (timeline.timeline) and legacy format (timeline.steps) | |
| const agentStatus = {}; | |
| const agentLogs = {}; | |
| let totalSteps = 0; | |
| // Get the actual steps array from the response | |
| const steps = timeline.timeline || timeline.steps || []; | |
| if (steps && steps.length > 0) { | |
| steps.forEach(step => { | |
| const mapped = agentNameMap[step.agent_name]; | |
| if (mapped) { | |
| agentStatus[mapped.id] = 'active'; | |
| if (!agentLogs[mapped.id]) { | |
| agentLogs[mapped.id] = []; | |
| } | |
| agentLogs[mapped.id].push(step); | |
| totalSteps++; | |
| } | |
| }); | |
| } | |
| // Store logs for terminal display | |
| this._agentLogs = agentLogs; | |
| const agents = [ | |
| { | |
| id: 'central_orchestrator', | |
| name: 'Central Orchestrator Agent', | |
| description: 'Central Intelligence Hub', | |
| status: 'active', | |
| type: 'orchestrator', | |
| meta: `${Object.keys(agentStatus).length} Active` | |
| }, | |
| { | |
| id: 'route_database', | |
| name: 'Route Database', | |
| description: `Run: ${allocationRun.id.substring(0, 8)}...`, | |
| status: 'active', | |
| type: 'database', | |
| meta: 'Connected' | |
| }, | |
| { | |
| id: 'route_planner', | |
| name: 'Route Planner Agent', | |
| description: agentLogs['route_planner'] ? | |
| `${agentLogs['route_planner'].length} decisions` : 'Optimizing routes', | |
| status: agentStatus['route_planner'] || 'idle', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'fairness_manager', | |
| name: 'Fairness Manager Agent', | |
| description: agentLogs['fairness_manager'] ? | |
| `Gini: ${allocationRun.gini_index?.toFixed(3) || 'N/A'}` : 'Ensuring equitable distribution', | |
| status: agentStatus['fairness_manager'] || 'idle', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'driver_liaison', | |
| name: 'Driver Liaison Agent', | |
| description: agentLogs['driver_liaison'] ? | |
| `${agentLogs['driver_liaison'].length} interactions` : 'Driver communication hub', | |
| status: agentStatus['driver_liaison'] || 'idle', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'explainability', | |
| name: 'Explainability Agent', | |
| description: agentLogs['explainability'] ? | |
| `${agentLogs['explainability'].length} explanations` : 'Generating explanations', | |
| status: agentStatus['explainability'] || 'idle', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'learning', | |
| name: 'Learning Agent', | |
| description: 'Continuous improvement loop', | |
| status: agentStatus['learning'] || 'idle', | |
| type: 'agent', | |
| meta: null | |
| } | |
| ]; | |
| // Build connections - active if agent has data | |
| const connections = [ | |
| { from: 'central_orchestrator', to: 'route_database', active: true }, | |
| { from: 'central_orchestrator', to: 'route_planner', active: !!agentStatus['route_planner'] }, | |
| { from: 'central_orchestrator', to: 'fairness_manager', active: !!agentStatus['fairness_manager'] }, | |
| { from: 'central_orchestrator', to: 'driver_liaison', active: !!agentStatus['driver_liaison'] }, | |
| { from: 'central_orchestrator', to: 'explainability', active: !!agentStatus['explainability'] }, | |
| { from: 'central_orchestrator', to: 'learning', active: !!agentStatus['learning'] }, | |
| { from: 'route_database', to: 'route_planner', active: !!agentStatus['route_planner'] }, | |
| { from: 'route_planner', to: 'fairness_manager', active: !!agentStatus['fairness_manager'] }, | |
| { from: 'fairness_manager', to: 'driver_liaison', active: !!agentStatus['driver_liaison'] }, | |
| { from: 'driver_liaison', to: 'explainability', active: !!agentStatus['explainability'] }, | |
| { from: 'explainability', to: 'learning', active: !!agentStatus['learning'] } | |
| ]; | |
| const activeConnections = connections.filter(c => c.active).length; | |
| return { | |
| agents, | |
| connections, | |
| stats: { | |
| processing: 0, | |
| dataFlows: activeConnections, | |
| totalAgents: Object.keys(agentStatus).length | |
| }, | |
| allocationRun, | |
| timeline, | |
| isRealData: true | |
| }; | |
| }, | |
| /** | |
| * Get cached agent logs for terminal display | |
| */ | |
| getAgentLogs(agentId) { | |
| if (this._agentLogs && this._agentLogs[agentId]) { | |
| return this._agentLogs[agentId]; | |
| } | |
| return null; | |
| }, | |
| /** | |
| * Get current allocation run | |
| */ | |
| getCurrentAllocationRun() { | |
| return this._currentAllocationRun; | |
| }, | |
| /** | |
| * Get mock workflow state for demo mode | |
| */ | |
| getMockWorkflowState() { | |
| return { | |
| agents: [ | |
| { | |
| id: 'central_orchestrator', | |
| name: 'Central Orchestrator Agent', | |
| description: 'Central Intelligence Hub', | |
| status: 'active', | |
| type: 'orchestrator', | |
| meta: '6 Agents' | |
| }, | |
| { | |
| id: 'route_database', | |
| name: 'Route Database', | |
| description: '@ 18.9K records', | |
| status: 'active', | |
| type: 'database', | |
| meta: 'Connected' | |
| }, | |
| { | |
| id: 'route_planner', | |
| name: 'Route Planner Agent', | |
| description: 'Optimizing delivery routes', | |
| status: 'processing', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'fairness_manager', | |
| name: 'Fairness Manager Agent', | |
| description: 'Ensuring equitable distribution', | |
| status: 'idle', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'driver_liaison', | |
| name: 'Driver Liaison Agent', | |
| description: 'Driver communication hub', | |
| status: 'idle', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'explainability', | |
| name: 'Explainability Agent', | |
| description: 'Generating detailed explanations', | |
| status: 'idle', | |
| type: 'agent', | |
| meta: null | |
| }, | |
| { | |
| id: 'learning', | |
| name: 'Learning Agent', | |
| description: 'Continuous improvement loop', | |
| status: 'idle', | |
| type: 'agent', | |
| meta: null | |
| } | |
| ], | |
| connections: [ | |
| { from: 'central_orchestrator', to: 'route_database', active: true }, | |
| { from: 'central_orchestrator', to: 'route_planner', active: true }, | |
| { from: 'central_orchestrator', to: 'fairness_manager', active: true }, | |
| { from: 'central_orchestrator', to: 'driver_liaison', active: true }, | |
| { from: 'central_orchestrator', to: 'explainability', active: true }, | |
| { from: 'central_orchestrator', to: 'learning', active: true }, | |
| { from: 'route_database', to: 'route_planner', active: false }, | |
| { from: 'route_planner', to: 'fairness_manager', active: false }, | |
| { from: 'fairness_manager', to: 'driver_liaison', active: false }, | |
| { from: 'driver_liaison', to: 'explainability', active: false }, | |
| { from: 'explainability', to: 'learning', active: false } | |
| ], | |
| stats: { | |
| processing: 1, | |
| dataFlows: 6, | |
| totalAgents: 6 | |
| }, | |
| isRealData: false | |
| }; | |
| } | |
| }; | |
| // Export for use in app.js | |
| window.API = API; | |