chore: initial clean commit
This commit is contained in:
334
static/js/pages/analytics.js
Normal file
334
static/js/pages/analytics.js
Normal file
@@ -0,0 +1,334 @@
|
||||
// Analytics Page Module
|
||||
|
||||
class AnalyticsPage {
|
||||
constructor() {
|
||||
this.filters = {
|
||||
dateRange: '7d',
|
||||
client: 'all',
|
||||
provider: 'all'
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
// Load initial data
|
||||
await this.loadFilters();
|
||||
await this.loadCharts();
|
||||
await this.loadUsageData();
|
||||
|
||||
// Setup event listeners
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
async loadFilters() {
|
||||
try {
|
||||
// Load clients for filter dropdown
|
||||
// In a real app, this would fetch from /api/clients
|
||||
const clients = [
|
||||
{ id: 'client-1', name: 'Web Application' },
|
||||
{ id: 'client-2', name: 'Mobile App' },
|
||||
{ id: 'client-3', name: 'API Integration' },
|
||||
{ id: 'client-4', name: 'Internal Tools' },
|
||||
{ id: 'client-5', name: 'Testing Suite' }
|
||||
];
|
||||
|
||||
this.renderClientFilter(clients);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading filters:', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderClientFilter(clients) {
|
||||
const select = document.getElementById('client-filter');
|
||||
if (!select) return;
|
||||
|
||||
// Clear existing options except "All Clients"
|
||||
while (select.options.length > 1) {
|
||||
select.remove(1);
|
||||
}
|
||||
|
||||
// Add client options
|
||||
clients.forEach(client => {
|
||||
const option = document.createElement('option');
|
||||
option.value = client.id;
|
||||
option.textContent = client.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async loadCharts() {
|
||||
await this.loadAnalyticsChart();
|
||||
await this.loadClientsChart();
|
||||
await this.loadModelsChart();
|
||||
}
|
||||
|
||||
async loadAnalyticsChart() {
|
||||
try {
|
||||
// Generate demo data
|
||||
const labels = window.chartManager.generateDateLabels(7);
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Requests',
|
||||
data: labels.map(() => Math.floor(Math.random() * 1000) + 500),
|
||||
color: '#3b82f6',
|
||||
fill: true
|
||||
},
|
||||
{
|
||||
label: 'Tokens',
|
||||
data: labels.map(() => Math.floor(Math.random() * 100000) + 50000),
|
||||
color: '#10b981',
|
||||
fill: true
|
||||
},
|
||||
{
|
||||
label: 'Cost ($)',
|
||||
data: labels.map(() => Math.random() * 50 + 10),
|
||||
color: '#f59e0b',
|
||||
fill: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Create chart
|
||||
window.chartManager.createLineChart('analytics-chart', data, {
|
||||
scales: {
|
||||
y: {
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
return value.toLocaleString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading analytics chart:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadClientsChart() {
|
||||
try {
|
||||
const data = {
|
||||
labels: ['Web App', 'Mobile App', 'API Integration', 'Internal Tools', 'Testing'],
|
||||
datasets: [{
|
||||
label: 'Requests',
|
||||
data: [45, 25, 15, 10, 5],
|
||||
color: '#3b82f6'
|
||||
}]
|
||||
};
|
||||
|
||||
window.chartManager.createHorizontalBarChart('clients-chart', data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading clients chart:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadModelsChart() {
|
||||
try {
|
||||
const data = {
|
||||
labels: ['gpt-4', 'gpt-3.5-turbo', 'gemini-pro', 'deepseek-chat', 'grok-beta'],
|
||||
data: [35, 30, 20, 10, 5],
|
||||
colors: ['#3b82f6', '#60a5fa', '#10b981', '#f59e0b', '#8b5cf6']
|
||||
};
|
||||
|
||||
window.chartManager.createDoughnutChart('models-chart', data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading models chart:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async loadUsageData() {
|
||||
try {
|
||||
// In a real app, this would fetch from /api/usage/detailed
|
||||
const usageData = [
|
||||
{ date: '2024-01-15', client: 'Web App', provider: 'OpenAI', model: 'gpt-4', requests: 245, tokens: 125000, cost: 12.50 },
|
||||
{ date: '2024-01-15', client: 'Mobile App', provider: 'Gemini', model: 'gemini-pro', requests: 180, tokens: 89000, cost: 8.90 },
|
||||
{ date: '2024-01-15', client: 'API Integration', provider: 'OpenAI', model: 'gpt-3.5-turbo', requests: 320, tokens: 156000, cost: 15.60 },
|
||||
{ date: '2024-01-14', client: 'Web App', provider: 'OpenAI', model: 'gpt-4', requests: 210, tokens: 110000, cost: 11.00 },
|
||||
{ date: '2024-01-14', client: 'Internal Tools', provider: 'DeepSeek', model: 'deepseek-chat', requests: 95, tokens: 48000, cost: 4.80 },
|
||||
{ date: '2024-01-14', client: 'Testing Suite', provider: 'Grok', model: 'grok-beta', requests: 45, tokens: 22000, cost: 2.20 },
|
||||
{ date: '2024-01-13', client: 'Web App', provider: 'OpenAI', model: 'gpt-4', requests: 195, tokens: 98000, cost: 9.80 },
|
||||
{ date: '2024-01-13', client: 'Mobile App', provider: 'Gemini', model: 'gemini-pro', requests: 165, tokens: 82000, cost: 8.20 },
|
||||
{ date: '2024-01-13', client: 'API Integration', provider: 'OpenAI', model: 'gpt-3.5-turbo', requests: 285, tokens: 142000, cost: 14.20 },
|
||||
{ date: '2024-01-12', client: 'Web App', provider: 'OpenAI', model: 'gpt-4', requests: 230, tokens: 118000, cost: 11.80 }
|
||||
];
|
||||
|
||||
this.renderUsageTable(usageData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading usage data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
renderUsageTable(data) {
|
||||
const tableBody = document.querySelector('#usage-table tbody');
|
||||
if (!tableBody) return;
|
||||
|
||||
tableBody.innerHTML = data.map(row => `
|
||||
<tr>
|
||||
<td>${row.date}</td>
|
||||
<td>${row.client}</td>
|
||||
<td>${row.provider}</td>
|
||||
<td>${row.model}</td>
|
||||
<td>${row.requests.toLocaleString()}</td>
|
||||
<td>${row.tokens.toLocaleString()}</td>
|
||||
<td>$${row.cost.toFixed(2)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Filter controls
|
||||
const dateRangeSelect = document.getElementById('date-range');
|
||||
const clientSelect = document.getElementById('client-filter');
|
||||
const providerSelect = document.getElementById('provider-filter');
|
||||
|
||||
if (dateRangeSelect) {
|
||||
dateRangeSelect.addEventListener('change', (e) => {
|
||||
this.filters.dateRange = e.target.value;
|
||||
this.applyFilters();
|
||||
});
|
||||
}
|
||||
|
||||
if (clientSelect) {
|
||||
clientSelect.addEventListener('change', (e) => {
|
||||
this.filters.client = e.target.value;
|
||||
this.applyFilters();
|
||||
});
|
||||
}
|
||||
|
||||
if (providerSelect) {
|
||||
providerSelect.addEventListener('change', (e) => {
|
||||
this.filters.provider = e.target.value;
|
||||
this.applyFilters();
|
||||
});
|
||||
}
|
||||
|
||||
// Chart metric buttons
|
||||
const metricButtons = document.querySelectorAll('.chart-control-btn[data-metric]');
|
||||
metricButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
// Update active state
|
||||
metricButtons.forEach(btn => btn.classList.remove('active'));
|
||||
button.classList.add('active');
|
||||
|
||||
// Update chart based on metric
|
||||
this.updateAnalyticsChart(button.dataset.metric);
|
||||
});
|
||||
});
|
||||
|
||||
// Export button
|
||||
const exportBtn = document.querySelector('#analytics .btn-secondary');
|
||||
if (exportBtn) {
|
||||
exportBtn.addEventListener('click', () => {
|
||||
this.exportData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
applyFilters() {
|
||||
console.log('Applying filters:', this.filters);
|
||||
// In a real app, this would fetch filtered data from the API
|
||||
// For now, just show a toast
|
||||
if (window.authManager) {
|
||||
window.authManager.showToast('Filters applied', 'success');
|
||||
}
|
||||
|
||||
// Refresh data
|
||||
this.loadCharts();
|
||||
this.loadUsageData();
|
||||
}
|
||||
|
||||
updateAnalyticsChart(metric) {
|
||||
// Update the main analytics chart to show the selected metric
|
||||
const labels = window.chartManager.generateDateLabels(7);
|
||||
|
||||
let data;
|
||||
if (metric === 'requests') {
|
||||
data = {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Requests',
|
||||
data: labels.map(() => Math.floor(Math.random() * 1000) + 500),
|
||||
color: '#3b82f6',
|
||||
fill: true
|
||||
}]
|
||||
};
|
||||
} else if (metric === 'tokens') {
|
||||
data = {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Tokens',
|
||||
data: labels.map(() => Math.floor(Math.random() * 100000) + 50000),
|
||||
color: '#10b981',
|
||||
fill: true
|
||||
}]
|
||||
};
|
||||
} else if (metric === 'cost') {
|
||||
data = {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Cost ($)',
|
||||
data: labels.map(() => Math.random() * 50 + 10),
|
||||
color: '#f59e0b',
|
||||
fill: true
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
window.chartManager.updateChartData('analytics-chart', data);
|
||||
}
|
||||
|
||||
exportData() {
|
||||
// Create CSV data
|
||||
const table = document.getElementById('usage-table');
|
||||
if (!table) return;
|
||||
|
||||
const rows = table.querySelectorAll('tr');
|
||||
const csv = [];
|
||||
|
||||
rows.forEach(row => {
|
||||
const rowData = [];
|
||||
row.querySelectorAll('th, td').forEach(cell => {
|
||||
rowData.push(`"${cell.textContent.replace(/"/g, '""')}"`);
|
||||
});
|
||||
csv.push(rowData.join(','));
|
||||
});
|
||||
|
||||
// Create download link
|
||||
const blob = new Blob([csv.join('\n')], { type: 'text/csv' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `llm-proxy-analytics-${new Date().toISOString().split('T')[0]}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Show success message
|
||||
if (window.authManager) {
|
||||
window.authManager.showToast('Data exported successfully', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.loadCharts();
|
||||
this.loadUsageData();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize analytics page when needed
|
||||
window.initAnalytics = async () => {
|
||||
window.analyticsPage = new AnalyticsPage();
|
||||
};
|
||||
|
||||
// Export for use in other modules
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = AnalyticsPage;
|
||||
}
|
||||
Reference in New Issue
Block a user