Utiric commited on
Commit
557fe34
·
verified ·
1 Parent(s): 038cbb8

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +648 -0
index.html ADDED
@@ -0,0 +1,648 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Text Moderation API</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <script>
11
+ tailwind.config = {
12
+ darkMode: 'class',
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ primary: {
17
+ 50: '#eff6ff',
18
+ 100: '#dbeafe',
19
+ 200: '#bfdbfe',
20
+ 300: '#93c5fd',
21
+ 400: '#60a5fa',
22
+ 500: '#3b82f6',
23
+ 600: '#2563eb',
24
+ 700: '#1d4ed8',
25
+ 800: '#1e40af',
26
+ 900: '#1e3a8a',
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ </script>
33
+ <style>
34
+ .gradient-bg {
35
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
36
+ }
37
+ .dark .gradient-bg {
38
+ background: linear-gradient(135deg, #1e3a8a 0%, #4c1d95 100%);
39
+ }
40
+ .glass-effect {
41
+ background: rgba(255, 255, 255, 0.1);
42
+ backdrop-filter: blur(10px);
43
+ border: 1px solid rgba(255, 255, 255, 0.2);
44
+ }
45
+ .dark .glass-effect {
46
+ background: rgba(30, 41, 59, 0.5);
47
+ border: 1px solid rgba(100, 116, 139, 0.3);
48
+ }
49
+ .category-card {
50
+ transition: all 0.3s ease;
51
+ }
52
+ .category-card:hover {
53
+ transform: translateY(-5px);
54
+ }
55
+ .loading-spinner {
56
+ border-top-color: #3b82f6;
57
+ animation: spinner 1.5s linear infinite;
58
+ }
59
+ @keyframes spinner {
60
+ 0% { transform: rotate(0deg); }
61
+ 100% { transform: rotate(360deg); }
62
+ }
63
+ </style>
64
+ </head>
65
+ <body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
66
+ <!-- Header -->
67
+ <header class="gradient-bg text-white shadow-lg">
68
+ <div class="container mx-auto px-4 py-6 flex justify-between items-center">
69
+ <div class="flex items-center space-x-3">
70
+ <div class="w-10 h-10 rounded-full bg-white flex items-center justify-center">
71
+ <i class="fas fa-shield-alt text-primary-600 text-xl"></i>
72
+ </div>
73
+ <h1 class="text-2xl font-bold">Text Moderation API</h1>
74
+ </div>
75
+ <div class="flex items-center space-x-4">
76
+ <button id="refreshMetrics" class="glass-effect px-4 py-2 rounded-lg hover:bg-white/20 transition">
77
+ <i class="fas fa-sync-alt mr-2"></i>Refresh Metrics
78
+ </button>
79
+ <button id="darkModeToggle" class="glass-effect p-2 rounded-lg hover:bg-white/20 transition">
80
+ <i class="fas fa-moon dark:hidden"></i>
81
+ <i class="fas fa-sun hidden dark:inline"></i>
82
+ </button>
83
+ </div>
84
+ </div>
85
+ </header>
86
+
87
+ <main class="container mx-auto px-4 py-8">
88
+ <!-- Performance Metrics Section -->
89
+ <section class="mb-12">
90
+ <h2 class="text-2xl font-bold mb-6 flex items-center">
91
+ <i class="fas fa-chart-line mr-3 text-primary-600"></i>
92
+ Performance Metrics
93
+ </h2>
94
+
95
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
96
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
97
+ <div class="flex items-center justify-between">
98
+ <div>
99
+ <p class="text-gray-500 dark:text-gray-400 text-sm">Avg. Response Time</p>
100
+ <p class="text-2xl font-bold" id="avgResponseTime">0ms</p>
101
+ </div>
102
+ <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
103
+ <i class="fas fa-clock text-primary-600 dark:text-primary-400"></i>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
109
+ <div class="flex items-center justify-between">
110
+ <div>
111
+ <p class="text-gray-500 dark:text-gray-400 text-sm">Requests/Second</p>
112
+ <p class="text-2xl font-bold" id="requestsPerSecond">0</p>
113
+ </div>
114
+ <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
115
+ <i class="fas fa-tachometer-alt text-primary-600 dark:text-primary-400"></i>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
121
+ <div class="flex items-center justify-between">
122
+ <div>
123
+ <p class="text-gray-500 dark:text-gray-400 text-sm">Concurrent Requests</p>
124
+ <p class="text-2xl font-bold" id="concurrentRequests">0</p>
125
+ </div>
126
+ <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
127
+ <i class="fas fa-network-wired text-primary-600 dark:text-primary-400"></i>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
133
+ <div class="flex items-center justify-between">
134
+ <div>
135
+ <p class="text-gray-500 dark:text-gray-400 text-sm">Today's Tokens</p>
136
+ <p class="text-2xl font-bold" id="todayTokens">0</p>
137
+ </div>
138
+ <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
139
+ <i class="fas fa-key text-primary-600 dark:text-primary-400"></i>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
146
+ <h3 class="text-lg font-semibold mb-4">Last 7 Days Activity</h3>
147
+ <div class="h-64">
148
+ <canvas id="activityChart"></canvas>
149
+ </div>
150
+ </div>
151
+ </section>
152
+
153
+ <!-- API Testing Section -->
154
+ <section class="mb-12">
155
+ <h2 class="text-2xl font-bold mb-6 flex items-center">
156
+ <i class="fas fa-code mr-3 text-primary-600"></i>
157
+ API Tester
158
+ </h2>
159
+
160
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
161
+ <form id="apiTestForm">
162
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
163
+ <div>
164
+ <label class="block text-sm font-medium mb-2" for="apiKey">API Key</label>
165
+ <input type="password" id="apiKey" class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500" placeholder="Enter your API key">
166
+ </div>
167
+
168
+ <div>
169
+ <label class="block text-sm font-medium mb-2" for="model">Model</label>
170
+ <select id="model" class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500">
171
+ <option value="unitaryai/detoxify-multilingual" selected>Detoxify Multilingual</option>
172
+ </select>
173
+ </div>
174
+ </div>
175
+
176
+ <div class="mb-6">
177
+ <label class="block text-sm font-medium mb-2">Text Inputs</label>
178
+ <div id="textInputsContainer">
179
+ <div class="text-input-group mb-4">
180
+ <textarea class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500" rows="3" placeholder="Enter text to moderate..."></textarea>
181
+ <button type="button" class="remove-input mt-2 text-red-500 hover:text-red-700 hidden">
182
+ <i class="fas fa-trash-alt mr-1"></i> Remove
183
+ </button>
184
+ </div>
185
+ </div>
186
+ <button type="button" id="addTextInput" class="mt-2 text-primary-600 hover:text-primary-800 dark:text-primary-400 dark:hover:text-primary-300">
187
+ <i class="fas fa-plus-circle mr-1"></i> Add another text input
188
+ </button>
189
+ </div>
190
+
191
+ <div class="flex justify-between items-center">
192
+ <div>
193
+ <button type="submit" id="analyzeBtn" class="bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-6 rounded-lg transition">
194
+ <i class="fas fa-search mr-2"></i> Analyze Text
195
+ </button>
196
+ <button type="button" id="clearBtn" class="ml-2 bg-gray-300 hover:bg-gray-400 dark:bg-gray-600 dark:hover:bg-gray-700 text-gray-800 dark:text-gray-200 font-medium py-2 px-6 rounded-lg transition">
197
+ <i class="fas fa-eraser mr-2"></i> Clear
198
+ </button>
199
+ </div>
200
+ <div class="text-sm text-gray-500 dark:text-gray-400">
201
+ <i class="fas fa-info-circle mr-1"></i> Maximum 10 text inputs allowed
202
+ </div>
203
+ </div>
204
+ </form>
205
+ </div>
206
+ </section>
207
+
208
+ <!-- Results Section -->
209
+ <section id="resultsSection" class="hidden">
210
+ <h2 class="text-2xl font-bold mb-6 flex items-center">
211
+ <i class="fas fa-clipboard-check mr-3 text-primary-600"></i>
212
+ Analysis Results
213
+ </h2>
214
+
215
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-6">
216
+ <div class="flex justify-between items-center mb-4">
217
+ <h3 class="text-lg font-semibold">Summary</h3>
218
+ <div class="text-sm text-gray-500 dark:text-gray-400">
219
+ <i class="fas fa-clock mr-1"></i> Response time: <span id="responseTime">0ms</span> |
220
+ <i class="fas fa-key ml-2 mr-1"></i> Tokens: <span id="tokenCount">0</span>
221
+ </div>
222
+ </div>
223
+
224
+ <div id="resultsContainer" class="space-y-6">
225
+ <!-- Results will be dynamically inserted here -->
226
+ </div>
227
+ </div>
228
+ </section>
229
+
230
+ <!-- API Documentation Section -->
231
+ <section>
232
+ <h2 class="text-2xl font-bold mb-6 flex items-center">
233
+ <i class="fas fa-book mr-3 text-primary-600"></i>
234
+ API Documentation
235
+ </h2>
236
+
237
+ <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
238
+ <h3 class="text-lg font-semibold mb-4">Endpoint</h3>
239
+ <div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg mb-6 font-mono text-sm">
240
+ POST /v1/moderations
241
+ </div>
242
+
243
+ <h3 class="text-lg font-semibold mb-4">Request Body</h3>
244
+ <div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg mb-6 overflow-x-auto">
245
+ <pre class="text-sm"><code>{
246
+ "model": "unitaryai/detoxify-multilingual",
247
+ "input": "Text to moderate"
248
+ }</code></pre>
249
+ </div>
250
+
251
+ <h3 class="text-lg font-semibold mb-4">Response</h3>
252
+ <div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg overflow-x-auto">
253
+ <pre class="text-sm"><code>{
254
+ "id": "modr-1234567890abcdef",
255
+ "model": "unitaryai/detoxify-multilingual",
256
+ "results": [
257
+ {
258
+ "flagged": true,
259
+ "categories": {
260
+ "toxicity": true,
261
+ "severe_toxicity": false,
262
+ "obscene": true,
263
+ "threat": false,
264
+ "insult": true,
265
+ "identity_attack": false,
266
+ "sexual_explicit": false
267
+ },
268
+ "category_scores": {
269
+ "toxicity": 0.95,
270
+ "severe_toxicity": 0.1,
271
+ "obscene": 0.8,
272
+ "threat": 0.05,
273
+ "insult": 0.7,
274
+ "identity_attack": 0.2,
275
+ "sexual_explicit": 0.01
276
+ },
277
+ "category_applied_input_types": {
278
+ "toxicity": ["text"],
279
+ "severe_toxicity": [],
280
+ "obscene": ["text"],
281
+ "threat": [],
282
+ "insult": ["text"],
283
+ "identity_attack": [],
284
+ "sexual_explicit": []
285
+ }
286
+ }
287
+ ],
288
+ "object": "moderation",
289
+ "usage": {
290
+ "total_tokens": 5
291
+ }
292
+ }</code></pre>
293
+ </div>
294
+ </div>
295
+ </section>
296
+ </main>
297
+
298
+ <!-- Footer -->
299
+ <footer class="bg-gray-100 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12">
300
+ <div class="container mx-auto px-4 py-6">
301
+ <div class="flex flex-col md:flex-row justify-between items-center">
302
+ <div class="mb-4 md:mb-0">
303
+ <p class="text-gray-600 dark:text-gray-400">© 2025 Text Moderation API. All rights reserved.</p>
304
+ </div>
305
+ <div class="flex space-x-4">
306
+ <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400">
307
+ <i class="fab fa-github"></i>
308
+ </a>
309
+ <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400">
310
+ <i class="fab fa-twitter"></i>
311
+ </a>
312
+ <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400">
313
+ <i class="fas fa-envelope"></i>
314
+ </a>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ </footer>
319
+
320
+ <script>
321
+ // Dark mode toggle
322
+ const darkModeToggle = document.getElementById('darkModeToggle');
323
+ const html = document.documentElement;
324
+
325
+ // Check for saved dark mode preference or default to light mode
326
+ const currentTheme = localStorage.getItem('theme') || 'light';
327
+ if (currentTheme === 'dark') {
328
+ html.classList.add('dark');
329
+ }
330
+
331
+ darkModeToggle.addEventListener('click', () => {
332
+ html.classList.toggle('dark');
333
+ const theme = html.classList.contains('dark') ? 'dark' : 'light';
334
+ localStorage.setItem('theme', theme);
335
+ });
336
+
337
+ // Chart for activity
338
+ let activityChart;
339
+
340
+ function initActivityChart() {
341
+ const ctx = document.getElementById('activityChart').getContext('2d');
342
+ activityChart = new Chart(ctx, {
343
+ type: 'bar',
344
+ data: {
345
+ labels: [],
346
+ datasets: [
347
+ {
348
+ label: 'Requests',
349
+ data: [],
350
+ backgroundColor: 'rgba(59, 130, 246, 0.5)',
351
+ borderColor: 'rgba(59, 130, 246, 1)',
352
+ borderWidth: 1
353
+ },
354
+ {
355
+ label: 'Tokens',
356
+ data: [],
357
+ backgroundColor: 'rgba(16, 185, 129, 0.5)',
358
+ borderColor: 'rgba(16, 185, 129, 1)',
359
+ borderWidth: 1,
360
+ yAxisID: 'y1'
361
+ }
362
+ ]
363
+ },
364
+ options: {
365
+ responsive: true,
366
+ maintainAspectRatio: false,
367
+ scales: {
368
+ y: {
369
+ beginAtZero: true,
370
+ position: 'left',
371
+ title: {
372
+ display: true,
373
+ text: 'Requests'
374
+ }
375
+ },
376
+ y1: {
377
+ beginAtZero: true,
378
+ position: 'right',
379
+ title: {
380
+ display: true,
381
+ text: 'Tokens'
382
+ },
383
+ grid: {
384
+ drawOnChartArea: false
385
+ }
386
+ }
387
+ }
388
+ }
389
+ });
390
+ }
391
+
392
+ // Fetch performance metrics
393
+ async function fetchMetrics() {
394
+ const apiKey = document.getElementById('apiKey').value;
395
+ if (!apiKey) {
396
+ console.error('API key is required');
397
+ return;
398
+ }
399
+
400
+ try {
401
+ const response = await fetch('/v1/metrics', {
402
+ method: 'GET',
403
+ headers: {
404
+ 'Content-Type': 'application/json',
405
+ 'Authorization': 'Bearer ' + apiKey
406
+ }
407
+ });
408
+
409
+ if (!response.ok) {
410
+ throw new Error('Failed to fetch metrics');
411
+ }
412
+
413
+ const data = await response.json();
414
+ updateMetricsDisplay(data);
415
+ } catch (error) {
416
+ console.error('Error fetching metrics:', error);
417
+ }
418
+ }
419
+
420
+ // Update metrics display
421
+ function updateMetricsDisplay(data) {
422
+ document.getElementById('avgResponseTime').textContent = (data.avg_request_time * 1000).toFixed(2) + 'ms';
423
+ document.getElementById('requestsPerSecond').textContent = data.requests_per_second.toFixed(2);
424
+ document.getElementById('concurrentRequests').textContent = data.concurrent_requests;
425
+ document.getElementById('todayTokens').textContent = data.today_tokens.toLocaleString();
426
+
427
+ // Update activity chart
428
+ if (activityChart) {
429
+ const labels = data.last_7_days.map(day => {
430
+ const date = new Date(day.date);
431
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
432
+ }).reverse();
433
+
434
+ const requests = data.last_7_days.map(day => day.requests).reverse();
435
+ const tokens = data.last_7_days.map(day => day.tokens).reverse();
436
+
437
+ activityChart.data.labels = labels;
438
+ activityChart.data.datasets[0].data = requests;
439
+ activityChart.data.datasets[1].data = tokens;
440
+ activityChart.update();
441
+ }
442
+ }
443
+
444
+ // Add text input
445
+ document.getElementById('addTextInput').addEventListener('click', () => {
446
+ const container = document.getElementById('textInputsContainer');
447
+ const inputGroups = container.querySelectorAll('.text-input-group');
448
+
449
+ if (inputGroups.length >= 10) {
450
+ alert('Maximum 10 text inputs allowed');
451
+ return;
452
+ }
453
+
454
+ const newInputGroup = document.createElement('div');
455
+ newInputGroup.className = 'text-input-group mb-4';
456
+ newInputGroup.innerHTML = `
457
+ <textarea class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500" rows="3" placeholder="Enter text to moderate..."></textarea>
458
+ <button type="button" class="remove-input mt-2 text-red-500 hover:text-red-700">
459
+ <i class="fas fa-trash-alt mr-1"></i> Remove
460
+ </button>
461
+ `;
462
+
463
+ container.appendChild(newInputGroup);
464
+
465
+ // Add event listener to remove button
466
+ newInputGroup.querySelector('.remove-input').addEventListener('click', function() {
467
+ newInputGroup.remove();
468
+ updateRemoveButtons();
469
+ });
470
+
471
+ updateRemoveButtons();
472
+ });
473
+
474
+ // Update remove buttons visibility
475
+ function updateRemoveButtons() {
476
+ const inputGroups = document.querySelectorAll('.text-input-group');
477
+ inputGroups.forEach((group, index) => {
478
+ const removeBtn = group.querySelector('.remove-input');
479
+ if (inputGroups.length > 1) {
480
+ removeBtn.classList.remove('hidden');
481
+ } else {
482
+ removeBtn.classList.add('hidden');
483
+ }
484
+ });
485
+ }
486
+
487
+ // Remove text input
488
+ document.addEventListener('click', function(e) {
489
+ if (e.target.closest('.remove-input')) {
490
+ e.target.closest('.text-input-group').remove();
491
+ updateRemoveButtons();
492
+ }
493
+ });
494
+
495
+ // Clear form
496
+ document.getElementById('clearBtn').addEventListener('click', () => {
497
+ document.getElementById('apiTestForm').reset();
498
+ const container = document.getElementById('textInputsContainer');
499
+ container.innerHTML = `
500
+ <div class="text-input-group mb-4">
501
+ <textarea class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500" rows="3" placeholder="Enter text to moderate..."></textarea>
502
+ <button type="button" class="remove-input mt-2 text-red-500 hover:text-red-700 hidden">
503
+ <i class="fas fa-trash-alt mr-1"></i> Remove
504
+ </button>
505
+ </div>
506
+ `;
507
+ document.getElementById('resultsSection').classList.add('hidden');
508
+ });
509
+
510
+ // Analyze text
511
+ document.getElementById('apiTestForm').addEventListener('submit', async (e) => {
512
+ e.preventDefault();
513
+
514
+ const apiKey = document.getElementById('apiKey').value;
515
+ const model = document.getElementById('model').value;
516
+ const textInputs = document.querySelectorAll('#textInputsContainer textarea');
517
+
518
+ if (!apiKey) {
519
+ alert('Please enter your API key');
520
+ return;
521
+ }
522
+
523
+ const texts = [];
524
+ textInputs.forEach(input => {
525
+ if (input.value.trim()) {
526
+ texts.push(input.value.trim());
527
+ }
528
+ });
529
+
530
+ if (texts.length === 0) {
531
+ alert('Please enter at least one text to analyze');
532
+ return;
533
+ }
534
+
535
+ const analyzeBtn = document.getElementById('analyzeBtn');
536
+ const originalBtnContent = analyzeBtn.innerHTML;
537
+ analyzeBtn.innerHTML = '<div class="loading-spinner inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full mr-2"></div> Analyzing...';
538
+ analyzeBtn.disabled = true;
539
+
540
+ const startTime = Date.now();
541
+
542
+ try {
543
+ const response = await fetch('/v1/moderations', {
544
+ method: 'POST',
545
+ headers: {
546
+ 'Content-Type': 'application/json',
547
+ 'Authorization': 'Bearer ' + apiKey
548
+ },
549
+ body: JSON.stringify({
550
+ model: model,
551
+ input: texts
552
+ })
553
+ });
554
+
555
+ const endTime = Date.now();
556
+ const responseTime = endTime - startTime;
557
+
558
+ if (!response.ok) {
559
+ const errorData = await response.json();
560
+ throw new Error(errorData.error || 'Failed to analyze text');
561
+ }
562
+
563
+ const data = await response.json();
564
+ displayResults(data, responseTime);
565
+
566
+ } catch (error) {
567
+ console.error('Error analyzing text:', error);
568
+ alert('Error: ' + error.message);
569
+ } finally {
570
+ analyzeBtn.innerHTML = originalBtnContent;
571
+ analyzeBtn.disabled = false;
572
+ }
573
+ });
574
+
575
+ // Display results
576
+ function displayResults(data, responseTime) {
577
+ const resultsSection = document.getElementById('resultsSection');
578
+ const resultsContainer = document.getElementById('resultsContainer');
579
+
580
+ document.getElementById('responseTime').textContent = responseTime + 'ms';
581
+ document.getElementById('tokenCount').textContent = data.usage.total_tokens.toLocaleString();
582
+
583
+ resultsContainer.innerHTML = '';
584
+
585
+ data.results.forEach((result, index) => {
586
+ const resultCard = document.createElement('div');
587
+ resultCard.className = 'border border-gray-200 dark:border-gray-700 rounded-lg p-4';
588
+
589
+ const flaggedBadge = result.flagged
590
+ ? '<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100"><i class="fas fa-exclamation-triangle mr-1"></i> Flagged</span>'
591
+ : '<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100"><i class="fas fa-check-circle mr-1"></i> Safe</span>';
592
+
593
+ let categoriesHtml = '';
594
+ for (const [category, isFlagged] of Object.entries(result.categories)) {
595
+ const score = result.category_scores[category];
596
+ const categoryClass = isFlagged ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400';
597
+ const scoreClass = score > 0.7 ? 'text-red-600 dark:text-red-400' : score > 0.4 ? 'text-yellow-600 dark:text-yellow-400' : 'text-green-600 dark:text-green-400';
598
+
599
+ categoriesHtml += `
600
+ <div class="flex justify-between items-center py-2 border-b border-gray-100 dark:border-gray-700">
601
+ <span class="font-medium capitalize">${category.replace('_', ' ')}</span>
602
+ <div class="flex items-center">
603
+ <span class="${categoryClass} mr-2">${isFlagged ? 'Flagged' : 'Safe'}</span>
604
+ <span class="text-sm ${scoreClass} font-mono">${score.toFixed(4)}</span>
605
+ </div>
606
+ </div>
607
+ `;
608
+ }
609
+
610
+ resultCard.innerHTML = `
611
+ <div class="flex justify-between items-start mb-3">
612
+ <h4 class="text-lg font-semibold">Text ${index + 1}</h4>
613
+ ${flaggedBadge}
614
+ </div>
615
+ <div class="mb-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg text-sm">
616
+ ${data.input ? data.input[index] : 'Text content not available'}
617
+ </div>
618
+ <div class="category-card">
619
+ <h5 class="font-medium mb-2">Categories</h5>
620
+ <div class="bg-white dark:bg-gray-700 rounded-lg overflow-hidden">
621
+ ${categoriesHtml}
622
+ </div>
623
+ </div>
624
+ `;
625
+
626
+ resultsContainer.appendChild(resultCard);
627
+ });
628
+
629
+ resultsSection.classList.remove('hidden');
630
+ resultsSection.scrollIntoView({ behavior: 'smooth' });
631
+ }
632
+
633
+ // Initialize chart on page load
634
+ document.addEventListener('DOMContentLoaded', () => {
635
+ initActivityChart();
636
+
637
+ // Set up refresh metrics button
638
+ document.getElementById('refreshMetrics').addEventListener('click', fetchMetrics);
639
+
640
+ // Initial metrics fetch
641
+ fetchMetrics();
642
+
643
+ // Auto-refresh metrics every 30 seconds
644
+ setInterval(fetchMetrics, 30000);
645
+ });
646
+ </script>
647
+ </body>
648
+ </html>