vectra-js 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/npm-publish.yml +42 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/LICENSE +674 -674
- package/README.md +30 -0
- package/bin/vectra.js +9 -3
- package/package.json +23 -21
- package/src/backends/chroma_store.js +13 -2
- package/src/config.js +9 -0
- package/src/core.js +203 -1
- package/src/dashboard/dashboard-script.js +260 -0
- package/src/dashboard/index.html +362 -0
- package/src/dashboard/logo.png +0 -0
- package/src/dashboard/trace-script.js +184 -0
- package/src/dashboard/trace.html +239 -0
- package/src/observability.js +226 -0
- package/src/processor.js +1 -1
- package/src/ui/index.html +278 -236
- package/src/ui/logo.png +0 -0
- package/src/ui/script.js +59 -10
- package/src/ui/style.css +2 -2
- package/src/webconfig_server.js +162 -2
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// State
|
|
2
|
+
let currentView = 'overview';
|
|
3
|
+
let currentProject = 'all';
|
|
4
|
+
let lastStats = null;
|
|
5
|
+
|
|
6
|
+
// Init
|
|
7
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
8
|
+
loadProjects();
|
|
9
|
+
loadDashboardData();
|
|
10
|
+
|
|
11
|
+
// Auto-refresh every 30s
|
|
12
|
+
setInterval(loadDashboardData, 30000);
|
|
13
|
+
|
|
14
|
+
// Initialize Icons
|
|
15
|
+
if (window.lucide) {
|
|
16
|
+
window.lucide.createIcons();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Project filter
|
|
20
|
+
const projectSelect = document.getElementById('projectSelect');
|
|
21
|
+
if (projectSelect) {
|
|
22
|
+
projectSelect.addEventListener('change', (e) => {
|
|
23
|
+
currentProject = e.target.value;
|
|
24
|
+
loadDashboardData();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function switchView(view) {
|
|
30
|
+
currentView = view;
|
|
31
|
+
|
|
32
|
+
// Update content visibility
|
|
33
|
+
['overview', 'traces', 'sessions'].forEach(v => {
|
|
34
|
+
const div = document.getElementById(`view-${v}`);
|
|
35
|
+
if (v === view) {
|
|
36
|
+
div.classList.remove('hidden');
|
|
37
|
+
} else {
|
|
38
|
+
div.classList.add('hidden');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Update Sidebar Links
|
|
43
|
+
document.querySelectorAll('.sidebar-link').forEach(el => {
|
|
44
|
+
el.classList.remove('active', 'bg-gray-100', 'text-slate-900');
|
|
45
|
+
el.classList.add('text-gray-600');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const btn = document.getElementById(`btn-${view}`);
|
|
49
|
+
if (btn) {
|
|
50
|
+
btn.classList.add('active', 'bg-gray-100', 'text-slate-900');
|
|
51
|
+
btn.classList.remove('text-gray-600');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (view === 'traces') loadTraces();
|
|
55
|
+
if (view === 'sessions') loadSessions();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function fetchAPI(endpoint) {
|
|
59
|
+
try {
|
|
60
|
+
const url = `/api/observability/${endpoint}${currentProject !== 'all' ? `?projectId=${currentProject}` : ''}`;
|
|
61
|
+
const res = await fetch(url);
|
|
62
|
+
if (!res.ok) throw new Error('API Error');
|
|
63
|
+
return await res.json();
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error('Fetch error:', e);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function loadProjects() {
|
|
71
|
+
const projects = await fetchAPI('projects');
|
|
72
|
+
const select = document.getElementById('projectSelect');
|
|
73
|
+
if (projects && projects.length > 0) {
|
|
74
|
+
// Keep 'all' option
|
|
75
|
+
// Add projects
|
|
76
|
+
projects.forEach(p => {
|
|
77
|
+
if (p === 'all') return; // skip if somehow in db
|
|
78
|
+
const opt = document.createElement('option');
|
|
79
|
+
opt.value = p;
|
|
80
|
+
opt.textContent = p;
|
|
81
|
+
select.appendChild(opt);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function loadDashboardData() {
|
|
87
|
+
const stats = await fetchAPI('stats');
|
|
88
|
+
if (!stats) return;
|
|
89
|
+
|
|
90
|
+
lastStats = stats;
|
|
91
|
+
|
|
92
|
+
// Update Stats Cards
|
|
93
|
+
document.getElementById('stat-total-req').textContent = stats.totalRequests || 0;
|
|
94
|
+
document.getElementById('stat-avg-latency').textContent = Math.round(stats.avgLatency || 0);
|
|
95
|
+
document.getElementById('stat-tokens').textContent = ((stats.totalPromptChars || 0) + (stats.totalCompletionChars || 0)).toLocaleString();
|
|
96
|
+
document.getElementById('stat-errors').textContent = '0%'; // Placeholder for now
|
|
97
|
+
|
|
98
|
+
// Update Charts
|
|
99
|
+
updateCharts(stats.history);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let latencyChartInst = null;
|
|
103
|
+
let tokenChartInst = null;
|
|
104
|
+
|
|
105
|
+
function updateCharts(history = []) {
|
|
106
|
+
// History is expected to be array of { timestamp, latency, tokens }
|
|
107
|
+
// If not provided by API yet, mock or skip
|
|
108
|
+
if (!history.length) return;
|
|
109
|
+
|
|
110
|
+
const labels = history.map(h => new Date(h.timestamp).toLocaleTimeString());
|
|
111
|
+
const latencies = history.map(h => h.latency);
|
|
112
|
+
const tokens = history.map(h => h.tokens);
|
|
113
|
+
|
|
114
|
+
// Latency Chart
|
|
115
|
+
const ctxL = document.getElementById('latencyChart').getContext('2d');
|
|
116
|
+
const isDark = document.documentElement.classList.contains('dark');
|
|
117
|
+
const gridColor = isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
|
|
118
|
+
const textColor = isDark ? '#9ca3af' : '#64748b';
|
|
119
|
+
|
|
120
|
+
if (latencyChartInst) latencyChartInst.destroy();
|
|
121
|
+
latencyChartInst = new Chart(ctxL, {
|
|
122
|
+
type: 'line',
|
|
123
|
+
data: {
|
|
124
|
+
labels,
|
|
125
|
+
datasets: [{
|
|
126
|
+
label: 'Latency (ms)',
|
|
127
|
+
data: latencies,
|
|
128
|
+
borderColor: '#8b5cf6',
|
|
129
|
+
tension: 0.4,
|
|
130
|
+
fill: true,
|
|
131
|
+
backgroundColor: isDark ? 'rgba(139, 92, 246, 0.1)' : 'rgba(139, 92, 246, 0.05)'
|
|
132
|
+
}]
|
|
133
|
+
},
|
|
134
|
+
options: {
|
|
135
|
+
responsive: true,
|
|
136
|
+
maintainAspectRatio: false,
|
|
137
|
+
plugins: { legend: { display: false } },
|
|
138
|
+
scales: {
|
|
139
|
+
y: {
|
|
140
|
+
beginAtZero: true,
|
|
141
|
+
grid: { color: gridColor, borderDash: [2, 4] },
|
|
142
|
+
ticks: { color: textColor }
|
|
143
|
+
},
|
|
144
|
+
x: {
|
|
145
|
+
grid: { display: false },
|
|
146
|
+
ticks: { color: textColor }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Token Chart
|
|
153
|
+
const ctxT = document.getElementById('tokenChart').getContext('2d');
|
|
154
|
+
if (tokenChartInst) tokenChartInst.destroy();
|
|
155
|
+
tokenChartInst = new Chart(ctxT, {
|
|
156
|
+
type: 'bar',
|
|
157
|
+
data: {
|
|
158
|
+
labels,
|
|
159
|
+
datasets: [{
|
|
160
|
+
label: 'Tokens',
|
|
161
|
+
data: tokens,
|
|
162
|
+
backgroundColor: '#0ea5e9',
|
|
163
|
+
borderRadius: 4
|
|
164
|
+
}]
|
|
165
|
+
},
|
|
166
|
+
options: {
|
|
167
|
+
responsive: true,
|
|
168
|
+
maintainAspectRatio: false,
|
|
169
|
+
plugins: { legend: { display: false } },
|
|
170
|
+
scales: {
|
|
171
|
+
y: {
|
|
172
|
+
beginAtZero: true,
|
|
173
|
+
grid: { color: gridColor, borderDash: [2, 4] },
|
|
174
|
+
ticks: { color: textColor }
|
|
175
|
+
},
|
|
176
|
+
x: {
|
|
177
|
+
grid: { display: false },
|
|
178
|
+
ticks: { color: textColor }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function loadTraces() {
|
|
186
|
+
const traces = await fetchAPI('traces'); // Expects list of recent traces
|
|
187
|
+
const tbody = document.getElementById('traces-table-body');
|
|
188
|
+
tbody.innerHTML = '';
|
|
189
|
+
|
|
190
|
+
if (!traces || traces.length === 0) {
|
|
191
|
+
tbody.innerHTML = '<tr><td colspan="6" class="px-6 py-4 text-center text-sm text-slate-500 dark:text-gray-400">No traces found</td></tr>';
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
traces.forEach(t => {
|
|
196
|
+
const row = document.createElement('tr');
|
|
197
|
+
row.className = 'hover:bg-slate-50 dark:hover:bg-white/5 transition-colors cursor-pointer';
|
|
198
|
+
row.onclick = () => window.location.href = `/dashboard/trace.html?id=${t.trace_id}`;
|
|
199
|
+
|
|
200
|
+
const statusColor = t.error && t.error !== '{}' ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400' : 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400';
|
|
201
|
+
const statusText = t.error && t.error !== '{}' ? 'Error' : 'Success';
|
|
202
|
+
|
|
203
|
+
row.innerHTML = `
|
|
204
|
+
<td class="px-6 py-4 whitespace-nowrap text-xs font-mono text-slate-500 dark:text-gray-400">${t.trace_id.slice(0, 8)}...</td>
|
|
205
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-slate-900 dark:text-white">${t.name}</td>
|
|
206
|
+
<td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusColor}">${statusText}</span></td>
|
|
207
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-gray-400">${t.end_time - t.start_time}ms</td>
|
|
208
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-gray-400">${new Date(t.start_time).toLocaleString()}</td>
|
|
209
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium text-brand-600 dark:text-brand-400 hover:text-brand-900 dark:hover:text-brand-300">View</td>
|
|
210
|
+
`;
|
|
211
|
+
tbody.appendChild(row);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function loadSessions() {
|
|
216
|
+
const sessions = await fetchAPI('sessions');
|
|
217
|
+
const tbody = document.getElementById('sessions-table-body');
|
|
218
|
+
tbody.innerHTML = '';
|
|
219
|
+
|
|
220
|
+
if (!sessions || sessions.length === 0) {
|
|
221
|
+
tbody.innerHTML = '<tr><td colspan="4" class="px-6 py-4 text-center text-sm text-slate-500">No active sessions</td></tr>';
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
sessions.forEach(s => {
|
|
226
|
+
const row = document.createElement('tr');
|
|
227
|
+
row.className = 'hover:bg-slate-50 dark:hover:bg-white/5 transition-colors';
|
|
228
|
+
|
|
229
|
+
row.innerHTML = `
|
|
230
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-slate-900 dark:text-white">${s.session_id}</td>
|
|
231
|
+
<td class="px-6 py-4 text-sm text-slate-500 dark:text-gray-400 truncate max-w-xs">${s.metadata?.last_query || '-'}</td>
|
|
232
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-gray-400">${new Date(s.last_activity_time).toLocaleString()}</td>
|
|
233
|
+
<td class="px-6 py-4 text-sm text-slate-500 dark:text-gray-400 font-mono text-xs">${JSON.stringify(s.metadata || {}).slice(0, 30)}...</td>
|
|
234
|
+
`;
|
|
235
|
+
tbody.appendChild(row);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Modal functions removed as trace details are now on a separate page */
|
|
240
|
+
function showTraceDetails(traceId) {
|
|
241
|
+
window.location.href = `/dashboard/trace.html?id=${traceId}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function closeModal() {
|
|
245
|
+
// Deprecated
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function toggleTheme() {
|
|
249
|
+
if (document.documentElement.classList.contains('dark')) {
|
|
250
|
+
document.documentElement.classList.remove('dark');
|
|
251
|
+
localStorage.setItem('color-theme', 'light');
|
|
252
|
+
} else {
|
|
253
|
+
document.documentElement.classList.add('dark');
|
|
254
|
+
localStorage.setItem('color-theme', 'dark');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (lastStats && lastStats.history) {
|
|
258
|
+
updateCharts(lastStats.history);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<link rel="icon" type="image/png" href="logo.png">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Vectra Dashboard</title>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<script>
|
|
10
|
+
tailwind.config = {
|
|
11
|
+
darkMode: 'class',
|
|
12
|
+
theme: {
|
|
13
|
+
extend: {
|
|
14
|
+
colors: {
|
|
15
|
+
brand: {
|
|
16
|
+
50: '#f5f3ff',
|
|
17
|
+
100: '#ede9fe',
|
|
18
|
+
200: '#ddd6fe',
|
|
19
|
+
300: '#c4b5fd',
|
|
20
|
+
400: '#a78bfa',
|
|
21
|
+
500: '#8b5cf6',
|
|
22
|
+
600: '#7c3aed',
|
|
23
|
+
700: '#6d28d9',
|
|
24
|
+
800: '#5b21b6',
|
|
25
|
+
900: '#4c1d95',
|
|
26
|
+
950: '#2e1065',
|
|
27
|
+
},
|
|
28
|
+
dark: {
|
|
29
|
+
950: '#020204',
|
|
30
|
+
900: '#08080C',
|
|
31
|
+
850: '#0F0F16',
|
|
32
|
+
800: '#14141F',
|
|
33
|
+
700: '#1C1C2E',
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for saved theme preference or use system preference
|
|
41
|
+
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
42
|
+
document.documentElement.classList.add('dark');
|
|
43
|
+
} else {
|
|
44
|
+
document.documentElement.classList.remove('dark');
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
48
|
+
<script src="https://unpkg.com/lucide@latest"></script>
|
|
49
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
50
|
+
<style>
|
|
51
|
+
body { font-family: 'Inter', sans-serif; }
|
|
52
|
+
/* Custom scrollbar for dark mode compatibility */
|
|
53
|
+
::-webkit-scrollbar {
|
|
54
|
+
width: 8px;
|
|
55
|
+
height: 8px;
|
|
56
|
+
}
|
|
57
|
+
::-webkit-scrollbar-track {
|
|
58
|
+
background: transparent;
|
|
59
|
+
}
|
|
60
|
+
::-webkit-scrollbar-thumb {
|
|
61
|
+
background: #cbd5e1;
|
|
62
|
+
border-radius: 4px;
|
|
63
|
+
}
|
|
64
|
+
.dark ::-webkit-scrollbar-thumb {
|
|
65
|
+
background: #334155;
|
|
66
|
+
}
|
|
67
|
+
::-webkit-scrollbar-thumb:hover {
|
|
68
|
+
background: #94a3b8;
|
|
69
|
+
}
|
|
70
|
+
.dark ::-webkit-scrollbar-thumb:hover {
|
|
71
|
+
background: #475569;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body class="bg-gray-50 text-slate-900 dark:bg-dark-950 dark:text-white h-screen overflow-hidden flex transition-colors duration-200">
|
|
76
|
+
|
|
77
|
+
<!-- Sidebar -->
|
|
78
|
+
<aside class="w-64 bg-white border-r border-slate-200 dark:bg-dark-900 dark:border-white/5 flex-col hidden md:flex transition-colors duration-200">
|
|
79
|
+
<!-- Logo -->
|
|
80
|
+
<div class="h-16 flex items-center px-6 border-b border-slate-200 dark:border-white/5">
|
|
81
|
+
<div class="flex items-center gap-3">
|
|
82
|
+
<img src="logo.png" alt="Vectra" class="w-8 h-8">
|
|
83
|
+
<span class="text-xl font-bold text-slate-900 dark:text-white tracking-tight">Vectra</span>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Navigation -->
|
|
88
|
+
<nav class="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
|
|
89
|
+
<div class="mb-6">
|
|
90
|
+
<p class="px-2 text-xs font-semibold text-slate-500 dark:text-gray-500 uppercase tracking-wider mb-2">Menu</p>
|
|
91
|
+
<button onclick="switchView('overview')" id="btn-overview" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg sidebar-link active group transition-colors">
|
|
92
|
+
<i data-lucide="layout-dashboard" class="w-5 h-5 mr-3"></i>
|
|
93
|
+
Dashboard
|
|
94
|
+
</button>
|
|
95
|
+
<button onclick="switchView('traces')" id="btn-traces" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg sidebar-link group transition-colors">
|
|
96
|
+
<i data-lucide="activity" class="w-5 h-5 mr-3"></i>
|
|
97
|
+
Traces
|
|
98
|
+
</button>
|
|
99
|
+
<button onclick="switchView('sessions')" id="btn-sessions" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg sidebar-link group transition-colors">
|
|
100
|
+
<i data-lucide="users" class="w-5 h-5 mr-3"></i>
|
|
101
|
+
Sessions
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div>
|
|
106
|
+
<p class="px-2 text-xs font-semibold text-slate-500 dark:text-gray-500 uppercase tracking-wider mb-2">Management</p>
|
|
107
|
+
<a href="#" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg text-slate-600 dark:text-gray-400 hover:bg-slate-50 dark:hover:bg-white/5 hover:text-slate-900 dark:hover:text-white group transition-colors">
|
|
108
|
+
<i data-lucide="settings" class="w-5 h-5 mr-3 text-slate-400 dark:text-gray-500 group-hover:text-slate-600 dark:group-hover:text-gray-300"></i>
|
|
109
|
+
Settings
|
|
110
|
+
</a>
|
|
111
|
+
<a href="#" class="w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg text-slate-600 dark:text-gray-400 hover:bg-slate-50 dark:hover:bg-white/5 hover:text-slate-900 dark:hover:text-white group transition-colors">
|
|
112
|
+
<i data-lucide="book-open" class="w-5 h-5 mr-3 text-slate-400 dark:text-gray-500 group-hover:text-slate-600 dark:group-hover:text-gray-300"></i>
|
|
113
|
+
Documentation
|
|
114
|
+
</a>
|
|
115
|
+
</div>
|
|
116
|
+
</nav>
|
|
117
|
+
|
|
118
|
+
<!-- User Profile (Bottom) -->
|
|
119
|
+
<div class="p-4 border-t border-slate-200 dark:border-white/5">
|
|
120
|
+
<div class="flex items-center gap-3">
|
|
121
|
+
<div class="w-8 h-8 rounded-full bg-brand-100 dark:bg-brand-900/50 flex items-center justify-center text-brand-600 dark:text-brand-400 font-bold text-xs border border-brand-200 dark:border-brand-500/20">
|
|
122
|
+
AD
|
|
123
|
+
</div>
|
|
124
|
+
<div>
|
|
125
|
+
<p class="text-sm font-medium text-slate-900 dark:text-white">Admin User</p>
|
|
126
|
+
<p class="text-xs text-slate-500 dark:text-gray-500">admin@vectra.ai</p>
|
|
127
|
+
</div>
|
|
128
|
+
<button onclick="toggleTheme()" class="ml-auto p-1.5 text-slate-400 hover:text-slate-600 dark:text-gray-500 dark:hover:text-gray-300 rounded-lg hover:bg-slate-100 dark:hover:bg-white/5 transition-colors">
|
|
129
|
+
<i data-lucide="moon" class="w-4 h-4 hidden dark:block"></i>
|
|
130
|
+
<i data-lucide="sun" class="w-4 h-4 block dark:hidden"></i>
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</aside>
|
|
135
|
+
|
|
136
|
+
<!-- Main Content -->
|
|
137
|
+
<div class="flex-1 flex flex-col min-w-0 overflow-hidden bg-gray-50 dark:bg-dark-950 transition-colors duration-200">
|
|
138
|
+
|
|
139
|
+
<!-- Top Header -->
|
|
140
|
+
<header class="bg-white dark:bg-dark-900 border-b border-slate-200 dark:border-white/5 h-16 flex items-center justify-between px-6 lg:px-8 transition-colors duration-200">
|
|
141
|
+
<div class="flex items-center flex-1">
|
|
142
|
+
<div class="relative w-full max-w-md">
|
|
143
|
+
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
144
|
+
<i data-lucide="search" class="h-5 w-5 text-slate-400 dark:text-gray-500"></i>
|
|
145
|
+
</div>
|
|
146
|
+
<input type="text" class="block w-full pl-10 pr-3 py-2 border border-slate-200 dark:border-white/10 rounded-lg leading-5 bg-slate-50 dark:bg-dark-800 text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-gray-500 focus:outline-none focus:bg-white dark:focus:bg-dark-800 focus:ring-1 focus:ring-brand-500 focus:border-brand-500 sm:text-sm transition duration-150 ease-in-out" placeholder="Search traces, sessions, or logs...">
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="ml-4 flex items-center gap-4">
|
|
150
|
+
<div class="flex items-center gap-2">
|
|
151
|
+
<label for="projectSelect" class="text-sm font-medium text-slate-600 dark:text-gray-500">Project:</label>
|
|
152
|
+
<select id="projectSelect" class="bg-slate-50 dark:bg-dark-800 border border-slate-200 dark:border-white/10 text-slate-900 dark:text-white text-sm rounded-lg focus:ring-brand-500 focus:border-brand-500 block p-2">
|
|
153
|
+
<option value="all">All Projects</option>
|
|
154
|
+
</select>
|
|
155
|
+
</div>
|
|
156
|
+
<button class="p-2 text-slate-400 hover:text-slate-600 dark:text-gray-400 dark:hover:text-white transition-colors">
|
|
157
|
+
<i data-lucide="bell" class="w-6 h-6"></i>
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
</header>
|
|
161
|
+
|
|
162
|
+
<!-- Main Scrollable Area -->
|
|
163
|
+
<main class="flex-1 overflow-y-auto p-6 lg:p-8">
|
|
164
|
+
|
|
165
|
+
<!-- View: Overview -->
|
|
166
|
+
<div id="view-overview" class="space-y-8 max-w-7xl mx-auto">
|
|
167
|
+
<div>
|
|
168
|
+
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">System Overview</h2>
|
|
169
|
+
<p class="mt-1 text-sm text-slate-500 dark:text-gray-400">Real-time metrics and system status.</p>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- Status Cards -->
|
|
173
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
174
|
+
|
|
175
|
+
<!-- Total Requests -->
|
|
176
|
+
<div class="bg-white dark:bg-dark-900 rounded-xl p-6 shadow-sm border border-slate-200 dark:border-white/5 relative overflow-hidden group transition-colors duration-200">
|
|
177
|
+
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-emerald-500"></div>
|
|
178
|
+
<div class="flex justify-between items-start">
|
|
179
|
+
<div>
|
|
180
|
+
<h3 class="text-slate-500 dark:text-gray-400 text-sm font-medium">Total Requests</h3>
|
|
181
|
+
<div class="mt-4 flex items-baseline">
|
|
182
|
+
<span class="text-4xl font-bold text-slate-900 dark:text-white tracking-tight" id="stat-total-req">-</span>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="mt-2 flex items-center text-sm text-emerald-600 dark:text-emerald-400 font-medium">
|
|
185
|
+
<span class="w-2 h-2 rounded-full bg-emerald-500 mr-2"></span>
|
|
186
|
+
Operational
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
<div class="p-3 bg-emerald-50 dark:bg-emerald-500/10 rounded-xl text-emerald-600 dark:text-emerald-400">
|
|
190
|
+
<i data-lucide="server" class="w-6 h-6"></i>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<!-- Error Rate -->
|
|
196
|
+
<div class="bg-white dark:bg-dark-900 rounded-xl p-6 shadow-sm border border-slate-200 dark:border-white/5 relative overflow-hidden group transition-colors duration-200">
|
|
197
|
+
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-rose-500"></div>
|
|
198
|
+
<div class="flex justify-between items-start">
|
|
199
|
+
<div>
|
|
200
|
+
<h3 class="text-slate-500 dark:text-gray-400 text-sm font-medium">Error Rate</h3>
|
|
201
|
+
<div class="mt-4 flex items-baseline">
|
|
202
|
+
<span class="text-4xl font-bold text-slate-900 dark:text-white tracking-tight" id="stat-errors">-</span>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="mt-2 flex items-center text-sm text-rose-600 dark:text-rose-400 font-medium">
|
|
205
|
+
<i data-lucide="alert-circle" class="w-4 h-4 mr-1"></i>
|
|
206
|
+
Check Logs
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
<div class="p-3 bg-rose-50 dark:bg-rose-500/10 rounded-xl text-rose-600 dark:text-rose-400">
|
|
210
|
+
<i data-lucide="activity" class="w-6 h-6"></i>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<!-- Token Usage -->
|
|
216
|
+
<div class="bg-white dark:bg-dark-900 rounded-xl p-6 shadow-sm border border-slate-200 dark:border-white/5 flex flex-col justify-between transition-colors duration-200">
|
|
217
|
+
<div>
|
|
218
|
+
<div class="flex justify-between items-center mb-4">
|
|
219
|
+
<h3 class="text-slate-500 dark:text-gray-400 text-sm font-medium">Token Usage</h3>
|
|
220
|
+
<span class="text-xs font-medium text-brand-600 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/10 px-2 py-1 rounded-full">Monthly</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="flex items-baseline mb-2">
|
|
223
|
+
<span class="text-2xl font-bold text-slate-900 dark:text-white" id="stat-tokens">-</span>
|
|
224
|
+
<span class="ml-2 text-sm text-slate-500 dark:text-gray-500">tokens</span>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="w-full bg-slate-100 dark:bg-dark-800 rounded-full h-2 mb-4 overflow-hidden">
|
|
227
|
+
<div class="bg-brand-500 h-2 rounded-full" style="width: 65%"></div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="grid grid-cols-2 gap-4 pt-4 border-t border-slate-200 dark:border-white/5">
|
|
231
|
+
<div>
|
|
232
|
+
<p class="text-xs text-slate-500 dark:text-gray-500">Prompt</p>
|
|
233
|
+
<p class="font-semibold text-slate-900 dark:text-white">~65%</p>
|
|
234
|
+
</div>
|
|
235
|
+
<div>
|
|
236
|
+
<p class="text-xs text-slate-500 dark:text-gray-500">Completion</p>
|
|
237
|
+
<p class="font-semibold text-slate-900 dark:text-white">~35%</p>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<!-- Charts Section -->
|
|
244
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
245
|
+
<!-- Latency Chart -->
|
|
246
|
+
<div class="lg:col-span-2 bg-white dark:bg-dark-900 rounded-xl shadow-sm border border-slate-200 dark:border-white/5 p-6 transition-colors duration-200">
|
|
247
|
+
<div class="flex justify-between items-center mb-6">
|
|
248
|
+
<div>
|
|
249
|
+
<h3 class="text-lg font-bold text-slate-900 dark:text-white">Latency Trend</h3>
|
|
250
|
+
<p class="text-sm text-slate-500 dark:text-gray-400">Response time over last 24 hours</p>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="text-right">
|
|
253
|
+
<p class="text-2xl font-bold text-brand-600 dark:text-brand-400" id="stat-avg-latency">-</p>
|
|
254
|
+
<p class="text-xs text-slate-500 dark:text-gray-500">Avg ms</p>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="h-72 w-full">
|
|
258
|
+
<canvas id="latencyChart"></canvas>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<!-- Secondary Chart -->
|
|
263
|
+
<div class="bg-white dark:bg-dark-900 rounded-xl shadow-sm border border-slate-200 dark:border-white/5 p-6 transition-colors duration-200">
|
|
264
|
+
<div class="mb-6">
|
|
265
|
+
<h3 class="text-lg font-bold text-slate-900 dark:text-white">Token Distribution</h3>
|
|
266
|
+
<p class="text-sm text-slate-500 dark:text-gray-400">Usage by interaction</p>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="h-64 w-full">
|
|
269
|
+
<canvas id="tokenChart"></canvas>
|
|
270
|
+
</div>
|
|
271
|
+
<div class="mt-4 pt-4 border-t border-slate-200 dark:border-white/5">
|
|
272
|
+
<button class="w-full py-2 px-4 bg-slate-100 hover:bg-slate-200 dark:bg-dark-800 dark:hover:bg-dark-700 text-slate-600 dark:text-gray-300 text-sm font-medium rounded-lg transition-colors flex items-center justify-center">
|
|
273
|
+
View Full Report
|
|
274
|
+
<i data-lucide="arrow-right" class="w-4 h-4 ml-2"></i>
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<!-- View: Traces -->
|
|
282
|
+
<div id="view-traces" class="hidden space-y-6 max-w-7xl mx-auto">
|
|
283
|
+
<div class="flex justify-between items-center">
|
|
284
|
+
<div>
|
|
285
|
+
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">Traces</h2>
|
|
286
|
+
<p class="mt-1 text-sm text-slate-500 dark:text-gray-400">Detailed logs of all LLM interactions.</p>
|
|
287
|
+
</div>
|
|
288
|
+
<div class="flex gap-2">
|
|
289
|
+
<button class="px-4 py-2 bg-white dark:bg-dark-800 border border-slate-200 dark:border-white/10 text-slate-600 dark:text-gray-300 rounded-lg text-sm font-medium hover:bg-slate-50 dark:hover:bg-dark-700 flex items-center transition-colors">
|
|
290
|
+
<i data-lucide="filter" class="w-4 h-4 mr-2"></i> Filter
|
|
291
|
+
</button>
|
|
292
|
+
<button class="px-4 py-2 bg-brand-600 text-white rounded-lg text-sm font-medium hover:bg-brand-700 flex items-center transition-colors">
|
|
293
|
+
<i data-lucide="download" class="w-4 h-4 mr-2"></i> Export
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<div class="bg-white dark:bg-dark-900 rounded-xl shadow-sm border border-slate-200 dark:border-white/5 overflow-hidden transition-colors duration-200">
|
|
299
|
+
<div class="overflow-x-auto">
|
|
300
|
+
<table class="min-w-full divide-y divide-slate-200 dark:divide-white/5">
|
|
301
|
+
<thead class="bg-slate-50 dark:bg-dark-800">
|
|
302
|
+
<tr>
|
|
303
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Trace ID</th>
|
|
304
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Name</th>
|
|
305
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
|
306
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Latency</th>
|
|
307
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Time</th>
|
|
308
|
+
<th class="px-6 py-4 text-right text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Action</th>
|
|
309
|
+
</tr>
|
|
310
|
+
</thead>
|
|
311
|
+
<tbody class="bg-white dark:bg-dark-900 divide-y divide-slate-200 dark:divide-white/5" id="traces-table-body">
|
|
312
|
+
<!-- Populated via JS -->
|
|
313
|
+
</tbody>
|
|
314
|
+
</table>
|
|
315
|
+
</div>
|
|
316
|
+
<!-- Pagination (Mock) -->
|
|
317
|
+
<div class="bg-slate-50 dark:bg-dark-800 px-6 py-4 border-t border-slate-200 dark:border-white/5 flex items-center justify-between">
|
|
318
|
+
<span class="text-sm text-slate-500 dark:text-gray-400">Showing recent items</span>
|
|
319
|
+
<div class="flex gap-2">
|
|
320
|
+
<button class="p-2 border border-slate-200 dark:border-white/10 rounded-md bg-white dark:bg-dark-900 text-slate-400 dark:text-gray-400 hover:bg-slate-50 dark:hover:bg-dark-800 disabled:opacity-50" disabled>
|
|
321
|
+
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
|
322
|
+
</button>
|
|
323
|
+
<button class="p-2 border border-slate-200 dark:border-white/10 rounded-md bg-white dark:bg-dark-900 text-slate-400 dark:text-gray-400 hover:bg-slate-50 dark:hover:bg-dark-800">
|
|
324
|
+
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
|
325
|
+
</button>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<!-- View: Sessions -->
|
|
332
|
+
<div id="view-sessions" class="hidden space-y-6 max-w-7xl mx-auto">
|
|
333
|
+
<div>
|
|
334
|
+
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">Active Sessions</h2>
|
|
335
|
+
<p class="mt-1 text-sm text-slate-500 dark:text-gray-400">User sessions and conversation history.</p>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<div class="bg-white dark:bg-dark-900 rounded-xl shadow-sm border border-slate-200 dark:border-white/5 overflow-hidden transition-colors duration-200">
|
|
339
|
+
<div class="overflow-x-auto">
|
|
340
|
+
<table class="min-w-full divide-y divide-slate-200 dark:divide-white/5">
|
|
341
|
+
<thead class="bg-slate-50 dark:bg-dark-800">
|
|
342
|
+
<tr>
|
|
343
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Session ID</th>
|
|
344
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Last Query</th>
|
|
345
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Last Active</th>
|
|
346
|
+
<th class="px-6 py-4 text-left text-xs font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider">Metadata</th>
|
|
347
|
+
</tr>
|
|
348
|
+
</thead>
|
|
349
|
+
<tbody class="bg-white dark:bg-dark-900 divide-y divide-slate-200 dark:divide-white/5" id="sessions-table-body">
|
|
350
|
+
<!-- Populated via JS -->
|
|
351
|
+
</tbody>
|
|
352
|
+
</table>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
</main>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<script src="dashboard-script.js"></script>
|
|
361
|
+
</body>
|
|
362
|
+
</html>
|
|
Binary file
|