ourafla commited on
Commit
bd8cc3c
·
verified ·
1 Parent(s): 60ed450

Update app.py

Browse files

Major update on the UI, Color schema, functional theme change button.

Files changed (1) hide show
  1. app.py +422 -1041
app.py CHANGED
@@ -1,1041 +1,422 @@
1
- import os
2
- import torch
3
- import numpy as np
4
- import gradio as gr
5
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
6
-
7
-
8
- # === Model setup ===
9
- MODEL_NAME = "ourafla/mental-health-bert-finetuned"
10
- id2label = {0: "Anxiety", 1: "Depression", 2: "Normal", 3: "Suicidal"}
11
- hf_token = os.getenv("HUGGINGFACE_HUB_TOKEN")
12
-
13
-
14
- tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, token=hf_token)
15
- model = AutoModelForSequenceClassification.from_pretrained(
16
- MODEL_NAME, token=hf_token, output_attentions=True
17
- )
18
- model.eval()
19
-
20
-
21
- # === Core function (Unchanged) ===
22
- def analyze_text(text):
23
- text = text.strip()
24
- if not text or len(text) < 5:
25
- error_html = """
26
- <div style='text-align:center; background: rgba(239, 68, 68, 0.1);
27
- color: #fca5a5; padding: 20px; border-radius: 12px;
28
- border: 2px solid rgba(239, 68, 68, 0.3); font-size: 1.1em;'>
29
- <div style='font-size: 2em; margin-bottom: 8px;'>⚠️</div>
30
- <div style='font-weight: 600;'>Please enter more meaningful text</div>
31
- <div style='margin-top: 8px; font-size: 0.9em; opacity: 0.9;'>
32
- (at least 5 characters required)
33
- </div>
34
- </div>
35
- """
36
- return error_html, "N/A"
37
-
38
- inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=128)
39
- with torch.no_grad():
40
- outputs = model(**inputs)
41
-
42
- probs = torch.softmax(outputs.logits, dim=-1)[0]
43
- label_idx = torch.argmax(probs).item()
44
- label = id2label[label_idx]
45
- confidence = float(probs[label_idx])
46
-
47
- # Get all probabilities for statistics
48
- all_probs = {id2label[i]: float(probs[i]) for i in range(len(id2label))}
49
- sorted_probs = sorted(all_probs.items(), key=lambda x: x[1], reverse=True)
50
-
51
- try:
52
- attn = outputs.attentions[-1]
53
- scores = attn.mean(dim=1).mean(dim=1)[0]
54
- scores = scores / (scores.max() + 1e-8)
55
- tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
56
- top = torch.argsort(scores)[-5:].tolist()[::-1]
57
- top_tokens = [tokens[i] for i in top if tokens[i] not in ["[CLS]", "[SEP]", "[PAD]"]]
58
- explanation = ", ".join(top_tokens[:3])
59
- except Exception:
60
- explanation = "Explanation not available."
61
-
62
- # Determine status class and emoji based on label
63
- status_map = {
64
- "Anxiety": ("status-anxiety", "😰", "#fbbf24"),
65
- "Depression": ("status-depression", "😔", "#3b82f6"),
66
- "Normal": ("status-normal", "😊", "#22c55e"),
67
- "Suicidal": ("status-suicidal", "⚠️", "#ef4444")
68
- }
69
- status_class, emoji, status_color = status_map.get(label, ("", "📊", "#6b7280"))
70
-
71
- # Create confidence bar
72
- confidence_percent = int(confidence * 100)
73
- confidence_bar_width = confidence_percent
74
-
75
- # Build probability breakdown HTML
76
- prob_breakdown = ""
77
- for prob_label, prob_value in sorted_probs:
78
- prob_pct = int(prob_value * 100)
79
- bar_color = status_map.get(prob_label, ("", "", "#6b7280"))[2]
80
- prob_breakdown += f"""
81
- <div style='margin: 12px 0;'>
82
- <div style='display: flex; justify-content: space-between; margin-bottom: 6px;'>
83
- <span style='color: #e2e8f0; font-weight: 500;'>{prob_label}</span>
84
- <span style='color: #93c5fd; font-weight: 600;'>{prob_pct}%</span>
85
- </div>
86
- <div style='width: 100%; background: rgba(15, 23, 42, 0.5);
87
- border-radius: 8px; height: 8px; overflow: hidden;'>
88
- <div style='background: {bar_color}; height: 100%; width: {prob_pct}%;
89
- transition: width 0.5s ease; border-radius: 8px;'></div>
90
- </div>
91
- </div>
92
- """
93
-
94
- summary_html = f"""
95
- <div class='prediction-result {status_class}'>
96
- <div style='font-size: 3em; margin-bottom: 16px; animation: pulse 2s ease-in-out infinite;'>{emoji}</div>
97
- <div class='prediction-label'>{label}</div>
98
- <div class='prediction-confidence'>
99
- Confidence: {confidence_percent}%
100
- </div>
101
- <div style='margin-top: 20px; width: 100%; background: rgba(15, 23, 42, 0.5);
102
- border-radius: 10px; height: 14px; overflow: hidden;'>
103
- <div style='background: linear-gradient(90deg, {status_color}, {status_color}dd);
104
- height: 100%; width: {confidence_bar_width}%;
105
- transition: width 0.5s ease; border-radius: 10px;
106
- box-shadow: 0 0 10px {status_color}40;'></div>
107
- </div>
108
- <div style='margin-top: 24px; padding-top: 20px; border-top: 1px solid rgba(148, 163, 184, 0.2);'>
109
- <div style='font-size: 0.95em; color: #94a3b8; margin-bottom: 12px; font-weight: 600;'>
110
- Probability Breakdown:
111
- </div>
112
- {prob_breakdown}
113
- </div>
114
- <div style='margin-top: 16px; font-size: 0.85em; color: #64748b; font-style: italic;'>
115
- Analysis based on BERT-based sentiment classification
116
- </div>
117
- </div>
118
- """
119
- return summary_html, explanation
120
-
121
-
122
-
123
- # === ENHANCED GRADIO INTERFACE WITH MODERN UI ===
124
- with gr.Blocks(
125
- theme=gr.themes.Soft(
126
- primary_hue="blue",
127
- secondary_hue="gray",
128
- neutral_hue="slate",
129
- ),
130
- css="""
131
- /* === 1. Main Container: Modern Gradient Background === */
132
- .gradio-container {
133
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
134
- background-attachment: fixed;
135
- color: #F9FAFB;
136
- width: 100%;
137
- min-height: 100vh;
138
- padding: 2rem;
139
- margin: 0;
140
- box-sizing: border-box;
141
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
142
- }
143
-
144
- /* App panel with glassmorphism effect */
145
- .gradio-container > .wrap {
146
- max-width: 1000px;
147
- margin: 0 auto;
148
- background: rgba(30, 41, 59, 0.85);
149
- backdrop-filter: blur(20px);
150
- border-radius: 20px;
151
- padding: 3rem;
152
- border: 1px solid rgba(148, 163, 184, 0.1);
153
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5),
154
- 0 0 0 1px rgba(255, 255, 255, 0.05) inset;
155
- }
156
-
157
- /* === 2. Enhanced Typography === */
158
- .title-container {
159
- text-align: center;
160
- width: 100%;
161
- margin-bottom: 1rem;
162
- position: relative;
163
- }
164
-
165
- .title-container::after {
166
- content: '';
167
- position: absolute;
168
- bottom: -10px;
169
- left: 50%;
170
- transform: translateX(-50%);
171
- width: 80px;
172
- height: 4px;
173
- background: linear-gradient(90deg, #3b82f6, #8b5cf6);
174
- border-radius: 2px;
175
- }
176
-
177
- h1 {
178
- background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 100%);
179
- -webkit-background-clip: text;
180
- -webkit-text-fill-color: transparent;
181
- background-clip: text;
182
- font-weight: 900;
183
- font-size: 2.5em;
184
- letter-spacing: -0.5px;
185
- margin-bottom: 0.5rem;
186
- text-shadow: 0 0 30px rgba(96, 165, 250, 0.3);
187
- }
188
-
189
- .subtitle {
190
- color: #cbd5e1;
191
- font-size: 1.15em;
192
- margin: 2rem 0 2.5rem 0;
193
- text-align: center;
194
- line-height: 1.6;
195
- font-weight: 400;
196
- }
197
-
198
- .subtitle b {
199
- color: #93c5fd;
200
- font-weight: 600;
201
- }
202
-
203
- /* === 3. Enhanced Input Components === */
204
- textarea, .gr-textbox, .gr-input textarea {
205
- background: linear-gradient(135deg, rgba(15, 23, 42, 0.7) 0%, rgba(30, 41, 59, 0.7) 100%) !important;
206
- border: 2px solid rgba(148, 163, 184, 0.2) !important;
207
- border-radius: 16px !important;
208
- color: #f1f5f9 !important;
209
- padding: 20px !important;
210
- font-size: 1.05em !important;
211
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
212
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
213
- line-height: 1.7 !important;
214
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1),
215
- inset 0 1px 0 rgba(255, 255, 255, 0.05) !important;
216
- resize: vertical !important;
217
- min-height: 120px !important;
218
- }
219
-
220
- textarea::placeholder, .gr-textbox::placeholder {
221
- color: #64748b !important;
222
- opacity: 0.7 !important;
223
- font-style: italic !important;
224
- }
225
-
226
- textarea:hover, .gr-textbox:hover, .gr-input textarea:hover {
227
- border-color: rgba(59, 130, 246, 0.4) !important;
228
- box-shadow: 0 6px 12px rgba(59, 130, 246, 0.15),
229
- inset 0 1px 0 rgba(255, 255, 255, 0.05) !important;
230
- transform: translateY(-1px) !important;
231
- }
232
-
233
- textarea:focus, .gr-textbox:focus, .gr-input textarea:focus {
234
- border-color: #3b82f6 !important;
235
- box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15),
236
- 0 8px 24px rgba(59, 130, 246, 0.3),
237
- inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
238
- background: linear-gradient(135deg, rgba(15, 23, 42, 0.9) 0%, rgba(30, 41, 59, 0.9) 100%) !important;
239
- outline: none !important;
240
- transform: translateY(-2px) scale(1.01) !important;
241
- animation: inputGlow 2s ease-in-out infinite !important;
242
- }
243
-
244
- @keyframes inputGlow {
245
- 0%, 100% {
246
- box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15),
247
- 0 8px 24px rgba(59, 130, 246, 0.3),
248
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
249
- }
250
- 50% {
251
- box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.25),
252
- 0 8px 24px rgba(59, 130, 246, 0.4),
253
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
254
- }
255
- }
256
-
257
- .gr-textbox label, .gr-input label {
258
- color: #e2e8f0 !important;
259
- font-weight: 700 !important;
260
- font-size: 1.15em !important;
261
- margin-bottom: 1rem !important;
262
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
263
- letter-spacing: 0.3px !important;
264
- }
265
-
266
- /* === 4. Enhanced Button === */
267
- button.primary {
268
- background: linear-gradient(135deg, #3b82f6 0%, #6366f1 50%, #8b5cf6 100%) !important;
269
- background-size: 200% 200% !important;
270
- color: #ffffff !important;
271
- border-radius: 14px !important;
272
- border: none !important;
273
- font-weight: 800 !important;
274
- font-size: 1.15em !important;
275
- padding: 18px 36px !important;
276
- width: 100% !important;
277
- margin-top: 1.5rem !important;
278
- cursor: pointer !important;
279
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
280
- box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4),
281
- 0 0 0 0 rgba(59, 130, 246, 0.4),
282
- inset 0 1px 0 rgba(255, 255, 255, 0.2) !important;
283
- text-transform: none !important;
284
- letter-spacing: 0.5px !important;
285
- position: relative !important;
286
- overflow: hidden !important;
287
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) !important;
288
- animation: buttonShimmer 3s ease-in-out infinite !important;
289
- }
290
-
291
- button.primary::before {
292
- content: '';
293
- position: absolute;
294
- top: 0;
295
- left: -100%;
296
- width: 100%;
297
- height: 100%;
298
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
299
- transition: left 0.5s;
300
- }
301
-
302
- button.primary:hover::before {
303
- left: 100%;
304
- }
305
-
306
- @keyframes buttonShimmer {
307
- 0%, 100% {
308
- background-position: 0% 50%;
309
- }
310
- 50% {
311
- background-position: 100% 50%;
312
- }
313
- }
314
-
315
- button.primary:hover {
316
- background: linear-gradient(135deg, #2563eb 0%, #4f46e5 50%, #7c3aed 100%) !important;
317
- background-size: 200% 200% !important;
318
- box-shadow: 0 8px 30px rgba(59, 130, 246, 0.6),
319
- 0 0 0 4px rgba(59, 130, 246, 0.2),
320
- inset 0 1px 0 rgba(255, 255, 255, 0.3) !important;
321
- transform: translateY(-3px) scale(1.02) !important;
322
- animation: buttonShimmer 2s ease-in-out infinite !important;
323
- }
324
-
325
- button.primary:active {
326
- transform: translateY(-1px) scale(0.98) !important;
327
- box-shadow: 0 4px 15px rgba(59, 130, 246, 0.5),
328
- 0 0 0 2px rgba(59, 130, 246, 0.3) !important;
329
- transition: all 0.1s ease !important;
330
- }
331
-
332
- button.primary:disabled {
333
- opacity: 0.6 !important;
334
- cursor: not-allowed !important;
335
- transform: none !important;
336
- }
337
-
338
- /* Example Buttons */
339
- button.secondary {
340
- background: linear-gradient(135deg, rgba(30, 41, 59, 0.8) 0%, rgba(15, 23, 42, 0.8) 100%) !important;
341
- color: #cbd5e1 !important;
342
- border: 2px solid rgba(148, 163, 184, 0.3) !important;
343
- border-radius: 12px !important;
344
- font-weight: 600 !important;
345
- padding: 10px 20px !important;
346
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
347
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
348
- }
349
-
350
- button.secondary:hover {
351
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(139, 92, 246, 0.2) 100%) !important;
352
- border-color: rgba(59, 130, 246, 0.5) !important;
353
- color: #60a5fa !important;
354
- transform: translateY(-2px) !important;
355
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
356
- }
357
-
358
- button.secondary:active {
359
- transform: translateY(0) scale(0.98) !important;
360
- }
361
-
362
- /* === 5. Enhanced Output Cards === */
363
- .output-card {
364
- background: rgba(15, 23, 42, 0.6) !important;
365
- border: 2px solid rgba(148, 163, 184, 0.15) !important;
366
- border-radius: 12px !important;
367
- padding: 16px !important;
368
- color: #f1f5f9 !important;
369
- font-size: 1.05em !important;
370
- transition: all 0.3s ease !important;
371
- }
372
-
373
- .output-card-html {
374
- background: rgba(15, 23, 42, 0.6) !important;
375
- border-radius: 12px !important;
376
- padding: 20px !important;
377
- border: 2px solid rgba(148, 163, 184, 0.15) !important;
378
- }
379
-
380
- /* Prediction result styling */
381
- .prediction-result {
382
- text-align: center;
383
- background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%);
384
- background-size: 200% 200%;
385
- color: #f9fafb;
386
- border-radius: 20px;
387
- padding: 32px;
388
- border: 2px solid rgba(59, 130, 246, 0.3);
389
- box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4),
390
- 0 0 0 1px rgba(255, 255, 255, 0.05) inset,
391
- 0 0 60px rgba(59, 130, 246, 0.1);
392
- font-size: 1.1em;
393
- margin: 10px 0;
394
- position: relative;
395
- overflow: hidden;
396
- animation: resultAppear 0.6s cubic-bezier(0.4, 0, 0.2, 1),
397
- gradientShift 8s ease infinite;
398
- }
399
-
400
- .prediction-result::before {
401
- content: '';
402
- position: absolute;
403
- top: -50%;
404
- left: -50%;
405
- width: 200%;
406
- height: 200%;
407
- background: radial-gradient(circle, rgba(59, 130, 246, 0.1) 0%, transparent 70%);
408
- animation: rotate 20s linear infinite;
409
- pointer-events: none;
410
- }
411
-
412
- @keyframes resultAppear {
413
- 0% {
414
- opacity: 0;
415
- transform: scale(0.9) translateY(20px);
416
- }
417
- 100% {
418
- opacity: 1;
419
- transform: scale(1) translateY(0);
420
- }
421
- }
422
-
423
- @keyframes rotate {
424
- 0% {
425
- transform: rotate(0deg);
426
- }
427
- 100% {
428
- transform: rotate(360deg);
429
- }
430
- }
431
-
432
- .prediction-label {
433
- font-size: 1.5em;
434
- font-weight: 800;
435
- margin: 12px 0;
436
- background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 100%);
437
- -webkit-background-clip: text;
438
- -webkit-text-fill-color: transparent;
439
- background-clip: text;
440
- }
441
-
442
- .prediction-confidence {
443
- font-size: 1.2em;
444
- color: #93c5fd;
445
- font-weight: 600;
446
- margin-top: 8px;
447
- }
448
-
449
- /* Status indicators */
450
- .status-anxiety { border-color: rgba(251, 191, 36, 0.4) !important; }
451
- .status-depression { border-color: rgba(59, 130, 246, 0.4) !important; }
452
- .status-normal { border-color: rgba(34, 197, 94, 0.4) !important; }
453
- .status-suicidal { border-color: rgba(239, 68, 68, 0.4) !important; }
454
-
455
- /* === 6. Enhanced Footer === */
456
- .app-credits {
457
- text-align: center;
458
- color: #94a3b8;
459
- font-size: 0.95em;
460
- margin-top: 40px;
461
- border-top: 1px solid rgba(148, 163, 184, 0.2);
462
- padding-top: 24px;
463
- line-height: 1.8;
464
- }
465
-
466
- .app-credits a {
467
- color: #60a5fa;
468
- text-decoration: none;
469
- font-weight: 600;
470
- transition: all 0.2s ease;
471
- }
472
-
473
- .app-credits a:hover {
474
- color: #93c5fd;
475
- text-decoration: underline;
476
- }
477
-
478
- footer {
479
- width: 100% !important;
480
- text-align: center !important;
481
- background: transparent !important;
482
- color: #94a3b8 !important;
483
- padding: 20px 0 0 0 !important;
484
- border-top: 1px solid rgba(148, 163, 184, 0.2) !important;
485
- margin-top: 30px !important;
486
- }
487
-
488
- footer .built-with {
489
- display: none !important;
490
- }
491
-
492
- footer .duplicate-button {
493
- background: rgba(30, 41, 59, 0.8) !important;
494
- color: #e2e8f0 !important;
495
- border: 1px solid rgba(148, 163, 184, 0.3) !important;
496
- border-radius: 8px !important;
497
- padding: 8px 16px !important;
498
- text-decoration: none !important;
499
- font-weight: 600 !important;
500
- margin: 0 6px !important;
501
- transition: all 0.2s ease !important;
502
- }
503
-
504
- footer .duplicate-button:hover {
505
- background: rgba(59, 130, 246, 0.2) !important;
506
- border-color: rgba(59, 130, 246, 0.4) !important;
507
- color: #93c5fd !important;
508
- }
509
-
510
- /* === 7. Loading States === */
511
- .gr-loading {
512
- opacity: 0.7;
513
- }
514
-
515
- /* === 8. Mobile Responsive === */
516
- @media (max-width: 768px) {
517
- .gradio-container {
518
- padding: 1rem;
519
- }
520
-
521
- .gradio-container > .wrap {
522
- padding: 2rem 1.5rem;
523
- border-radius: 16px;
524
- }
525
-
526
- h1 {
527
- font-size: 2em;
528
- }
529
-
530
- .subtitle {
531
- font-size: 1em;
532
- margin: 1.5rem 0 2rem 0;
533
- }
534
-
535
- button.primary {
536
- padding: 14px 24px !important;
537
- font-size: 1em !important;
538
- }
539
- }
540
-
541
- @media (max-width: 480px) {
542
- h1 {
543
- font-size: 1.75em;
544
- }
545
-
546
- .gradio-container > .wrap {
547
- padding: 1.5rem 1rem;
548
- }
549
- }
550
-
551
- /* === 9. Smooth Animations === */
552
- @keyframes fadeIn {
553
- from {
554
- opacity: 0;
555
- transform: translateY(10px);
556
- }
557
- to {
558
- opacity: 1;
559
- transform: translateY(0);
560
- }
561
- }
562
-
563
- @keyframes pulse {
564
- 0%, 100% {
565
- transform: scale(1);
566
- opacity: 1;
567
- filter: drop-shadow(0 0 10px rgba(96, 165, 250, 0.5));
568
- }
569
- 50% {
570
- transform: scale(1.15);
571
- opacity: 0.95;
572
- filter: drop-shadow(0 0 20px rgba(96, 165, 250, 0.8));
573
- }
574
- }
575
-
576
- @keyframes float {
577
- 0%, 100% {
578
- transform: translateY(0px);
579
- }
580
- 50% {
581
- transform: translateY(-10px);
582
- }
583
- }
584
-
585
- @keyframes gradientShift {
586
- 0% {
587
- background-position: 0% 50%;
588
- }
589
- 50% {
590
- background-position: 100% 50%;
591
- }
592
- 100% {
593
- background-position: 0% 50%;
594
- }
595
- }
596
-
597
- @keyframes slideIn {
598
- from {
599
- opacity: 0;
600
- transform: translateX(-20px);
601
- }
602
- to {
603
- opacity: 1;
604
- transform: translateX(0);
605
- }
606
- }
607
-
608
- .output-card, .output-card-html {
609
- animation: fadeIn 0.4s ease-out;
610
- }
611
-
612
- /* === 11. Container-based Layout System === */
613
- .main-container {
614
- display: flex;
615
- flex-direction: column;
616
- gap: 2rem;
617
- width: 100%;
618
- max-width: 1200px;
619
- margin: 0 auto;
620
- }
621
-
622
- .content-container {
623
- display: grid;
624
- grid-template-columns: 1fr;
625
- gap: 1.5rem;
626
- width: 100%;
627
- }
628
-
629
- .input-container {
630
- background: linear-gradient(135deg, rgba(15, 23, 42, 0.5) 0%, rgba(30, 41, 59, 0.5) 100%);
631
- border-radius: 20px;
632
- padding: 2.5rem;
633
- border: 2px solid rgba(148, 163, 184, 0.1);
634
- backdrop-filter: blur(15px);
635
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
636
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2),
637
- inset 0 1px 0 rgba(255, 255, 255, 0.05);
638
- position: relative;
639
- overflow: hidden;
640
- }
641
-
642
- .input-container::before {
643
- content: '';
644
- position: absolute;
645
- top: 0;
646
- left: -100%;
647
- width: 100%;
648
- height: 100%;
649
- background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.05), transparent);
650
- transition: left 0.6s;
651
- }
652
-
653
- .input-container:hover::before {
654
- left: 100%;
655
- }
656
-
657
- .input-container:hover {
658
- border-color: rgba(59, 130, 246, 0.4);
659
- box-shadow: 0 12px 40px rgba(59, 130, 246, 0.15),
660
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
661
- transform: translateY(-2px);
662
- }
663
-
664
- .output-container {
665
- display: grid;
666
- grid-template-columns: 1fr;
667
- gap: 1.5rem;
668
- margin-top: 1rem;
669
- }
670
-
671
- .result-container {
672
- background: linear-gradient(135deg, rgba(15, 23, 42, 0.5) 0%, rgba(30, 41, 59, 0.5) 100%);
673
- border-radius: 20px;
674
- padding: 2rem;
675
- border: 2px solid rgba(148, 163, 184, 0.1);
676
- backdrop-filter: blur(15px);
677
- animation: slideIn 0.6s cubic-bezier(0.4, 0, 0.2, 1);
678
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2),
679
- inset 0 1px 0 rgba(255, 255, 255, 0.05);
680
- position: relative;
681
- overflow: hidden;
682
- }
683
-
684
- .result-container::after {
685
- content: '';
686
- position: absolute;
687
- top: 0;
688
- left: 0;
689
- right: 0;
690
- height: 3px;
691
- background: linear-gradient(90deg, #3b82f6, #8b5cf6, #3b82f6);
692
- background-size: 200% 100%;
693
- animation: gradientShift 3s ease infinite;
694
- }
695
-
696
- .stats-container {
697
- display: grid;
698
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
699
- gap: 1rem;
700
- margin-top: 1.5rem;
701
- }
702
-
703
- .stat-card {
704
- background: linear-gradient(135deg, rgba(30, 41, 59, 0.7) 0%, rgba(15, 23, 42, 0.7) 100%);
705
- border-radius: 14px;
706
- padding: 1.5rem;
707
- border: 2px solid rgba(148, 163, 184, 0.15);
708
- text-align: center;
709
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
710
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
711
- position: relative;
712
- overflow: hidden;
713
- }
714
-
715
- .stat-card::before {
716
- content: '';
717
- position: absolute;
718
- top: 0;
719
- left: -100%;
720
- width: 100%;
721
- height: 100%;
722
- background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1), transparent);
723
- transition: left 0.5s;
724
- }
725
-
726
- .stat-card:hover::before {
727
- left: 100%;
728
- }
729
-
730
- .stat-card:hover {
731
- transform: translateY(-6px) scale(1.05);
732
- border-color: rgba(59, 130, 246, 0.5);
733
- box-shadow: 0 12px 32px rgba(59, 130, 246, 0.3),
734
- 0 0 0 2px rgba(59, 130, 246, 0.2);
735
- }
736
-
737
- .stat-label {
738
- font-size: 0.85em;
739
- color: #94a3b8;
740
- margin-bottom: 0.5rem;
741
- text-transform: uppercase;
742
- letter-spacing: 0.5px;
743
- }
744
-
745
- .stat-value {
746
- font-size: 1.5em;
747
- font-weight: 700;
748
- color: #60a5fa;
749
- }
750
-
751
- /* Tab Container */
752
- .tab-container {
753
- background: linear-gradient(135deg, rgba(15, 23, 42, 0.5) 0%, rgba(30, 41, 59, 0.5) 100%);
754
- border-radius: 20px;
755
- padding: 2rem;
756
- margin-top: 1rem;
757
- border: 2px solid rgba(148, 163, 184, 0.1);
758
- backdrop-filter: blur(15px);
759
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
760
- animation: fadeIn 0.5s ease-out;
761
- }
762
-
763
- /* Enhanced Tab Styling */
764
- .tab-nav {
765
- border-bottom: 2px solid rgba(148, 163, 184, 0.1) !important;
766
- margin-bottom: 1.5rem !important;
767
- }
768
-
769
- .tab-nav button {
770
- background: transparent !important;
771
- border: none !important;
772
- color: #94a3b8 !important;
773
- padding: 12px 24px !important;
774
- font-weight: 600 !important;
775
- transition: all 0.3s ease !important;
776
- border-radius: 8px 8px 0 0 !important;
777
- position: relative !important;
778
- }
779
-
780
- .tab-nav button:hover {
781
- color: #60a5fa !important;
782
- background: rgba(59, 130, 246, 0.1) !important;
783
- }
784
-
785
- .tab-nav button.selected {
786
- color: #60a5fa !important;
787
- background: rgba(59, 130, 246, 0.15) !important;
788
- }
789
-
790
- .tab-nav button.selected::after {
791
- content: '';
792
- position: absolute;
793
- bottom: -2px;
794
- left: 0;
795
- right: 0;
796
- height: 3px;
797
- background: linear-gradient(90deg, #3b82f6, #8b5cf6);
798
- border-radius: 2px 2px 0 0;
799
- animation: gradientShift 3s ease infinite;
800
- }
801
-
802
- .example-container {
803
- display: grid;
804
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
805
- gap: 1rem;
806
- margin-top: 1rem;
807
- }
808
-
809
- .example-card {
810
- background: linear-gradient(135deg, rgba(30, 41, 59, 0.7) 0%, rgba(15, 23, 42, 0.7) 100%);
811
- border-radius: 14px;
812
- padding: 1.25rem;
813
- border: 2px solid rgba(148, 163, 184, 0.15);
814
- cursor: pointer;
815
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
816
- position: relative;
817
- overflow: hidden;
818
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
819
- }
820
-
821
- .example-card::before {
822
- content: '';
823
- position: absolute;
824
- top: 0;
825
- left: -100%;
826
- width: 100%;
827
- height: 100%;
828
- background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1), transparent);
829
- transition: left 0.5s;
830
- }
831
-
832
- .example-card:hover::before {
833
- left: 100%;
834
- }
835
-
836
- .example-card:hover {
837
- background: linear-gradient(135deg, rgba(30, 41, 59, 0.9) 0%, rgba(15, 23, 42, 0.9) 100%);
838
- border-color: rgba(59, 130, 246, 0.5);
839
- transform: translateY(-4px) scale(1.02);
840
- box-shadow: 0 8px 24px rgba(59, 130, 246, 0.3),
841
- 0 0 0 2px rgba(59, 130, 246, 0.2);
842
- }
843
-
844
- .example-card:active {
845
- transform: translateY(-2px) scale(0.98);
846
- transition: all 0.1s ease;
847
- }
848
-
849
- .example-label {
850
- font-size: 0.75em;
851
- color: #60a5fa;
852
- font-weight: 600;
853
- margin-bottom: 0.5rem;
854
- text-transform: uppercase;
855
- }
856
-
857
- .example-text {
858
- font-size: 0.9em;
859
- color: #cbd5e1;
860
- line-height: 1.5;
861
- font-style: italic;
862
- }
863
-
864
- /* === 10. Scrollbar Styling === */
865
- ::-webkit-scrollbar {
866
- width: 10px;
867
- height: 10px;
868
- }
869
-
870
- ::-webkit-scrollbar-track {
871
- background: rgba(15, 23, 42, 0.5);
872
- border-radius: 5px;
873
- }
874
-
875
- ::-webkit-scrollbar-thumb {
876
- background: rgba(59, 130, 246, 0.5);
877
- border-radius: 5px;
878
- }
879
-
880
- ::-webkit-scrollbar-thumb:hover {
881
- background: rgba(59, 130, 246, 0.7);
882
- }
883
- """) as demo:
884
-
885
- # Header Section
886
- gr.HTML("""
887
- <div class='title-container'>
888
- <h1>🧠 Mental Health Text Analyzer</h1>
889
- </div>
890
- """)
891
-
892
- gr.HTML("""
893
- <div class='subtitle'>
894
- Analyze text to detect mental health indicators:
895
- <b>Anxiety</b>, <b>Depression</b>, <b>Normal</b>, or <b>Suicidal</b> tendencies.
896
- <br><br>
897
- <span style="font-size: 0.9em; color: #94a3b8;">
898
- Powered by advanced BERT-based NLP models for accurate sentiment analysis.
899
- </span>
900
- </div>
901
- """)
902
-
903
- # Main Container with Tabs
904
- with gr.Tabs() as tabs:
905
- with gr.Tab("🔍 Analyze", id="analyze_tab"):
906
- # Input Container
907
- gr.HTML("""
908
- <div class='input-container'>
909
- <div style='font-size: 1.1em; color: #e2e8f0; margin-bottom: 1rem; font-weight: 600;'>
910
- 📝 Enter Your Text
911
- </div>
912
- </div>
913
- """)
914
-
915
- text_input = gr.Textbox(
916
- label="",
917
- placeholder="Example: I've been feeling overwhelmed lately and struggling to find motivation...",
918
- lines=6,
919
- value="",
920
- container=True,
921
- )
922
-
923
- # Example Cards Container
924
- with gr.Row():
925
- example_anxiety = gr.Button("😰 Anxiety Example", variant="secondary", size="sm", scale=1)
926
- example_depression = gr.Button("😔 Depression Example", variant="secondary", size="sm", scale=1)
927
- example_normal = gr.Button("😊 Normal Example", variant="secondary", size="sm", scale=1)
928
-
929
- gr.HTML("""
930
- <div class='example-container'>
931
- <div class='example-card'>
932
- <div class='example-label'>💡 Try Example Texts</div>
933
- <div class='example-text'>Click the buttons above to load example texts for different mental health indicators</div>
934
- </div>
935
- </div>
936
- """)
937
-
938
- # Example text handlers
939
- example_anxiety.click(
940
- fn=lambda: "I feel anxious about everything lately. My heart races and I can't stop worrying about the future.",
941
- outputs=text_input
942
- )
943
- example_depression.click(
944
- fn=lambda: "I've been feeling really down and hopeless. Nothing seems to bring me joy anymore.",
945
- outputs=text_input
946
- )
947
- example_normal.click(
948
- fn=lambda: "I'm doing well today! Had a great day at work and feeling positive about the week ahead.",
949
- outputs=text_input
950
- )
951
-
952
- # Analyze Button
953
- analyze_btn = gr.Button("🔍 Analyze Text", elem_classes="primary")
954
-
955
- # Output Container
956
- gr.HTML("""
957
- <div class='output-container'>
958
- <div class='result-container'>
959
- <div style='font-size: 1.1em; color: #e2e8f0; margin-bottom: 1rem; font-weight: 600;'>
960
- 📊 Analysis Results
961
- </div>
962
- </div>
963
- </div>
964
- """)
965
-
966
- msg_box = gr.HTML(elem_classes="output-card-html", value="", container=True)
967
-
968
- expl_output = gr.Textbox(
969
- label="🔑 Key Phrases & Explanation",
970
- interactive=False,
971
- elem_classes="output-card",
972
- placeholder="Analysis results will appear here...",
973
- container=True
974
- )
975
-
976
- with gr.Tab("ℹ️ About", id="about_tab"):
977
- gr.HTML("""
978
- <div class='tab-container'>
979
- <h2 style='color: #60a5fa; margin-bottom: 1.5rem;'>About This Tool</h2>
980
- <div style='color: #cbd5e1; line-height: 1.8;'>
981
- <p style='margin-bottom: 1rem;'>
982
- This Mental Health Text Analyzer uses advanced BERT-based natural language processing
983
- to analyze text and detect potential mental health indicators.
984
- </p>
985
- <h3 style='color: #93c5fd; margin-top: 1.5rem; margin-bottom: 0.75rem;'>Detection Categories:</h3>
986
- <ul style='margin-left: 1.5rem; margin-bottom: 1rem;'>
987
- <li><b style='color: #fbbf24;'>Anxiety:</b> Signs of worry, nervousness, or excessive concern</li>
988
- <li><b style='color: #3b82f6;'>Depression:</b> Indicators of sadness, hopelessness, or low mood</li>
989
- <li><b style='color: #22c55e;'>Normal:</b> Healthy, positive, or neutral emotional state</li>
990
- <li><b style='color: #ef4444;'>Suicidal:</b> Warning signs of self-harm or suicidal thoughts</li>
991
- </ul>
992
- <p style='margin-top: 1.5rem; font-size: 0.9em; color: #94a3b8;'>
993
- <b>Note:</b> This tool is for informational purposes only and should not replace
994
- professional mental health advice. If you're experiencing a mental health crisis,
995
- please contact a mental health professional or emergency services.
996
- </p>
997
- </div>
998
- </div>
999
- """)
1000
-
1001
- # Event Handler
1002
- analyze_btn.click(
1003
- fn=analyze_text,
1004
- inputs=text_input,
1005
- outputs=[msg_box, expl_output],
1006
- )
1007
-
1008
- # Credits Footer
1009
- gr.Markdown(
1010
- """
1011
- <div class='app-credits'>
1012
- <p>
1013
- Built with <a href="https://huggingface.co/transformers" target="_blank">🤗 Transformers</a>
1014
- & <a href="https://gradio.app" target="_blank">Gradio</a>
1015
- </p>
1016
- <p>
1017
- Model by <a href="https://huggingface.co/ourafla" target="_blank">Priyangshu Mukherjee</a>
1018
- </p>
1019
- </div>
1020
- """,
1021
- elem_classes="app-credits"
1022
- )
1023
-
1024
-
1025
- if __name__ == "__main__":
1026
- print("\n" + "="*60)
1027
- print("🧠 Mental Health Text Analyzer - Starting Server...")
1028
- print("="*60)
1029
- print("\n📌 Access the app at:")
1030
- print(" → http://localhost:7860")
1031
- print(" → http://127.0.0.1:7860")
1032
- print("\n⏳ Loading model (this may take a moment on first run)...")
1033
- print("="*60 + "\n")
1034
-
1035
- demo.launch(
1036
- server_name="0.0.0.0", # Allow access from all network interfaces
1037
- server_port=7860, # Default Gradio port
1038
- share=False, # Set to True if you want a public link
1039
- show_error=True, # Show errors in the UI
1040
- inbrowser=True # Automatically open browser
1041
- )
 
1
+ import os
2
+ import torch
3
+ import gradio as gr
4
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
5
+
6
+ # === 1. Model Setup ===
7
+ MODEL_NAME = "ourafla/mental-health-bert-finetuned"
8
+ hf_token = os.getenv("HUGGINGFACE_HUB_TOKEN")
9
+
10
+ print("⏳ Loading model... this may take a few seconds.")
11
+ try:
12
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, token=hf_token)
13
+ model = AutoModelForSequenceClassification.from_pretrained(
14
+ MODEL_NAME, token=hf_token, output_attentions=True
15
+ )
16
+ model.eval()
17
+ print("✅ Model loaded successfully.")
18
+ except Exception as e:
19
+ print(f"❌ Error loading model: {e}")
20
+ tokenizer = None
21
+ model = None
22
+
23
+ id2label = {0: "Anxiety", 1: "Depression", 2: "Normal", 3: "Suicidal"}
24
+
25
+ # === 2. CSS & THEME VARIABLES ===
26
+ # This JS toggles the 'dark' class on the body element
27
+ js_func = """
28
+ function toggleTheme() {
29
+ const body = document.querySelector('body');
30
+ if (body.classList.contains('dark')) {
31
+ body.classList.remove('dark');
32
+ } else {
33
+ body.classList.add('dark');
34
+ }
35
+ }
36
+ """
37
+
38
+ CUSTOM_CSS = """
39
+ /* === COLOR VARIABLES === */
40
+ :root {
41
+ /* Light Mode (Default) */
42
+ --bg-app: #F3F4F6;
43
+ --bg-card: #FFFFFF;
44
+ --bg-input: #F9FAFB;
45
+ --border-color: #E5E7EB;
46
+ --text-main: #111827;
47
+ --text-sub: #4B5563;
48
+ --primary: #2563EB;
49
+ --primary-hover: #1D4ED8;
50
+ --accent-bg: #EFF6FF;
51
+
52
+ /* API Modal Specifics - Light */
53
+ --modal-bg: #FFFFFF;
54
+ --code-bg: #F3F4F6;
55
+ --code-text: #1F2937;
56
+ }
57
+
58
+ body.dark {
59
+ /* Dark Mode */
60
+ --bg-app: #0B0F19; /* Very dark blue/black */
61
+ --bg-card: #111827; /* Dark Slate */
62
+ --bg-input: #1F2937; /* Darker Slate */
63
+ --border-color: #374151; /* Medium Gray */
64
+ --text-main: #F9FAFB; /* Almost White */
65
+ --text-sub: #D1D5DB; /* Light Gray */
66
+ --primary: #3B82F6; /* Bright Blue */
67
+ --primary-hover: #60A5FA;
68
+ --accent-bg: #1E293B;
69
+
70
+ /* API Modal Specifics - Dark */
71
+ --modal-bg: #111827;
72
+ --code-bg: #020617;
73
+ --code-text: #E2E8F0;
74
+ }
75
+
76
+ /* Global Background Application */
77
+ body, .gradio-container {
78
+ background-color: var(--bg-app) !important;
79
+ color: var(--text-main) !important;
80
+ transition: background-color 0.3s, color 0.3s;
81
+ font-family: 'Inter', system-ui, sans-serif !important;
82
+ }
83
+
84
+ /* === MAIN COMPONENT STYLES === */
85
+
86
+ /* Card Container */
87
+ .main-card {
88
+ background-color: var(--bg-card) !important;
89
+ border: 1px solid var(--border-color) !important;
90
+ border-radius: 16px !important;
91
+ padding: 32px !important;
92
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important;
93
+ max-width: 900px !important;
94
+ margin: 20px auto !important;
95
+ }
96
+
97
+ /* Header & Typography */
98
+ h1 {
99
+ color: var(--text-main) !important;
100
+ font-weight: 800 !important;
101
+ text-align: center;
102
+ }
103
+
104
+ .subtitle {
105
+ color: var(--text-sub) !important;
106
+ text-align: center;
107
+ font-size: 1.1rem;
108
+ margin-bottom: 2rem;
109
+ }
110
+
111
+ /* Theme Toggle Button */
112
+ .theme-btn {
113
+ position: absolute !important;
114
+ top: 20px !important;
115
+ right: 20px !important;
116
+ background: var(--bg-card) !important;
117
+ border: 1px solid var(--border-color) !important;
118
+ color: var(--text-main) !important;
119
+ border-radius: 50% !important;
120
+ width: 40px !important;
121
+ height: 40px !important;
122
+ padding: 0 !important;
123
+ display: flex !important;
124
+ align-items: center !important;
125
+ justify-content: center !important;
126
+ font-size: 1.2rem !important;
127
+ cursor: pointer !important;
128
+ z-index: 100 !important;
129
+ }
130
+
131
+ /* Tabs */
132
+ .tabs {
133
+ border-bottom: 2px solid var(--border-color) !important;
134
+ background: transparent !important;
135
+ margin-bottom: 24px !important;
136
+ }
137
+ .tab-nav button {
138
+ color: var(--text-sub) !important;
139
+ font-weight: 600 !important;
140
+ }
141
+ .tab-nav button.selected {
142
+ color: var(--primary) !important;
143
+ border-bottom-color: var(--primary) !important;
144
+ }
145
+
146
+ /* Input Fields */
147
+ textarea {
148
+ background-color: var(--bg-input) !important;
149
+ border: 1px solid var(--border-color) !important;
150
+ color: var(--text-main) !important;
151
+ border-radius: 12px !important;
152
+ }
153
+ textarea::placeholder {
154
+ color: var(--text-sub) !important;
155
+ opacity: 0.7;
156
+ }
157
+
158
+ /* Read-only Textbox (Explanation) */
159
+ textarea:read-only {
160
+ background-color: var(--accent-bg) !important;
161
+ border-color: var(--border-color) !important;
162
+ }
163
+
164
+ /* Action Button */
165
+ .analyze-btn {
166
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%) !important;
167
+ color: white !important;
168
+ font-weight: 700 !important;
169
+ border: none !important;
170
+ border-radius: 12px !important;
171
+ padding: 16px !important;
172
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important;
173
+ }
174
+
175
+ /* Example Chips */
176
+ .example-btn {
177
+ background-color: var(--bg-card) !important;
178
+ border: 1px solid var(--border-color) !important;
179
+ color: var(--text-main) !important;
180
+ border-radius: 99px !important;
181
+ }
182
+ .example-btn:hover {
183
+ border-color: var(--primary) !important;
184
+ color: var(--primary) !important;
185
+ }
186
+
187
+ /* About Text */
188
+ .about-text p, .about-text li {
189
+ color: var(--text-sub) !important;
190
+ }
191
+ .about-text h3, .about-text b {
192
+ color: var(--text-main) !important;
193
+ }
194
+
195
+ /* === API DOCUMENTATION MODAL FIXES === */
196
+ /* Force the API Modal to respect our theme variables */
197
+ .gradio-container div[role="dialog"],
198
+ div[data-testid="settings-dialog"],
199
+ .modal {
200
+ background-color: var(--modal-bg) !important;
201
+ border: 1px solid var(--border-color) !important;
202
+ }
203
+
204
+ /* Text inside modal */
205
+ div[role="dialog"] h2, div[role="dialog"] h3, div[role="dialog"] p,
206
+ div[role="dialog"] label, div[role="dialog"] span, div[role="dialog"] td {
207
+ color: var(--text-main) !important;
208
+ }
209
+
210
+ /* Code Blocks in API Modal */
211
+ div[role="dialog"] pre, div[role="dialog"] code {
212
+ background-color: var(--code-bg) !important;
213
+ color: var(--code-text) !important;
214
+ border: 1px solid var(--border-color) !important;
215
+ }
216
+
217
+ /* Close button in modal */
218
+ div[role="dialog"] button.close {
219
+ color: var(--text-main) !important;
220
+ }
221
+ """
222
+
223
+ # === 3. HELPER FUNCTIONS (WITH DOCSTRINGS) ===
224
+ # Docstrings are crucial: they appear in the API documentation
225
+ def get_anxiety_example():
226
+ """Returns a sample text demonstrating anxiety symptoms."""
227
+ return "I feel incredibly anxious and my heart won't stop racing."
228
+
229
+ def get_depression_example():
230
+ """Returns a sample text demonstrating depression symptoms."""
231
+ return "I wake up feeling empty and I don't see the point in anything anymore."
232
+
233
+ def get_normal_example():
234
+ """Returns a sample text demonstrating a healthy emotional state."""
235
+ return "I had a pretty good day at work and looking forward to the weekend."
236
+
237
+ # === 4. ANALYSIS LOGIC ===
238
+ def analyze_text(text):
239
+ """
240
+ Analyzes text to detect mental health indicators (Anxiety, Depression, Normal, Suicidal).
241
+
242
+ Parameters:
243
+ text (str): Input text (min 5 chars).
244
+
245
+ Returns:
246
+ (html_output, explanation_text): Visual HTML dashboard and key token extraction.
247
+ """
248
+ if not text or len(text.strip()) < 5:
249
+ return f"""
250
+ <div style='background: #FEF2F2; border: 1px solid #FECACA; border-radius: 12px; padding: 24px; text-align: center;'>
251
+ <div style='font-size: 3rem; margin-bottom: 16px;'>⚠️</div>
252
+ <div style='color: #991B1B; font-weight: 800; font-size: 1.25rem; margin-bottom: 8px;'>Input too short</div>
253
+ <div style='color: #7F1D1D; font-size: 1rem; font-weight: 500;'>Please enter at least 5 characters.</div>
254
+ </div>
255
+ """, "N/A"
256
+
257
+ if model is None:
258
+ return "Model not loaded.", "Error"
259
+
260
+ inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=128)
261
+ with torch.no_grad():
262
+ outputs = model(**inputs)
263
+
264
+ probs = torch.softmax(outputs.logits, dim=-1)[0]
265
+ label_idx = torch.argmax(probs).item()
266
+ label = id2label[label_idx]
267
+ confidence = float(probs[label_idx])
268
+
269
+ all_probs = {id2label[i]: float(probs[i]) for i in range(len(id2label))}
270
+ sorted_probs = sorted(all_probs.items(), key=lambda x: x[1], reverse=True)
271
+
272
+ # Explanation Logic
273
+ try:
274
+ attn = outputs.attentions[-1]
275
+ scores = attn.mean(dim=1).mean(dim=1)[0]
276
+ tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
277
+ token_scores = []
278
+ for i, score in enumerate(scores):
279
+ tok = tokens[i]
280
+ if tok not in ["[CLS]", "[SEP]", "[PAD]"]:
281
+ token_scores.append((tok, score.item()))
282
+ token_scores.sort(key=lambda x: x[1], reverse=True)
283
+ top_tokens = [t[0].replace("##", "") for t in token_scores[:3]]
284
+ explanation = ", ".join(top_tokens)
285
+ except:
286
+ explanation = "Analysis unavailable"
287
+
288
+ # UI Configurations
289
+ status_config = {
290
+ "Anxiety": {"emoji": "😰", "color": "#D97706"},
291
+ "Depression": {"emoji": "😔", "color": "#2563EB"},
292
+ "Normal": {"emoji": "😊", "color": "#059669"},
293
+ "Suicidal": {"emoji": "🆘", "color": "#DC2626"},
294
+ }
295
+ cfg = status_config.get(label, {"emoji": "😐", "color": "#4B5563"})
296
+
297
+ # HTML Generation (Inline styles used for results to ensure they persist in all themes)
298
+ bars_html = ""
299
+ for name, score in sorted_probs:
300
+ pct = int(score * 100)
301
+ bar_color = status_config.get(name, {}).get("color", "#6B7280")
302
+ opacity = "1.0" if name == label else "0.5"
303
+
304
+ # We use CSS variables for text inside the HTML block to adapt to dark mode
305
+ bars_html += f"""
306
+ <div style='margin-bottom: 16px;'>
307
+ <div style='display: flex; justify-content: space-between; font-size: 0.95rem; margin-bottom: 6px;'>
308
+ <span style='font-weight: 700; color: var(--text-main);'>{name}</span>
309
+ <span style='font-weight: 700; color: var(--text-sub);'>{pct}%</span>
310
+ </div>
311
+ <div style='background: var(--border-color); border-radius: 8px; height: 12px; width: 100%; overflow: hidden;'>
312
+ <div style='background: {bar_color}; opacity: {opacity}; width: {pct}%; height: 100%; border-radius: 8px;'></div>
313
+ </div>
314
+ </div>
315
+ """
316
+
317
+ html_output = f"""
318
+ <div class='result-container' style='border-top: 6px solid {cfg['color']}; background: var(--bg-card); padding: 32px; border-radius: 16px; border: 1px solid var(--border-color);'>
319
+ <div style='display: flex; align-items: center; justify-content: center; flex-direction: column; padding-bottom: 32px; border-bottom: 2px solid var(--border-color);'>
320
+ <div style='font-size: 4rem; margin-bottom: 16px;'>{cfg['emoji']}</div>
321
+ <div style='font-size: 2rem; font-weight: 800; color: var(--text-main); margin-bottom: 8px;'>{label}</div>
322
+ <div style='color: var(--text-sub); font-size: 1.1rem; font-weight: 600;'>Confidence: {int(confidence*100)}%</div>
323
+ </div>
324
+ <div style='margin-top: 32px;'>
325
+ <div style='font-size: 1.1rem; font-weight: 800; color: var(--text-main); margin-bottom: 24px;'>Detailed Breakdown</div>
326
+ {bars_html}
327
+ </div>
328
+ </div>
329
+ """
330
+
331
+ return html_output, explanation
332
+
333
+ # === 5. UI LAYOUT ===
334
+ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default()) as demo:
335
+
336
+ # Theme Toggle Button (Floating)
337
+ toggle_btn = gr.Button("🌗", elem_classes="theme-btn")
338
+
339
+ with gr.Column(elem_classes="main-card"):
340
+
341
+ gr.HTML("""
342
+ <h1>Mental Health Text Analyzer</h1>
343
+ <div class="subtitle">AI-powered analysis for Anxiety, Depression, and Suicidal tendencies</div>
344
+ """)
345
+
346
+ with gr.Tabs():
347
+
348
+ # --- Tab 1: Analyze ---
349
+ with gr.TabItem("Analyze Text"):
350
+
351
+ input_text = gr.Textbox(
352
+ placeholder="Type or paste text here...",
353
+ lines=5,
354
+ label="Input Text",
355
+ )
356
+
357
+ gr.HTML("<div style='font-size: 0.95rem; color: var(--text-sub); font-weight: 600; margin-bottom: 12px; margin-top: 20px;'>Try an example:</div>")
358
+
359
+ with gr.Row():
360
+ ex_anxiety = gr.Button("😰 Anxiety", elem_classes="example-btn")
361
+ ex_depression = gr.Button("😔 Depression", elem_classes="example-btn")
362
+ ex_normal = gr.Button("😊 Normal", elem_classes="example-btn")
363
+
364
+ analyze_btn = gr.Button("Analyze Mental Health", elem_classes="analyze-btn")
365
+
366
+ html_result = gr.HTML(label="Analysis Result")
367
+
368
+ explanation_box = gr.Textbox(
369
+ label="Key Keywords Detected",
370
+ interactive=False,
371
+ visible=True
372
+ )
373
+
374
+ # --- Tab 2: About ---
375
+ with gr.TabItem("About & Privacy"):
376
+ gr.HTML("""
377
+ <div class="about-text">
378
+ <h3>🤖 How it works</h3>
379
+ <p>This tool utilizes a fine-tuned BERT model. It analyzes semantic patterns to classify text.</p>
380
+ <h3>📊 Detection Categories</h3>
381
+ <ul>
382
+ <li><b>Anxiety:</b> Signs of worry or excessive concern.</li>
383
+ <li><b>Depression:</b> Signs of sadness or hopelessness.</li>
384
+ <li><b>Normal:</b> Neutral or positive state.</li>
385
+ <li><b>Suicidal:</b> Warning signs of self-harm.</li>
386
+ </ul>
387
+ </div>
388
+ """)
389
+
390
+ # === 6. EVENT WIRING (Including API Names for Documentation) ===
391
+
392
+ # 1. Theme Toggle
393
+ toggle_btn.click(None, js=js_func)
394
+
395
+ # 2. Example Buttons (Named APIs for Doc)
396
+ ex_anxiety.click(
397
+ fn=get_anxiety_example,
398
+ outputs=input_text,
399
+ api_name="get_anxiety_example" # <--- API NAME 1
400
+ )
401
+ ex_depression.click(
402
+ fn=get_depression_example,
403
+ outputs=input_text,
404
+ api_name="get_depression_example" # <--- API NAME 2
405
+ )
406
+ ex_normal.click(
407
+ fn=get_normal_example,
408
+ outputs=input_text,
409
+ api_name="get_normal_example" # <--- API NAME 3
410
+ )
411
+
412
+ # 3. Main Analysis (Named API for Doc)
413
+ analyze_btn.click(
414
+ fn=analyze_text,
415
+ inputs=input_text,
416
+ outputs=[html_result, explanation_box],
417
+ api_name="predict" # <--- API NAME 4
418
+ )
419
+
420
+ if __name__ == "__main__":
421
+ print("🚀 Starting Gradio Server...")
422
+ demo.launch(server_name="0.0.0.0", server_port=7860)