Utiric commited on
Commit
3709277
·
verified ·
1 Parent(s): aefee54

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -867
index.html DELETED
@@ -1,867 +0,0 @@
1
- from flask import Flask, request, jsonify, render_template, send_from_directory
2
- import os
3
- import uuid
4
- import time
5
- import threading
6
- import tiktoken
7
- from datetime import datetime, timedelta
8
- from collections import defaultdict, deque
9
- from detoxify import Detoxify
10
-
11
- app = Flask(__name__, static_folder='static', template_folder='templates')
12
-
13
- # Load the detoxify model
14
- detoxify_model = Detoxify('multilingual')
15
-
16
- # API key from environment variable
17
- API_KEY = os.getenv('API_KEY', 'your-api-key-here')
18
-
19
- # Performance metrics tracking
20
- request_times = deque(maxlen=1000) # Store last 1000 request times
21
- daily_requests = defaultdict(int)
22
- daily_tokens = defaultdict(int)
23
- concurrent_requests = 0
24
- concurrent_requests_lock = threading.Lock()
25
-
26
- # Token encoding for counting tokens
27
- encoding = tiktoken.get_encoding("cl100k_base")
28
-
29
- def count_tokens(text):
30
- """Count tokens in the given text using tiktoken."""
31
- return len(encoding.encode(text))
32
-
33
- def transform_predictions(prediction_dict):
34
- """
35
- Transform predictions to OpenAI-style format.
36
- Returns: flagged, categories, category_scores, category_applied_input_types
37
- """
38
- # Define the categories we want to track
39
- category_keys = [
40
- "toxicity", "severe_toxicity", "obscene", "threat",
41
- "insult", "identity_attack", "sexual_explicit"
42
- ]
43
-
44
- # Map detoxify output to our categories
45
- scores = {}
46
- for key in category_keys:
47
- scores[key] = prediction_dict.get(key, 0.0)
48
-
49
- # Set threshold for flagging content
50
- threshold = 0.5
51
- bool_categories = {key: (scores[key] > threshold) for key in category_keys}
52
- cat_applied_input_types = {key: (["text"] if scores[key] > 0 else []) for key in category_keys}
53
- flagged = any(bool_categories.values())
54
-
55
- return flagged, bool_categories, scores, cat_applied_input_types
56
-
57
- def track_request_metrics(start_time, tokens_count):
58
- """Track performance metrics for requests."""
59
- end_time = time.time()
60
- request_time = end_time - start_time
61
- request_times.append(request_time)
62
-
63
- today = datetime.now().strftime("%Y-%m-%d")
64
- daily_requests[today] += 1
65
- daily_tokens[today] += tokens_count
66
-
67
- def get_performance_metrics():
68
- """Get current performance metrics."""
69
- global concurrent_requests
70
- with concurrent_requests_lock:
71
- current_concurrent = concurrent_requests
72
-
73
- # Calculate average request time
74
- avg_request_time = sum(request_times) / len(request_times) if request_times else 0
75
-
76
- # Get today's date
77
- today = datetime.now().strftime("%Y-%m-%d")
78
-
79
- # Calculate requests per second (based on last 100 requests)
80
- recent_requests = list(request_times)[-100:] if len(request_times) >= 100 else list(request_times)
81
- requests_per_second = len(recent_requests) / sum(recent_requests) if recent_requests and sum(recent_requests) > 0 else 0
82
-
83
- # Get daily stats
84
- today_requests = daily_requests.get(today, 0)
85
- today_tokens = daily_tokens.get(today, 0)
86
-
87
- # Get last 7 days stats
88
- last_7_days = []
89
- for i in range(7):
90
- date = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d")
91
- last_7_days.append({
92
- "date": date,
93
- "requests": daily_requests.get(date, 0),
94
- "tokens": daily_tokens.get(date, 0)
95
- })
96
-
97
- return {
98
- "avg_request_time": avg_request_time,
99
- "requests_per_second": requests_per_second,
100
- "concurrent_requests": current_concurrent,
101
- "today_requests": today_requests,
102
- "today_tokens": today_tokens,
103
- "last_7_days": last_7_days
104
- }
105
-
106
- @app.route('/')
107
- def home():
108
- return render_template('index.html')
109
-
110
- @app.route('/v1/moderations', methods=['POST'])
111
- def moderations():
112
- global concurrent_requests
113
-
114
- # Track concurrent requests
115
- with concurrent_requests_lock:
116
- concurrent_requests += 1
117
-
118
- start_time = time.time()
119
- total_tokens = 0
120
-
121
- try:
122
- # Check authorization
123
- auth_header = request.headers.get('Authorization')
124
- if not auth_header or not auth_header.startswith("Bearer "):
125
- return jsonify({"error": "Unauthorized"}), 401
126
-
127
- provided_api_key = auth_header.split(" ")[1]
128
- if provided_api_key != API_KEY:
129
- return jsonify({"error": "Unauthorized"}), 401
130
-
131
- # Get input data
132
- data = request.get_json()
133
- raw_input = data.get('input') or data.get('texts')
134
-
135
- if raw_input is None:
136
- return jsonify({"error": "Invalid input, expected 'input' or 'texts' field"}), 400
137
-
138
- # Handle both string and list inputs
139
- if isinstance(raw_input, str):
140
- texts = [raw_input]
141
- elif isinstance(raw_input, list):
142
- texts = raw_input
143
- else:
144
- return jsonify({"error": "Invalid input format, expected string or list of strings"}), 400
145
-
146
- # Validate input size
147
- if len(texts) > 10:
148
- return jsonify({"error": "Too many input items. Maximum 10 allowed."}), 400
149
-
150
- for text in texts:
151
- if not isinstance(text, str) or len(text) > 100000:
152
- return jsonify({"error": "Each input item must be a string with a maximum of 100k characters."}), 400
153
- total_tokens += count_tokens(text)
154
-
155
- # Process each text
156
- results = []
157
- for text in texts:
158
- pred = detoxify_model.predict([text])
159
- prediction = {k: v[0] for k, v in pred.items()}
160
- flagged, bool_categories, scores, cat_applied_input_types = transform_predictions(prediction)
161
-
162
- results.append({
163
- "flagged": flagged,
164
- "categories": bool_categories,
165
- "category_scores": scores,
166
- "category_applied_input_types": cat_applied_input_types
167
- })
168
-
169
- # Track metrics
170
- track_request_metrics(start_time, total_tokens)
171
-
172
- # Prepare response
173
- response_data = {
174
- "id": "modr-" + uuid.uuid4().hex[:24],
175
- "model": "unitaryai/detoxify-multilingual",
176
- "results": results,
177
- "object": "moderation",
178
- "usage": {
179
- "total_tokens": total_tokens
180
- }
181
- }
182
-
183
- return jsonify(response_data)
184
-
185
- finally:
186
- # Decrement concurrent requests counter
187
- with concurrent_requests_lock:
188
- concurrent_requests -= 1
189
-
190
- @app.route('/v1/metrics', methods=['GET'])
191
- def metrics():
192
- """Endpoint to get performance metrics."""
193
- auth_header = request.headers.get('Authorization')
194
- if not auth_header or not auth_header.startswith("Bearer "):
195
- return jsonify({"error": "Unauthorized"}), 401
196
-
197
- provided_api_key = auth_header.split(" ")[1]
198
- if provided_api_key != API_KEY:
199
- return jsonify({"error": "Unauthorized"}), 401
200
-
201
- return jsonify(get_performance_metrics())
202
-
203
- # Create a simple file-based approach for the HTML template
204
- def create_directories_and_files():
205
- """Create necessary directories and files if they don't exist."""
206
- os.makedirs('templates', exist_ok=True)
207
- os.makedirs('static', exist_ok=True)
208
-
209
- # Create index.html if it doesn't exist
210
- index_path = os.path.join('templates', 'index.html')
211
- if not os.path.exists(index_path):
212
- with open(index_path, 'w') as f:
213
- f.write('''<!DOCTYPE html>
214
- <html lang="en">
215
- <head>
216
- <meta charset="UTF-8">
217
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
218
- <title>Text Moderation API</title>
219
- <script src="https://cdn.tailwindcss.com"></script>
220
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
221
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
222
- <script>
223
- tailwind.config = {
224
- darkMode: 'class',
225
- theme: {
226
- extend: {
227
- colors: {
228
- primary: {
229
- 50: '#eff6ff',
230
- 100: '#dbeafe',
231
- 200: '#bfdbfe',
232
- 300: '#93c5fd',
233
- 400: '#60a5fa',
234
- 500: '#3b82f6',
235
- 600: '#2563eb',
236
- 700: '#1d4ed8',
237
- 800: '#1e40af',
238
- 900: '#1e3a8a',
239
- }
240
- }
241
- }
242
- }
243
- }
244
- </script>
245
- <style>
246
- .gradient-bg {
247
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
248
- }
249
- .dark .gradient-bg {
250
- background: linear-gradient(135deg, #1e3a8a 0%, #4c1d95 100%);
251
- }
252
- .glass-effect {
253
- background: rgba(255, 255, 255, 0.1);
254
- backdrop-filter: blur(10px);
255
- border: 1px solid rgba(255, 255, 255, 0.2);
256
- }
257
- .dark .glass-effect {
258
- background: rgba(30, 41, 59, 0.5);
259
- border: 1px solid rgba(100, 116, 139, 0.3);
260
- }
261
- .category-card {
262
- transition: all 0.3s ease;
263
- }
264
- .category-card:hover {
265
- transform: translateY(-5px);
266
- }
267
- .loading-spinner {
268
- border-top-color: #3b82f6;
269
- animation: spinner 1.5s linear infinite;
270
- }
271
- @keyframes spinner {
272
- 0% { transform: rotate(0deg); }
273
- 100% { transform: rotate(360deg); }
274
- }
275
- </style>
276
- </head>
277
- <body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
278
- <!-- Header -->
279
- <header class="gradient-bg text-white shadow-lg">
280
- <div class="container mx-auto px-4 py-6 flex justify-between items-center">
281
- <div class="flex items-center space-x-3">
282
- <div class="w-10 h-10 rounded-full bg-white flex items-center justify-center">
283
- <i class="fas fa-shield-alt text-primary-600 text-xl"></i>
284
- </div>
285
- <h1 class="text-2xl font-bold">Text Moderation API</h1>
286
- </div>
287
- <div class="flex items-center space-x-4">
288
- <button id="refreshMetrics" class="glass-effect px-4 py-2 rounded-lg hover:bg-white/20 transition">
289
- <i class="fas fa-sync-alt mr-2"></i>Refresh Metrics
290
- </button>
291
- <button id="darkModeToggle" class="glass-effect p-2 rounded-lg hover:bg-white/20 transition">
292
- <i class="fas fa-moon dark:hidden"></i>
293
- <i class="fas fa-sun hidden dark:inline"></i>
294
- </button>
295
- </div>
296
- </div>
297
- </header>
298
-
299
- <main class="container mx-auto px-4 py-8">
300
- <!-- Performance Metrics Section -->
301
- <section class="mb-12">
302
- <h2 class="text-2xl font-bold mb-6 flex items-center">
303
- <i class="fas fa-chart-line mr-3 text-primary-600"></i>
304
- Performance Metrics
305
- </h2>
306
-
307
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
308
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
309
- <div class="flex items-center justify-between">
310
- <div>
311
- <p class="text-gray-500 dark:text-gray-400 text-sm">Avg. Response Time</p>
312
- <p class="text-2xl font-bold" id="avgResponseTime">0ms</p>
313
- </div>
314
- <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
315
- <i class="fas fa-clock text-primary-600 dark:text-primary-400"></i>
316
- </div>
317
- </div>
318
- </div>
319
-
320
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
321
- <div class="flex items-center justify-between">
322
- <div>
323
- <p class="text-gray-500 dark:text-gray-400 text-sm">Requests/Second</p>
324
- <p class="text-2xl font-bold" id="requestsPerSecond">0</p>
325
- </div>
326
- <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
327
- <i class="fas fa-tachometer-alt text-primary-600 dark:text-primary-400"></i>
328
- </div>
329
- </div>
330
- </div>
331
-
332
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
333
- <div class="flex items-center justify-between">
334
- <div>
335
- <p class="text-gray-500 dark:text-gray-400 text-sm">Concurrent Requests</p>
336
- <p class="text-2xl font-bold" id="concurrentRequests">0</p>
337
- </div>
338
- <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
339
- <i class="fas fa-network-wired text-primary-600 dark:text-primary-400"></i>
340
- </div>
341
- </div>
342
- </div>
343
-
344
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
345
- <div class="flex items-center justify-between">
346
- <div>
347
- <p class="text-gray-500 dark:text-gray-400 text-sm">Today's Tokens</p>
348
- <p class="text-2xl font-bold" id="todayTokens">0</p>
349
- </div>
350
- <div class="w-12 h-12 rounded-full bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
351
- <i class="fas fa-key text-primary-600 dark:text-primary-400"></i>
352
- </div>
353
- </div>
354
- </div>
355
- </div>
356
-
357
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
358
- <h3 class="text-lg font-semibold mb-4">Last 7 Days Activity</h3>
359
- <div class="h-64">
360
- <canvas id="activityChart"></canvas>
361
- </div>
362
- </div>
363
- </section>
364
-
365
- <!-- API Testing Section -->
366
- <section class="mb-12">
367
- <h2 class="text-2xl font-bold mb-6 flex items-center">
368
- <i class="fas fa-code mr-3 text-primary-600"></i>
369
- API Tester
370
- </h2>
371
-
372
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
373
- <form id="apiTestForm">
374
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
375
- <div>
376
- <label class="block text-sm font-medium mb-2" for="apiKey">API Key</label>
377
- <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">
378
- </div>
379
-
380
- <div>
381
- <label class="block text-sm font-medium mb-2" for="model">Model</label>
382
- <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">
383
- <option value="unitaryai/detoxify-multilingual" selected>Detoxify Multilingual</option>
384
- </select>
385
- </div>
386
- </div>
387
-
388
- <div class="mb-6">
389
- <label class="block text-sm font-medium mb-2">Text Inputs</label>
390
- <div id="textInputsContainer">
391
- <div class="text-input-group mb-4">
392
- <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>
393
- <button type="button" class="remove-input mt-2 text-red-500 hover:text-red-700 hidden">
394
- <i class="fas fa-trash-alt mr-1"></i> Remove
395
- </button>
396
- </div>
397
- </div>
398
- <button type="button" id="addTextInput" class="mt-2 text-primary-600 hover:text-primary-800 dark:text-primary-400 dark:hover:text-primary-300">
399
- <i class="fas fa-plus-circle mr-1"></i> Add another text input
400
- </button>
401
- </div>
402
-
403
- <div class="flex justify-between items-center">
404
- <div>
405
- <button type="submit" id="analyzeBtn" class="bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-6 rounded-lg transition">
406
- <i class="fas fa-search mr-2"></i> Analyze Text
407
- </button>
408
- <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">
409
- <i class="fas fa-eraser mr-2"></i> Clear
410
- </button>
411
- </div>
412
- <div class="text-sm text-gray-500 dark:text-gray-400">
413
- <i class="fas fa-info-circle mr-1"></i> Maximum 10 text inputs allowed
414
- </div>
415
- </div>
416
- </form>
417
- </div>
418
- </section>
419
-
420
- <!-- Results Section -->
421
- <section id="resultsSection" class="hidden">
422
- <h2 class="text-2xl font-bold mb-6 flex items-center">
423
- <i class="fas fa-clipboard-check mr-3 text-primary-600"></i>
424
- Analysis Results
425
- </h2>
426
-
427
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-6">
428
- <div class="flex justify-between items-center mb-4">
429
- <h3 class="text-lg font-semibold">Summary</h3>
430
- <div class="text-sm text-gray-500 dark:text-gray-400">
431
- <i class="fas fa-clock mr-1"></i> Response time: <span id="responseTime">0ms</span> |
432
- <i class="fas fa-key ml-2 mr-1"></i> Tokens: <span id="tokenCount">0</span>
433
- </div>
434
- </div>
435
-
436
- <div id="resultsContainer" class="space-y-6">
437
- <!-- Results will be dynamically inserted here -->
438
- </div>
439
- </div>
440
- </section>
441
-
442
- <!-- API Documentation Section -->
443
- <section>
444
- <h2 class="text-2xl font-bold mb-6 flex items-center">
445
- <i class="fas fa-book mr-3 text-primary-600"></i>
446
- API Documentation
447
- </h2>
448
-
449
- <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
450
- <h3 class="text-lg font-semibold mb-4">Endpoint</h3>
451
- <div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg mb-6 font-mono text-sm">
452
- POST /v1/moderations
453
- </div>
454
-
455
- <h3 class="text-lg font-semibold mb-4">Request Body</h3>
456
- <div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg mb-6 overflow-x-auto">
457
- <pre class="text-sm"><code>{
458
- "model": "unitaryai/detoxify-multilingual",
459
- "input": "Text to moderate"
460
- }</code></pre>
461
- </div>
462
-
463
- <h3 class="text-lg font-semibold mb-4">Response</h3>
464
- <div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg overflow-x-auto">
465
- <pre class="text-sm"><code>{
466
- "id": "modr-1234567890abcdef",
467
- "model": "unitaryai/detoxify-multilingual",
468
- "results": [
469
- {
470
- "flagged": true,
471
- "categories": {
472
- "toxicity": true,
473
- "severe_toxicity": false,
474
- "obscene": true,
475
- "threat": false,
476
- "insult": true,
477
- "identity_attack": false,
478
- "sexual_explicit": false
479
- },
480
- "category_scores": {
481
- "toxicity": 0.95,
482
- "severe_toxicity": 0.1,
483
- "obscene": 0.8,
484
- "threat": 0.05,
485
- "insult": 0.7,
486
- "identity_attack": 0.2,
487
- "sexual_explicit": 0.01
488
- },
489
- "category_applied_input_types": {
490
- "toxicity": ["text"],
491
- "severe_toxicity": [],
492
- "obscene": ["text"],
493
- "threat": [],
494
- "insult": ["text"],
495
- "identity_attack": [],
496
- "sexual_explicit": []
497
- }
498
- }
499
- ],
500
- "object": "moderation",
501
- "usage": {
502
- "total_tokens": 5
503
- }
504
- }</code></pre>
505
- </div>
506
- </div>
507
- </section>
508
- </main>
509
-
510
- <!-- Footer -->
511
- <footer class="bg-gray-100 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12">
512
- <div class="container mx-auto px-4 py-6">
513
- <div class="flex flex-col md:flex-row justify-between items-center">
514
- <div class="mb-4 md:mb-0">
515
- <p class="text-gray-600 dark:text-gray-400">© 2025 Text Moderation API. All rights reserved.</p>
516
- </div>
517
- <div class="flex space-x-4">
518
- <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400">
519
- <i class="fab fa-github"></i>
520
- </a>
521
- <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400">
522
- <i class="fab fa-twitter"></i>
523
- </a>
524
- <a href="#" class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400">
525
- <i class="fas fa-envelope"></i>
526
- </a>
527
- </div>
528
- </div>
529
- </div>
530
- </footer>
531
-
532
- <script>
533
- // Dark mode toggle
534
- const darkModeToggle = document.getElementById('darkModeToggle');
535
- const html = document.documentElement;
536
-
537
- // Check for saved dark mode preference or default to light mode
538
- const currentTheme = localStorage.getItem('theme') || 'light';
539
- if (currentTheme === 'dark') {
540
- html.classList.add('dark');
541
- }
542
-
543
- darkModeToggle.addEventListener('click', () => {
544
- html.classList.toggle('dark');
545
- const theme = html.classList.contains('dark') ? 'dark' : 'light';
546
- localStorage.setItem('theme', theme);
547
- });
548
-
549
- // Chart for activity
550
- let activityChart;
551
-
552
- function initActivityChart() {
553
- const ctx = document.getElementById('activityChart').getContext('2d');
554
- activityChart = new Chart(ctx, {
555
- type: 'bar',
556
- data: {
557
- labels: [],
558
- datasets: [
559
- {
560
- label: 'Requests',
561
- data: [],
562
- backgroundColor: 'rgba(59, 130, 246, 0.5)',
563
- borderColor: 'rgba(59, 130, 246, 1)',
564
- borderWidth: 1
565
- },
566
- {
567
- label: 'Tokens',
568
- data: [],
569
- backgroundColor: 'rgba(16, 185, 129, 0.5)',
570
- borderColor: 'rgba(16, 185, 129, 1)',
571
- borderWidth: 1,
572
- yAxisID: 'y1'
573
- }
574
- ]
575
- },
576
- options: {
577
- responsive: true,
578
- maintainAspectRatio: false,
579
- scales: {
580
- y: {
581
- beginAtZero: true,
582
- position: 'left',
583
- title: {
584
- display: true,
585
- text: 'Requests'
586
- }
587
- },
588
- y1: {
589
- beginAtZero: true,
590
- position: 'right',
591
- title: {
592
- display: true,
593
- text: 'Tokens'
594
- },
595
- grid: {
596
- drawOnChartArea: false
597
- }
598
- }
599
- }
600
- }
601
- });
602
- }
603
-
604
- // Fetch performance metrics
605
- async function fetchMetrics() {
606
- const apiKey = document.getElementById('apiKey').value;
607
- if (!apiKey) {
608
- console.error('API key is required');
609
- return;
610
- }
611
-
612
- try {
613
- const response = await fetch('/v1/metrics', {
614
- method: 'GET',
615
- headers: {
616
- 'Content-Type': 'application/json',
617
- 'Authorization': 'Bearer ' + apiKey
618
- }
619
- });
620
-
621
- if (!response.ok) {
622
- throw new Error('Failed to fetch metrics');
623
- }
624
-
625
- const data = await response.json();
626
- updateMetricsDisplay(data);
627
- } catch (error) {
628
- console.error('Error fetching metrics:', error);
629
- }
630
- }
631
-
632
- // Update metrics display
633
- function updateMetricsDisplay(data) {
634
- document.getElementById('avgResponseTime').textContent = (data.avg_request_time * 1000).toFixed(2) + 'ms';
635
- document.getElementById('requestsPerSecond').textContent = data.requests_per_second.toFixed(2);
636
- document.getElementById('concurrentRequests').textContent = data.concurrent_requests;
637
- document.getElementById('todayTokens').textContent = data.today_tokens.toLocaleString();
638
-
639
- // Update activity chart
640
- if (activityChart) {
641
- const labels = data.last_7_days.map(day => {
642
- const date = new Date(day.date);
643
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
644
- }).reverse();
645
-
646
- const requests = data.last_7_days.map(day => day.requests).reverse();
647
- const tokens = data.last_7_days.map(day => day.tokens).reverse();
648
-
649
- activityChart.data.labels = labels;
650
- activityChart.data.datasets[0].data = requests;
651
- activityChart.data.datasets[1].data = tokens;
652
- activityChart.update();
653
- }
654
- }
655
-
656
- // Add text input
657
- document.getElementById('addTextInput').addEventListener('click', () => {
658
- const container = document.getElementById('textInputsContainer');
659
- const inputGroups = container.querySelectorAll('.text-input-group');
660
-
661
- if (inputGroups.length >= 10) {
662
- alert('Maximum 10 text inputs allowed');
663
- return;
664
- }
665
-
666
- const newInputGroup = document.createElement('div');
667
- newInputGroup.className = 'text-input-group mb-4';
668
- newInputGroup.innerHTML = `
669
- <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>
670
- <button type="button" class="remove-input mt-2 text-red-500 hover:text-red-700">
671
- <i class="fas fa-trash-alt mr-1"></i> Remove
672
- </button>
673
- `;
674
-
675
- container.appendChild(newInputGroup);
676
-
677
- // Add event listener to remove button
678
- newInputGroup.querySelector('.remove-input').addEventListener('click', function() {
679
- newInputGroup.remove();
680
- updateRemoveButtons();
681
- });
682
-
683
- updateRemoveButtons();
684
- });
685
-
686
- // Update remove buttons visibility
687
- function updateRemoveButtons() {
688
- const inputGroups = document.querySelectorAll('.text-input-group');
689
- inputGroups.forEach((group, index) => {
690
- const removeBtn = group.querySelector('.remove-input');
691
- if (inputGroups.length > 1) {
692
- removeBtn.classList.remove('hidden');
693
- } else {
694
- removeBtn.classList.add('hidden');
695
- }
696
- });
697
- }
698
-
699
- // Remove text input
700
- document.addEventListener('click', function(e) {
701
- if (e.target.closest('.remove-input')) {
702
- e.target.closest('.text-input-group').remove();
703
- updateRemoveButtons();
704
- }
705
- });
706
-
707
- // Clear form
708
- document.getElementById('clearBtn').addEventListener('click', () => {
709
- document.getElementById('apiTestForm').reset();
710
- const container = document.getElementById('textInputsContainer');
711
- container.innerHTML = `
712
- <div class="text-input-group mb-4">
713
- <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>
714
- <button type="button" class="remove-input mt-2 text-red-500 hover:text-red-700 hidden">
715
- <i class="fas fa-trash-alt mr-1"></i> Remove
716
- </button>
717
- </div>
718
- `;
719
- document.getElementById('resultsSection').classList.add('hidden');
720
- });
721
-
722
- // Analyze text
723
- document.getElementById('apiTestForm').addEventListener('submit', async (e) => {
724
- e.preventDefault();
725
-
726
- const apiKey = document.getElementById('apiKey').value;
727
- const model = document.getElementById('model').value;
728
- const textInputs = document.querySelectorAll('#textInputsContainer textarea');
729
-
730
- if (!apiKey) {
731
- alert('Please enter your API key');
732
- return;
733
- }
734
-
735
- const texts = [];
736
- textInputs.forEach(input => {
737
- if (input.value.trim()) {
738
- texts.push(input.value.trim());
739
- }
740
- });
741
-
742
- if (texts.length === 0) {
743
- alert('Please enter at least one text to analyze');
744
- return;
745
- }
746
-
747
- const analyzeBtn = document.getElementById('analyzeBtn');
748
- const originalBtnContent = analyzeBtn.innerHTML;
749
- 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...';
750
- analyzeBtn.disabled = true;
751
-
752
- const startTime = Date.now();
753
-
754
- try {
755
- const response = await fetch('/v1/moderations', {
756
- method: 'POST',
757
- headers: {
758
- 'Content-Type': 'application/json',
759
- 'Authorization': 'Bearer ' + apiKey
760
- },
761
- body: JSON.stringify({
762
- model: model,
763
- input: texts
764
- })
765
- });
766
-
767
- const endTime = Date.now();
768
- const responseTime = endTime - startTime;
769
-
770
- if (!response.ok) {
771
- const errorData = await response.json();
772
- throw new Error(errorData.error || 'Failed to analyze text');
773
- }
774
-
775
- const data = await response.json();
776
- displayResults(data, responseTime);
777
-
778
- } catch (error) {
779
- console.error('Error analyzing text:', error);
780
- alert('Error: ' + error.message);
781
- } finally {
782
- analyzeBtn.innerHTML = originalBtnContent;
783
- analyzeBtn.disabled = false;
784
- }
785
- });
786
-
787
- // Display results
788
- function displayResults(data, responseTime) {
789
- const resultsSection = document.getElementById('resultsSection');
790
- const resultsContainer = document.getElementById('resultsContainer');
791
-
792
- document.getElementById('responseTime').textContent = responseTime + 'ms';
793
- document.getElementById('tokenCount').textContent = data.usage.total_tokens.toLocaleString();
794
-
795
- resultsContainer.innerHTML = '';
796
-
797
- data.results.forEach((result, index) => {
798
- const resultCard = document.createElement('div');
799
- resultCard.className = 'border border-gray-200 dark:border-gray-700 rounded-lg p-4';
800
-
801
- const flaggedBadge = result.flagged
802
- ? '<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>'
803
- : '<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>';
804
-
805
- let categoriesHtml = '';
806
- for (const [category, isFlagged] of Object.entries(result.categories)) {
807
- const score = result.category_scores[category];
808
- const categoryClass = isFlagged ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400';
809
- 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';
810
-
811
- categoriesHtml += `
812
- <div class="flex justify-between items-center py-2 border-b border-gray-100 dark:border-gray-700">
813
- <span class="font-medium capitalize">${category.replace('_', ' ')}</span>
814
- <div class="flex items-center">
815
- <span class="${categoryClass} mr-2">${isFlagged ? 'Flagged' : 'Safe'}</span>
816
- <span class="text-sm ${scoreClass} font-mono">${score.toFixed(4)}</span>
817
- </div>
818
- </div>
819
- `;
820
- }
821
-
822
- resultCard.innerHTML = `
823
- <div class="flex justify-between items-start mb-3">
824
- <h4 class="text-lg font-semibold">Text ${index + 1}</h4>
825
- ${flaggedBadge}
826
- </div>
827
- <div class="mb-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg text-sm">
828
- ${texts[index]}
829
- </div>
830
- <div class="category-card">
831
- <h5 class="font-medium mb-2">Categories</h5>
832
- <div class="bg-white dark:bg-gray-700 rounded-lg overflow-hidden">
833
- ${categoriesHtml}
834
- </div>
835
- </div>
836
- `;
837
-
838
- resultsContainer.appendChild(resultCard);
839
- });
840
-
841
- resultsSection.classList.remove('hidden');
842
- resultsSection.scrollIntoView({ behavior: 'smooth' });
843
- }
844
-
845
- // Initialize chart on page load
846
- document.addEventListener('DOMContentLoaded', () => {
847
- initActivityChart();
848
-
849
- // Set up refresh metrics button
850
- document.getElementById('refreshMetrics').addEventListener('click', fetchMetrics);
851
-
852
- // Initial metrics fetch
853
- fetchMetrics();
854
-
855
- // Auto-refresh metrics every 30 seconds
856
- setInterval(fetchMetrics, 30000);
857
- });
858
- </script>
859
- </body>
860
- </html>''')
861
-
862
- if __name__ == '__main__':
863
- # Create directories and files
864
- create_directories_and_files()
865
-
866
- port = int(os.getenv('PORT', 7860))
867
- app.run(host='0.0.0.0', port=port, debug=True)