apac-ops-bot/frontend/src/services/api.ts
SamoilenkoVadym 2e6597ee08 Add admin analytics and update OpenAI integration
Backend changes:
- Add admin analytics endpoints for daily usage per user
- Add GET /tokens/daily-users endpoint with date/user breakdown
- Update OpenAI SDK from 1.58.1 to 2.6.1
- Switch from Assistants API to Responses API with file_search tool
- Implement strict RAG-only system instructions
- Add citation validation to prevent hallucinations
- Add get_daily_usage_by_user repository method
- Add DailyUserUsage schema for admin analytics

Frontend changes:
- Implement comprehensive admin usage dashboard
- Add overall system statistics (users, conversations, messages, tokens, cost)
- Add daily usage table with per-user breakdown
- Add chat state clearing on logout and user change for isolation
- Center welcome message and input field in chat interface
- Add admin-specific styling for usage analytics tables
- Fix useCallback dependencies to prevent infinite loops

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-27 21:36:36 +00:00

159 lines
4.5 KiB
TypeScript

/**
* API Service
*
* Axios configuration and API client for backend communication
*/
import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000/api/v1';
// Create axios instance
const apiClient: AxiosInstance = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor to add auth token
apiClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('access_token');
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor for error handling
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Handle 401 errors (token expired)
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
const response = await axios.post(`${API_URL}/auth/refresh`, {
refresh_token: refreshToken,
});
const { access_token } = response.data;
localStorage.setItem('access_token', access_token);
// Retry original request with new token
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return apiClient(originalRequest);
}
} catch (refreshError) {
// Refresh failed, clear tokens and redirect to login
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
window.location.href = '/';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default apiClient;
// API methods
export const authAPI = {
login: (idToken: string) =>
apiClient.post('/auth/login', { id_token: idToken }),
loginSimple: (email: string, password: string) =>
apiClient.post('/auth/login/simple', { email, password }),
logout: () =>
apiClient.post('/auth/logout'),
getCurrentUser: () =>
apiClient.get('/auth/me'),
refreshToken: (refreshToken: string) =>
apiClient.post('/auth/refresh', { refresh_token: refreshToken }),
};
export const conversationAPI = {
list: (includeArchived: boolean = false, skip: number = 0, limit: number = 50) =>
apiClient.get('/conversations', {
params: { include_archived: includeArchived, skip, limit },
}),
create: (title?: string) =>
apiClient.post('/conversations', { title }),
get: (id: string) =>
apiClient.get(`/conversations/${id}`),
update: (id: string, title: string) =>
apiClient.put(`/conversations/${id}`, { title }),
archive: (id: string) =>
apiClient.post(`/conversations/${id}/archive`),
delete: (id: string) =>
apiClient.delete(`/conversations/${id}`),
};
export const messageAPI = {
list: (conversationId: string, skip: number = 0, limit: number = 100) =>
apiClient.get(`/conversations/${conversationId}/messages`, {
params: { skip, limit },
}),
send: (conversationId: string, content: string) =>
apiClient.post(`/conversations/${conversationId}/messages`, { content }),
};
export const tokenAPI = {
getUsage: (days: number = 30) =>
apiClient.get('/tokens/usage', { params: { days } }),
getUsersUsage: (days: number = 30) =>
apiClient.get('/tokens/users', { params: { days } }),
getDailyUsageByUser: (days: number = 30) =>
apiClient.get('/tokens/daily-users', { params: { days } }),
};
export const adminAPI = {
// User management
listUsers: (skip: number = 0, limit: number = 100) =>
apiClient.get('/admin/users', { params: { skip, limit } }),
updateUserRole: (userId: string, role: string) =>
apiClient.put(`/admin/users/${userId}/role`, { role }),
activateUser: (userId: string) =>
apiClient.put(`/admin/users/${userId}/activate`),
deactivateUser: (userId: string) =>
apiClient.put(`/admin/users/${userId}/deactivate`),
// Analytics
getSystemAnalytics: () =>
apiClient.get('/admin/analytics/system'),
getUsersAnalytics: (days: number = 30, limit: number = 50) =>
apiClient.get('/admin/analytics/users', { params: { days, limit } }),
getAllConversations: (skip: number = 0, limit: number = 50) =>
apiClient.get('/admin/conversations/all', { params: { skip, limit } }),
};