kir / dashboard /src /lib /api.ts
Shinway Deploy
fix(dashboard): improve error handling and polling for live updates
c3d1852
Raw
History Blame Contribute Delete
3.41 kB
// Use relative path since dashboard is served from same origin
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '';
const API_KEY = process.env.NEXT_PUBLIC_DASHBOARD_API_KEY || '';
async function fetchAPI<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${API_BASE_URL}${endpoint}`;
const headers: HeadersInit = {
'Content-Type': 'application/json',
...(API_KEY ? { 'X-Dashboard-Key': API_KEY } : {}),
...options.headers,
};
try {
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
console.error(`API error: ${response.status} ${response.statusText} for ${endpoint}`);
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json();
} catch (error) {
console.error(`Failed to fetch ${endpoint}:`, error);
throw error;
}
}
// Default/fallback data for when API is unavailable
const defaultStats = {
uptime_seconds: 0,
total_requests: 0,
total_errors: 0,
error_rate: 0,
active_requests: 0,
total_input_tokens: 0,
total_output_tokens: 0,
requests_by_model: {},
tokens_by_model: {},
avg_latency_ms: 0,
p50_latency_ms: 0,
p95_latency_ms: 0,
p99_latency_ms: 0,
accounts_total: 0,
accounts_healthy: 0,
accounts_unhealthy: 0,
};
export const api = {
getStats: async () => {
try {
return await fetchAPI<import('@/types/metrics').DashboardStats>('/api/dashboard/stats');
} catch {
return defaultStats as import('@/types/metrics').DashboardStats;
}
},
getMetrics: async (params?: { start_time?: string; end_time?: string; model?: string; granularity?: string; limit?: number }) => {
try {
const searchParams = new URLSearchParams();
if (params?.start_time) searchParams.set('start_time', params.start_time);
if (params?.end_time) searchParams.set('end_time', params.end_time);
if (params?.model) searchParams.set('model', params.model);
if (params?.granularity) searchParams.set('granularity', params.granularity);
if (params?.limit) searchParams.set('limit', params.limit.toString());
const queryString = searchParams.toString();
return await fetchAPI<import('@/types/metrics').MetricsResponse>(
`/api/dashboard/metrics${queryString ? `?${queryString}` : ''}`
);
} catch {
return { query: { granularity: '1m', limit: 100 }, data: [], total_requests: 0, total_errors: 0, total_input_tokens: 0, total_output_tokens: 0 } as import('@/types/metrics').MetricsResponse;
}
},
getAccounts: async () => {
try {
return await fetchAPI<import('@/types/metrics').Account[]>('/api/dashboard/accounts');
} catch {
return [] as import('@/types/metrics').Account[];
}
},
getConfig: async () => {
try {
return await fetchAPI<import('@/types/metrics').GatewayConfig>('/api/dashboard/config');
} catch {
return null;
}
},
getHealth: async () => {
try {
return await fetchAPI<import('@/types/metrics').HealthResponse>('/api/dashboard/health');
} catch {
return { status: 'unhealthy', version: 'unknown', uptime_seconds: 0, auth_healthy: false, api_reachable: false, accounts_status: null, metrics_enabled: false, prometheus_ready: false } as import('@/types/metrics').HealthResponse;
}
},
};
export default api;