Vishwanath77 commited on
Commit
be76c74
·
verified ·
1 Parent(s): a2f131f

Upload 7 files

Browse files
src/apps/templates/Judgechatbot.html CHANGED
@@ -1,875 +1,317 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Law bot</title>
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
- <link rel="stylesheet" href="/static/css/modern-chat.css">
10
- <style>
11
- :root {
12
- --primary-purple: #9b87f5;
13
- --dark-purple: #1A1F2C;
14
- --light-purple: #D6BCFA;
15
- --neutral-gray: #8E9196;
16
- --light-gray: #C8C8C9;
17
- --off-white: #eee;
18
- }
19
-
20
- * {
21
- margin: 0;
22
- padding: 0;
23
- box-sizing: border-box;
24
- font-family: 'Inter', sans-serif;
25
- }
26
-
27
- body {
28
- transition: background-color 0.3s, color 0.3s;
29
- }
30
-
31
- body.dark {
32
- color: var(--off-white);
33
- }
34
-
35
- body.light {
36
- color: var(--dark-purple);
37
- }
38
-
39
- .container {
40
- max-width: 800px;
41
- margin: 0 auto;
42
- padding: 2rem;
43
- min-height: 100vh;
44
- display: flex;
45
- flex-direction: column;
46
- }
47
-
48
- .header {
49
- display: flex;
50
- justify-content: space-between;
51
- align-items: center;
52
- margin-bottom: 2rem;
53
- }
54
-
55
- .header-right {
56
- display: flex;
57
- align-items: center;
58
- gap: 1rem;
59
- }
60
-
61
-
62
- .app-title {
63
- margin-left: 40px;
64
- font-size: 2rem;
65
- font-weight: 600;
66
- background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
67
- -webkit-background-clip: text;
68
- background-clip: text;
69
- color: transparent;
70
- }
71
-
72
- .theme-toggle {
73
- background: none;
74
- border: none;
75
- cursor: pointer;
76
- padding: 0.5rem;
77
- color: var(--primary-purple);
78
- transition: opacity 0.3s;
79
- }
80
-
81
- .theme-toggle:hover {
82
- opacity: 0.8;
83
- }
84
-
85
- .theme-toggle svg {
86
- width: 24px;
87
- height: 24px;
88
- }
89
-
90
- .chat-container {
91
- flex-grow: 1;
92
- overflow-y: auto;
93
- margin-bottom: 2rem;
94
- padding: 1rem;
95
- }
96
-
97
- .message {
98
- margin-bottom: 1.5rem;
99
- padding: 1rem;
100
- border-radius: 8px;
101
- animation: fadeIn 0.3s ease-in;
102
- }
103
-
104
- .user-message {
105
- background-color: rgba(155, 135, 245, 0.1);
106
- }
107
-
108
- .ai-message {
109
- background-color: rgba(255, 255, 255, 0.05);
110
- border: 1px solid rgba(200, 200, 201, 0.2);
111
- }
112
-
113
- body.light .user-message {
114
- background-color: var(--off-white);
115
- }
116
-
117
- body.light .ai-message {
118
- background-color: #ffffff;
119
- border: 1px solid var(--light-gray);
120
- }
121
-
122
- .message-header {
123
- display: flex;
124
- align-items: center;
125
- margin-bottom: 0.5rem;
126
- font-weight: 500;
127
- }
128
-
129
-
130
- .sidebar-welcome {
131
- padding: 1.5rem;
132
- border-bottom: 1px solid rgba(200, 200, 201, 0.15);
133
- margin-bottom: 1.5rem;
134
- }
135
-
136
- .user-icon,
137
- .ai-icon {
138
- width: 24px;
139
- height: 24px;
140
- border-radius: 50%;
141
- margin-right: 0.5rem;
142
- display: flex;
143
- align-items: center;
144
- justify-content: center;
145
- font-size: 12px;
146
- }
147
-
148
- .user-icon {
149
- background-color: var(--primary-purple);
150
- color: white;
151
- }
152
-
153
- .ai-icon {
154
- background-color: var(--light-purple);
155
- color: var(--dark-purple);
156
- }
157
-
158
- .input-container {
159
- position: fixed;
160
- bottom: 0;
161
- left: 0;
162
- right: 0;
163
- padding: 1rem;
164
- background-color: var(--input-bg);
165
- border-top: 1px solid var(--sidebar-border);
166
- }
167
-
168
- body.light .input-container {
169
- background-color: #ffffff;
170
- border-top: 1px solid var(--light-gray);
171
- }
172
-
173
- .input-wrapper {
174
- max-width: 800px;
175
- margin: 0 auto;
176
- position: relative;
177
- }
178
-
179
- #messageInput {
180
- width: 100%;
181
- padding: 1rem;
182
- padding-right: 3rem;
183
- border: 1px solid rgba(200, 200, 201, 0.3);
184
- border-radius: 8px;
185
- font-size: 1rem;
186
- outline: none;
187
- transition: border-color 0.3s;
188
- background-color: transparent;
189
- color: inherit;
190
- }
191
-
192
- body.light #messageInput {
193
- border: 1px solid var(--light-gray);
194
- }
195
-
196
- #messageInput:focus {
197
- border-color: var(--primary-purple);
198
- }
199
-
200
- #sendButton {
201
- position: absolute;
202
- right: 0.5rem;
203
- top: 50%;
204
- transform: translateY(-50%);
205
- background: none;
206
- border: none;
207
- cursor: pointer;
208
- padding: 0.5rem;
209
- color: var(--primary-purple);
210
- opacity: 0.8;
211
- transition: opacity 0.3s;
212
- }
213
-
214
- #sendButton:hover {
215
- opacity: 1;
216
- }
217
-
218
- .typing-indicator {
219
- display: none;
220
- color: #888;
221
- font-style: italic;
222
- margin-top: 10px;
223
- padding: 8px 12px;
224
- background-color: #f1f1f1;
225
- border-radius: 12px;
226
- max-width: fit-content;
227
- margin-left: 10px;
228
- font-size: 14px;
229
- animation: pulse 1.2s infinite;
230
- }
231
-
232
- /* Optional: soft pulse animation for effect */
233
- @keyframes pulse {
234
- 0% {
235
- opacity: 0.4;
236
- }
237
-
238
- 50% {
239
- opacity: 1;
240
- }
241
-
242
- 100% {
243
- opacity: 0.4;
244
- }
245
- }
246
-
247
- .welcome-title {
248
- margin: 6rem 0 0.5rem 0;
249
- text-align: center;
250
- color: var(--neutral-gray);
251
- padding: 1rem;
252
- font-size: 1.24rem;
253
- animation: fadeIn 0.3s ease-in;
254
- }
255
-
256
- .welcome-subtitle {
257
- line-height: 1.6;
258
- font-size: 1.1rem;
259
- color: var(--neutral-gray);
260
- max-width: 900px;
261
- margin: 0 auto;
262
- font-weight: 800;
263
-
264
-
265
- }
266
-
267
- .sidebar-welcome {
268
- margin-bottom: 2rem;
269
- padding: 0 1rem;
270
- border-bottom: none;
271
- }
272
-
273
- .role-selection-link {
274
- display: inline-flex;
275
- align-items: center;
276
- gap: initial;
277
- color: var(--light-purple);
278
- text-decoration: none;
279
- margin-top: auto;
280
- padding: auto;
281
- transition: opacity 0.3s ease;
282
- position: absolute;
283
- top: 550px;
284
- left: 30px;
285
-
286
- }
287
-
288
- .role-selection-link:hover {
289
- opacity: 0.8;
290
- }
291
-
292
- .role-selection-link svg {
293
- width: 16px;
294
- height: 16px;
295
- stroke: currentColor;
296
- }
297
-
298
- /* Add this media query for mobile responsiveness */
299
- @media (max-width: 480px) {
300
- .welcome-message p {
301
- font-size: 1rem;
302
- padding: 0 1rem;
303
- }
304
- }
305
-
306
- @keyframes fadeIn {
307
- from {
308
- opacity: 0;
309
- transform: translateY(10px);
310
- }
311
-
312
- to {
313
- opacity: 1;
314
- transform: translateY(0);
315
- }
316
- }
317
-
318
- .connection-status {
319
- position: fixed;
320
- top: 1rem;
321
- right: 1rem;
322
- padding: 0.5rem 1rem;
323
- border-radius: 4px;
324
- font-size: 0.875rem;
325
- display: none;
326
- }
327
-
328
- .connection-status.connected {
329
- background-color: #4ade80;
330
- color: white;
331
- }
332
-
333
- .connection-status.disconnected {
334
- background-color: #ef4444;
335
- color: white;
336
- }
337
-
338
- /* Styling for formatted text */
339
- .message-content {
340
- line-height: 1.5;
341
- }
342
-
343
- .message-content h1,
344
- .message-content h2,
345
- .message-content h3 {
346
- margin: 1rem 0 0.5rem;
347
- font-weight: 600;
348
- }
349
-
350
- .message-content h1 {
351
- font-size: 1.5rem;
352
- }
353
-
354
- .message-content h2 {
355
- font-size: 1.25rem;
356
- }
357
-
358
- .message-content h3 {
359
- font-size: 1.1rem;
360
- }
361
-
362
- .message-content ul,
363
- .message-content ol {
364
- margin-left: 1.5rem;
365
- margin-bottom: 1rem;
366
- }
367
-
368
- .message-content a {
369
- color: var(--primary-purple);
370
- text-decoration: none;
371
- }
372
-
373
- .message-content a:hover {
374
- text-decoration: underline;
375
- }
376
-
377
- .usage-indicator {
378
- padding: 4px 12px;
379
- background: rgba(155, 135, 245, 0.1);
380
- border: 1px solid rgba(155, 135, 245, 0.3);
381
- border-radius: 20px;
382
- font-size: 0.8rem;
383
- color: var(--primary-purple);
384
- font-weight: 500;
385
- display: none;
386
- /* Hidden by default for Admins */
387
- }
388
- </style>
389
- </head>
390
-
391
- <button class="sidebar-floating-toggle" id="floatingToggle">
392
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
393
- <path d="M3 12h18M3 6h18M3 18h18" />
394
- </svg>
395
- </button>
396
-
397
- <aside class="sidebar" id="sidebar">
398
- <div class="sidebar-header">
399
- <button class="new-chat-btn" id="newChatBtn">
400
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
401
- <path d="M12 5v14M5 12h14" />
402
- </svg>
403
- New Chat
404
- </button>
405
- <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
406
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
407
- <path d="M15 18l-6-6 6-6" />
408
- </svg>
409
- </button>
410
- </div>
411
- <div class="sidebar-title">Recent Deliberations</div>
412
- <div class="history-list" id="historyList">
413
- <!-- Recent prompts will appear here -->
414
- </div>
415
-
416
- <div class="perspective-container" style="display: none;">
417
- <div class="sidebar-title">Answer Perspective</div>
418
- <div class="perspective-list">
419
- <div class="perspective-option active" data-role="Judge">⚖️ Judge</div>
420
- </div>
421
- </div>
422
-
423
- <div class="sidebar-footer">
424
- <a href="judgedashboard.html" class="role-selection-link">
425
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
426
- <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
427
- </svg>
428
- Back to Dashboard
429
- </a>
430
- </div>
431
- </aside>
432
-
433
- <!-- Existing messages will be added here dynamically -->
434
- <div class="container">
435
- <header class="header">
436
- <h1 class="app-title">Law Bot (Judge)</h1>
437
- <div class="header-right">
438
- <div class="usage-indicator" id="usageIndicator">
439
- Questions Remaining: <span id="remainingCount">--</span>
440
- </div>
441
- <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
442
- <!-- Icon injected by JS -->
443
- </button>
444
- </div>
445
- </header>
446
- <div class="welcome-title">Welcome, Your Honor! ⚖️</div>
447
- <div class="welcome-subtitle">It's a privilege to support your judicial wisdom with precise legal resources.
448
- </div>
449
- <div class="welcome-subtitle">How may I respectfully aid your deliberations today?</div>
450
-
451
- <div class="chat-container" id="chatContainer">
452
- <div class="typing-indicator" id="typingIndicator">
453
- <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--judge-color)" stroke-width="2">
454
- <path d="M14.5 2L3.5 13L11 20.5L22 9.5L14.5 2Z" />
455
- <path d="M7 16.5L2 21.5" />
456
- </svg>
457
- <span>His Honor is deliberating...</span>
458
- </div>
459
- </div>
460
- <div class="input-container">
461
- <div class="input-wrapper">
462
- <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
463
- <button id="sendButton">
464
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
465
- stroke-linecap="round" stroke-linejoin="round">
466
- <line x1="22" y1="2" x2="11" y2="13"></line>
467
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
468
- </svg>
469
- </button>
470
- </div>
471
- </div>
472
- <footer class="professional-footer">
473
- &copy; 2026 Law Bot AI. All Rights Reserved.
474
- <br>
475
- Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
476
- </footer>
477
- </div>
478
- <div class="connection-status" id="connectionStatus"></div>
479
-
480
- <script>
481
- let ws;
482
- const chatContainer = document.getElementById('chatContainer');
483
- const messageInput = document.getElementById('messageInput');
484
- const sendButton = document.getElementById('sendButton');
485
- const typingIndicator = document.getElementById('typingIndicator');
486
- const connectionStatus = document.getElementById('connectionStatus');
487
- const sidebar = document.getElementById('sidebar');
488
-
489
- let currentUserMessage = '';
490
- let currentAiMessage = '';
491
- let currentAiMessageElement = null;
492
- let currentCaseId = crypto.randomUUID();
493
- const activeRole = 'Judge'; // LOCKED ROLE
494
- let userLimitReached = false;
495
-
496
- async function checkUserStatus() {
497
- const token = localStorage.getItem('token');
498
- if (!token) return;
499
-
500
- try {
501
- const response = await fetch('/api/user-status', {
502
- headers: { 'Authorization': `Bearer ${token}` }
503
- });
504
- const status = await response.json();
505
-
506
- const indicator = document.getElementById('usageIndicator');
507
- if (status.is_admin) {
508
- indicator.style.display = 'none';
509
- } else {
510
- indicator.style.display = 'block';
511
- const remaining = Math.max(0, 2 - status.question_count);
512
- document.getElementById('remainingCount').innerText = remaining;
513
- if (remaining <= 0) {
514
- userLimitReached = true;
515
- }
516
- }
517
- } catch (err) {
518
- console.error('Failed to fetch user status:', err);
519
- }
520
- }
521
-
522
- function formatText(text) {
523
- // Extract references for Evidence Box
524
- const references = [];
525
- const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
526
- let match;
527
- while ((match = refRegex.exec(text)) !== null) {
528
- references.push({ title: match[1], pages: match[2] });
529
- }
530
-
531
- let formatted = text
532
- .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
533
- .replace(/\*(.*?)\*/g, "<em>$1</em>")
534
- .replace(/### (.*?)\n/g, "<h3>$1</h3>")
535
- .replace(/## (.*?)\n/g, "<h2>$1</h2>")
536
- .replace(/# (.*?)\n/g, "<h1>$1</h1>")
537
- .replace(/\n/g, "<br>")
538
- .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
539
- .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
540
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
541
-
542
- // Add Evidence Box if references found
543
- if (references.length > 0) {
544
- let evidenceHtml = `<div class="evidence-box">
545
- <div class="evidence-title">
546
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
547
- Legal Evidence
548
- </div>`;
549
- references.forEach(ref => {
550
- evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
551
- });
552
- evidenceHtml += `</div>`;
553
- formatted += evidenceHtml;
554
- }
555
- return formatted;
556
- }
557
-
558
- function connectWebSocket() {
559
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
560
- ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
561
-
562
- ws.onopen = () => {
563
- console.log('Connected to WebSocket');
564
- connectionStatus.textContent = 'Connected';
565
- connectionStatus.className = 'connection-status connected';
566
- connectionStatus.style.display = 'block';
567
- setTimeout(() => {
568
- connectionStatus.style.display = 'none';
569
- }, 3000);
570
- };
571
-
572
- ws.onclose = () => {
573
- console.log('Disconnected from WebSocket');
574
- connectionStatus.textContent = 'Reconnecting...';
575
- connectionStatus.className = 'connection-status disconnected';
576
- connectionStatus.style.display = 'block';
577
-
578
- // Exponential backoff for reconnection
579
- const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
580
- window._reconnectCount = (window._reconnectCount || 0) + 1;
581
-
582
- setTimeout(() => {
583
- reconnectWebSocket();
584
- loadHistory();
585
- }, backoff);
586
- };
587
-
588
- ws.onerror = (error) => {
589
- console.error('WebSocket Error:', error);
590
- };
591
-
592
-
593
- ws.onmessage = (event) => {
594
- console.log('Received message:', event.data);
595
-
596
- // ✅ Hide typing indicator on response
597
- typingIndicator.style.display = 'none';
598
-
599
- if (event.data === '[DONE]') {
600
- // Save complete chat interaction
601
- saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
602
- checkUserStatus(); // Update remaining count
603
- return;
604
- }
605
-
606
- // Handle usage limit blocks from backend
607
- if (event.data.includes("Free usage limit reached")) {
608
- typingIndicator.style.display = 'none';
609
- userLimitReached = true;
610
- checkUserStatus();
611
- }
612
-
613
- if (!currentAiMessageElement) {
614
- currentAiMessage = event.data;
615
- addMessage(currentAiMessage, 'ai');
616
- } else {
617
- currentAiMessage += event.data;
618
- const formattedMessage = formatText(currentAiMessage);
619
- currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
620
- }
621
- };
622
-
623
- // ✅ Scroll fix (move into sendMessage function if needed)
624
- chatContainer.scrollTop = chatContainer.scrollHeight;
625
- }
626
-
627
- function reconnectWebSocket() {
628
- console.log('Attempting to reconnect...');
629
- connectWebSocket();
630
- loadHistory();
631
- }
632
-
633
- function addMessage(content, type) {
634
- if (type === 'user') {
635
- currentUserMessage = content;
636
- currentAiMessage = '';
637
- currentAiMessageElement = null;
638
- }
639
-
640
- const messageDiv = document.createElement('div');
641
- messageDiv.className = `message ${type}-message`;
642
-
643
- const header = document.createElement('div');
644
- header.className = 'message-header';
645
-
646
- const icon = document.createElement('div');
647
- icon.className = `${type}-icon`;
648
- icon.textContent = type === 'user' ? 'U' : 'L';
649
-
650
- const name = document.createElement('span');
651
- name.textContent = type === 'user' ? 'You' : 'Law Bot';
652
-
653
- header.appendChild(icon);
654
- header.appendChild(name);
655
-
656
- const text = document.createElement('div');
657
- text.className = 'message-content';
658
- text.innerHTML = type === 'ai' ? formatText(content) : content;
659
-
660
- messageDiv.appendChild(header);
661
- messageDiv.appendChild(text);
662
- chatContainer.appendChild(messageDiv);
663
- chatContainer.scrollTop = chatContainer.scrollHeight;
664
-
665
- if (type === 'ai') {
666
- currentAiMessageElement = messageDiv;
667
- }
668
- }
669
-
670
- function sendMessage() {
671
- const message = messageInput.value.trim();
672
- if (!message) return;
673
-
674
- if (userLimitReached) {
675
- addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
676
- return;
677
- }
678
-
679
- if (!activeRole) {
680
- addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
681
- return;
682
- }
683
-
684
- if (ws.readyState === WebSocket.OPEN) {
685
- addMessage(message, 'user');
686
- ws.send(message);
687
- messageInput.value = '';
688
-
689
- // ✅ Show typing indicator after sending
690
- typingIndicator.style.display = 'block';
691
- chatContainer.scrollTop = chatContainer.scrollHeight;
692
- }
693
- }
694
- const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
695
- if (isAtBottom) {
696
- chatContainer.scrollTop = chatContainer.scrollHeight;
697
- }
698
-
699
-
700
- const historyList = document.getElementById('historyList');
701
-
702
- async function loadHistory() {
703
- const token = localStorage.getItem('token');
704
- if (!token) return;
705
-
706
- try {
707
- const response = await fetch(`/api/interactions?role=${activeRole}`, {
708
- headers: { 'Authorization': `Bearer ${token}` }
709
- });
710
- const interactions = await response.json();
711
- historyList.innerHTML = '';
712
- interactions.forEach(item => {
713
- const div = document.createElement('div');
714
- div.className = 'history-item';
715
-
716
- // Readable preview: first sentence or truncated query
717
- const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
718
-
719
- div.innerHTML = `
720
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
721
- <span>${preview}</span>
722
- <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
723
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
724
- </button>
725
- `;
726
- div.onclick = () => loadConversation(item.case_id);
727
- historyList.appendChild(div);
728
- });
729
- } catch (err) {
730
- console.error('Failed to load history:', err);
731
- }
732
- }
733
-
734
- async function loadConversation(caseId) {
735
- const token = localStorage.getItem('token');
736
- try {
737
- const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
738
- headers: { 'Authorization': `Bearer ${token}` }
739
- });
740
- const thread = await response.json();
741
-
742
- currentCaseId = caseId;
743
- chatContainer.innerHTML = ''; // Clear window
744
-
745
- thread.forEach(msg => {
746
- addMessage(msg.query, 'user');
747
- // Render AI Response immediately
748
- const aiMsgDiv = document.createElement('div');
749
- aiMsgDiv.className = 'message ai-message';
750
- aiMsgDiv.innerHTML = `
751
- <div class="message-header">
752
- <div class="ai-icon">L</div>
753
- <span>Law Bot</span>
754
- </div>
755
- <div class="message-content">${formatText(msg.response)}</div>
756
- `;
757
- chatContainer.appendChild(aiMsgDiv);
758
- });
759
- chatContainer.scrollTop = chatContainer.scrollHeight;
760
- } catch (err) {
761
- console.error('Failed to load thread:', err);
762
- }
763
- }
764
-
765
- async function deleteConversation(caseId) {
766
- const token = localStorage.getItem('token');
767
- try {
768
- await fetch(`/api/interactions/${caseId}`, {
769
- method: 'DELETE',
770
- headers: { 'Authorization': `Bearer ${token}` }
771
- });
772
- if (currentCaseId === caseId) {
773
- newChat();
774
- } else {
775
- loadHistory();
776
- }
777
- } catch (err) {
778
- console.error('Failed to delete:', err);
779
- }
780
- }
781
-
782
- function newChat() {
783
- currentCaseId = crypto.randomUUID();
784
- chatContainer.innerHTML = '';
785
- messageInput.value = '';
786
- messageInput.focus();
787
- loadHistory();
788
- }
789
-
790
- // ✅ Modified: Uses Bearer token
791
- const saveChatInteraction = async (caseId, query, response) => {
792
- const token = localStorage.getItem('token');
793
- if (!token) return;
794
-
795
- try {
796
- await fetch('/api/save-interaction', {
797
- method: 'POST',
798
- headers: {
799
- 'Content-Type': 'application/json',
800
- 'Authorization': `Bearer ${token}`
801
- },
802
- body: JSON.stringify({ caseId, query, response, role: activeRole })
803
- });
804
- loadHistory(); // Refresh sidebar history
805
- } catch (error) {
806
- console.error('Error saving chat:', error);
807
- }
808
- };
809
-
810
- sendButton.addEventListener('click', sendMessage);
811
- messageInput.addEventListener('keypress', (e) => {
812
- if (e.key === 'Enter') {
813
- sendMessage();
814
- }
815
- });
816
-
817
-
818
-
819
- // Init
820
- const themeToggle = document.getElementById('themeToggle');
821
- const body = document.body;
822
- const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
823
- const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
824
-
825
- function updateTheme(isDark) {
826
- if (isDark) {
827
- body.classList.add('dark');
828
- body.classList.remove('light');
829
- themeToggle.innerHTML = moonIcon;
830
- localStorage.setItem('theme', 'dark');
831
- } else {
832
- body.classList.add('light');
833
- body.classList.remove('dark');
834
- themeToggle.innerHTML = sunIcon;
835
- localStorage.setItem('theme', 'light');
836
- }
837
- }
838
-
839
- themeToggle.addEventListener('click', () => {
840
- const isNowDark = !body.classList.contains('dark');
841
- updateTheme(isNowDark);
842
- });
843
-
844
- // Initialize Theme
845
- const savedTheme = localStorage.getItem('theme') || 'dark';
846
- updateTheme(savedTheme === 'dark');
847
-
848
- // Sidebar Collapse Logic
849
- document.getElementById('collapseSidebar').onclick = () => {
850
- document.body.classList.add('sidebar-collapsed');
851
- };
852
- document.getElementById('floatingToggle').onclick = () => {
853
- document.body.classList.remove('sidebar-collapsed');
854
- };
855
- document.getElementById('newChatBtn').onclick = newChat;
856
-
857
- // Perspective Selection
858
- document.querySelectorAll('.perspective-option').forEach(opt => {
859
- opt.onclick = () => {
860
- document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
861
- opt.classList.add('active');
862
- activeRole = opt.dataset.role;
863
- localStorage.setItem('activeRole', activeRole);
864
- // No session reset needed, just role update for next message
865
- };
866
- });
867
-
868
- checkUserStatus();
869
- connectWebSocket();
870
- loadHistory();
871
-
872
- </script>
873
- </body>
874
-
875
  </html>
 
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>AI Legal Assistant | Judicial Deliberation System</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-dark: #030617;
11
+ --sidebar-bg: #050a24;
12
+ --accent-gold: #fbbf24;
13
+ --accent-navy: #1e3a8a;
14
+ --text-primary: #f1f5f9;
15
+ --text-dim: #94a3b8;
16
+ --glass-bg: rgba(255, 255, 255, 0.03);
17
+ --glass-border: rgba(255, 255, 255, 0.08);
18
+ }
19
+
20
+ * {
21
+ margin: 0; padding: 0; box-sizing: border-box;
22
+ font-family: 'Poppins', sans-serif;
23
+ }
24
+
25
+ body {
26
+ background-color: var(--bg-dark);
27
+ color: var(--text-primary);
28
+ height: 100vh;
29
+ display: flex;
30
+ overflow: hidden;
31
+ }
32
+
33
+ /* SIDEBAR */
34
+ .sidebar {
35
+ width: 300px;
36
+ background: var(--sidebar-bg);
37
+ border-right: 1px solid var(--glass-border);
38
+ display: flex; flex-direction: column;
39
+ z-index: 100;
40
+ }
41
+
42
+ .sidebar-header { padding: 40px 25px; }
43
+
44
+ .new-chat-btn {
45
+ width: 100%; padding: 16px;
46
+ background: linear-gradient(135deg, var(--accent-gold), #d97706);
47
+ border: none; border-radius: 12px;
48
+ color: #030617; font-weight: 700;
49
+ display: flex; align-items: center; justify-content: center; gap: 10px;
50
+ cursor: pointer; transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
51
+ box-shadow: 0 10px 20px rgba(251, 191, 36, 0.15);
52
+ }
53
+
54
+ .new-chat-btn:hover { transform: translateY(-3px); box-shadow: 0 15px 30px rgba(251, 191, 36, 0.25); }
55
+
56
+ .history-label { padding: 0 25px 10px; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 2.5px; color: var(--text-dim); font-weight: 700; opacity: 0.8; }
57
+ .history-list { flex: 1; overflow-y: auto; padding: 15px; }
58
+ .history-item {
59
+ padding: 14px 18px; border-radius: 10px; margin-bottom: 8px;
60
+ cursor: pointer; transition: 0.2s; font-size: 0.9rem; color: var(--text-dim);
61
+ display: flex; align-items: center; gap: 12px; border: 1px solid transparent;
62
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
63
+ }
64
+ .history-item:hover { background: rgba(251, 191, 36, 0.05); color: var(--accent-gold); border-color: rgba(251, 191, 36, 0.2); }
65
+
66
+ .sidebar-footer { padding: 25px; border-top: 1px solid var(--glass-border); }
67
+ .user-badge { display: flex; align-items: center; gap: 15px; padding: 12px; background: rgba(251, 191, 36, 0.05); border-radius: 14px; border: 1px solid rgba(251, 191, 36, 0.1); }
68
+ .user-avatar { width: 36px; height: 36px; background: var(--accent-gold); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-weight: 800; color: #030617; font-size: 0.9rem; }
69
+
70
+ /* MAIN AREA */
71
+ .main-chat { flex: 1; display: flex; flex-direction: column; position: relative; background: radial-gradient(circle at top right, rgba(251, 191, 36, 0.02), transparent); }
72
+
73
+ .top-bar {
74
+ height: 80px; background: rgba(3, 6, 23, 0.85); backdrop-filter: blur(25px);
75
+ border-bottom: 1px solid var(--glass-border);
76
+ display: flex; align-items: center; justify-content: space-between; padding: 0 45px; z-index: 50;
77
+ }
78
+
79
+ .branding-title { font-weight: 700; color: white; letter-spacing: -0.5px; font-size: 1.1rem; }
80
+ .status-pill { display: flex; align-items: center; gap: 10px; font-size: 0.75rem; background: rgba(251, 191, 36, 0.1); color: var(--accent-gold); padding: 5px 15px; border-radius: 20px; font-weight: 700; border: 1px solid rgba(251, 191, 36, 0.2); }
81
+ .status-dot { width: 7px; height: 7px; background: var(--accent-gold); border-radius: 50%; animation: pulse-dot 2s infinite; }
82
+
83
+ @keyframes pulse-dot { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.6); opacity: 0.3; } }
84
+
85
+ .messages-container { flex: 1; overflow-y: auto; padding: 50px 12%; display: flex; flex-direction: column; gap: 40px; scroll-behavior: smooth; }
86
+ .message { max-width: 900px; animation: message-slide 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; }
87
+ @keyframes message-slide { from { opacity: 0; transform: translateY(25px); } to { opacity: 1; transform: translateY(0); } }
88
+
89
+ .user-msg { align-self: flex-end; background: linear-gradient(135deg, var(--accent-gold), #d97706); padding: 18px 26px; border-radius: 22px 22px 4px 22px; color: #030617; font-weight: 500; max-width: 70%; box-shadow: 0 10px 30px rgba(251, 191, 36, 0.2); }
90
+ .ai-msg { align-self: flex-start; background: rgba(255, 255, 255, 0.02); border: 1px solid var(--glass-border); padding: 35px; border-radius: 24px 24px 24px 6px; max-width: 88%; line-height: 1.9; color: #e2e8f0; }
91
+ .ai-msg h3 { color: var(--accent-gold); font-size: 1.4rem; margin-bottom: 25px; font-weight: 700; display: flex; align-items: center; gap: 15px; }
92
+ .ai-msg ul { margin: 25px 0; list-style: none; }
93
+ .ai-msg li { position: relative; padding-left: 32px; margin-bottom: 18px; }
94
+ .ai-msg li::before { content: "⚖️"; position: absolute; left: 0; font-size: 1rem; }
95
+ .highlight { background: rgba(251, 191, 36, 0.05); border-left: 5px solid var(--accent-gold); padding: 25px; margin: 30px 0; border-radius: 0 18px 18px 0; font-weight: 600; color: #fff; }
96
+
97
+ .typing { display: flex; gap: 7px; padding: 20px 30px; background: var(--glass-bg); border-radius: 24px; width: fit-content; margin: 25px 12%; border: 1px solid var(--glass-border); }
98
+ .typing span { width: 9px; height: 9px; background: var(--accent-gold); border-radius: 50%; animation: typing-blink 1.4s infinite ease-in-out; }
99
+ .typing span:nth-child(2) { animation-delay: 0.22s; }
100
+ .typing span:nth-child(3) { animation-delay: 0.44s; }
101
+ @keyframes typing-blink { 0%, 100% { opacity: 0.15; transform: scale(0.85); } 50% { opacity: 1; transform: scale(1.15); } }
102
+
103
+ .input-area { padding: 40px 12%; background: linear-gradient(to top, var(--bg-dark) 60%, transparent); border-top: 1px solid var(--glass-border); z-index: 60; }
104
+ .input-box { background: rgba(255, 255, 255, 0.03); border: 1px solid var(--glass-border); border-radius: 20px; padding: 8px; display: flex; align-items: center; transition: 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
105
+ .input-box:focus-within { border-color: var(--accent-gold); box-shadow: 0 0 40px rgba(251, 191, 36, 0.1); background: rgba(255, 255, 255, 0.05); }
106
+ .input-box input { flex: 1; background: transparent; border: none; padding: 18px 28px; color: white; font-size: 1.1rem; outline: none; }
107
+ .send-btn { background: var(--accent-gold); color: #030617; border: none; width: 55px; height: 55px; border-radius: 16px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: 0.3s; margin-right: 8px; }
108
+ .send-btn:hover { transform: scale(1.1) rotate(-5deg); filter: brightness(1.1); }
109
+
110
+ .hidden { display: none !important; }
111
+ @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20px); } }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <aside class="sidebar">
116
+ <div class="sidebar-header">
117
+ <button class="new-chat-btn" id="newChatBtn"><span>🏛️</span> New Deliberation</button>
118
+ </div>
119
+ <div class="history-label">Case Files & Records</div>
120
+ <div class="history-list" id="historyList"></div>
121
+ <div class="sidebar-footer">
122
+ <div class="user-badge">
123
+ <div class="user-avatar" id="avatarName">JD</div>
124
+ <div>
125
+ <div id="usernameLabel" style="font-size: 0.9rem; font-weight: 700;">Your Honor</div>
126
+ <div style="font-size: 0.75rem; color: var(--text-dim); font-weight: 500;">Judicial Authority</div>
127
+ </div>
128
+ </div>
129
+ <div style="display: flex; flex-direction: column; gap: 10px; margin-top: 20px;">
130
+ <a href="/judgedashboard.html" style="color: var(--accent-gold); text-decoration: none; font-size: 0.85rem; text-align: center; font-weight: 700; letter-spacing: 0.5px;">Return to Chambers</a>
131
+ <a href="/role" style="color: var(--text-dim); text-decoration: none; font-size: 0.8rem; text-align: center;">← Change Role</a>
132
+ </div>
133
+ </div>
134
+ </aside>
135
+
136
+ <main class="main-chat">
137
+ <header class="top-bar">
138
+ <div class="system-branding">
139
+ <span class="branding-title">🏛️ Judicial Intelligence System</span>
140
+ <div class="status-pill"><div class="status-dot"></div>Secure Deliberation Active</div>
141
+ </div>
142
+ <div style="font-size: 0.85rem; color: var(--text-dim); font-weight: 600; letter-spacing: 1px;">JUDICIAL FRAMEWORK v5.0 PRO</div>
143
+ </header>
144
+
145
+ <div id="welcomeScreen" style="position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 40px; z-index: 1;">
146
+ <div style="font-size: 5rem; margin-bottom: 30px; animation: float 8s ease-in-out infinite;">⚖️</div>
147
+ <h1 style="font-size: 3.2rem; font-weight: 800; margin-bottom: 15px; letter-spacing: -1.5px;">Protocol Initiated, Your Honor.</h1>
148
+ <p style="color: var(--text-dim); max-width: 600px; line-height: 1.8; font-size: 1.2rem; font-weight: 300;">Enhanced judicial support is online.</p>
149
+ </div>
150
+
151
+ <div class="messages-container" id="chatContainer"></div>
152
+ <div class="typing hidden" id="typingIndicator"><span></span><span></span><span></span></div>
153
+
154
+ <div class="input-area">
155
+ <div class="input-box">
156
+ <input type="text" id="messageInput" placeholder="Initiate legal inquiry..." autocomplete="off">
157
+ <button class="send-btn" id="sendButton">
158
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
159
+ </button>
160
+ </div>
161
+ <p style="text-align: center; font-size: 0.8rem; color: var(--text-dim); margin-top: 25px; font-weight: 500; opacity: 0.7; letter-spacing: 0.5px;">🔒 ENCRYPTED END-TO-END • JUDICIAL PRIVACY PROTECTED</p>
162
+ </div>
163
+ </main>
164
+
165
+ <script>
166
+ let ws;
167
+ const chatContainer = document.getElementById('chatContainer');
168
+ const messageInput = document.getElementById('messageInput');
169
+ const sendButton = document.getElementById('sendButton');
170
+ const typingIndicator = document.getElementById('typingIndicator');
171
+ const welcomeScreen = document.getElementById('welcomeScreen');
172
+ const historyList = document.getElementById('historyList');
173
+
174
+ let currentAiMessage = '';
175
+ let currentAiMessageElement = null;
176
+ let currentCaseId = crypto.randomUUID();
177
+ const activeRole = 'Judge';
178
+
179
+ function formatResponse(text) {
180
+ let formatted = text;
181
+ formatted = formatted.replace(/### (.*?)\s*(\n|$)/g, "<h3>🏛️ $1</h3>");
182
+ formatted = formatted.replace(/^! (.*?)$/gm, '<div class="highlight">$1</div>');
183
+ formatted = formatted.replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>");
184
+ formatted = formatted.replace(/^\*\s(.*?)$/gm, "<li>$1</li>");
185
+ formatted = formatted.replace(/- (.*?)$/gm, "<li>$1</li>");
186
+ formatted = formatted.replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>");
187
+ formatted = formatted.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
188
+ formatted = formatted.replace(/\n/g, "<br>");
189
+ return formatted;
190
+ }
191
+
192
+ function connectWS() {
193
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
194
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
195
+ ws.onmessage = (event) => {
196
+ typingIndicator.classList.add('hidden');
197
+ welcomeScreen.classList.add('hidden');
198
+ if (event.data === '[DONE]') {
199
+ saveInteraction(currentCaseId, messageInput.value, currentAiMessage);
200
+ return;
201
+ }
202
+ if (!currentAiMessageElement) {
203
+ currentAiMessage = event.data;
204
+ createNewMessage('ai', currentAiMessage);
205
+ } else {
206
+ currentAiMessage += event.data;
207
+ currentAiMessageElement.innerHTML = formatResponse(currentAiMessage);
208
+ }
209
+ chatContainer.scrollTop = chatContainer.scrollHeight;
210
+ };
211
+ ws.onclose = () => setTimeout(connectWS, 2000);
212
+ }
213
+
214
+ function createNewMessage(type, content) {
215
+ welcomeScreen.classList.add('hidden');
216
+ const msgDiv = document.createElement('div');
217
+ msgDiv.className = `message ${type}-msg`;
218
+ if (type === 'ai') {
219
+ msgDiv.innerHTML = formatResponse(content);
220
+ currentAiMessageElement = msgDiv;
221
+ } else {
222
+ msgDiv.textContent = content;
223
+ }
224
+ chatContainer.appendChild(msgDiv);
225
+ chatContainer.scrollTop = chatContainer.scrollHeight;
226
+ }
227
+
228
+ function sendMessage() {
229
+ const text = messageInput.value.trim();
230
+ if (!text || ws.readyState !== WebSocket.OPEN) return;
231
+ createNewMessage('user', text);
232
+ ws.send(text);
233
+ messageInput.value = '';
234
+ currentAiMessage = '';
235
+ currentAiMessageElement = null;
236
+ typingIndicator.classList.remove('hidden');
237
+ chatContainer.scrollTop = chatContainer.scrollHeight;
238
+ }
239
+
240
+ async function saveInteraction(caseId, query, response) {
241
+ const token = localStorage.getItem('token');
242
+ if (!token) return;
243
+ try {
244
+ await fetch('/api/save-interaction', {
245
+ method: 'POST',
246
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
247
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
248
+ });
249
+ loadHistory();
250
+ } catch (err) { console.error(err); }
251
+ }
252
+
253
+ async function loadHistory() {
254
+ const token = localStorage.getItem('token');
255
+ if (!token) return;
256
+ try {
257
+ const res = await fetch(`/api/interactions?role=${activeRole}`, {
258
+ headers: { 'Authorization': `Bearer ${token}` }
259
+ });
260
+ const data = await res.json();
261
+ historyList.innerHTML = '';
262
+ data.forEach(item => {
263
+ const div = document.createElement('div');
264
+ div.className = 'history-item';
265
+ const preview = item.query.substring(0, 30) + "...";
266
+ div.innerHTML = `<span>📂</span> ${preview}`;
267
+ div.onclick = () => loadThread(item.case_id);
268
+ historyList.appendChild(div);
269
+ });
270
+ } catch (err) { console.error(err); }
271
+ }
272
+
273
+ async function loadThread(caseId) {
274
+ const token = localStorage.getItem('token');
275
+ try {
276
+ const res = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
277
+ headers: { 'Authorization': `Bearer ${token}` }
278
+ });
279
+ const thread = await res.json();
280
+ currentCaseId = caseId;
281
+ chatContainer.innerHTML = '';
282
+ welcomeScreen.classList.add('hidden');
283
+ thread.forEach(msg => {
284
+ createNewMessage('user', msg.query);
285
+ createNewMessage('ai', msg.response);
286
+ });
287
+ currentAiMessageElement = null;
288
+ } catch (err) { console.error(err); }
289
+ }
290
+
291
+ async function fetchUserStatus() {
292
+ const token = localStorage.getItem('token');
293
+ if (!token) return;
294
+ try {
295
+ const res = await fetch('/api/user-status', {
296
+ headers: { 'Authorization': `Bearer ${token}` }
297
+ });
298
+ const status = await res.json();
299
+ document.getElementById('usernameLabel').innerText = status.username.charAt(0).toUpperCase() + status.username.slice(1);
300
+ document.getElementById('avatarName').innerText = status.username.substring(0, 2).toUpperCase();
301
+ } catch (err) { console.error(err); }
302
+ }
303
+
304
+ sendButton.onclick = sendMessage;
305
+ messageInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
306
+ document.getElementById('newChatBtn').onclick = () => {
307
+ currentCaseId = crypto.randomUUID();
308
+ chatContainer.innerHTML = '';
309
+ welcomeScreen.classList.remove('hidden');
310
+ };
311
+
312
+ connectWS();
313
+ loadHistory();
314
+ fetchUserStatus();
315
+ </script>
316
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  </html>
src/apps/templates/advocatechatbot.html CHANGED
@@ -1,894 +1,273 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Law bot</title>
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
- <link rel="stylesheet" href="/static/css/modern-chat.css">
10
- <style>
11
- :root {
12
- --primary-purple: #9b87f5;
13
- --dark-purple: #1A1F2C;
14
- --light-purple: #D6BCFA;
15
- --neutral-gray: #8E9196;
16
- --light-gray: #C8C8C9;
17
- --off-white: #eee;
18
- }
19
-
20
- * {
21
- margin: 0;
22
- padding: 0;
23
- box-sizing: border-box;
24
- font-family: 'Inter', sans-serif;
25
- }
26
-
27
- body {
28
- transition: background-color 0.3s, color 0.3s;
29
- }
30
-
31
- body.dark {
32
- color: var(--off-white);
33
- }
34
-
35
- body.light {
36
- color: var(--dark-purple);
37
- }
38
-
39
- .container {
40
- max-width: 800px;
41
- margin: 0 auto;
42
- padding: 2rem;
43
- min-height: 100vh;
44
- display: flex;
45
- flex-direction: column;
46
- }
47
-
48
- .header {
49
- display: flex;
50
- justify-content: space-between;
51
- align-items: center;
52
- margin-bottom: 2rem;
53
- }
54
-
55
- .header-right {
56
- display: flex;
57
- align-items: center;
58
- gap: 1rem;
59
- }
60
-
61
-
62
- .app-title {
63
- margin-left: 40px;
64
- font-size: 2rem;
65
- font-weight: 600;
66
- background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
67
- -webkit-background-clip: text;
68
- background-clip: text;
69
- color: transparent;
70
- }
71
-
72
- .theme-toggle {
73
- background: none;
74
- border: none;
75
- cursor: pointer;
76
- padding: 0.5rem;
77
- color: var(--primary-purple);
78
- transition: opacity 0.3s;
79
- }
80
-
81
- .theme-toggle:hover {
82
- opacity: 0.8;
83
- }
84
-
85
- .theme-toggle svg {
86
- width: 24px;
87
- height: 24px;
88
- }
89
-
90
- .chat-container {
91
- flex-grow: 1;
92
- overflow-y: auto;
93
- margin-bottom: 2rem;
94
- padding: 1rem;
95
- }
96
-
97
- .message {
98
- margin-bottom: 1.5rem;
99
- padding: 1rem;
100
- border-radius: 8px;
101
- animation: fadeIn 0.3s ease-in;
102
- }
103
-
104
- .user-message {
105
- background-color: rgba(155, 135, 245, 0.1);
106
- }
107
-
108
- .ai-message {
109
- background-color: rgba(255, 255, 255, 0.05);
110
- border: 1px solid rgba(200, 200, 201, 0.2);
111
- }
112
-
113
- body.light .user-message {
114
- background-color: var(--off-white);
115
- }
116
-
117
- body.light .ai-message {
118
- background-color: #ffffff;
119
- border: 1px solid var(--light-gray);
120
- }
121
-
122
- .message-header {
123
- display: flex;
124
- align-items: center;
125
- margin-bottom: 0.5rem;
126
- font-weight: 500;
127
- }
128
-
129
-
130
- .sidebar-welcome {
131
- padding: 1.5rem;
132
- border-bottom: 1px solid rgba(200, 200, 201, 0.15);
133
- margin-bottom: 1.5rem;
134
- }
135
-
136
- .user-icon,
137
- .ai-icon {
138
- width: 24px;
139
- height: 24px;
140
- border-radius: 50%;
141
- margin-right: 0.5rem;
142
- display: flex;
143
- align-items: center;
144
- justify-content: center;
145
- font-size: 12px;
146
- }
147
-
148
- .user-icon {
149
- background-color: var(--primary-purple);
150
- color: white;
151
- }
152
-
153
- .ai-icon {
154
- background-color: var(--light-purple);
155
- color: var(--dark-purple);
156
- }
157
-
158
- .input-container {
159
- position: fixed;
160
- bottom: 0;
161
- left: 0;
162
- right: 0;
163
- padding: 1rem;
164
- background-color: var(--input-bg);
165
- border-top: 1px solid var(--sidebar-border);
166
- }
167
-
168
- body.light .input-container {
169
- background-color: #ffffff;
170
- border-top: 1px solid var(--light-gray);
171
- }
172
-
173
- .input-wrapper {
174
- max-width: 800px;
175
- margin: 0 auto;
176
- position: relative;
177
- }
178
-
179
- #messageInput {
180
- width: 100%;
181
- padding: 1rem;
182
- padding-right: 3rem;
183
- border: 1px solid rgba(200, 200, 201, 0.3);
184
- border-radius: 8px;
185
- font-size: 1rem;
186
- outline: none;
187
- transition: border-color 0.3s;
188
- background-color: transparent;
189
- color: inherit;
190
- }
191
-
192
- body.light #messageInput {
193
- border: 1px solid var(--light-gray);
194
- }
195
-
196
- #messageInput:focus {
197
- border-color: var(--primary-purple);
198
- }
199
-
200
- #sendButton {
201
- position: absolute;
202
- right: 0.5rem;
203
- top: 50%;
204
- transform: translateY(-50%);
205
- background: none;
206
- border: none;
207
- cursor: pointer;
208
- padding: 0.5rem;
209
- color: var(--primary-purple);
210
- opacity: 0.8;
211
- transition: opacity 0.3s;
212
- }
213
-
214
- #sendButton:hover {
215
- opacity: 1;
216
- }
217
-
218
- .typing-indicator {
219
- padding: 1rem;
220
- color: var(--neutral-gray);
221
- font-style: italic;
222
- display: none;
223
- }
224
-
225
- .welcome-title {
226
- margin: 6rem 0 0.5rem 0;
227
- text-align: center;
228
- color: var(--neutral-gray);
229
- padding: 1rem;
230
- font-size: 1.24rem;
231
- animation: fadeIn 0.3s ease-in;
232
- }
233
-
234
- .welcome-subtitle {
235
- line-height: 1.6;
236
- font-size: 1.24rem;
237
- color: var(--neutral-gray);
238
- max-width: 600px;
239
- margin: 0 auto;
240
- font-weight: 600;
241
- color: var(--primary-purple);
242
- }
243
-
244
- .sidebar-welcome {
245
- margin-bottom: 2rem;
246
- padding: 0 1rem;
247
- border-bottom: none;
248
- }
249
-
250
- .role-selection-link {
251
- display: inline-flex;
252
- align-items: center;
253
- gap: initial;
254
- color: var(--light-purple);
255
- text-decoration: none;
256
- margin-top: auto;
257
- padding: auto;
258
- transition: opacity 0.3s ease;
259
- position: absolute;
260
- top: 550px;
261
- left: 30px;
262
-
263
- }
264
-
265
- .role-selection-link:hover {
266
- opacity: 0.8;
267
- }
268
-
269
- .role-selection-link svg {
270
- width: 16px;
271
- height: 16px;
272
- stroke: currentColor;
273
- }
274
-
275
- /* Add this media query for mobile responsiveness */
276
- @media (max-width: 480px) {
277
- .welcome-message p {
278
- font-size: 1rem;
279
- padding: 0 1rem;
280
- }
281
- }
282
-
283
- @keyframes fadeIn {
284
- from {
285
- opacity: 0;
286
- transform: translateY(10px);
287
- }
288
-
289
- to {
290
- opacity: 1;
291
- transform: translateY(0);
292
- }
293
- }
294
-
295
- .connection-status {
296
- position: fixed;
297
- top: 1rem;
298
- right: 1rem;
299
- padding: 0.5rem 1rem;
300
- border-radius: 4px;
301
- font-size: 0.875rem;
302
- display: none;
303
- }
304
-
305
- .connection-status.connected {
306
- background-color: #4ade80;
307
- color: white;
308
- }
309
-
310
- .connection-status.disconnected {
311
- background-color: #ef4444;
312
- color: white;
313
- }
314
-
315
- /* Styling for formatted text */
316
- .message-content {
317
- line-height: 1.5;
318
- }
319
-
320
- .message-content h1,
321
- .message-content h2,
322
- .message-content h3 {
323
- margin: 1rem 0 0.5rem;
324
- font-weight: 600;
325
- }
326
-
327
- .message-content h1 {
328
- font-size: 1.5rem;
329
- }
330
-
331
- .message-content h2 {
332
- font-size: 1.25rem;
333
- }
334
-
335
- .message-content h3 {
336
- font-size: 1.1rem;
337
- }
338
-
339
- .message-content ul,
340
- .message-content ol {
341
- margin-left: 1.5rem;
342
- margin-bottom: 1rem;
343
- }
344
-
345
- .message-content a {
346
- color: var(--primary-purple);
347
- text-decoration: none;
348
- }
349
-
350
- .message-content a:hover {
351
- text-decoration: underline;
352
- }
353
-
354
- .usage-indicator {
355
- padding: 4px 12px;
356
- background: rgba(155, 135, 245, 0.1);
357
- border: 1px solid rgba(155, 135, 245, 0.3);
358
- border-radius: 20px;
359
- font-size: 0.8rem;
360
- color: var(--primary-purple);
361
- font-weight: 500;
362
- display: none;
363
- /* Hidden by default for Admins */
364
- }
365
- </style>
366
- </head>
367
-
368
- <button class="sidebar-floating-toggle" id="floatingToggle">
369
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
370
- <path d="M3 12h18M3 6h18M3 18h18" />
371
- </svg>
372
- </button>
373
-
374
- <aside class="sidebar" id="sidebar">
375
- <div class="sidebar-header">
376
- <button class="new-chat-btn" id="newChatBtn">
377
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
378
- <path d="M12 5v14M5 12h14" />
379
- </svg>
380
- New Chat
381
- </button>
382
- <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
383
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
384
- <path d="M15 18l-6-6 6-6" />
385
- </svg>
386
- </button>
387
- </div>
388
- <div class="sidebar-title">Recent Cases</div>
389
- <div class="history-list" id="historyList">
390
- <!-- Recent prompts will appear here -->
391
- </div>
392
-
393
- <div class="perspective-container" style="display: none;">
394
- <div class="sidebar-title">Answer Perspective</div>
395
- <div class="perspective-list">
396
- <div class="perspective-option active" data-role="Advocate">🛡️ Advocate</div>
397
- </div>
398
- </div>
399
-
400
- <div class="sidebar-footer">
401
- <a href="/advocatedashboard.html" class="role-selection-link">
402
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
403
- <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
404
- </svg>
405
- Back to Dashboard
406
- </a>
407
- </div>
408
- </aside>
409
-
410
- <!-- Existing messages will be added here dynamically -->
411
- <div class="container">
412
- <header class="header">
413
- <h1 class="app-title">Law Bot (Advocate)</h1>
414
- <div class="header-right">
415
- <div class="usage-indicator" id="usageIndicator">
416
- Questions Remaining: <span id="remainingCount">--</span>
417
- </div>
418
- <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
419
- <!-- Icon injected by JS -->
420
- </button>
421
- <div class="user-profile-menu" id="userProfileMenu">
422
- <div class="profile-icon-btn">
423
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
424
- <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
425
- <circle cx="12" cy="7" r="4"></circle>
426
- </svg>
427
- </div>
428
- <div class="dropdown-menu" id="dropdownMenu">
429
- <div class="dropdown-item role-info">
430
- Role: Advocate
431
- </div>
432
- <a href="#" class="dropdown-item logout-action" id="headerLogoutBtn">
433
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
434
- stroke-width="2">
435
- <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
436
- <polyline points="16 17 21 12 16 7"></polyline>
437
- <line x1="21" y1="12" x2="9" y2="12"></line>
438
- </svg>
439
- Logout
440
- </a>
441
- </div>
442
- </div>
443
- </div>
444
- </header>
445
- <div class="welcome-title">Welcome, Advocate! 🎓</div>
446
- <div class="welcome-subtitle">How can I help you with legal references and case laws?</div>
447
-
448
-
449
- <div class="chat-container" id="chatContainer">
450
- <div class="typing-indicator" id="typingIndicator">
451
- <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--advocate-color)" stroke-width="2">
452
- <circle cx="12" cy="12" r="10" />
453
- <line x1="8" y1="12" x2="16" y2="12" />
454
- <line x1="12" y1="8" x2="12" y2="16" />
455
- </svg>
456
- <span>Analyzing case law...</span>
457
- </div>
458
- </div>
459
- <div class="input-container">
460
- <div class="input-wrapper">
461
- <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
462
- <button id="sendButton">
463
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
464
- stroke-linecap="round" stroke-linejoin="round">
465
- <line x1="22" y1="2" x2="11" y2="13"></line>
466
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
467
- </svg>
468
- </button>
469
- </div>
470
- </div>
471
- <footer class="professional-footer">
472
- &copy; 2026 Law Bot AI. All Rights Reserved.
473
- <br>
474
- Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
475
- </footer>
476
- </div>
477
- <div class="connection-status" id="connectionStatus"></div>
478
-
479
- <script>
480
- let ws;
481
- const chatContainer = document.getElementById('chatContainer');
482
- const messageInput = document.getElementById('messageInput');
483
- const sendButton = document.getElementById('sendButton');
484
- const typingIndicator = document.getElementById('typingIndicator');
485
- const connectionStatus = document.getElementById('connectionStatus');
486
- const sidebar = document.getElementById('sidebar');
487
-
488
- let currentUserMessage = '';
489
- let currentAiMessage = '';
490
- let currentAiMessageElement = null;
491
- let currentCaseId = crypto.randomUUID();
492
- const activeRole = 'Advocate'; // LOCKED ROLE
493
- let userLimitReached = false;
494
-
495
- async function checkUserStatus() {
496
- const token = localStorage.getItem('token');
497
- if (!token) return;
498
-
499
- try {
500
- const response = await fetch('/api/user-status', {
501
- headers: { 'Authorization': `Bearer ${token}` }
502
- });
503
- const status = await response.json();
504
-
505
- const indicator = document.getElementById('usageIndicator');
506
- if (status.is_admin) {
507
- indicator.style.display = 'none';
508
- } else {
509
- indicator.style.display = 'block';
510
- const remaining = Math.max(0, 2 - status.question_count);
511
- document.getElementById('remainingCount').innerText = remaining;
512
- if (remaining <= 0) {
513
- userLimitReached = true;
514
- }
515
- }
516
- } catch (err) {
517
- console.error('Failed to fetch user status:', err);
518
- }
519
- }
520
-
521
- function formatText(text) {
522
- // Extract references for Evidence Box
523
- const references = [];
524
- const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
525
- let match;
526
- while ((match = refRegex.exec(text)) !== null) {
527
- references.push({ title: match[1], pages: match[2] });
528
- }
529
-
530
- let formatted = text
531
- .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
532
- .replace(/\*(.*?)\*/g, "<em>$1</em>")
533
- .replace(/### (.*?)\n/g, "<h3>$1</h3>")
534
- .replace(/## (.*?)\n/g, "<h2>$1</h2>")
535
- .replace(/# (.*?)\n/g, "<h1>$1</h1>")
536
- .replace(/\n/g, "<br>")
537
- .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
538
- .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
539
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
540
-
541
- // Add Evidence Box if references found
542
- if (references.length > 0) {
543
- let evidenceHtml = `<div class="evidence-box">
544
- <div class="evidence-title">
545
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
546
- Legal Evidence
547
- </div>`;
548
- references.forEach(ref => {
549
- evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
550
- });
551
- evidenceHtml += `</div>`;
552
- formatted += evidenceHtml;
553
- }
554
- return formatted;
555
- }
556
-
557
- function connectWebSocket() {
558
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
559
- ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
560
-
561
- ws.onopen = () => {
562
- console.log('Connected to WebSocket');
563
- connectionStatus.textContent = 'Connected';
564
- connectionStatus.className = 'connection-status connected';
565
- connectionStatus.style.display = 'block';
566
- setTimeout(() => {
567
- connectionStatus.style.display = 'none';
568
- }, 3000);
569
- };
570
-
571
- ws.onclose = () => {
572
- console.log('Disconnected from WebSocket');
573
- connectionStatus.textContent = 'Reconnecting...';
574
- connectionStatus.className = 'connection-status disconnected';
575
- connectionStatus.style.display = 'block';
576
-
577
- // Exponential backoff for reconnection
578
- const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
579
- window._reconnectCount = (window._reconnectCount || 0) + 1;
580
-
581
- setTimeout(() => {
582
- reconnectWebSocket();
583
- loadHistory();
584
- }, backoff);
585
- };
586
-
587
- ws.onerror = (error) => {
588
- console.error('WebSocket Error:', error);
589
- };
590
-
591
-
592
- ws.onmessage = (event) => {
593
- console.log('Received message:', event.data);
594
-
595
- // ✅ Hide typing indicator on response
596
- typingIndicator.style.display = 'none';
597
-
598
- if (event.data === '[DONE]') {
599
- // Save complete chat interaction
600
- saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
601
- checkUserStatus(); // Update remaining count
602
- return;
603
- }
604
-
605
- // Handle usage limit blocks from backend
606
- if (event.data.includes("Free usage limit reached")) {
607
- typingIndicator.style.display = 'none';
608
- userLimitReached = true;
609
- checkUserStatus();
610
- }
611
-
612
- if (!currentAiMessageElement) {
613
- currentAiMessage = event.data;
614
- addMessage(currentAiMessage, 'ai');
615
- } else {
616
- currentAiMessage += event.data;
617
- const formattedMessage = formatText(currentAiMessage);
618
- currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
619
- }
620
- };
621
-
622
- // ✅ Scroll fix (move into sendMessage function if needed)
623
- chatContainer.scrollTop = chatContainer.scrollHeight;
624
- }
625
-
626
- function reconnectWebSocket() {
627
- console.log('Attempting to reconnect...');
628
- connectWebSocket();
629
- loadHistory();
630
- }
631
-
632
- function addMessage(content, type) {
633
- if (type === 'user') {
634
- currentUserMessage = content;
635
- currentAiMessage = '';
636
- currentAiMessageElement = null;
637
- }
638
-
639
- const messageDiv = document.createElement('div');
640
- messageDiv.className = `message ${type}-message`;
641
-
642
- const header = document.createElement('div');
643
- header.className = 'message-header';
644
-
645
- const icon = document.createElement('div');
646
- icon.className = `${type}-icon`;
647
- icon.textContent = type === 'user' ? 'U' : 'L';
648
-
649
- const name = document.createElement('span');
650
- name.textContent = type === 'user' ? 'You' : 'Law Bot';
651
-
652
- header.appendChild(icon);
653
- header.appendChild(name);
654
-
655
- const text = document.createElement('div');
656
- text.className = 'message-content';
657
- text.innerHTML = type === 'ai' ? formatText(content) : content;
658
-
659
- messageDiv.appendChild(header);
660
- messageDiv.appendChild(text);
661
- chatContainer.appendChild(messageDiv);
662
- chatContainer.scrollTop = chatContainer.scrollHeight;
663
-
664
- if (type === 'ai') {
665
- currentAiMessageElement = messageDiv;
666
- }
667
- }
668
-
669
- function sendMessage() {
670
- const message = messageInput.value.trim();
671
- if (!message) return;
672
-
673
- if (userLimitReached) {
674
- addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
675
- return;
676
- }
677
-
678
- if (!activeRole) {
679
- addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
680
- return;
681
- }
682
-
683
- if (ws.readyState === WebSocket.OPEN) {
684
- addMessage(message, 'user');
685
- ws.send(message);
686
- messageInput.value = '';
687
-
688
- // ✅ Show typing indicator after sending
689
- typingIndicator.style.display = 'block';
690
- chatContainer.scrollTop = chatContainer.scrollHeight;
691
- }
692
- }
693
- const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
694
- if (isAtBottom) {
695
- chatContainer.scrollTop = chatContainer.scrollHeight;
696
- }
697
-
698
-
699
- const historyList = document.getElementById('historyList');
700
-
701
- async function loadHistory() {
702
- const token = localStorage.getItem('token');
703
- if (!token) return;
704
-
705
- try {
706
- const response = await fetch(`/api/interactions?role=${activeRole}`, {
707
- headers: { 'Authorization': `Bearer ${token}` }
708
- });
709
- const interactions = await response.json();
710
- historyList.innerHTML = '';
711
- interactions.forEach(item => {
712
- const div = document.createElement('div');
713
- div.className = 'history-item';
714
-
715
- // Readable preview: first sentence or truncated query
716
- const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
717
-
718
- div.innerHTML = `
719
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
720
- <span>${preview}</span>
721
- <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
722
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
723
- </button>
724
- `;
725
- div.onclick = () => loadConversation(item.case_id);
726
- historyList.appendChild(div);
727
- });
728
- } catch (err) {
729
- console.error('Failed to load history:', err);
730
- }
731
- }
732
-
733
- async function loadConversation(caseId) {
734
- const token = localStorage.getItem('token');
735
- try {
736
- const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
737
- headers: { 'Authorization': `Bearer ${token}` }
738
- });
739
- const thread = await response.json();
740
-
741
- currentCaseId = caseId;
742
- chatContainer.innerHTML = ''; // Clear window
743
-
744
- thread.forEach(msg => {
745
- addMessage(msg.query, 'user');
746
- // Render AI Response immediately
747
- const aiMsgDiv = document.createElement('div');
748
- aiMsgDiv.className = 'message ai-message';
749
- aiMsgDiv.innerHTML = `
750
- <div class="message-header">
751
- <div class="ai-icon">L</div>
752
- <span>Law Bot</span>
753
- </div>
754
- <div class="message-content">${formatText(msg.response)}</div>
755
- `;
756
- chatContainer.appendChild(aiMsgDiv);
757
- });
758
- chatContainer.scrollTop = chatContainer.scrollHeight;
759
- } catch (err) {
760
- console.error('Failed to load thread:', err);
761
- }
762
- }
763
-
764
- async function deleteConversation(caseId) {
765
- const token = localStorage.getItem('token');
766
- try {
767
- await fetch(`/api/interactions/${caseId}`, {
768
- method: 'DELETE',
769
- headers: { 'Authorization': `Bearer ${token}` }
770
- });
771
- if (currentCaseId === caseId) {
772
- newChat();
773
- } else {
774
- loadHistory();
775
- }
776
- } catch (err) {
777
- console.error('Failed to delete:', err);
778
- }
779
- }
780
-
781
- function newChat() {
782
- currentCaseId = crypto.randomUUID();
783
- chatContainer.innerHTML = '';
784
- messageInput.value = '';
785
- messageInput.focus();
786
- loadHistory();
787
- }
788
-
789
- // ✅ Modified: Uses Bearer token
790
- const saveChatInteraction = async (caseId, query, response) => {
791
- const token = localStorage.getItem('token');
792
- if (!token) return;
793
-
794
- try {
795
- await fetch('/api/save-interaction', {
796
- method: 'POST',
797
- headers: {
798
- 'Content-Type': 'application/json',
799
- 'Authorization': `Bearer ${token}`
800
- },
801
- body: JSON.stringify({ caseId, query, response, role: activeRole })
802
- });
803
- loadHistory(); // Refresh sidebar history
804
- } catch (error) {
805
- console.error('Error saving chat:', error);
806
- }
807
- };
808
-
809
- sendButton.addEventListener('click', sendMessage);
810
- messageInput.addEventListener('keypress', (e) => {
811
- if (e.key === 'Enter') {
812
- sendMessage();
813
- }
814
- });
815
-
816
- // Theme toggle
817
- // --- User Profile Dropdown ---
818
- const userProfileMenu = document.getElementById('userProfileMenu');
819
- const dropdownMenu = document.getElementById('dropdownMenu');
820
- const headerLogoutBtn = document.getElementById('headerLogoutBtn');
821
-
822
- userProfileMenu.addEventListener('click', (e) => {
823
- e.stopPropagation();
824
- dropdownMenu.classList.toggle('show');
825
- });
826
-
827
- document.addEventListener('click', () => {
828
- if (dropdownMenu.classList.contains('show')) {
829
- dropdownMenu.classList.remove('show');
830
- }
831
- });
832
-
833
- headerLogoutBtn.addEventListener('click', (e) => {
834
- e.preventDefault();
835
- localStorage.removeItem('token');
836
- window.location.href = '/role';
837
- });
838
-
839
- const themeToggle = document.getElementById('themeToggle');
840
- const body = document.body;
841
- const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
842
- const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
843
-
844
- function updateTheme(isDark) {
845
- if (isDark) {
846
- body.classList.add('dark');
847
- body.classList.remove('light');
848
- themeToggle.innerHTML = moonIcon;
849
- localStorage.setItem('theme', 'dark');
850
- } else {
851
- body.classList.add('light');
852
- body.classList.remove('dark');
853
- themeToggle.innerHTML = sunIcon;
854
- localStorage.setItem('theme', 'light');
855
- }
856
- }
857
-
858
- themeToggle.addEventListener('click', () => {
859
- const isNowDark = !body.classList.contains('dark');
860
- updateTheme(isNowDark);
861
- });
862
-
863
- // Initialize Theme
864
- const savedTheme = localStorage.getItem('theme') || 'dark';
865
- updateTheme(savedTheme === 'dark');
866
-
867
- // Sidebar Collapse Logic
868
- document.getElementById('collapseSidebar').onclick = () => {
869
- document.body.classList.add('sidebar-collapsed');
870
- };
871
- document.getElementById('floatingToggle').onclick = () => {
872
- document.body.classList.remove('sidebar-collapsed');
873
- };
874
- document.getElementById('newChatBtn').onclick = newChat;
875
-
876
- // Perspective Selection
877
- document.querySelectorAll('.perspective-option').forEach(opt => {
878
- opt.onclick = () => {
879
- document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
880
- opt.classList.add('active');
881
- activeRole = opt.dataset.role;
882
- localStorage.setItem('activeRole', activeRole);
883
- // No session reset needed, just role update for next message
884
- };
885
- });
886
-
887
- checkUserStatus();
888
- connectWebSocket();
889
- loadHistory();
890
-
891
- </script>
892
- </body>
893
-
894
  </html>
 
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>AI Legal Assistant | Advocate Command Center</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-dark: #030617;
11
+ --sidebar-bg: #050a24;
12
+ --accent-blue: #2563eb;
13
+ --accent-indigo: #4338ca;
14
+ --text-primary: #f1f5f9;
15
+ --text-dim: #94a3b8;
16
+ --glass-bg: rgba(255, 255, 255, 0.03);
17
+ --glass-border: rgba(255, 255, 255, 0.08);
18
+ }
19
+
20
+ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Poppins', sans-serif; }
21
+ body { background-color: var(--bg-dark); color: var(--text-primary); height: 100vh; display: flex; overflow: hidden; }
22
+
23
+ .sidebar { width: 280px; background: var(--sidebar-bg); border-right: 1px solid var(--glass-border); display: flex; flex-direction: column; z-index: 100; }
24
+ .sidebar-header { padding: 30px 20px; }
25
+ .new-chat-btn {
26
+ width: 100%; padding: 14px; background: linear-gradient(135deg, var(--accent-blue), var(--accent-indigo));
27
+ border: none; border-radius: 12px; color: white; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 10px; cursor: pointer; transition: 0.3s;
28
+ box-shadow: 0 10px 20px rgba(37, 99, 235, 0.2);
29
+ }
30
+ .new-chat-btn:hover { transform: translateY(-2px); box-shadow: 0 15px 25px rgba(37, 99, 235, 0.3); }
31
+
32
+ .history-label { padding: 0 20px 10px; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 2px; color: var(--text-dim); font-weight: 700; }
33
+ .history-list { flex: 1; overflow-y: auto; padding: 10px; }
34
+ .history-item { padding: 12px 15px; border-radius: 10px; margin-bottom: 5px; cursor: pointer; transition: 0.2s; font-size: 0.85rem; color: var(--text-dim); display: flex; align-items: center; gap: 10px; border: 1px solid transparent; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
35
+ .history-item:hover { background: var(--glass-bg); color: white; border-color: var(--glass-border); }
36
+
37
+ .sidebar-footer { padding: 20px; border-top: 1px solid var(--glass-border); }
38
+ .user-badge { display: flex; align-items: center; gap: 12px; padding: 10px; background: var(--glass-bg); border-radius: 12px; }
39
+ .user-avatar { width: 32px; height: 32px; background: var(--accent-blue); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: 700; color: white; font-size: 0.8rem; }
40
+
41
+ .main-chat { flex: 1; display: flex; flex-direction: column; position: relative; background: radial-gradient(circle at top right, rgba(37, 99, 235, 0.03), transparent); }
42
+ .top-bar { height: 70px; background: rgba(3, 6, 23, 0.82); backdrop-filter: blur(25px); border-bottom: 1px solid var(--glass-border); display: flex; align-items: center; justify-content: space-between; padding: 0 40px; z-index: 50; }
43
+ .status-pill { display: flex; align-items: center; gap: 8px; font-size: 0.75rem; background: rgba(37, 99, 235, 0.1); color: #60a5fa; padding: 4px 12px; border-radius: 20px; font-weight: 600; border: 1px solid rgba(37, 99, 235, 0.2); }
44
+ .status-dot { width: 6px; height: 6px; background: #60a5fa; border-radius: 50%; animation: pulse-dot 2s infinite; }
45
+ @keyframes pulse-dot { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.4; } }
46
+
47
+ .messages-container { flex: 1; overflow-y: auto; padding: 40px 10%; display: flex; flex-direction: column; gap: 30px; scroll-behavior: smooth; }
48
+ .message { max-width: 850px; animation: message-slide 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; }
49
+ @keyframes message-slide { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
50
+
51
+ .user-msg { align-self: flex-end; background: linear-gradient(135deg, var(--accent-blue), var(--accent-indigo)); padding: 16px 24px; border-radius: 20px 20px 4px 20px; color: white; max-width: 70%; line-height: 1.6; }
52
+ .ai-msg { align-self: flex-start; background: var(--glass-bg); border: 1px solid var(--glass-border); padding: 30px; border-radius: 20px 20px 20px 4px; max-width: 85%; line-height: 1.8; color: #e2e8f0; }
53
+ .ai-msg h3 { color: #60a5fa; font-size: 1.3rem; margin-bottom: 20px; font-weight: 600; display: flex; align-items: center; gap: 12px; }
54
+ .ai-msg li { position: relative; padding-left: 28px; margin-bottom: 15px; }
55
+ .ai-msg li::before { content: "📘"; position: absolute; left: 0; font-size: 0.9rem; }
56
+ .highlight { background: rgba(37, 99, 235, 0.08); border-left: 4px solid var(--accent-blue); padding: 22px; margin: 25px 0; border-radius: 0 16px 16px 0; font-weight: 500; color: #fff; }
57
+
58
+ .typing { display: flex; gap: 6px; padding: 18px 28px; background: var(--glass-bg); border-radius: 20px; width: fit-content; margin: 20px 10%; border: 1px solid var(--glass-border); }
59
+ .typing span { width: 8px; height: 8px; background: var(--accent-blue); border-radius: 50%; animation: typing-blink 1.4s infinite ease-in-out; }
60
+ @keyframes typing-blink { 0%, 100% { opacity: 0.2; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.1); } }
61
+
62
+ .input-area { padding: 30px 10%; background: linear-gradient(to top, var(--bg-dark) 50%, transparent); border-top: 1px solid var(--glass-border); z-index: 60; }
63
+ .input-box { background: rgba(255, 255, 255, 0.04); border: 1px solid var(--glass-border); border-radius: 18px; padding: 6px; display: flex; align-items: center; transition: 0.3s; }
64
+ .input-box:focus-within { border-color: var(--accent-blue); box-shadow: 0 0 30px rgba(37, 99, 235, 0.2); }
65
+ .input-box input { flex: 1; background: transparent; border: none; padding: 16px 24px; color: white; font-size: 1.05rem; outline: none; }
66
+ .send-btn { background: var(--accent-blue); color: white; border: none; width: 50px; height: 50px; border-radius: 14px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: 0.3s; margin-right: 6px; }
67
+
68
+ .hidden { display: none !important; }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <aside class="sidebar">
73
+ <div class="sidebar-header">
74
+ <button class="new-chat-btn" id="newChatBtn"><span>+</span> New Case Research</button>
75
+ </div>
76
+ <div class="history-label">Legal Research History</div>
77
+ <div class="history-list" id="historyList"></div>
78
+ <div class="sidebar-footer">
79
+ <div class="user-badge">
80
+ <div class="user-avatar" id="avatarName">AD</div>
81
+ <div>
82
+ <div id="usernameLabel" style="font-size: 0.85rem; font-weight: 600;">Advocate</div>
83
+ <div style="font-size: 0.7rem; color: var(--text-dim);">Executive Access</div>
84
+ </div>
85
+ </div>
86
+ <div style="display: flex; flex-direction: column; gap: 8px; margin-top: 15px;">
87
+ <a href="/advocatedashboard.html" style="color: var(--accent-blue); text-decoration: none; font-size: 0.8rem; text-align: center; font-weight: 600;">Back to Dashboard</a>
88
+ <a href="/role" style="color: var(--text-dim); text-decoration: none; font-size: 0.8rem; text-align: center;">← Change Role</a>
89
+ </div>
90
+ </div>
91
+ </aside>
92
+
93
+ <main class="main-chat">
94
+ <header class="top-bar">
95
+ <div class="system-branding">
96
+ <span style="font-weight: 700; color: white;">⚖️ Law Bot (Advocate)</span>
97
+ <div class="status-pill"><div class="status-dot"></div>Advocate Secure Access</div>
98
+ </div>
99
+ <div style="font-size: 0.85rem; color: var(--text-dim); font-weight: 500;">Case Management v4.0 PRO</div>
100
+ </header>
101
+
102
+ <div id="welcomeScreen" style="position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 40px; z-index: 1;">
103
+ <div style="font-size: 4rem; margin-bottom: 25px;">💼</div>
104
+ <h1 style="font-size: 2.8rem; font-weight: 700; margin-bottom: 12px; letter-spacing: -1px;">Advancing Justice through AI.</h1>
105
+ <p style="color: var(--text-dim); max-width: 550px; line-height: 1.7; font-size: 1.1rem;">Professional research tools at your disposal. Analyze precedents, retrieve sections, and build your case strategy with precision.</p>
106
+ </div>
107
+
108
+ <div class="messages-container" id="chatContainer"></div>
109
+ <div class="typing hidden" id="typingIndicator"><span></span><span></span><span></span></div>
110
+
111
+ <div class="input-area">
112
+ <div class="input-box">
113
+ <input type="text" id="messageInput" placeholder="Describe case or request legal sections..." autocomplete="off">
114
+ <button class="send-btn" id="sendButton">
115
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
116
+ </button>
117
+ </div>
118
+ </div>
119
+ </main>
120
+
121
+ <script>
122
+ let ws;
123
+ const chatContainer = document.getElementById('chatContainer');
124
+ const messageInput = document.getElementById('messageInput');
125
+ const sendButton = document.getElementById('sendButton');
126
+ const typingIndicator = document.getElementById('typingIndicator');
127
+ const welcomeScreen = document.getElementById('welcomeScreen');
128
+ const historyList = document.getElementById('historyList');
129
+
130
+ let currentAiMessage = '';
131
+ let currentAiMessageElement = null;
132
+ let currentCaseId = crypto.randomUUID();
133
+ const activeRole = 'Advocate';
134
+
135
+ function formatResponse(text) {
136
+ let formatted = text;
137
+ formatted = formatted.replace(/### (.*?)\s*(\n|$)/g, "<h3>📚 $1</h3>");
138
+ formatted = formatted.replace(/^! (.*?)$/gm, '<div class="highlight">$1</div>');
139
+ formatted = formatted.replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>");
140
+ formatted = formatted.replace(/^\*\s(.*?)$/gm, "<li>$1</li>");
141
+ formatted = formatted.replace(/- (.*?)$/gm, "<li>$1</li>");
142
+ formatted = formatted.replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>");
143
+ formatted = formatted.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
144
+ formatted = formatted.replace(/\n/g, "<br>");
145
+ return formatted;
146
+ }
147
+
148
+ function connectWS() {
149
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
150
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
151
+ ws.onmessage = (event) => {
152
+ typingIndicator.classList.add('hidden');
153
+ welcomeScreen.classList.add('hidden');
154
+ if (event.data === '[DONE]') {
155
+ saveInteraction(currentCaseId, messageInput.value, currentAiMessage);
156
+ return;
157
+ }
158
+ if (!currentAiMessageElement) {
159
+ currentAiMessage = event.data;
160
+ createNewMessage('ai', currentAiMessage);
161
+ } else {
162
+ currentAiMessage += event.data;
163
+ currentAiMessageElement.innerHTML = formatResponse(currentAiMessage);
164
+ }
165
+ chatContainer.scrollTop = chatContainer.scrollHeight;
166
+ };
167
+ ws.onclose = () => setTimeout(connectWS, 2000);
168
+ }
169
+
170
+ function createNewMessage(type, content) {
171
+ welcomeScreen.classList.add('hidden');
172
+ const msgDiv = document.createElement('div');
173
+ msgDiv.className = `message ${type}-msg`;
174
+ if (type === 'ai') {
175
+ msgDiv.innerHTML = formatResponse(content);
176
+ currentAiMessageElement = msgDiv;
177
+ } else {
178
+ msgDiv.textContent = content;
179
+ }
180
+ chatContainer.appendChild(msgDiv);
181
+ chatContainer.scrollTop = chatContainer.scrollHeight;
182
+ }
183
+
184
+ function sendMessage() {
185
+ const text = messageInput.value.trim();
186
+ if (!text || ws.readyState !== WebSocket.OPEN) return;
187
+ createNewMessage('user', text);
188
+ ws.send(text);
189
+ messageInput.value = '';
190
+ currentAiMessage = '';
191
+ currentAiMessageElement = null;
192
+ typingIndicator.classList.remove('hidden');
193
+ chatContainer.scrollTop = chatContainer.scrollHeight;
194
+ }
195
+
196
+ async function saveInteraction(caseId, query, response) {
197
+ const token = localStorage.getItem('token');
198
+ if (!token) return;
199
+ try {
200
+ await fetch('/api/save-interaction', {
201
+ method: 'POST',
202
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
203
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
204
+ });
205
+ loadHistory();
206
+ } catch (err) { console.error(err); }
207
+ }
208
+
209
+ async function loadHistory() {
210
+ const token = localStorage.getItem('token');
211
+ if (!token) return;
212
+ try {
213
+ const res = await fetch(`/api/interactions?role=${activeRole}`, {
214
+ headers: { 'Authorization': `Bearer ${token}` }
215
+ });
216
+ const data = await res.json();
217
+ historyList.innerHTML = '';
218
+ data.forEach(item => {
219
+ const div = document.createElement('div');
220
+ div.className = 'history-item';
221
+ const preview = item.query.substring(0, 30) + "...";
222
+ div.innerHTML = `<span>📂</span> ${preview}`;
223
+ div.onclick = () => loadThread(item.case_id);
224
+ historyList.appendChild(div);
225
+ });
226
+ } catch (err) { console.error(err); }
227
+ }
228
+
229
+ async function loadThread(caseId) {
230
+ const token = localStorage.getItem('token');
231
+ try {
232
+ const res = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
233
+ headers: { 'Authorization': `Bearer ${token}` }
234
+ });
235
+ const thread = await res.json();
236
+ currentCaseId = caseId;
237
+ chatContainer.innerHTML = '';
238
+ welcomeScreen.classList.add('hidden');
239
+ thread.forEach(msg => {
240
+ createNewMessage('user', msg.query);
241
+ createNewMessage('ai', msg.response);
242
+ });
243
+ currentAiMessageElement = null;
244
+ } catch (err) { console.error(err); }
245
+ }
246
+
247
+ async function fetchUserStatus() {
248
+ const token = localStorage.getItem('token');
249
+ if (!token) return;
250
+ try {
251
+ const res = await fetch('/api/user-status', {
252
+ headers: { 'Authorization': `Bearer ${token}` }
253
+ });
254
+ const status = await res.json();
255
+ document.getElementById('usernameLabel').innerText = status.username.charAt(0).toUpperCase() + status.username.slice(1);
256
+ document.getElementById('avatarName').innerText = status.username.substring(0, 2).toUpperCase();
257
+ } catch (err) { console.error(err); }
258
+ }
259
+
260
+ sendButton.onclick = sendMessage;
261
+ messageInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
262
+ document.getElementById('newChatBtn').onclick = () => {
263
+ currentCaseId = crypto.randomUUID();
264
+ chatContainer.innerHTML = '';
265
+ welcomeScreen.classList.remove('hidden');
266
+ };
267
+
268
+ connectWS();
269
+ loadHistory();
270
+ fetchUserStatus();
271
+ </script>
272
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  </html>
src/apps/templates/citizen.html CHANGED
@@ -1,791 +1,636 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Law bot</title>
8
- <link rel="stylesheet" href="/static/css/entrance.css">
9
- <script src="/static/js/entrance.js" defer></script>
10
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
11
- <link rel="stylesheet" href="/static/css/modern-chat.css">
12
- <style>
13
- :root {
14
- --primary-purple: #9b87f5;
15
- --dark-purple: #1A1F2C;
16
- --light-purple: #D6BCFA;
17
- --neutral-gray: #8E9196;
18
- --light-gray: #C8C8C9;
19
- --off-white: #eee;
20
- }
21
-
22
- * {
23
- margin: 0;
24
- padding: 0;
25
- box-sizing: border-box;
26
- font-family: 'Inter', sans-serif;
27
- }
28
-
29
- body {
30
- transition: background-color 0.3s, color 0.3s;
31
- }
32
-
33
- body.dark {
34
- color: var(--off-white);
35
- }
36
-
37
- body.light {
38
- color: var(--dark-purple);
39
- }
40
-
41
- .container {
42
- max-width: 800px;
43
- margin: 0 auto;
44
- padding: 2rem;
45
- min-height: 100vh;
46
- display: flex;
47
- flex-direction: column;
48
- }
49
-
50
- .header {
51
- display: flex;
52
- justify-content: space-between;
53
- align-items: center;
54
- margin-bottom: 2rem;
55
- }
56
-
57
- .app-title {
58
- margin-left: 40px;
59
- font-size: 2rem;
60
- font-weight: 600;
61
- background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
62
- -webkit-background-clip: text;
63
- background-clip: text;
64
- color: transparent;
65
- }
66
-
67
- .theme-toggle {
68
- background: none;
69
- border: none;
70
- cursor: pointer;
71
- padding: 0.5rem;
72
- color: var(--primary-purple);
73
- transition: opacity 0.3s;
74
- }
75
-
76
- .theme-toggle:hover {
77
- opacity: 0.8;
78
- }
79
-
80
- .theme-toggle svg {
81
- width: 24px;
82
- height: 24px;
83
- }
84
-
85
- .input-wrapper {
86
- max-width: 800px;
87
- margin: 0 auto;
88
- position: relative;
89
- }
90
-
91
- #messageInput {
92
- width: 100%;
93
- padding: 1rem;
94
- padding-right: 3rem;
95
- border: 1px solid rgba(200, 200, 201, 0.3);
96
- border-radius: 8px;
97
- font-size: 1rem;
98
- outline: none;
99
- transition: border-color 0.3s;
100
- background-color: transparent;
101
- color: inherit;
102
- }
103
-
104
- body.light #messageInput {
105
- border: 1px solid var(--light-gray);
106
- }
107
-
108
- #messageInput:focus {
109
- border-color: var(--primary-purple);
110
- }
111
-
112
- #sendButton {
113
- position: absolute;
114
- right: 0.5rem;
115
- top: 50%;
116
- transform: translateY(-50%);
117
- background: none;
118
- border: none;
119
- cursor: pointer;
120
- padding: 0.5rem;
121
- color: var(--primary-purple);
122
- opacity: 0.8;
123
- transition: opacity 0.3s;
124
- }
125
-
126
- #sendButton:hover {
127
- opacity: 1;
128
- }
129
-
130
- .typing-indicator {
131
- padding: 1rem;
132
- color: var(--neutral-gray);
133
- font-style: italic;
134
- display: none;
135
- }
136
-
137
- .welcome-title {
138
- margin: 6rem 0 0.5rem 0;
139
- text-align: center;
140
- color: var(--neutral-gray);
141
- padding: 1rem;
142
- font-size: 1.24rem;
143
- animation: fadeIn 0.3s ease-in;
144
- }
145
-
146
- .welcome-subtitle {
147
- line-height: 1.6;
148
- font-size: 1.1rem;
149
- color: var(--neutral-gray);
150
- max-width: 800px;
151
- margin: 0 auto;
152
- font-weight: 400;
153
- color: var(--primary-purple);
154
- }
155
-
156
- .sidebar-welcome {
157
- margin-bottom: 2rem;
158
- padding: 0 1rem;
159
- border-bottom: none;
160
- }
161
-
162
- .role-selection-link {
163
- display: inline-flex;
164
- align-items: center;
165
- gap: initial;
166
- color: var(--light-purple);
167
- text-decoration: none;
168
- margin-top: auto;
169
- padding: auto;
170
- transition: opacity 0.3s ease;
171
- position: absolute;
172
- top: 550px;
173
- left: 30px;
174
- }
175
-
176
- .role-selection-link:hover {
177
- opacity: 0.8;
178
- }
179
-
180
- .role-selection-link svg {
181
- width: 16px;
182
- height: 16px;
183
- stroke: currentColor;
184
- }
185
-
186
- @media (max-width: 480px) {
187
- .welcome-message p {
188
- font-size: 1rem;
189
- padding: 0 1rem;
190
- }
191
- }
192
-
193
- @keyframes fadeIn {
194
- from {
195
- opacity: 0;
196
- transform: translateY(10px);
197
- }
198
-
199
- to {
200
- opacity: 1;
201
- transform: translateY(0);
202
- }
203
- }
204
-
205
- .connection-status {
206
- position: fixed;
207
- top: 1rem;
208
- right: 1rem;
209
- padding: 0.5rem 1rem;
210
- border-radius: 4px;
211
- font-size: 0.875rem;
212
- display: none;
213
- }
214
-
215
- .connection-status.connected {
216
- background-color: #4ade80;
217
- color: white;
218
- }
219
-
220
- .connection-status.disconnected {
221
- background-color: #ef4444;
222
- color: white;
223
- }
224
-
225
- .message-content {
226
- line-height: 1.5;
227
- }
228
-
229
- .message-content h1,
230
- .message-content h2,
231
- .message-content h3 {
232
- margin: 1rem 0 0.5rem;
233
- font-weight: 600;
234
- }
235
-
236
- .message-content h1 {
237
- font-size: 1.5rem;
238
- }
239
-
240
- .message-content h2 {
241
- font-size: 1.25rem;
242
- }
243
-
244
- .message-content h3 {
245
- font-size: 1.1rem;
246
- }
247
-
248
- .message-content ul,
249
- .message-content ol {
250
- margin-left: 1.5rem;
251
- margin-bottom: 1rem;
252
- }
253
-
254
- .message-content a {
255
- color: var(--primary-purple);
256
- text-decoration: none;
257
- }
258
-
259
- .message-content a:hover {
260
- text-decoration: underline;
261
- }
262
-
263
- .usage-indicator {
264
- padding: 4px 12px;
265
- background: rgba(155, 135, 245, 0.1);
266
- border: 1px solid rgba(155, 135, 245, 0.3);
267
- border-radius: 20px;
268
- font-size: 0.8rem;
269
- color: var(--primary-purple);
270
- font-weight: 500;
271
- display: none;
272
- /* Hidden by default for Admins */
273
- }
274
- </style>
275
- </head>
276
-
277
- <body>
278
-
279
-
280
-
281
- <button class="sidebar-floating-toggle" id="floatingToggle">
282
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
283
- <path d="M3 12h18M3 6h18M3 18h18" />
284
- </svg>
285
- </button>
286
-
287
- <aside class="sidebar" id="sidebar">
288
- <div class="sidebar-header">
289
- <button class="new-chat-btn" id="newChatBtn">
290
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
291
- <path d="M12 5v14M5 12h14" />
292
- </svg>
293
- New Chat
294
- </button>
295
- <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
296
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
297
- <path d="M15 18l-6-6 6-6" />
298
- </svg>
299
- </button>
300
- </div>
301
- <div class="sidebar-title">Recent Chats</div>
302
- <div class="history-list" id="historyList">
303
- <!-- Recent prompts will appear here -->
304
- </div>
305
-
306
- <!-- HIDDEN Answer Perspective for Citizen Role (Strict Isolation) -->
307
- <div class="perspective-container" style="display: none;">
308
- <div class="sidebar-title">Answer Perspective</div>
309
- <div class="perspective-list">
310
- <div class="perspective-option active" data-role="Citizen">👨‍⚖️ Citizen</div>
311
- </div>
312
- </div>
313
-
314
-
315
- </aside>
316
-
317
- <!-- Existing messages will be added here dynamically -->
318
- <div class="container">
319
- <header class="header">
320
- <h1 class="app-title">Law Bot (Citizen)</h1>
321
- <div class="header-right">
322
- <div class="usage-indicator" id="usageIndicator">
323
- Questions Remaining: <span id="remainingCount">--</span>
324
- </div>
325
- <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
326
- <!-- Icon injected by JS -->
327
- </button>
328
- <div class="user-profile-menu" id="userProfileMenu">
329
- <div class="profile-icon-btn">
330
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
331
- stroke-width="2">
332
- <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
333
- <circle cx="12" cy="7" r="4"></circle>
334
- </svg>
335
- </div>
336
- <div class="dropdown-menu" id="dropdownMenu">
337
- <div class="dropdown-item role-info">
338
- Role: Citizen
339
- </div>
340
- <a href="#" class="dropdown-item logout-action" id="headerLogoutBtn">
341
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
342
- stroke-width="2">
343
- <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
344
- <polyline points="16 17 21 12 16 7"></polyline>
345
- <line x1="21" y1="12" x2="9" y2="12"></line>
346
- </svg>
347
- Logout
348
- </a>
349
- </div>
350
- </div>
351
- </div>
352
- </header>
353
- <div class="welcome-title">Welcome, Responsible Citizen! 👨‍⚖️</div>
354
- <div class="welcome-subtitle">Let’s simplify legal processes together. How can I help clarify the laws for
355
- you?
356
- </div>
357
-
358
-
359
- <div class="chat-container" id="chatContainer">
360
- <div class="typing-indicator" id="typingIndicator">
361
- <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--primary-purple)" stroke-width="2">
362
- <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
363
- <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
364
- </svg>
365
- <span>Law Bot is thinking...</span>
366
- </div>
367
- </div>
368
- <div class="input-container">
369
- <div class="input-wrapper">
370
- <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
371
- <button id="sendButton">
372
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
373
- stroke-linecap="round" stroke-linejoin="round">
374
- <line x1="22" y1="2" x2="11" y2="13"></line>
375
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
376
- </svg>
377
- </button>
378
- </div>
379
- </div>
380
- <footer class="legal-footer">
381
- &copy; 2026 Law Bot AI. Professional Legal Assistant for Citizens.
382
- <br>Disclaimer: This AI system provides information for educational/assisting purposes and does not
383
- constitute formal legal advice.
384
- </footer>
385
- </div>
386
- <div class="connection-status" id="connectionStatus"></div>
387
-
388
- <script>
389
- let ws;
390
- const chatContainer = document.getElementById('chatContainer');
391
- const messageInput = document.getElementById('messageInput');
392
- const sendButton = document.getElementById('sendButton');
393
- const typingIndicator = document.getElementById('typingIndicator');
394
- const connectionStatus = document.getElementById('connectionStatus');
395
- const sidebar = document.getElementById('sidebar');
396
-
397
- let currentUserMessage = '';
398
- let currentAiMessage = '';
399
- let currentAiMessageElement = null;
400
- let currentCaseId = crypto.randomUUID();
401
- const activeRole = 'Citizen'; // LOCKED ROLE
402
- let userLimitReached = false;
403
-
404
- async function checkUserStatus() {
405
- const token = localStorage.getItem('token');
406
- if (!token) return;
407
-
408
- try {
409
- const response = await fetch('/api/user-status', {
410
- headers: { 'Authorization': `Bearer ${token}` }
411
- });
412
- const status = await response.json();
413
-
414
- const indicator = document.getElementById('usageIndicator');
415
- if (status.is_admin) {
416
- indicator.style.display = 'none';
417
- } else {
418
- indicator.style.display = 'block';
419
- const remaining = Math.max(0, 2 - status.question_count);
420
- document.getElementById('remainingCount').innerText = remaining;
421
- if (remaining <= 0) {
422
- userLimitReached = true;
423
- }
424
- }
425
- } catch (err) {
426
- console.error('Failed to fetch user status:', err);
427
- }
428
- }
429
-
430
- function formatText(text) {
431
- // Extract references for Evidence Box
432
- const references = [];
433
- const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
434
- let match;
435
- while ((match = refRegex.exec(text)) !== null) {
436
- references.push({ title: match[1], pages: match[2] });
437
- }
438
-
439
- let formatted = text
440
- .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
441
- .replace(/\*(.*?)\*/g, "<em>$1</em>")
442
- .replace(/### (.*?)\n/g, "<h3>$1</h3>")
443
- .replace(/## (.*?)\n/g, "<h2>$1</h2>")
444
- .replace(/# (.*?)\n/g, "<h1>$1</h1>")
445
- .replace(/\n/g, "<br>")
446
- .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
447
- .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
448
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
449
-
450
- // Add Evidence Box if references found
451
- if (references.length > 0) {
452
- let evidenceHtml = `<div class="evidence-box">
453
- <div class="evidence-title">
454
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
455
- Study Evidence
456
- </div>`;
457
- references.forEach(ref => {
458
- evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
459
- });
460
- evidenceHtml += `</div>`;
461
- formatted += evidenceHtml;
462
- }
463
- return formatted;
464
- }
465
-
466
- function connectWebSocket() {
467
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
468
- ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
469
-
470
- ws.onopen = () => {
471
- console.log('Connected to WebSocket');
472
- connectionStatus.textContent = 'Connected';
473
- connectionStatus.className = 'connection-status connected';
474
- connectionStatus.style.display = 'block';
475
- setTimeout(() => {
476
- connectionStatus.style.display = 'none';
477
- }, 3000);
478
- };
479
-
480
- ws.onclose = () => {
481
- // ...
482
- setTimeout(() => {
483
- reconnectWebSocket();
484
- }, 5000);
485
- };
486
-
487
- ws.onmessage = (event) => {
488
- console.log('Received message:', event.data);
489
-
490
- // ✅ Hide typing indicator on response
491
- typingIndicator.style.display = 'none';
492
-
493
- if (event.data === '[DONE]') {
494
- // Save complete chat interaction with Role
495
- saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
496
- checkUserStatus(); // Update remaining count
497
- return;
498
- }
499
-
500
- // Handle usage limit blocks from backend
501
- if (event.data.includes("Free usage limit reached")) {
502
- typingIndicator.style.display = 'none';
503
- userLimitReached = true;
504
- checkUserStatus();
505
- }
506
-
507
- if (!currentAiMessageElement) {
508
- currentAiMessage = event.data;
509
- addMessage(currentAiMessage, 'ai');
510
- } else {
511
- currentAiMessage += event.data;
512
- const formattedMessage = formatText(currentAiMessage);
513
- currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
514
- }
515
- };
516
-
517
- // ... (existing scroll logic) ...
518
- chatContainer.scrollTop = chatContainer.scrollHeight;
519
- }
520
-
521
- function reconnectWebSocket() {
522
- console.log('Attempting to reconnect...');
523
- connectWebSocket();
524
- loadHistory();
525
- }
526
-
527
- function addMessage(content, type) {
528
- if (type === 'user') {
529
- currentUserMessage = content;
530
- currentAiMessage = '';
531
- currentAiMessageElement = null;
532
- }
533
-
534
- const messageDiv = document.createElement('div');
535
- messageDiv.className = `message ${type}-message`;
536
-
537
- const header = document.createElement('div');
538
- header.className = 'message-header';
539
-
540
- const icon = document.createElement('div');
541
- icon.className = `${type}-icon`;
542
- icon.textContent = type === 'user' ? 'U' : 'L';
543
-
544
- const name = document.createElement('span');
545
- name.textContent = type === 'user' ? 'You' : 'Law Bot';
546
-
547
- header.appendChild(icon);
548
- header.appendChild(name);
549
-
550
- const text = document.createElement('div');
551
- text.className = 'message-content';
552
- text.innerHTML = type === 'ai' ? formatText(content) : content;
553
-
554
- messageDiv.appendChild(header);
555
- messageDiv.appendChild(text);
556
- chatContainer.appendChild(messageDiv);
557
- chatContainer.scrollTop = chatContainer.scrollHeight;
558
-
559
- if (type === 'ai') {
560
- currentAiMessageElement = messageDiv;
561
- }
562
- }
563
-
564
- function sendMessage() {
565
- const message = messageInput.value.trim();
566
- if (!message) return;
567
-
568
- if (userLimitReached) {
569
- addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
570
- return;
571
- }
572
-
573
- if (ws.readyState === WebSocket.OPEN) {
574
- addMessage(message, 'user');
575
- ws.send(message);
576
- messageInput.value = '';
577
-
578
- // Show typing indicator after sending
579
- typingIndicator.style.display = 'block';
580
- chatContainer.scrollTop = chatContainer.scrollHeight;
581
- }
582
- }
583
- const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
584
- if (isAtBottom) {
585
- chatContainer.scrollTop = chatContainer.scrollHeight;
586
- }
587
-
588
- const historyList = document.getElementById('historyList');
589
-
590
- async function loadHistory() {
591
- const token = localStorage.getItem('token');
592
- if (!token) return;
593
-
594
- try {
595
- // Role-locked: fetch filtered history
596
- const response = await fetch(`/api/interactions?role=${activeRole}`, {
597
- headers: { 'Authorization': `Bearer ${token}` }
598
- });
599
- const interactions = await response.json();
600
- historyList.innerHTML = '';
601
- interactions.forEach(item => {
602
- const div = document.createElement('div');
603
- div.className = 'history-item';
604
-
605
- // Readable preview
606
- const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
607
-
608
- div.innerHTML = `
609
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
610
- <span>${preview}</span>
611
- <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
612
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
613
- </button>
614
- `;
615
- div.onclick = () => loadConversation(item.case_id);
616
- historyList.appendChild(div);
617
- });
618
- } catch (err) {
619
- console.error('Failed to load history:', err);
620
- }
621
- }
622
-
623
- async function loadConversation(caseId) {
624
- const token = localStorage.getItem('token');
625
- try {
626
- // Role-locked conversation fetching
627
- const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
628
- headers: { 'Authorization': `Bearer ${token}` }
629
- });
630
- const thread = await response.json();
631
-
632
- currentCaseId = caseId;
633
- chatContainer.innerHTML = ''; // Clear window
634
-
635
- thread.forEach(msg => {
636
- addMessage(msg.query, 'user');
637
- // Render AI Response immediately
638
- const aiMsgDiv = document.createElement('div');
639
- aiMsgDiv.className = 'message ai-message';
640
- aiMsgDiv.innerHTML = `
641
- <div class="message-header">
642
- <div class="ai-icon">L</div>
643
- <span>Law Bot</span>
644
- </div>
645
- <div class="message-content">${formatText(msg.response)}</div>
646
- `;
647
- chatContainer.appendChild(aiMsgDiv);
648
- });
649
- chatContainer.scrollTop = chatContainer.scrollHeight;
650
- } catch (err) {
651
- console.error('Failed to load thread:', err);
652
- }
653
- }
654
-
655
- async function deleteConversation(caseId) {
656
- const token = localStorage.getItem('token');
657
- try {
658
- await fetch(`/api/interactions/${caseId}`, {
659
- method: 'DELETE',
660
- headers: { 'Authorization': `Bearer ${token}` }
661
- });
662
- if (currentCaseId === caseId) {
663
- newChat();
664
- } else {
665
- loadHistory();
666
- }
667
- } catch (err) {
668
- console.error('Failed to delete:', err);
669
- }
670
- }
671
-
672
- function newChat() {
673
- currentCaseId = crypto.randomUUID();
674
- chatContainer.innerHTML = '';
675
- messageInput.value = '';
676
- messageInput.focus();
677
- loadHistory();
678
- }
679
-
680
- // ✅ Modified: Uses Bearer token AND Role
681
- const saveChatInteraction = async (caseId, query, response) => {
682
- const token = localStorage.getItem('token');
683
- if (!token) return;
684
-
685
- try {
686
- await fetch('/api/save-interaction', {
687
- method: 'POST',
688
- headers: {
689
- 'Content-Type': 'application/json',
690
- 'Authorization': `Bearer ${token}`
691
- },
692
- body: JSON.stringify({ caseId, query, response, role: activeRole })
693
- });
694
- loadHistory(); // Refresh sidebar history
695
- } catch (error) {
696
- console.error('Error saving chat:', error);
697
- }
698
- };
699
-
700
- sendButton.addEventListener('click', sendMessage);
701
- messageInput.addEventListener('keypress', (e) => {
702
- if (e.key === 'Enter') {
703
- sendMessage();
704
- }
705
- });
706
-
707
- // Role Switching
708
- document.querySelectorAll('.perspective-option').forEach(opt => {
709
- opt.onclick = () => {
710
- document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
711
- opt.classList.add('active');
712
- activeRole = opt.dataset.role;
713
-
714
- // Refresh Context
715
- newChat();
716
- connectWebSocket(); // Reconnect with new role param
717
- };
718
- });
719
-
720
- // --- User Profile Dropdown ---
721
- const userProfileMenu = document.getElementById('userProfileMenu');
722
- const dropdownMenu = document.getElementById('dropdownMenu');
723
- const headerLogoutBtn = document.getElementById('headerLogoutBtn');
724
-
725
- userProfileMenu.addEventListener('click', (e) => {
726
- e.stopPropagation();
727
- dropdownMenu.classList.toggle('show');
728
- });
729
-
730
- document.addEventListener('click', () => {
731
- if (dropdownMenu.classList.contains('show')) {
732
- dropdownMenu.classList.remove('show');
733
- }
734
- });
735
-
736
- headerLogoutBtn.addEventListener('click', (e) => {
737
- e.preventDefault();
738
- if (window.dashboardEntrance) {
739
- window.dashboardEntrance.logout();
740
- } else {
741
- localStorage.removeItem('token');
742
- window.location.href = '/role';
743
- }
744
- });
745
-
746
- // Init
747
- const themeToggle = document.getElementById('themeToggle');
748
- const body = document.body;
749
- const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
750
- const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
751
-
752
- function updateTheme(isDark) {
753
- if (isDark) {
754
- body.classList.add('dark');
755
- body.classList.remove('light');
756
- themeToggle.innerHTML = moonIcon;
757
- localStorage.setItem('theme', 'dark');
758
- } else {
759
- body.classList.add('light');
760
- body.classList.remove('dark');
761
- themeToggle.innerHTML = sunIcon;
762
- localStorage.setItem('theme', 'light');
763
- }
764
- }
765
-
766
- themeToggle.addEventListener('click', () => {
767
- const isNowDark = !body.classList.contains('dark');
768
- updateTheme(isNowDark);
769
- });
770
-
771
- // Initialize Theme
772
- const savedTheme = localStorage.getItem('theme') || 'dark';
773
- updateTheme(savedTheme === 'dark');
774
-
775
- // Sidebar Collapse Logic
776
- document.getElementById('collapseSidebar').onclick = () => {
777
- document.body.classList.add('sidebar-collapsed');
778
- };
779
- document.getElementById('floatingToggle').onclick = () => {
780
- document.body.classList.remove('sidebar-collapsed');
781
- };
782
- document.getElementById('newChatBtn').onclick = newChat;
783
-
784
- checkUserStatus();
785
- connectWebSocket();
786
- loadHistory();
787
-
788
- </script>
789
- </body>
790
-
791
  </html>
 
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>AI Legal Assistant | Citizen Dashboard</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-dark: #030617;
11
+ --sidebar-bg: #050a24;
12
+ --accent-blue: #3b82f6;
13
+ --accent-purple: #9333ea;
14
+ --text-primary: #f1f5f9;
15
+ --text-dim: #94a3b8;
16
+ --glass-bg: rgba(255, 255, 255, 0.03);
17
+ --glass-border: rgba(255, 255, 255, 0.08);
18
+ }
19
+
20
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
24
+ font-family: 'Poppins', sans-serif;
25
+ }
26
+
27
+ body {
28
+ background-color: var(--bg-dark);
29
+ color: var(--text-primary);
30
+ height: 100vh;
31
+ display: flex;
32
+ overflow: hidden;
33
+ }
34
+
35
+ /* SIDEBAR (ZONE 1) */
36
+ .sidebar {
37
+ width: 280px;
38
+ background: var(--sidebar-bg);
39
+ border-right: 1px solid var(--glass-border);
40
+ display: flex;
41
+ flex-direction: column;
42
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
43
+ z-index: 100;
44
+ }
45
+
46
+ .sidebar-header {
47
+ padding: 30px 20px;
48
+ }
49
+
50
+ .new-chat-btn {
51
+ width: 100%;
52
+ padding: 14px;
53
+ background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
54
+ border: none;
55
+ border-radius: 12px;
56
+ color: white;
57
+ font-weight: 600;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ gap: 10px;
62
+ cursor: pointer;
63
+ transition: all 0.3s ease;
64
+ box-shadow: 0 10px 20px rgba(59, 130, 246, 0.2);
65
+ }
66
+
67
+ .new-chat-btn:hover {
68
+ transform: translateY(-2px);
69
+ box-shadow: 0 15px 25px rgba(59, 130, 246, 0.3);
70
+ }
71
+
72
+ .history-label {
73
+ padding: 0 20px 10px;
74
+ font-size: 0.75rem;
75
+ text-transform: uppercase;
76
+ letter-spacing: 2px;
77
+ color: var(--text-dim);
78
+ font-weight: 700;
79
+ }
80
+
81
+ .history-list {
82
+ flex: 1;
83
+ overflow-y: auto;
84
+ padding: 10px;
85
+ }
86
+
87
+ .history-item {
88
+ padding: 12px 15px;
89
+ border-radius: 10px;
90
+ margin-bottom: 5px;
91
+ cursor: pointer;
92
+ transition: all 0.2s;
93
+ font-size: 0.85rem;
94
+ color: var(--text-dim);
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 10px;
98
+ border: 1px solid transparent;
99
+ white-space: nowrap;
100
+ overflow: hidden;
101
+ text-overflow: ellipsis;
102
+ }
103
+
104
+ .history-item:hover {
105
+ background: var(--glass-bg);
106
+ color: white;
107
+ border-color: var(--glass-border);
108
+ }
109
+
110
+ .item-active {
111
+ background: rgba(59, 130, 246, 0.1) !important;
112
+ color: var(--accent-blue) !important;
113
+ border-color: rgba(59, 130, 246, 0.2) !important;
114
+ }
115
+
116
+ .sidebar-footer {
117
+ padding: 20px;
118
+ border-top: 1px solid var(--glass-border);
119
+ }
120
+
121
+ .user-badge {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 12px;
125
+ padding: 10px;
126
+ background: var(--glass-bg);
127
+ border-radius: 12px;
128
+ }
129
+
130
+ .user-avatar {
131
+ width: 32px;
132
+ height: 32px;
133
+ background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
134
+ border-radius: 8px;
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ font-weight: 700;
139
+ font-size: 0.8rem;
140
+ color: white;
141
+ }
142
+
143
+ /* MAIN CONTENT (ZONE 2) */
144
+ .main-chat {
145
+ flex: 1;
146
+ display: flex;
147
+ flex-direction: column;
148
+ position: relative;
149
+ background: radial-gradient(circle at top right, rgba(59, 130, 246, 0.03), transparent);
150
+ }
151
+
152
+ /* TOP BAR */
153
+ .top-bar {
154
+ height: 70px;
155
+ background: rgba(3, 6, 23, 0.82);
156
+ backdrop-filter: blur(25px);
157
+ -webkit-backdrop-filter: blur(25px);
158
+ border-bottom: 1px solid var(--glass-border);
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: space-between;
162
+ padding: 0 40px;
163
+ z-index: 50;
164
+ }
165
+
166
+ .system-branding {
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 15px;
170
+ }
171
+
172
+ .status-pill {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 8px;
176
+ font-size: 0.75rem;
177
+ background: rgba(34, 197, 94, 0.1);
178
+ color: #22c55e;
179
+ padding: 4px 12px;
180
+ border-radius: 20px;
181
+ font-weight: 600;
182
+ border: 1px solid rgba(34, 197, 94, 0.2);
183
+ }
184
+
185
+ .status-dot {
186
+ width: 6px;
187
+ height: 6px;
188
+ background: #22c55e;
189
+ border-radius: 50%;
190
+ animation: pulse-dot 2s infinite;
191
+ }
192
+
193
+ @keyframes pulse-dot {
194
+ 0% { transform: scale(1); opacity: 1; }
195
+ 50% { transform: scale(1.5); opacity: 0.4; }
196
+ 100% { transform: scale(1); opacity: 1; }
197
+ }
198
+
199
+ /* MESSAGES AREA */
200
+ .messages-container {
201
+ flex: 1;
202
+ overflow-y: auto;
203
+ padding: 40px 10%;
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 30px;
207
+ scroll-behavior: smooth;
208
+ }
209
+
210
+ .message {
211
+ max-width: 850px;
212
+ animation: message-slide 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
213
+ position: relative;
214
+ }
215
+
216
+ @keyframes message-slide {
217
+ from { opacity: 0; transform: translateY(20px); }
218
+ to { opacity: 1; transform: translateY(0); }
219
+ }
220
+
221
+ /* USER MSG */
222
+ .user-msg {
223
+ align-self: flex-end;
224
+ background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
225
+ padding: 16px 24px;
226
+ border-radius: 20px 20px 4px 20px;
227
+ max-width: 70%;
228
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
229
+ font-size: 1rem;
230
+ line-height: 1.6;
231
+ color: white;
232
+ }
233
+
234
+ /* AI MSG */
235
+ .ai-msg {
236
+ align-self: flex-start;
237
+ background: var(--glass-bg);
238
+ border: 1px solid var(--glass-border);
239
+ padding: 30px;
240
+ border-radius: 20px 20px 20px 4px;
241
+ max-width: 85%;
242
+ font-size: 1.05rem;
243
+ line-height: 1.8;
244
+ color: #e2e8f0;
245
+ }
246
+
247
+ .ai-msg h3 {
248
+ color: var(--accent-blue);
249
+ font-size: 1.3rem;
250
+ margin-bottom: 20px;
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 12px;
254
+ font-weight: 600;
255
+ }
256
+
257
+ .ai-msg p {
258
+ margin-bottom: 18px;
259
+ }
260
+
261
+ .ai-msg ul {
262
+ margin: 20px 0;
263
+ list-style: none;
264
+ }
265
+
266
+ .ai-msg li {
267
+ position: relative;
268
+ padding-left: 28px;
269
+ margin-bottom: 15px;
270
+ }
271
+
272
+ .ai-msg li::before {
273
+ content: "→";
274
+ position: absolute;
275
+ left: 0;
276
+ color: var(--accent-blue);
277
+ font-weight: 700;
278
+ }
279
+
280
+ .highlight {
281
+ background: rgba(59, 130, 246, 0.08);
282
+ border-left: 4px solid var(--accent-blue);
283
+ padding: 22px;
284
+ margin: 25px 0;
285
+ border-radius: 0 16px 16px 0;
286
+ font-weight: 500;
287
+ color: #fff;
288
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
289
+ }
290
+
291
+ /* TYPING INDICATOR */
292
+ .typing {
293
+ display: flex;
294
+ gap: 6px;
295
+ padding: 18px 28px;
296
+ background: var(--glass-bg);
297
+ border-radius: 20px;
298
+ width: fit-content;
299
+ margin: 20px 10%;
300
+ border: 1px solid var(--glass-border);
301
+ }
302
+
303
+ .typing span {
304
+ width: 8px;
305
+ height: 8px;
306
+ background: var(--accent-blue);
307
+ border-radius: 50%;
308
+ animation: typing-blink 1.4s infinite ease-in-out;
309
+ }
310
+
311
+ .typing span:nth-child(2) { animation-delay: 0.2s; }
312
+ .typing span:nth-child(3) { animation-delay: 0.4s; }
313
+
314
+ @keyframes typing-blink {
315
+ 0%, 100% { opacity: 0.2; transform: scale(0.8); }
316
+ 50% { opacity: 1; transform: scale(1.1); }
317
+ }
318
+
319
+ /* INPUT AREA */
320
+ .input-area {
321
+ padding: 30px 10%;
322
+ background: linear-gradient(to top, var(--bg-dark) 50%, transparent);
323
+ border-top: 1px solid var(--glass-border);
324
+ z-index: 60;
325
+ }
326
+
327
+ .input-box {
328
+ position: relative;
329
+ background: rgba(255, 255, 255, 0.04);
330
+ border: 1px solid var(--glass-border);
331
+ border-radius: 18px;
332
+ padding: 6px;
333
+ display: flex;
334
+ align-items: center;
335
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
336
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
337
+ }
338
+
339
+ .input-box:focus-within {
340
+ border-color: var(--accent-blue);
341
+ box-shadow: 0 0 30px rgba(59, 130, 246, 0.2);
342
+ background: rgba(255, 255, 255, 0.06);
343
+ }
344
+
345
+ .input-box input {
346
+ flex: 1;
347
+ background: transparent;
348
+ border: none;
349
+ padding: 16px 24px;
350
+ color: white;
351
+ font-size: 1.05rem;
352
+ outline: none;
353
+ font-weight: 400;
354
+ }
355
+
356
+ .input-box input::placeholder { color: var(--text-dim); }
357
+
358
+ .send-btn {
359
+ background: var(--accent-blue);
360
+ color: white;
361
+ border: none;
362
+ width: 50px;
363
+ height: 50px;
364
+ border-radius: 14px;
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: center;
368
+ cursor: pointer;
369
+ transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
370
+ margin-right: 6px;
371
+ }
372
+
373
+ .send-btn:hover {
374
+ background: var(--accent-purple);
375
+ transform: scale(1.1) rotate(5deg);
376
+ }
377
+
378
+ /* SCROLLBAR */
379
+ ::-webkit-scrollbar { width: 6px; }
380
+ ::-webkit-scrollbar-track { background: transparent; }
381
+ ::-webkit-scrollbar-thumb { background: var(--glass-border); border-radius: 10px; }
382
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
383
+
384
+ .hidden { display: none !important; }
385
+
386
+ @media (max-width: 1024px) {
387
+ .sidebar { width: 0; transform: translateX(-100%); position: absolute; }
388
+ .messages-container, .input-area { padding: 30px 5%; }
389
+ .top-bar { padding: 0 20px; }
390
+ }
391
+ </style>
392
+ </head>
393
+ <body>
394
+ <!-- ZONE 1: SIDEBAR -->
395
+ <aside class="sidebar" id="sidebar">
396
+ <div class="sidebar-header">
397
+ <button class="new-chat-btn" id="newChatBtn">
398
+ <span>+</span> New Case
399
+ </button>
400
+ </div>
401
+
402
+ <div class="history-label">Case Archives</div>
403
+ <div class="history-list" id="historyList">
404
+ <!-- Threads pop here -->
405
+ </div>
406
+
407
+ <div class="sidebar-footer">
408
+ <div class="user-badge">
409
+ <div class="user-avatar" id="avatarName">CT</div>
410
+ <div>
411
+ <div id="usernameLabel" style="font-size: 0.85rem; font-weight: 600;">Citizen</div>
412
+ <div style="font-size: 0.7rem; color: var(--text-dim);">Active Consultation</div>
413
+ </div>
414
+ </div>
415
+ <a href="/role" style="display: block; margin-top: 15px; color: var(--text-dim); text-decoration: none; font-size: 0.8rem; text-align: center; font-weight: 500;">← Change Role</a>
416
+ </div>
417
+ </aside>
418
+
419
+ <!-- ZONE 2: MAIN AREA -->
420
+ <main class="main-chat">
421
+ <header class="top-bar">
422
+ <div class="system-branding">
423
+ <span style="font-weight: 700; color: white; tracking-tight">⚖️ AI Legal Assistant</span>
424
+ <div class="status-pill">
425
+ <div class="status-dot"></div>
426
+ System Ready
427
+ </div>
428
+ </div>
429
+ <div style="font-size: 0.85rem; color: var(--text-dim); font-weight: 500;">Citizen Framework v4.0 PRO</div>
430
+ </header>
431
+
432
+ <div id="welcomeScreen" style="position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 40px; z-index: 1;">
433
+ <div style="font-size: 4rem; margin-bottom: 25px; animation: float 6s ease-in-out infinite;">🤖</div>
434
+ <h1 style="font-size: 2.8rem; font-weight: 700; margin-bottom: 12px; letter-spacing: -1px;">Intelligence at your service.</h1>
435
+ <p style="color: var(--text-dim); max-width: 550px; line-height: 1.7; font-size: 1.1rem;">Describe your legal issue, ask about sections, or get procedural guidance. Your Indian Law expert is ready to assist.</p>
436
+ </div>
437
+
438
+ <div class="messages-container" id="chatContainer">
439
+ <!-- Messages stream here -->
440
+ </div>
441
+
442
+ <div class="typing hidden" id="typingIndicator">
443
+ <span></span><span></span><span></span>
444
+ </div>
445
+
446
+ <div class="input-area">
447
+ <div class="input-box">
448
+ <input type="text" id="messageInput" placeholder="How can I help you navigate the law today?" autocomplete="off">
449
+ <button class="send-btn" id="sendButton">
450
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
451
+ </button>
452
+ </div>
453
+ <p style="text-align: center; font-size: 0.75rem; color: var(--text-dim); margin-top: 18px; font-weight: 400; opacity: 0.8;">⚠️ AI can provide guidance based on IPC/BNS/CrPC. For critical judicial matters, always consult a certified advocate.</p>
454
+ </div>
455
+ </main>
456
+
457
+ <script>
458
+ let ws;
459
+ const chatContainer = document.getElementById('chatContainer');
460
+ const messageInput = document.getElementById('messageInput');
461
+ const sendButton = document.getElementById('sendButton');
462
+ const typingIndicator = document.getElementById('typingIndicator');
463
+ const welcomeScreen = document.getElementById('welcomeScreen');
464
+ const historyList = document.getElementById('historyList');
465
+
466
+ let currentAiMessage = '';
467
+ let currentAiMessageElement = null;
468
+ let currentCaseId = crypto.randomUUID();
469
+ const activeRole = 'Citizen';
470
+
471
+ function formatResponse(text) {
472
+ let formatted = text;
473
+
474
+ // Header mapping (### titles)
475
+ formatted = formatted.replace(/### (.*?)\s*(\n|$)/g, "<h3>⚖️ $1</h3>");
476
+
477
+ // Highlight boxes (! at start of line)
478
+ formatted = formatted.replace(/^! (.*?)$/gm, '<div class="highlight">$1</div>');
479
+
480
+ // Bullet points
481
+ formatted = formatted.replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>");
482
+ formatted = formatted.replace(/^\*\s(.*?)$/gm, "<li>$1</li>");
483
+ formatted = formatted.replace(/- (.*?)$/gm, "<li>$1</li>");
484
+ formatted = formatted.replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>");
485
+
486
+ // Bold and Newlines
487
+ formatted = formatted.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
488
+ formatted = formatted.replace(/\n/g, "<br>");
489
+
490
+ return formatted;
491
+ }
492
+
493
+ function connectWS() {
494
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
495
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
496
+
497
+ ws.onmessage = (event) => {
498
+ typingIndicator.classList.add('hidden');
499
+ welcomeScreen.classList.add('hidden');
500
+
501
+ if (event.data === '[DONE]') {
502
+ saveInteraction(currentCaseId, messageInput.value, currentAiMessage);
503
+ return;
504
+ }
505
+
506
+ if (!currentAiMessageElement) {
507
+ currentAiMessage = event.data;
508
+ createNewMessage('ai', currentAiMessage);
509
+ } else {
510
+ currentAiMessage += event.data;
511
+ currentAiMessageElement.innerHTML = formatResponse(currentAiMessage);
512
+ }
513
+ chatContainer.scrollTop = chatContainer.scrollHeight;
514
+ };
515
+
516
+ ws.onclose = () => {
517
+ console.log("WebSocket Disconnected. Reconnecting...");
518
+ setTimeout(connectWS, 2000);
519
+ };
520
+ }
521
+
522
+ function createNewMessage(type, content) {
523
+ welcomeScreen.classList.add('hidden');
524
+ const msgDiv = document.createElement('div');
525
+ msgDiv.className = `message ${type}-msg`;
526
+
527
+ if (type === 'ai') {
528
+ msgDiv.innerHTML = formatResponse(content);
529
+ currentAiMessageElement = msgDiv;
530
+ } else {
531
+ msgDiv.textContent = content;
532
+ }
533
+
534
+ chatContainer.appendChild(msgDiv);
535
+ chatContainer.scrollTop = chatContainer.scrollHeight;
536
+ }
537
+
538
+ function sendMessage() {
539
+ const text = messageInput.value.trim();
540
+ if (!text || ws.readyState !== WebSocket.OPEN) return;
541
+
542
+ createNewMessage('user', text);
543
+ ws.send(text);
544
+
545
+ messageInput.value = '';
546
+ currentAiMessage = '';
547
+ currentAiMessageElement = null;
548
+ typingIndicator.classList.remove('hidden');
549
+ chatContainer.scrollTop = chatContainer.scrollHeight;
550
+ }
551
+
552
+ async function saveInteraction(caseId, query, response) {
553
+ const token = localStorage.getItem('token');
554
+ if (!token) return;
555
+
556
+ try {
557
+ await fetch('/api/save-interaction', {
558
+ method: 'POST',
559
+ headers: {
560
+ 'Content-Type': 'application/json',
561
+ 'Authorization': `Bearer ${token}`
562
+ },
563
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
564
+ });
565
+ loadHistory();
566
+ } catch (err) { console.error(err); }
567
+ }
568
+
569
+ async function loadHistory() {
570
+ const token = localStorage.getItem('token');
571
+ if (!token) return;
572
+
573
+ try {
574
+ const res = await fetch(`/api/interactions?role=${activeRole}`, {
575
+ headers: { 'Authorization': `Bearer ${token}` }
576
+ });
577
+ const data = await res.json();
578
+ historyList.innerHTML = '';
579
+ data.forEach(item => {
580
+ const div = document.createElement('div');
581
+ div.className = 'history-item';
582
+ const preview = item.query.substring(0, 30) + "...";
583
+ div.innerHTML = `<span>📂</span> ${preview}`;
584
+ div.onclick = () => loadThread(item.case_id);
585
+ historyList.appendChild(div);
586
+ });
587
+ } catch (err) { console.error(err); }
588
+ }
589
+
590
+ async function loadThread(caseId) {
591
+ const token = localStorage.getItem('token');
592
+ try {
593
+ const res = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
594
+ headers: { 'Authorization': `Bearer ${token}` }
595
+ });
596
+ const thread = await res.json();
597
+
598
+ currentCaseId = caseId;
599
+ chatContainer.innerHTML = '';
600
+ welcomeScreen.classList.add('hidden');
601
+
602
+ thread.forEach(msg => {
603
+ createNewMessage('user', msg.query);
604
+ createNewMessage('ai', msg.response);
605
+ });
606
+ currentAiMessageElement = null;
607
+ } catch (err) { console.error(err); }
608
+ }
609
+
610
+ async function fetchUserStatus() {
611
+ const token = localStorage.getItem('token');
612
+ if (!token) return;
613
+ try {
614
+ const res = await fetch('/api/user-status', {
615
+ headers: { 'Authorization': `Bearer ${token}` }
616
+ });
617
+ const status = await res.json();
618
+ document.getElementById('usernameLabel').innerText = status.username.charAt(0).toUpperCase() + status.username.slice(1);
619
+ document.getElementById('avatarName').innerText = status.username.substring(0, 2).toUpperCase();
620
+ } catch (err) { console.error(err); }
621
+ }
622
+
623
+ sendButton.onclick = sendMessage;
624
+ messageInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
625
+ document.getElementById('newChatBtn').onclick = () => {
626
+ currentCaseId = crypto.randomUUID();
627
+ chatContainer.innerHTML = '';
628
+ welcomeScreen.classList.remove('hidden');
629
+ };
630
+
631
+ connectWS();
632
+ loadHistory();
633
+ fetchUserStatus();
634
+ </script>
635
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  </html>
src/apps/templates/minor.html CHANGED
@@ -1,897 +1,272 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Law bot</title>
8
- <link rel="stylesheet" href="/static/css/entrance.css">
9
- <script src="/static/js/entrance.js" defer></script>
10
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
11
- <link rel="stylesheet" href="/static/css/modern-chat.css">
12
- <style>
13
- :root {
14
- --primary-purple: #9b87f5;
15
- --dark-purple: #1A1F2C;
16
- --light-purple: #D6BCFA;
17
- --neutral-gray: #8E9196;
18
- --light-gray: #C8C8C9;
19
- --off-white: #eee;
20
- }
21
-
22
- * {
23
- margin: 0;
24
- padding: 0;
25
- box-sizing: border-box;
26
- font-family: 'Inter', sans-serif;
27
- }
28
-
29
- body {
30
- transition: background-color 0.3s, color 0.3s;
31
- }
32
-
33
- body.dark {
34
- color: var(--off-white);
35
- }
36
-
37
- body.light {
38
- color: var(--dark-purple);
39
- }
40
-
41
- .container {
42
- max-width: 800px;
43
- margin: 0 auto;
44
- padding: 2rem;
45
- min-height: 100vh;
46
- display: flex;
47
- flex-direction: column;
48
- }
49
-
50
- .header {
51
- display: flex;
52
- justify-content: space-between;
53
- align-items: center;
54
- margin-bottom: 2rem;
55
- }
56
-
57
- .header-right {
58
- display: flex;
59
- align-items: center;
60
- gap: 1rem;
61
- }
62
-
63
-
64
- .app-title {
65
- margin-left: 40px;
66
- font-size: 2rem;
67
- font-weight: 600;
68
- background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
69
- -webkit-background-clip: text;
70
- background-clip: text;
71
- color: transparent;
72
- }
73
-
74
- .theme-toggle {
75
- background: none;
76
- border: none;
77
- cursor: pointer;
78
- padding: 0.5rem;
79
- color: var(--primary-purple);
80
- transition: opacity 0.3s;
81
- }
82
-
83
- .theme-toggle:hover {
84
- opacity: 0.8;
85
- }
86
-
87
- .theme-toggle svg {
88
- width: 24px;
89
- height: 24px;
90
- }
91
-
92
- .chat-container {
93
- flex-grow: 1;
94
- overflow-y: auto;
95
- margin-bottom: 2rem;
96
- padding: 1rem;
97
- }
98
-
99
- .message {
100
- margin-bottom: 1.5rem;
101
- padding: 1rem;
102
- border-radius: 8px;
103
- animation: fadeIn 0.3s ease-in;
104
- }
105
-
106
- .user-message {
107
- background-color: rgba(155, 135, 245, 0.1);
108
- }
109
-
110
- .ai-message {
111
- background-color: rgba(255, 255, 255, 0.05);
112
- border: 1px solid rgba(200, 200, 201, 0.2);
113
- }
114
-
115
- body.light .user-message {
116
- background-color: var(--off-white);
117
- }
118
-
119
- body.light .ai-message {
120
- background-color: #ffffff;
121
- border: 1px solid var(--light-gray);
122
- }
123
-
124
- .message-header {
125
- display: flex;
126
- align-items: center;
127
- margin-bottom: 0.5rem;
128
- font-weight: 500;
129
- }
130
-
131
-
132
- .sidebar-welcome {
133
- padding: 1.5rem;
134
- border-bottom: 1px solid rgba(200, 200, 201, 0.15);
135
- margin-bottom: 1.5rem;
136
- }
137
-
138
- .user-icon,
139
- .ai-icon {
140
- width: 24px;
141
- height: 24px;
142
- border-radius: 50%;
143
- margin-right: 0.5rem;
144
- display: flex;
145
- align-items: center;
146
- justify-content: center;
147
- font-size: 12px;
148
- }
149
-
150
- .user-icon {
151
- background-color: var(--primary-purple);
152
- color: white;
153
- }
154
-
155
- .ai-icon {
156
- background-color: var(--light-purple);
157
- color: var(--dark-purple);
158
- }
159
-
160
- .input-container {
161
- position: fixed;
162
- bottom: 0;
163
- left: 0;
164
- right: 0;
165
- padding: 1rem;
166
- background-color: var(--input-bg);
167
- border-top: 1px solid var(--sidebar-border);
168
- }
169
-
170
- body.light .input-container {
171
- background-color: #ffffff;
172
- border-top: 1px solid var(--light-gray);
173
- }
174
-
175
- .input-wrapper {
176
- max-width: 800px;
177
- margin: 0 auto;
178
- position: relative;
179
- }
180
-
181
- #messageInput {
182
- width: 100%;
183
- padding: 1rem;
184
- padding-right: 3rem;
185
- border: 1px solid rgba(200, 200, 201, 0.3);
186
- border-radius: 8px;
187
- font-size: 1rem;
188
- outline: none;
189
- transition: border-color 0.3s;
190
- background-color: transparent;
191
- color: inherit;
192
- }
193
-
194
- body.light #messageInput {
195
- border: 1px solid var(--light-gray);
196
- }
197
-
198
- #messageInput:focus {
199
- border-color: var(--primary-purple);
200
- }
201
-
202
- #sendButton {
203
- position: absolute;
204
- right: 0.5rem;
205
- top: 50%;
206
- transform: translateY(-50%);
207
- background: none;
208
- border: none;
209
- cursor: pointer;
210
- padding: 0.5rem;
211
- color: var(--primary-purple);
212
- opacity: 0.8;
213
- transition: opacity 0.3s;
214
- }
215
-
216
- #sendButton:hover {
217
- opacity: 1;
218
- }
219
-
220
- .typing-indicator {
221
- padding: 1rem;
222
- color: var(--neutral-gray);
223
- font-style: italic;
224
- display: none;
225
- }
226
-
227
- .welcome-title {
228
- margin: 6rem 0 0.5rem 0;
229
- text-align: center;
230
- color: var(--neutral-gray);
231
- padding: 1rem;
232
- font-size: 1.24rem;
233
- animation: fadeIn 0.3s ease-in;
234
- }
235
-
236
- .welcome-subtitle {
237
- line-height: 1.6;
238
- font-size: 1.1rem;
239
- color: var(--neutral-gray);
240
- max-width: 900px;
241
- margin: 0 auto;
242
- font-weight: 800;
243
- color: var(--primary-purple);
244
-
245
- }
246
-
247
- .sidebar-welcome {
248
- margin-bottom: 2rem;
249
- padding: 0 1rem;
250
- border-bottom: none;
251
- }
252
-
253
- .role-selection-link {
254
- display: inline-flex;
255
- align-items: center;
256
- gap: initial;
257
- color: var(--light-purple);
258
- text-decoration: none;
259
- margin-top: auto;
260
- padding: auto;
261
- transition: opacity 0.3s ease;
262
- position: absolute;
263
- top: 550px;
264
- left: 30px;
265
-
266
- }
267
-
268
- .role-selection-link:hover {
269
- opacity: 0.8;
270
- }
271
-
272
- .role-selection-link svg {
273
- width: 16px;
274
- height: 16px;
275
- stroke: currentColor;
276
- }
277
-
278
- /* Add this media query for mobile responsiveness */
279
- @media (max-width: 480px) {
280
- .welcome-message p {
281
- font-size: 1rem;
282
- padding: 0 1rem;
283
- }
284
- }
285
-
286
- @keyframes fadeIn {
287
- from {
288
- opacity: 0;
289
- transform: translateY(10px);
290
- }
291
-
292
- to {
293
- opacity: 1;
294
- transform: translateY(0);
295
- }
296
- }
297
-
298
- .connection-status {
299
- position: fixed;
300
- top: 1rem;
301
- right: 1rem;
302
- padding: 0.5rem 1rem;
303
- border-radius: 4px;
304
- font-size: 0.875rem;
305
- display: none;
306
- }
307
-
308
- .connection-status.connected {
309
- background-color: #4ade80;
310
- color: white;
311
- }
312
-
313
- .connection-status.disconnected {
314
- background-color: #ef4444;
315
- color: white;
316
- }
317
-
318
- /* Styling for formatted text */
319
- .message-content {
320
- line-height: 1.5;
321
- }
322
-
323
- .message-content h1,
324
- .message-content h2,
325
- .message-content h3 {
326
- margin: 1rem 0 0.5rem;
327
- font-weight: 600;
328
- }
329
-
330
- .message-content h1 {
331
- font-size: 1.5rem;
332
- }
333
-
334
- .message-content h2 {
335
- font-size: 1.25rem;
336
- }
337
-
338
- .message-content h3 {
339
- font-size: 1.1rem;
340
- }
341
-
342
- .message-content ul,
343
- .message-content ol {
344
- margin-left: 1.5rem;
345
- margin-bottom: 1rem;
346
- }
347
-
348
- .message-content a {
349
- color: var(--primary-purple);
350
- text-decoration: none;
351
- }
352
-
353
- .message-content a:hover {
354
- text-decoration: underline;
355
- }
356
-
357
- .usage-indicator {
358
- padding: 4px 12px;
359
- background: rgba(155, 135, 245, 0.1);
360
- border: 1px solid rgba(155, 135, 245, 0.3);
361
- border-radius: 20px;
362
- font-size: 0.8rem;
363
- color: var(--primary-purple);
364
- font-weight: 500;
365
- display: none;
366
- /* Hidden by default for Admins */
367
- }
368
- </style>
369
- </head>
370
-
371
- <button class="sidebar-floating-toggle" id="floatingToggle">
372
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
373
- <path d="M3 12h18M3 6h18M3 18h18" />
374
- </svg>
375
- </button>
376
-
377
- <aside class="sidebar" id="sidebar">
378
- <div class="sidebar-header">
379
- <button class="new-chat-btn" id="newChatBtn">
380
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
381
- <path d="M12 5v14M5 12h14" />
382
- </svg>
383
- New Chat
384
- </button>
385
- <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
386
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
387
- <path d="M15 18l-6-6 6-6" />
388
- </svg>
389
- </button>
390
- </div>
391
- <div class="sidebar-title">Recent Learning</div>
392
- <div class="history-list" id="historyList">
393
- <!-- Recent prompts will appear here -->
394
- </div>
395
-
396
- <div class="perspective-container" style="display: none;">
397
- <div class="sidebar-title">Answer Perspective</div>
398
- <div class="perspective-list">
399
- <div class="perspective-option active" data-role="Minor">🧒 Minor</div>
400
- </div>
401
- </div>
402
-
403
-
404
- </aside>
405
-
406
- <!-- Existing messages will be added here dynamically -->
407
- <div class="container">
408
- <header class="header">
409
- <h1 class="app-title">Law Bot (Minor)</h1>
410
- <div class="header-right">
411
- <div class="usage-indicator" id="usageIndicator">
412
- Questions Remaining: <span id="remainingCount">--</span>
413
- </div>
414
- <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
415
- <!-- Icon injected by JS -->
416
- </button>
417
- <div class="user-profile-menu" id="userProfileMenu">
418
- <div class="profile-icon-btn">
419
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
420
- <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
421
- <circle cx="12" cy="7" r="4"></circle>
422
- </svg>
423
- </div>
424
- <div class="dropdown-menu" id="dropdownMenu">
425
- <div class="dropdown-item role-info">
426
- Role: Minor
427
- </div>
428
- <a href="#" class="dropdown-item logout-action" id="headerLogoutBtn">
429
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
430
- stroke-width="2">
431
- <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
432
- <polyline points="16 17 21 12 16 7"></polyline>
433
- <line x1="21" y1="12" x2="9" y2="12"></line>
434
- </svg>
435
- Logout
436
- </a>
437
- </div>
438
- </div>
439
- </div>
440
- </header>
441
- <div class="welcome-title">Welcome, Future Leader! 👦 </div>
442
- <div class="welcome-subtitle">Let’s learn about laws in a fun, easy way! What would you like to understand
443
- today?</div>
444
-
445
-
446
-
447
- <div class="chat-container" id="chatContainer">
448
- <div class="typing-indicator" id="typingIndicator">
449
- <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--minor-color)" stroke-width="2">
450
- <circle cx="12" cy="12" r="10" />
451
- <path d="M8 14s1.5 2 4 2 4-2 4-2" />
452
- <line x1="9" y1="9" x2="9.01" y2="9" />
453
- <line x1="15" y1="9" x2="15.01" y2="9" />
454
- </svg>
455
- <span>Searching simple answers for you...</span>
456
- </div>
457
- </div>
458
- <div class="input-container">
459
- <div class="input-wrapper">
460
- <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
461
- <button id="sendButton">
462
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
463
- stroke-linecap="round" stroke-linejoin="round">
464
- <line x1="22" y1="2" x2="11" y2="13"></line>
465
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
466
- </svg>
467
- </button>
468
- </div>
469
- </div>
470
- <footer class="professional-footer">
471
- &copy; 2026 Law Bot AI. All Rights Reserved.
472
- <br>
473
- Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
474
- </footer>
475
- </div>
476
- <div class="connection-status" id="connectionStatus"></div>
477
-
478
- <script>
479
- let ws;
480
- const chatContainer = document.getElementById('chatContainer');
481
- const messageInput = document.getElementById('messageInput');
482
- const sendButton = document.getElementById('sendButton');
483
- const typingIndicator = document.getElementById('typingIndicator');
484
- const connectionStatus = document.getElementById('connectionStatus');
485
- const sidebar = document.getElementById('sidebar');
486
-
487
- let currentUserMessage = '';
488
- let currentAiMessage = '';
489
- let currentAiMessageElement = null;
490
- let currentCaseId = crypto.randomUUID();
491
- const activeRole = 'Minor'; // LOCKED ROLE
492
- let userLimitReached = false;
493
-
494
- async function checkUserStatus() {
495
- const token = localStorage.getItem('token');
496
- if (!token) return;
497
-
498
- try {
499
- const response = await fetch('/api/user-status', {
500
- headers: { 'Authorization': `Bearer ${token}` }
501
- });
502
- const status = await response.json();
503
-
504
- const indicator = document.getElementById('usageIndicator');
505
- if (status.is_admin) {
506
- indicator.style.display = 'none';
507
- } else {
508
- indicator.style.display = 'block';
509
- const remaining = Math.max(0, 2 - status.question_count);
510
- document.getElementById('remainingCount').innerText = remaining;
511
- if (remaining <= 0) {
512
- userLimitReached = true;
513
- }
514
- }
515
- } catch (err) {
516
- console.error('Failed to fetch user status:', err);
517
- }
518
- }
519
-
520
- function formatText(text) {
521
- // Extract references for Evidence Box
522
- const references = [];
523
- const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
524
- let match;
525
- while ((match = refRegex.exec(text)) !== null) {
526
- references.push({ title: match[1], pages: match[2] });
527
- }
528
-
529
- let formatted = text
530
- .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
531
- .replace(/\*(.*?)\*/g, "<em>$1</em>")
532
- .replace(/### (.*?)\n/g, "<h3>$1</h3>")
533
- .replace(/## (.*?)\n/g, "<h2>$1</h2>")
534
- .replace(/# (.*?)\n/g, "<h1>$1</h1>")
535
- .replace(/\n/g, "<br>")
536
- .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
537
- .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
538
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
539
-
540
- // Add Evidence Box if references found
541
- if (references.length > 0) {
542
- let evidenceHtml = `<div class="evidence-box">
543
- <div class="evidence-title">
544
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
545
- Where I found this
546
- </div>`;
547
- references.forEach(ref => {
548
- evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
549
- });
550
- evidenceHtml += `</div>`;
551
- formatted += evidenceHtml;
552
- }
553
- return formatted;
554
- }
555
-
556
- function connectWebSocket() {
557
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
558
- ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
559
-
560
- ws.onopen = () => {
561
- console.log('Connected to WebSocket');
562
- connectionStatus.textContent = 'Connected';
563
- connectionStatus.className = 'connection-status connected';
564
- connectionStatus.style.display = 'block';
565
- setTimeout(() => {
566
- connectionStatus.style.display = 'none';
567
- }, 3000);
568
- };
569
-
570
- ws.onclose = () => {
571
- console.log('Disconnected from WebSocket');
572
- connectionStatus.textContent = 'Trying to reconnect...';
573
- connectionStatus.className = 'connection-status disconnected';
574
- connectionStatus.style.display = 'block';
575
-
576
- // Exponential backoff for reconnection
577
- const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
578
- window._reconnectCount = (window._reconnectCount || 0) + 1;
579
-
580
- setTimeout(() => {
581
- reconnectWebSocket();
582
- loadHistory();
583
- }, backoff);
584
- };
585
-
586
- ws.onerror = (error) => {
587
- console.error('WebSocket Error:', error);
588
- };
589
-
590
-
591
- ws.onmessage = (event) => {
592
- console.log('Received message:', event.data);
593
-
594
- // ✅ Hide typing indicator on response
595
- typingIndicator.style.display = 'none';
596
-
597
- if (event.data === '[DONE]') {
598
- // Save complete chat interaction
599
- saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
600
- checkUserStatus(); // Update remaining count
601
- return;
602
- }
603
-
604
- // Handle usage limit blocks from backend
605
- if (event.data.includes("Free usage limit reached")) {
606
- typingIndicator.style.display = 'none';
607
- userLimitReached = true;
608
- checkUserStatus();
609
- }
610
-
611
- if (!currentAiMessageElement) {
612
- currentAiMessage = event.data;
613
- addMessage(currentAiMessage, 'ai');
614
- } else {
615
- currentAiMessage += event.data;
616
- const formattedMessage = formatText(currentAiMessage);
617
- currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
618
- }
619
- };
620
-
621
- // ✅ Scroll fix (move into sendMessage function if needed)
622
- chatContainer.scrollTop = chatContainer.scrollHeight;
623
- }
624
-
625
- function reconnectWebSocket() {
626
- console.log('Attempting to reconnect...');
627
- connectWebSocket();
628
- loadHistory();
629
- }
630
-
631
- function addMessage(content, type) {
632
- if (type === 'user') {
633
- currentUserMessage = content;
634
- currentAiMessage = '';
635
- currentAiMessageElement = null;
636
- }
637
-
638
- const messageDiv = document.createElement('div');
639
- messageDiv.className = `message ${type}-message`;
640
-
641
- const header = document.createElement('div');
642
- header.className = 'message-header';
643
-
644
- const icon = document.createElement('div');
645
- icon.className = `${type}-icon`;
646
- icon.textContent = type === 'user' ? 'U' : 'L';
647
-
648
- const name = document.createElement('span');
649
- name.textContent = type === 'user' ? 'You' : 'Law Bot';
650
-
651
- header.appendChild(icon);
652
- header.appendChild(name);
653
-
654
- const text = document.createElement('div');
655
- text.className = 'message-content';
656
- text.innerHTML = type === 'ai' ? formatText(content) : content;
657
-
658
- messageDiv.appendChild(header);
659
- messageDiv.appendChild(text);
660
- chatContainer.appendChild(messageDiv);
661
- chatContainer.scrollTop = chatContainer.scrollHeight;
662
-
663
- if (type === 'ai') {
664
- currentAiMessageElement = messageDiv;
665
- }
666
- }
667
-
668
- function sendMessage() {
669
- const message = messageInput.value.trim();
670
- if (!message) return;
671
-
672
- if (userLimitReached) {
673
- addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
674
- return;
675
- }
676
-
677
- if (!activeRole) {
678
- addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
679
- return;
680
- }
681
-
682
- if (ws.readyState === WebSocket.OPEN) {
683
- addMessage(message, 'user');
684
- ws.send(message);
685
- messageInput.value = '';
686
-
687
- // ✅ Show typing indicator after sending
688
- typingIndicator.style.display = 'block';
689
- chatContainer.scrollTop = chatContainer.scrollHeight;
690
- }
691
- }
692
- const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
693
- if (isAtBottom) {
694
- chatContainer.scrollTop = chatContainer.scrollHeight;
695
- }
696
-
697
-
698
- const historyList = document.getElementById('historyList');
699
-
700
- async function loadHistory() {
701
- const token = localStorage.getItem('token');
702
- if (!token) return;
703
-
704
- try {
705
- const response = await fetch(`/api/interactions?role=${activeRole}`, {
706
- headers: { 'Authorization': `Bearer ${token}` }
707
- });
708
- const interactions = await response.json();
709
- historyList.innerHTML = '';
710
- interactions.forEach(item => {
711
- const div = document.createElement('div');
712
- div.className = 'history-item';
713
-
714
- // Readable preview: first sentence or truncated query
715
- const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
716
-
717
- div.innerHTML = `
718
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
719
- <span>${preview}</span>
720
- <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
721
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
722
- </button>
723
- `;
724
- div.onclick = () => loadConversation(item.case_id);
725
- historyList.appendChild(div);
726
- });
727
- } catch (err) {
728
- console.error('Failed to load history:', err);
729
- }
730
- }
731
-
732
- async function loadConversation(caseId) {
733
- const token = localStorage.getItem('token');
734
- try {
735
- const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
736
- headers: { 'Authorization': `Bearer ${token}` }
737
- });
738
- const thread = await response.json();
739
-
740
- currentCaseId = caseId;
741
- chatContainer.innerHTML = ''; // Clear window
742
-
743
- thread.forEach(msg => {
744
- addMessage(msg.query, 'user');
745
- // Render AI Response immediately
746
- const aiMsgDiv = document.createElement('div');
747
- aiMsgDiv.className = 'message ai-message';
748
- aiMsgDiv.innerHTML = `
749
- <div class="message-header">
750
- <div class="ai-icon">L</div>
751
- <span>Law Bot</span>
752
- </div>
753
- <div class="message-content">${formatText(msg.response)}</div>
754
- `;
755
- chatContainer.appendChild(aiMsgDiv);
756
- });
757
- chatContainer.scrollTop = chatContainer.scrollHeight;
758
- } catch (err) {
759
- console.error('Failed to load thread:', err);
760
- }
761
- }
762
-
763
- async function deleteConversation(caseId) {
764
- const token = localStorage.getItem('token');
765
- try {
766
- await fetch(`/api/interactions/${caseId}`, {
767
- method: 'DELETE',
768
- headers: { 'Authorization': `Bearer ${token}` }
769
- });
770
- if (currentCaseId === caseId) {
771
- newChat();
772
- } else {
773
- loadHistory();
774
- }
775
- } catch (err) {
776
- console.error('Failed to delete:', err);
777
- }
778
- }
779
-
780
- function newChat() {
781
- currentCaseId = crypto.randomUUID();
782
- chatContainer.innerHTML = '';
783
- messageInput.value = '';
784
- messageInput.focus();
785
- loadHistory();
786
- }
787
-
788
- // ✅ Modified: Uses Bearer token
789
- const saveChatInteraction = async (caseId, query, response) => {
790
- const token = localStorage.getItem('token');
791
- if (!token) return;
792
-
793
- try {
794
- await fetch('/api/save-interaction', {
795
- method: 'POST',
796
- headers: {
797
- 'Content-Type': 'application/json',
798
- 'Authorization': `Bearer ${token}`
799
- },
800
- body: JSON.stringify({ caseId, query, response, role: activeRole })
801
- });
802
- loadHistory(); // Refresh sidebar history
803
- } catch (error) {
804
- console.error('Error saving chat:', error);
805
- }
806
- };
807
-
808
- sendButton.addEventListener('click', sendMessage);
809
- messageInput.addEventListener('keypress', (e) => {
810
- if (e.key === 'Enter') {
811
- sendMessage();
812
- }
813
- });
814
-
815
- // --- User Profile Dropdown ---
816
- const userProfileMenu = document.getElementById('userProfileMenu');
817
- const dropdownMenu = document.getElementById('dropdownMenu');
818
- const headerLogoutBtn = document.getElementById('headerLogoutBtn');
819
-
820
- userProfileMenu.addEventListener('click', (e) => {
821
- e.stopPropagation();
822
- dropdownMenu.classList.toggle('show');
823
- });
824
-
825
- document.addEventListener('click', () => {
826
- if (dropdownMenu.classList.contains('show')) {
827
- dropdownMenu.classList.remove('show');
828
- }
829
- });
830
-
831
- headerLogoutBtn.addEventListener('click', (e) => {
832
- e.preventDefault();
833
- if (window.dashboardEntrance) {
834
- window.dashboardEntrance.logout();
835
- } else {
836
- localStorage.removeItem('token');
837
- window.location.href = '/role';
838
- }
839
- });
840
-
841
- // Theme toggle
842
- const themeToggle = document.getElementById('themeToggle');
843
- const body = document.body;
844
- const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
845
- const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
846
-
847
- function updateTheme(isDark) {
848
- if (isDark) {
849
- body.classList.add('dark');
850
- body.classList.remove('light');
851
- themeToggle.innerHTML = moonIcon;
852
- localStorage.setItem('theme', 'dark');
853
- } else {
854
- body.classList.add('light');
855
- body.classList.remove('dark');
856
- themeToggle.innerHTML = sunIcon;
857
- localStorage.setItem('theme', 'light');
858
- }
859
- }
860
-
861
- themeToggle.addEventListener('click', () => {
862
- const isNowDark = !body.classList.contains('dark');
863
- updateTheme(isNowDark);
864
- });
865
-
866
- // Initialize Theme
867
- const savedTheme = localStorage.getItem('theme') || 'dark';
868
- updateTheme(savedTheme === 'dark');
869
-
870
- // Sidebar Collapse Logic
871
- document.getElementById('collapseSidebar').onclick = () => {
872
- document.body.classList.add('sidebar-collapsed');
873
- };
874
- document.getElementById('floatingToggle').onclick = () => {
875
- document.body.classList.remove('sidebar-collapsed');
876
- };
877
- document.getElementById('newChatBtn').onclick = newChat;
878
-
879
- // Perspective Selection
880
- document.querySelectorAll('.perspective-option').forEach(opt => {
881
- opt.onclick = () => {
882
- document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
883
- opt.classList.add('active');
884
- activeRole = opt.dataset.role;
885
- localStorage.setItem('activeRole', activeRole);
886
- // No session reset needed, just role update for next message
887
- };
888
- });
889
-
890
- checkUserStatus();
891
- connectWebSocket();
892
- loadHistory();
893
-
894
- </script>
895
- </body>
896
-
897
  </html>
 
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>AI Legal Assistant | Friendly Law Learning</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-dark: #030617;
11
+ --sidebar-bg: #050a24;
12
+ --accent-green: #10b981;
13
+ --accent-teal: #14b8a6;
14
+ --text-primary: #f1f5f9;
15
+ --text-dim: #94a3b8;
16
+ --glass-bg: rgba(255, 255, 255, 0.03);
17
+ --glass-border: rgba(255, 255, 255, 0.08);
18
+ }
19
+
20
+ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Poppins', sans-serif; }
21
+ body { background-color: var(--bg-dark); color: var(--text-primary); height: 100vh; display: flex; overflow: hidden; }
22
+
23
+ .sidebar { width: 280px; background: var(--sidebar-bg); border-right: 1px solid var(--glass-border); display: flex; flex-direction: column; z-index: 100; }
24
+ .sidebar-header { padding: 30px 20px; }
25
+ .new-chat-btn {
26
+ width: 100%; padding: 14px; background: linear-gradient(135deg, var(--accent-green), var(--accent-teal));
27
+ border: none; border-radius: 12px; color: white; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 10px; cursor: pointer; transition: 0.3s;
28
+ box-shadow: 0 10px 20px rgba(16, 185, 129, 0.2);
29
+ }
30
+ .new-chat-btn:hover { transform: translateY(-2px); box-shadow: 0 15px 25px rgba(16, 185, 129, 0.3); }
31
+
32
+ .history-label { padding: 0 20px 10px; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 2px; color: var(--text-dim); font-weight: 700; }
33
+ .history-list { flex: 1; overflow-y: auto; padding: 10px; }
34
+ .history-item { padding: 12px 15px; border-radius: 10px; margin-bottom: 5px; cursor: pointer; transition: 0.2s; font-size: 0.85rem; color: var(--text-dim); display: flex; align-items: center; gap: 10px; border: 1px solid transparent; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
35
+ .history-item:hover { background: var(--glass-bg); color: white; border-color: var(--glass-border); }
36
+
37
+ .sidebar-footer { padding: 20px; border-top: 1px solid var(--glass-border); }
38
+ .user-badge { display: flex; align-items: center; gap: 12px; padding: 10px; background: var(--glass-bg); border-radius: 12px; }
39
+ .user-avatar { width: 32px; height: 32px; background: var(--accent-green); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: 700; color: white; font-size: 0.8rem; }
40
+
41
+ .main-chat { flex: 1; display: flex; flex-direction: column; position: relative; background: radial-gradient(circle at top right, rgba(16, 185, 129, 0.03), transparent); }
42
+ .top-bar { height: 70px; background: rgba(3, 6, 23, 0.82); backdrop-filter: blur(25px); border-bottom: 1px solid var(--glass-border); display: flex; align-items: center; justify-content: space-between; padding: 0 40px; z-index: 50; }
43
+ .status-pill { display: flex; align-items: center; gap: 8px; font-size: 0.75rem; background: rgba(16, 185, 129, 0.1); color: #34d399; padding: 4px 12px; border-radius: 20px; font-weight: 600; border: 1px solid rgba(16, 185, 129, 0.2); }
44
+ .status-dot { width: 6px; height: 6px; background: #34d399; border-radius: 50%; animation: pulse-dot 2s infinite; }
45
+ @keyframes pulse-dot { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.4; } }
46
+
47
+ .messages-container { flex: 1; overflow-y: auto; padding: 40px 10%; display: flex; flex-direction: column; gap: 30px; scroll-behavior: smooth; }
48
+ .message { max-width: 850px; animation: message-slide 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; }
49
+ @keyframes message-slide { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
50
+
51
+ .user-msg { align-self: flex-end; background: linear-gradient(135deg, var(--accent-green), var(--accent-teal)); padding: 16px 24px; border-radius: 20px 20px 4px 20px; color: white; max-width: 70%; line-height: 1.6; }
52
+ .ai-msg { align-self: flex-start; background: var(--glass-bg); border: 1px solid var(--glass-border); padding: 30px; border-radius: 20px 20px 20px 4px; max-width: 85%; line-height: 1.8; color: #e2e8f0; }
53
+ .ai-msg h3 { color: #34d399; font-size: 1.3rem; margin-bottom: 20px; font-weight: 600; display: flex; align-items: center; gap: 12px; }
54
+ .ai-msg li { position: relative; padding-left: 28px; margin-bottom: 15px; }
55
+ .ai-msg li::before { content: "✨"; position: absolute; left: 0; font-size: 0.9rem; }
56
+ .highlight { background: rgba(16, 185, 129, 0.08); border-left: 4px solid var(--accent-green); padding: 22px; margin: 25px 0; border-radius: 0 16px 16px 0; font-weight: 500; color: #fff; }
57
+
58
+ .typing { display: flex; gap: 6px; padding: 18px 28px; background: var(--glass-bg); border-radius: 20px; width: fit-content; margin: 20px 10%; border: 1px solid var(--glass-border); }
59
+ .typing span { width: 8px; height: 8px; background: var(--accent-green); border-radius: 50%; animation: typing-blink 1.4s infinite ease-in-out; }
60
+ @keyframes typing-blink { 0%, 100% { opacity: 0.2; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.1); } }
61
+
62
+ .input-area { padding: 30px 10%; background: linear-gradient(to top, var(--bg-dark) 50%, transparent); border-top: 1px solid var(--glass-border); z-index: 60; }
63
+ .input-box { background: rgba(255, 255, 255, 0.04); border: 1px solid var(--glass-border); border-radius: 18px; padding: 6px; display: flex; align-items: center; transition: 0.3s; }
64
+ .input-box:focus-within { border-color: var(--accent-green); box-shadow: 0 0 30px rgba(16, 185, 129, 0.2); }
65
+ .input-box input { flex: 1; background: transparent; border: none; padding: 16px 24px; color: white; font-size: 1.05rem; outline: none; }
66
+ .send-btn { background: var(--accent-green); color: white; border: none; width: 50px; height: 50px; border-radius: 14px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: 0.3s; margin-right: 6px; }
67
+
68
+ .hidden { display: none !important; }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <aside class="sidebar">
73
+ <div class="sidebar-header">
74
+ <button class="new-chat-btn" id="newChatBtn"><span>+</span> Start New Chat</button>
75
+ </div>
76
+ <div class="history-label">My Learning Journey</div>
77
+ <div class="history-list" id="historyList"></div>
78
+ <div class="sidebar-footer">
79
+ <div class="user-badge">
80
+ <div class="user-avatar" id="avatarName">MN</div>
81
+ <div>
82
+ <div id="usernameLabel" style="font-size: 0.85rem; font-weight: 600;">Future Leader</div>
83
+ <div style="font-size: 0.7rem; color: var(--text-dim);">Safe Safe Access</div>
84
+ </div>
85
+ </div>
86
+ <div style="display: flex; flex-direction: column; gap: 8px; margin-top: 15px;">
87
+ <a href="/role" style="color: var(--text-dim); text-decoration: none; font-size: 0.8rem; text-align: center;">← Change Role</a>
88
+ </div>
89
+ </div>
90
+ </aside>
91
+
92
+ <main class="main-chat">
93
+ <header class="top-bar">
94
+ <div class="system-branding">
95
+ <span style="font-weight: 700; color: white;">⚖️ Law Bot (Fun Mode)</span>
96
+ <div class="status-pill"><div class="status-dot"></div>Learning Buddy Active</div>
97
+ </div>
98
+ <div style="font-size: 0.85rem; color: var(--text-dim); font-weight: 500;">Easy Explainer v4.0</div>
99
+ </header>
100
+
101
+ <div id="welcomeScreen" style="position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 40px; z-index: 1;">
102
+ <div style="font-size: 4rem; margin-bottom: 25px;">👦</div>
103
+ <h1 style="font-size: 2.8rem; font-weight: 700; margin-bottom: 12px; letter-spacing: -1px;">Law made simple.</h1>
104
+ <p style="color: var(--text-dim); max-width: 550px; line-height: 1.7; font-size: 1.1rem;">Let’s talk about your rights in a fun, easy way! Ask me anything about school, safety, or basic laws.</p>
105
+ </div>
106
+
107
+ <div class="messages-container" id="chatContainer"></div>
108
+ <div class="typing hidden" id="typingIndicator"><span></span><span></span><span></span></div>
109
+
110
+ <div class="input-area">
111
+ <div class="input-box">
112
+ <input type="text" id="messageInput" placeholder="Ask your buddy a legal question..." autocomplete="off">
113
+ <button class="send-btn" id="sendButton">
114
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
115
+ </button>
116
+ </div>
117
+ </div>
118
+ </main>
119
+
120
+ <script>
121
+ let ws;
122
+ const chatContainer = document.getElementById('chatContainer');
123
+ const messageInput = document.getElementById('messageInput');
124
+ const sendButton = document.getElementById('sendButton');
125
+ const typingIndicator = document.getElementById('typingIndicator');
126
+ const welcomeScreen = document.getElementById('welcomeScreen');
127
+ const historyList = document.getElementById('historyList');
128
+
129
+ let currentAiMessage = '';
130
+ let currentAiMessageElement = null;
131
+ let currentCaseId = crypto.randomUUID();
132
+ const activeRole = 'Minor';
133
+
134
+ function formatResponse(text) {
135
+ let formatted = text;
136
+ formatted = formatted.replace(/### (.*?)\s*(\n|$)/g, "<h3>✨ $1</h3>");
137
+ formatted = formatted.replace(/^! (.*?)$/gm, '<div class="highlight">$1</div>');
138
+ formatted = formatted.replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>");
139
+ formatted = formatted.replace(/^\*\s(.*?)$/gm, "<li>$1</li>");
140
+ formatted = formatted.replace(/- (.*?)$/gm, "<li>$1</li>");
141
+ formatted = formatted.replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>");
142
+ formatted = formatted.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
143
+ formatted = formatted.replace(/\n/g, "<br>");
144
+ return formatted;
145
+ }
146
+
147
+ function connectWS() {
148
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
149
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
150
+ ws.onmessage = (event) => {
151
+ typingIndicator.classList.add('hidden');
152
+ welcomeScreen.classList.add('hidden');
153
+ if (event.data === '[DONE]') {
154
+ saveInteraction(currentCaseId, messageInput.value, currentAiMessage);
155
+ return;
156
+ }
157
+ if (!currentAiMessageElement) {
158
+ currentAiMessage = event.data;
159
+ createNewMessage('ai', currentAiMessage);
160
+ } else {
161
+ currentAiMessage += event.data;
162
+ currentAiMessageElement.innerHTML = formatResponse(currentAiMessage);
163
+ }
164
+ chatContainer.scrollTop = chatContainer.scrollHeight;
165
+ };
166
+ ws.onclose = () => setTimeout(connectWS, 2000);
167
+ }
168
+
169
+ function createNewMessage(type, content) {
170
+ welcomeScreen.classList.add('hidden');
171
+ const msgDiv = document.createElement('div');
172
+ msgDiv.className = `message ${type}-msg`;
173
+ if (type === 'ai') {
174
+ msgDiv.innerHTML = formatResponse(content);
175
+ currentAiMessageElement = msgDiv;
176
+ } else {
177
+ msgDiv.textContent = content;
178
+ }
179
+ chatContainer.appendChild(msgDiv);
180
+ chatContainer.scrollTop = chatContainer.scrollHeight;
181
+ }
182
+
183
+ function sendMessage() {
184
+ const text = messageInput.value.trim();
185
+ if (!text || ws.readyState !== WebSocket.OPEN) return;
186
+ createNewMessage('user', text);
187
+ ws.send(text);
188
+ messageInput.value = '';
189
+ currentAiMessage = '';
190
+ currentAiMessageElement = null;
191
+ typingIndicator.classList.remove('hidden');
192
+ chatContainer.scrollTop = chatContainer.scrollHeight;
193
+ }
194
+
195
+ async function saveInteraction(caseId, query, response) {
196
+ const token = localStorage.getItem('token');
197
+ if (!token) return;
198
+ try {
199
+ await fetch('/api/save-interaction', {
200
+ method: 'POST',
201
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
202
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
203
+ });
204
+ loadHistory();
205
+ } catch (err) { console.error(err); }
206
+ }
207
+
208
+ async function loadHistory() {
209
+ const token = localStorage.getItem('token');
210
+ if (!token) return;
211
+ try {
212
+ const res = await fetch(`/api/interactions?role=${activeRole}`, {
213
+ headers: { 'Authorization': `Bearer ${token}` }
214
+ });
215
+ const data = await res.json();
216
+ historyList.innerHTML = '';
217
+ data.forEach(item => {
218
+ const div = document.createElement('div');
219
+ div.className = 'history-item';
220
+ const preview = item.query.substring(0, 30) + "...";
221
+ div.innerHTML = `<span>🌈</span> ${preview}`;
222
+ div.onclick = () => loadThread(item.case_id);
223
+ historyList.appendChild(div);
224
+ });
225
+ } catch (err) { console.error(err); }
226
+ }
227
+
228
+ async function loadThread(caseId) {
229
+ const token = localStorage.getItem('token');
230
+ try {
231
+ const res = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
232
+ headers: { 'Authorization': `Bearer ${token}` }
233
+ });
234
+ const thread = await res.json();
235
+ currentCaseId = caseId;
236
+ chatContainer.innerHTML = '';
237
+ welcomeScreen.classList.add('hidden');
238
+ thread.forEach(msg => {
239
+ createNewMessage('user', msg.query);
240
+ createNewMessage('ai', msg.response);
241
+ });
242
+ currentAiMessageElement = null;
243
+ } catch (err) { console.error(err); }
244
+ }
245
+
246
+ async function fetchUserStatus() {
247
+ const token = localStorage.getItem('token');
248
+ if (!token) return;
249
+ try {
250
+ const res = await fetch('/api/user-status', {
251
+ headers: { 'Authorization': `Bearer ${token}` }
252
+ });
253
+ const status = await res.json();
254
+ document.getElementById('usernameLabel').innerText = status.username.charAt(0).toUpperCase() + status.username.slice(1);
255
+ document.getElementById('avatarName').innerText = status.username.substring(0, 2).toUpperCase();
256
+ } catch (err) { console.error(err); }
257
+ }
258
+
259
+ sendButton.onclick = sendMessage;
260
+ messageInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
261
+ document.getElementById('newChatBtn').onclick = () => {
262
+ currentCaseId = crypto.randomUUID();
263
+ chatContainer.innerHTML = '';
264
+ welcomeScreen.classList.remove('hidden');
265
+ };
266
+
267
+ connectWS();
268
+ loadHistory();
269
+ fetchUserStatus();
270
+ </script>
271
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  </html>
src/apps/templates/roleselection.html CHANGED
@@ -1,51 +1,536 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Select Your Role</title>
8
- <link rel="stylesheet" href="/static/css/styles.css">
9
- <style>
10
- .professional-footer {
11
- margin-top: 2rem;
12
- padding: 1rem 0;
13
- text-align: center;
14
- border-top: 1px solid rgba(155, 135, 245, 0.1);
15
- color: rgb(19 18 18 / 50%);
16
- font-size: 0.8rem;
17
- width: 100%;
18
- }
19
-
20
- .professional-footer a {
21
- color: #9b87f5;
22
- text-decoration: none;
23
- font-weight: 500;
24
- }
25
- </style>
26
- </head>
27
-
28
- <body>
29
- <div class="overlay">
30
- <div class="role-selection">
31
- <h2>Who are you?</h2>
32
- <p>Please select your role to continue:</p>
33
- <div class="roles">
34
- <button class="role" data-role="Judge">👨⚖️ Judge</button>
35
- <button class="role" data-role="Advocate/Lawyer">👩⚖️ Advocate</button>
36
- <button class="role" data-role="Woman">🛡️ Woman (Special Provisions)</button>
37
- <button class="role" data-role="Citizen">👤 Citizen</button>
38
- <button class="role" data-role="Student">🎓 Student</button>
39
- <button class="role" data-role="Minor">👦 Minor</button>
40
- </div>
41
- <footer class="professional-footer">
42
- &copy; 2026 Law Bot AI | Managed by <a href="https://www.linkedin.com/in/vishwanath77"
43
- target="_blank">Vishwanath</a>
44
- </footer>
45
- </div>
46
- </div>
47
-
48
- <script src="/static/js/roleselection.js?v=3"></script>
49
- </body>
50
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  </html>
 
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>AI Legal Assistant | Pro Level AI Guidance</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ /* Existing styles... */
10
+ body.direct-roles #landing { display: none !important; }
11
+ body.direct-roles #rolePage { opacity: 1 !important; visibility: visible !important; }
12
+
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ font-family: 'Poppins', sans-serif;
18
+ }
19
+
20
+ body, html {
21
+ height: 100%;
22
+ overflow: hidden;
23
+ background-color: #030617;
24
+ cursor: default;
25
+ }
26
+
27
+ #particles {
28
+ position: absolute;
29
+ width: 100%;
30
+ height: 100%;
31
+ top: 0;
32
+ left: 0;
33
+ z-index: 1;
34
+ pointer-events: none;
35
+ }
36
+
37
+ .cursor-glow {
38
+ position: fixed;
39
+ width: 350px;
40
+ height: 350px;
41
+ background: radial-gradient(circle, rgba(99, 102, 241, 0.12), transparent 75%);
42
+ pointer-events: none;
43
+ transform: translate(-50%, -50%);
44
+ z-index: 0;
45
+ transition: transform 0.15s ease-out;
46
+ }
47
+
48
+ .hero {
49
+ height: 100vh;
50
+ background: radial-gradient(circle at 50% 50%, #0d122b, #030617);
51
+ display: flex;
52
+ justify-content: center;
53
+ align-items: center;
54
+ position: relative;
55
+ overflow: hidden;
56
+ transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
57
+ z-index: 10;
58
+ perspective: 1000px; /* For the tilt effect */
59
+ }
60
+
61
+ .hero::before {
62
+ content: "";
63
+ position: absolute;
64
+ width: 800px;
65
+ height: 800px;
66
+ background: radial-gradient(circle, rgba(59, 130, 246, 0.25), transparent 75%);
67
+ top: -200px;
68
+ left: -200px;
69
+ filter: blur(120px);
70
+ animation: moveLight 12s infinite alternate ease-in-out;
71
+ pointer-events: none;
72
+ }
73
+
74
+ .hero::after {
75
+ content: "";
76
+ position: absolute;
77
+ width: 700px;
78
+ height: 700px;
79
+ background: radial-gradient(circle, rgba(147, 51, 234, 0.25), transparent 75%);
80
+ bottom: -200px;
81
+ right: -200px;
82
+ filter: blur(120px);
83
+ animation: moveLight2 15s infinite alternate ease-in-out;
84
+ pointer-events: none;
85
+ }
86
+
87
+ @keyframes moveLight {
88
+ from { transform: translate(0, 0); }
89
+ to { transform: translate(250px, 150px); }
90
+ }
91
+
92
+ @keyframes moveLight2 {
93
+ from { transform: translate(0, 0); }
94
+ to { transform: translate(-250px, -150px); }
95
+ }
96
+
97
+ /* FLOATING CONTENT */
98
+ .content {
99
+ text-align: center;
100
+ color: white;
101
+ z-index: 20;
102
+ max-width: 850px;
103
+ padding: 40px;
104
+ transform-style: preserve-3d;
105
+ animation: float 6s ease-in-out infinite;
106
+ will-change: transform;
107
+ }
108
+
109
+ @keyframes float {
110
+ 0% { transform: translateY(0px); }
111
+ 50% { transform: translateY(-15px); }
112
+ 100% { transform: translateY(0px); }
113
+ }
114
+
115
+ .tag {
116
+ display: inline-block;
117
+ font-size: 0.8rem;
118
+ font-weight: 600;
119
+ color: #6ee7ff;
120
+ letter-spacing: 3px;
121
+ text-transform: uppercase;
122
+ margin-bottom: 25px;
123
+ background: rgba(110, 231, 255, 0.1);
124
+ padding: 6px 18px;
125
+ border-radius: 20px;
126
+ border: 1px solid rgba(110, 231, 255, 0.25);
127
+ animation: fadeIn 1s ease forwards;
128
+ }
129
+
130
+ .content h1 {
131
+ font-size: clamp(2.5rem, 8vw, 4.5rem);
132
+ white-space: nowrap;
133
+ font-weight: 700;
134
+ margin-bottom: 20px;
135
+ line-height: 1.1;
136
+ letter-spacing: -2px;
137
+ background: linear-gradient(to bottom, #fff 40%, #94a3b8);
138
+ -webkit-background-clip: text;
139
+ -webkit-text-fill-color: transparent;
140
+ animation: fadeSlide 1.2s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
141
+ }
142
+
143
+ @media (max-width: 768px) {
144
+ .content h1 { white-space: normal; font-size: 3rem; }
145
+ }
146
+
147
+ .content p#subtitle {
148
+ font-size: clamp(1.1rem, 3.5vw, 1.45rem);
149
+ opacity: 0.9;
150
+ margin-bottom: 30px;
151
+ line-height: 1.7;
152
+ color: #cbd5e1;
153
+ min-height: 3.5em;
154
+ font-weight: 300;
155
+ }
156
+
157
+ .status {
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ gap: 10px;
162
+ font-size: 0.85rem;
163
+ color: #6ee7ff;
164
+ opacity: 0.8;
165
+ letter-spacing: 1.5px;
166
+ margin-bottom: 50px;
167
+ text-transform: uppercase;
168
+ font-weight: 500;
169
+ animation: fadeIn 2s ease forwards;
170
+ }
171
+
172
+ .status::before {
173
+ content: "";
174
+ display: block;
175
+ width: 8px;
176
+ height: 8px;
177
+ background: #22c55e;
178
+ border-radius: 50%;
179
+ box-shadow: 0 0 10px #22c55e;
180
+ animation: blink 1.5s infinite;
181
+ }
182
+
183
+ @keyframes blink {
184
+ 0%, 100% { opacity: 1; transform: scale(1); }
185
+ 50% { opacity: 0.3; transform: scale(0.8); }
186
+ }
187
+
188
+ .cta-btn {
189
+ position: relative;
190
+ overflow: hidden;
191
+ padding: 22px 60px;
192
+ font-size: 1.2rem;
193
+ font-weight: 600;
194
+ border: none;
195
+ border-radius: 100px;
196
+ background: linear-gradient(135deg, #3b82f6, #9333ea);
197
+ color: white;
198
+ cursor: pointer;
199
+ transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
200
+ text-transform: uppercase;
201
+ letter-spacing: 2px;
202
+ display: inline-block;
203
+ animation: pulseGlow 2.5s infinite ease-in-out;
204
+ box-shadow: 0 10px 30px rgba(99, 102, 241, 0.4);
205
+ }
206
+
207
+ @keyframes pulseGlow {
208
+ 0% { box-shadow: 0 0 15px rgba(99, 102, 241, 0.4); }
209
+ 50% { box-shadow: 0 0 35px rgba(99, 102, 241, 0.7); }
210
+ 100% { box-shadow: 0 0 15px rgba(99, 102, 241, 0.4); }
211
+ }
212
+
213
+ .cta-btn::after {
214
+ content: "";
215
+ position: absolute;
216
+ width: 200%;
217
+ height: 200%;
218
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.6), transparent 75%);
219
+ top: -50%;
220
+ left: -50%;
221
+ opacity: 0;
222
+ transition: 0.6s;
223
+ transform: scale(0.3);
224
+ pointer-events: none;
225
+ }
226
+
227
+ .cta-btn:hover {
228
+ transform: translateY(-8px) scale(1.05);
229
+ box-shadow: 0 30px 60px rgba(59, 130, 246, 0.6) !important;
230
+ letter-spacing: 3px;
231
+ }
232
+
233
+ .cta-btn:hover::after {
234
+ opacity: 1;
235
+ transform: scale(1);
236
+ }
237
+
238
+ .cta-btn:active {
239
+ transform: scale(0.96);
240
+ }
241
+
242
+ #rolePage {
243
+ position: absolute;
244
+ top: 0;
245
+ left: 0;
246
+ width: 100%;
247
+ height: 100vh;
248
+ display: flex;
249
+ justify-content: center;
250
+ align-items: center;
251
+ background: radial-gradient(circle at center, #0f172a, #030617);
252
+ opacity: 0;
253
+ visibility: hidden;
254
+ transition: opacity 1s ease;
255
+ z-index: 5;
256
+ }
257
+
258
+ #rolePage.visible {
259
+ opacity: 1;
260
+ visibility: visible;
261
+ }
262
+
263
+ .role-selection {
264
+ background: rgba(15, 23, 42, 0.6);
265
+ backdrop-filter: blur(35px);
266
+ -webkit-backdrop-filter: blur(35px);
267
+ padding: 65px;
268
+ border-radius: 45px;
269
+ border: 1px solid rgba(255, 255, 255, 0.08);
270
+ text-align: center;
271
+ width: 95%;
272
+ max-width: 680px;
273
+ box-shadow: 0 60px 120px -20px rgba(0, 0, 0, 0.8);
274
+ animation: slideUp 1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
275
+ }
276
+
277
+ .role-selection h2 {
278
+ font-size: 3rem;
279
+ color: white;
280
+ margin-bottom: 20px;
281
+ font-weight: 700;
282
+ letter-spacing: -1.5px;
283
+ }
284
+
285
+ .role-selection p {
286
+ color: #94a3b8;
287
+ margin-bottom: 45px;
288
+ font-size: 1.15rem;
289
+ line-height: 1.6;
290
+ }
291
+
292
+ .roles {
293
+ display: grid;
294
+ grid-template-columns: repeat(2, 1fr);
295
+ gap: 25px;
296
+ }
297
+
298
+ @media (max-width: 600px) {
299
+ .roles { grid-template-columns: 1fr; gap: 15px; }
300
+ .role-selection { padding: 40px 25px; }
301
+ .content h1 { line-height: 1.1; }
302
+ }
303
+
304
+ .role {
305
+ position: relative;
306
+ overflow: hidden;
307
+ background: rgba(255, 255, 255, 0.04);
308
+ color: #f1f5f9;
309
+ border: 1px solid rgba(255, 255, 255, 0.06);
310
+ padding: 24px;
311
+ border-radius: 24px;
312
+ cursor: pointer;
313
+ font-size: 1.15rem;
314
+ font-weight: 500;
315
+ transition: all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
316
+ text-align: left;
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 18px;
320
+ }
321
+
322
+ .role::before {
323
+ content: "";
324
+ position: absolute;
325
+ inset: 0;
326
+ border-radius: 24px;
327
+ background: linear-gradient(120deg, transparent, rgba(99, 102, 241, 0.2), transparent);
328
+ opacity: 0;
329
+ transition: 0.4s;
330
+ pointer-events: none;
331
+ }
332
+
333
+ .role:hover::before {
334
+ opacity: 1;
335
+ transform: translateX(100%) skewX(-15deg);
336
+ animation: sweep 0.6s ease-out forwards;
337
+ }
338
+
339
+ @keyframes sweep {
340
+ from { transform: translateX(-100%) skewX(-15deg); }
341
+ to { transform: translateX(100%) skewX(-15deg); }
342
+ }
343
+
344
+ .role:hover {
345
+ background: rgba(59, 130, 246, 0.15);
346
+ border-color: rgba(59, 130, 246, 0.5);
347
+ transform: translateY(-6px) scale(1.03);
348
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4);
349
+ color: #fff;
350
+ }
351
+
352
+ .professional-footer {
353
+ margin-top: 60px;
354
+ color: #475569;
355
+ font-size: 0.95rem;
356
+ letter-spacing: 0.5px;
357
+ }
358
+
359
+ .professional-footer a {
360
+ color: #a5b4fc;
361
+ text-decoration: none;
362
+ font-weight: 600;
363
+ transition: 0.4s;
364
+ }
365
+
366
+ .professional-footer a:hover {
367
+ color: #818cf8;
368
+ text-shadow: 0 0 20px rgba(99, 102, 241, 0.6);
369
+ }
370
+
371
+ @keyframes fadeSlide {
372
+ from { opacity: 0; transform: translateY(35px); }
373
+ to { opacity: 1; transform: translateY(0); }
374
+ }
375
+
376
+ @keyframes fadeIn {
377
+ from { opacity: 0; }
378
+ to { opacity: 1; }
379
+ }
380
+
381
+ @keyframes slideUp {
382
+ from { opacity: 0; transform: translateY(60px); }
383
+ to { opacity: 1; transform: translateY(0); }
384
+ }
385
+
386
+ .hidden { display: none !important; }
387
+
388
+ .fade-out {
389
+ opacity: 0;
390
+ transform: scale(1.05);
391
+ pointer-events: none;
392
+ filter: blur(10px);
393
+ transition: 1s cubic-bezier(0.4, 0, 0.2, 1);
394
+ }
395
+ </style>
396
+ </head>
397
+
398
+ <body class="{% if show_roles %}direct-roles{% endif %}">
399
+ <canvas id="particles"></canvas>
400
+
401
+ <div class="hero" id="landing">
402
+ <div class="content">
403
+ <span class="tag">AI POWERED SYSTEM</span>
404
+ <h1>⚖️ AI Legal Assistant</h1>
405
+ <p id="subtitle"></p>
406
+ <div class="status">System Ready</div>
407
+ <button id="startBtn" class="cta-btn">
408
+ Start Consultation →
409
+ </button>
410
+ </div>
411
+ </div>
412
+
413
+ <div id="rolePage">
414
+ <div class="role-selection">
415
+ <h2>Who are you?</h2>
416
+ <p>Select your role to unlock precise legal intelligence and expert guidance tailored to your specific framework.</p>
417
+ <div class="roles">
418
+ <button class="role" data-role="Judge">👨⚖️ Judge</button>
419
+ <button class="role" data-role="Advocate/Lawyer">👩⚖️ Advocate</button>
420
+ <button class="role" data-role="Woman">🛡️ Woman</button>
421
+ <button class="role" data-role="Citizen">👤 Citizen</button>
422
+ <button class="role" data-role="Student">🎓 Student</button>
423
+ <button class="role" data-role="Minor">👦 Minor</button>
424
+ </div>
425
+ <footer class="professional-footer">
426
+ &copy; 2026 Law Bot AI • Forged for Justice • Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
427
+ </footer>
428
+ </div>
429
+ </div>
430
+
431
+ <script>
432
+ document.addEventListener("DOMContentLoaded", function () {
433
+ const contentEl = document.querySelector(".content");
434
+
435
+ // --- CURSOR GLOW ---
436
+ const glow = document.createElement("div");
437
+ glow.classList.add("cursor-glow");
438
+ document.body.appendChild(glow);
439
+
440
+ document.addEventListener("mousemove", (e) => {
441
+ glow.style.left = e.clientX + "px";
442
+ glow.style.top = e.clientY + "px";
443
+
444
+ // --- PARALLAX TILT EFFECT ---
445
+ const x = (window.innerWidth / 2 - e.clientX) / 75;
446
+ const y = (window.innerHeight / 2 - e.clientY) / 75;
447
+
448
+ // Keep the floating animation active by adding these transforms
449
+ contentEl.style.transform = `perspective(1000px) rotateY(${x}deg) rotateX(${y}deg)`;
450
+ });
451
+
452
+ // --- PARTICLES ANIMATION ---
453
+ const canvas = document.getElementById("particles");
454
+ const ctx = canvas.getContext("2d");
455
+
456
+ let w, h, particles = [];
457
+
458
+ function initCanvas() {
459
+ w = canvas.width = window.innerWidth;
460
+ h = canvas.height = window.innerHeight;
461
+ particles = [];
462
+ for (let i = 0; i < 90; i++) {
463
+ particles.push({
464
+ x: Math.random() * w,
465
+ y: Math.random() * h,
466
+ r: Math.random() * 1.5 + 0.5,
467
+ dx: (Math.random() - 0.5) * 0.4,
468
+ dy: (Math.random() - 0.5) * 0.4,
469
+ alpha: Math.random() * 0.4 + 0.1
470
+ });
471
+ }
472
+ }
473
+
474
+ function animate() {
475
+ ctx.clearRect(0, 0, w, h);
476
+ particles.forEach(p => {
477
+ p.x += p.dx;
478
+ p.y += p.dy;
479
+
480
+ if (p.x < 0 || p.x > w) p.dx *= -1;
481
+ if (p.y < 0 || p.y > h) p.dy *= -1;
482
+
483
+ ctx.beginPath();
484
+ ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
485
+ ctx.fillStyle = `rgba(110, 231, 255, ${p.alpha})`;
486
+ ctx.fill();
487
+ });
488
+ requestAnimationFrame(animate);
489
+ }
490
+
491
+ initCanvas();
492
+ animate();
493
+ window.addEventListener("resize", initCanvas);
494
+
495
+ // --- TYPING EFFECT ---
496
+ const subtitleText = "Precise legal guidance powered by state-of-the-art AI. Redefining how Indian citizens navigate justice with intelligence and speed.";
497
+ const subtitleEl = document.getElementById("subtitle");
498
+ let i = 0;
499
+
500
+ function typing() {
501
+ if (i < subtitleText.length) {
502
+ subtitleEl.innerHTML += subtitleText.charAt(i);
503
+ i++;
504
+ setTimeout(typing, 18);
505
+ }
506
+ }
507
+ setTimeout(typing, 800);
508
+
509
+ // --- NAVIGATION ---
510
+ const startBtn = document.getElementById("startBtn");
511
+ const landing = document.getElementById("landing");
512
+ const rolePage = document.getElementById("rolePage");
513
+
514
+ startBtn.addEventListener("click", () => {
515
+ landing.classList.add("fade-out");
516
+ setTimeout(() => {
517
+ landing.classList.add("hidden");
518
+ rolePage.classList.add("visible");
519
+ }, 1200);
520
+ });
521
+
522
+ document.querySelectorAll(".role").forEach(button => {
523
+ button.addEventListener("click", function () {
524
+ const role = this.getAttribute("data-role");
525
+ window.location.href = `/login?role=${encodeURIComponent(role)}`;
526
+ });
527
+ });
528
+
529
+ // --- PATHWAY PROTECTION (Fallback) ---
530
+ if (window.location.pathname.includes('/role')) {
531
+ document.body.classList.add('direct-roles');
532
+ }
533
+ });
534
+ </script>
535
+ </body>
536
  </html>
src/apps/templates/studentchatbot.html CHANGED
@@ -1,855 +1,314 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Law bot</title>
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
- <link rel="stylesheet" href="/static/css/modern-chat.css">
10
- <style>
11
- :root {
12
- --primary-purple: #9b87f5;
13
- --dark-purple: #1A1F2C;
14
- --light-purple: #D6BCFA;
15
- --neutral-gray: #8E9196;
16
- --light-gray: #C8C8C9;
17
- --off-white: #eee;
18
- }
19
-
20
- * {
21
- margin: 0;
22
- padding: 0;
23
- box-sizing: border-box;
24
- font-family: 'Inter', sans-serif;
25
- }
26
-
27
- body {
28
- transition: background-color 0.3s, color 0.3s;
29
- }
30
-
31
- body.dark {
32
- color: var(--off-white);
33
- }
34
-
35
- body.light {
36
- color: var(--dark-purple);
37
- }
38
-
39
- .container {
40
- max-width: 800px;
41
- margin: 0 auto;
42
- padding: 2rem;
43
- min-height: 100vh;
44
- display: flex;
45
- flex-direction: column;
46
- }
47
-
48
- .header {
49
- display: flex;
50
- justify-content: space-between;
51
- align-items: center;
52
- margin-bottom: 2rem;
53
- }
54
-
55
- .header-right {
56
- display: flex;
57
- align-items: center;
58
- gap: 1rem;
59
- }
60
-
61
-
62
- .app-title {
63
- margin-left: 40px;
64
- font-size: 2rem;
65
- font-weight: 600;
66
- background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
67
- -webkit-background-clip: text;
68
- background-clip: text;
69
- color: transparent;
70
- }
71
-
72
- .theme-toggle {
73
- background: none;
74
- border: none;
75
- cursor: pointer;
76
- padding: 0.5rem;
77
- color: var(--primary-purple);
78
- transition: opacity 0.3s;
79
- }
80
-
81
- .theme-toggle:hover {
82
- opacity: 0.8;
83
- }
84
-
85
- .theme-toggle svg {
86
- width: 24px;
87
- height: 24px;
88
- }
89
-
90
- .chat-container {
91
- flex-grow: 1;
92
- overflow-y: auto;
93
- margin-bottom: 2rem;
94
- padding: 1rem;
95
- }
96
-
97
- .message {
98
- margin-bottom: 1.5rem;
99
- padding: 1rem;
100
- border-radius: 8px;
101
- animation: fadeIn 0.3s ease-in;
102
- }
103
-
104
- .user-message {
105
- background-color: rgba(155, 135, 245, 0.1);
106
- }
107
-
108
- .ai-message {
109
- background-color: rgba(255, 255, 255, 0.05);
110
- border: 1px solid rgba(200, 200, 201, 0.2);
111
- }
112
-
113
- body.light .user-message {
114
- background-color: var(--off-white);
115
- }
116
-
117
- body.light .ai-message {
118
- background-color: #ffffff;
119
- border: 1px solid var(--light-gray);
120
- }
121
-
122
- .message-header {
123
- display: flex;
124
- align-items: center;
125
- margin-bottom: 0.5rem;
126
- font-weight: 500;
127
- }
128
-
129
-
130
- .sidebar-welcome {
131
- padding: 1.5rem;
132
- border-bottom: 1px solid rgba(200, 200, 201, 0.15);
133
- margin-bottom: 1.5rem;
134
- }
135
-
136
- .user-icon,
137
- .ai-icon {
138
- width: 24px;
139
- height: 24px;
140
- border-radius: 50%;
141
- margin-right: 0.5rem;
142
- display: flex;
143
- align-items: center;
144
- justify-content: center;
145
- font-size: 12px;
146
- }
147
-
148
- .user-icon {
149
- background-color: var(--primary-purple);
150
- color: white;
151
- }
152
-
153
- .ai-icon {
154
- background-color: var(--light-purple);
155
- color: var(--dark-purple);
156
- }
157
-
158
- .input-container {
159
- position: fixed;
160
- bottom: 0;
161
- left: 0;
162
- right: 0;
163
- padding: 1rem;
164
- background-color: var(--input-bg);
165
- border-top: 1px solid var(--sidebar-border);
166
- }
167
-
168
- body.light .input-container {
169
- background-color: #ffffff;
170
- border-top: 1px solid var(--light-gray);
171
- }
172
-
173
- .input-wrapper {
174
- max-width: 800px;
175
- margin: 0 auto;
176
- position: relative;
177
- }
178
-
179
- #messageInput {
180
- width: 100%;
181
- padding: 1rem;
182
- padding-right: 3rem;
183
- border: 1px solid rgba(200, 200, 201, 0.3);
184
- border-radius: 8px;
185
- font-size: 1rem;
186
- outline: none;
187
- transition: border-color 0.3s;
188
- background-color: transparent;
189
- color: inherit;
190
- }
191
-
192
- body.light #messageInput {
193
- border: 1px solid var(--light-gray);
194
- }
195
-
196
- #messageInput:focus {
197
- border-color: var(--primary-purple);
198
- }
199
-
200
- #sendButton {
201
- position: absolute;
202
- right: 0.5rem;
203
- top: 50%;
204
- transform: translateY(-50%);
205
- background: none;
206
- border: none;
207
- cursor: pointer;
208
- padding: 0.5rem;
209
- color: var(--primary-purple);
210
- opacity: 0.8;
211
- transition: opacity 0.3s;
212
- }
213
-
214
- #sendButton:hover {
215
- opacity: 1;
216
- }
217
-
218
- .typing-indicator {
219
- padding: 1rem;
220
- color: var(--neutral-gray);
221
- font-style: italic;
222
- display: none;
223
- }
224
-
225
- .welcome-title {
226
- margin: 6rem 0 0.5rem 0;
227
- text-align: center;
228
- color: var(--neutral-gray);
229
- padding: 1rem;
230
- font-size: 1.24rem;
231
- animation: fadeIn 0.3s ease-in;
232
- }
233
-
234
- .welcome-subtitle {
235
- line-height: 1.6;
236
- font-size: 1.1rem;
237
- color: var(--neutral-gray);
238
- max-width: 800px;
239
- margin: 0 auto;
240
- font-weight: 400;
241
- color: var(--primary-purple);
242
- }
243
-
244
- .sidebar-welcome {
245
- margin-bottom: 2rem;
246
- padding: 0 1rem;
247
- border-bottom: none;
248
- }
249
-
250
- .role-selection-link {
251
- display: inline-flex;
252
- align-items: center;
253
- gap: initial;
254
- color: var(--light-purple);
255
- text-decoration: none;
256
- margin-top: auto;
257
- padding: auto;
258
- transition: opacity 0.3s ease;
259
- position: absolute;
260
- top: 550px;
261
- left: 30px;
262
-
263
- }
264
-
265
- .role-selection-link:hover {
266
- opacity: 0.8;
267
- }
268
-
269
- .role-selection-link svg {
270
- width: 16px;
271
- height: 16px;
272
- stroke: currentColor;
273
- }
274
-
275
- /* Add this media query for mobile responsiveness */
276
- @media (max-width: 480px) {
277
- .welcome-message p {
278
- font-size: 1rem;
279
- padding: 0 1rem;
280
- }
281
- }
282
-
283
- @keyframes fadeIn {
284
- from {
285
- opacity: 0;
286
- transform: translateY(10px);
287
- }
288
-
289
- to {
290
- opacity: 1;
291
- transform: translateY(0);
292
- }
293
- }
294
-
295
- .connection-status {
296
- position: fixed;
297
- top: 1rem;
298
- right: 1rem;
299
- padding: 0.5rem 1rem;
300
- border-radius: 4px;
301
- font-size: 0.875rem;
302
- display: none;
303
- }
304
-
305
- .connection-status.connected {
306
- background-color: #4ade80;
307
- color: white;
308
- }
309
-
310
- .connection-status.disconnected {
311
- background-color: #ef4444;
312
- color: white;
313
- }
314
-
315
- /* Styling for formatted text */
316
- .message-content {
317
- line-height: 1.5;
318
- }
319
-
320
- .message-content h1,
321
- .message-content h2,
322
- .message-content h3 {
323
- margin: 1rem 0 0.5rem;
324
- font-weight: 600;
325
- }
326
-
327
- .message-content h1 {
328
- font-size: 1.5rem;
329
- }
330
-
331
- .message-content h2 {
332
- font-size: 1.25rem;
333
- }
334
-
335
- .message-content h3 {
336
- font-size: 1.1rem;
337
- }
338
-
339
- .message-content ul,
340
- .message-content ol {
341
- margin-left: 1.5rem;
342
- margin-bottom: 1rem;
343
- }
344
-
345
- .message-content a {
346
- color: var(--primary-purple);
347
- text-decoration: none;
348
- }
349
-
350
- .message-content a:hover {
351
- text-decoration: underline;
352
- }
353
-
354
- .usage-indicator {
355
- padding: 4px 12px;
356
- background: rgba(155, 135, 245, 0.1);
357
- border: 1px solid rgba(155, 135, 245, 0.3);
358
- border-radius: 20px;
359
- font-size: 0.8rem;
360
- color: var(--primary-purple);
361
- font-weight: 500;
362
- display: none;
363
- /* Hidden by default for Admins */
364
- }
365
- </style>
366
- </head>
367
-
368
- <button class="sidebar-floating-toggle" id="floatingToggle">
369
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
370
- <path d="M3 12h18M3 6h18M3 18h18" />
371
- </svg>
372
- </button>
373
-
374
- <aside class="sidebar" id="sidebar">
375
- <div class="sidebar-header">
376
- <button class="new-chat-btn" id="newChatBtn">
377
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
378
- <path d="M12 5v14M5 12h14" />
379
- </svg>
380
- New Chat
381
- </button>
382
- <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
383
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
384
- <path d="M15 18l-6-6 6-6" />
385
- </svg>
386
- </button>
387
- </div>
388
- <div class="sidebar-title">Recent Research</div>
389
- <div class="history-list" id="historyList">
390
- <!-- Recent prompts will appear here -->
391
- </div>
392
-
393
- <div class="perspective-container" style="display: none;">
394
- <div class="sidebar-title">Answer Perspective</div>
395
- <div class="perspective-list">
396
- <div class="perspective-option active" data-role="Student">🎓 Student</div>
397
- </div>
398
- </div>
399
-
400
-
401
- </aside>
402
-
403
- <!-- Existing messages will be added here dynamically -->
404
- <div class="container">
405
- <header class="header">
406
- <h1 class="app-title">Law Bot (Student)</h1>
407
- <div class="header-right">
408
- <div class="usage-indicator" id="usageIndicator">
409
- Questions Remaining: <span id="remainingCount">--</span>
410
- </div>
411
- <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
412
- <!-- Icon injected by JS -->
413
- </button>
414
-
415
- <a href="/studentdashboard.html" class="dashboard-link" title="Back to Dashboard">
416
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
417
- <rect x="3" y="3" width="7" height="7"></rect>
418
- <rect x="14" y="3" width="7" height="7"></rect>
419
- <rect x="14" y="14" width="7" height="7"></rect>
420
- <rect x="3" y="14" width="7" height="7"></rect>
421
- </svg>
422
- </a>
423
- </div>
424
- </header>
425
- <div class="welcome-title">Welcome, Future Advocate! 👨‍🎓</div>
426
- <div class="welcome-subtitle">Ready to sharpen your legal reasoning? How can I assist your preparations today?
427
- </div>
428
-
429
-
430
- <div class="chat-container" id="chatContainer">
431
- <div class="typing-indicator" id="typingIndicator">
432
- <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--student-color)" stroke-width="2">
433
- <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
434
- <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
435
- </svg>
436
- <span>Researching for your studies...</span>
437
- </div>
438
- </div>
439
- <div class="input-container">
440
- <div class="input-wrapper">
441
- <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
442
- <button id="sendButton">
443
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
444
- stroke-linecap="round" stroke-linejoin="round">
445
- <line x1="22" y1="2" x2="11" y2="13"></line>
446
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
447
- </svg>
448
- </button>
449
- </div>
450
- </div>
451
- <footer class="professional-footer">
452
- &copy; 2026 Law Bot AI. All Rights Reserved.
453
- <br>
454
- Developed & Managed by <a href="https://www.linkedin.com/in/vishwanath77" target="_blank">Vishwanath</a>
455
- </footer>
456
- </div>
457
- <div class="connection-status" id="connectionStatus"></div>
458
-
459
- <script>
460
- let ws;
461
- const chatContainer = document.getElementById('chatContainer');
462
- const messageInput = document.getElementById('messageInput');
463
- const sendButton = document.getElementById('sendButton');
464
- const typingIndicator = document.getElementById('typingIndicator');
465
- const connectionStatus = document.getElementById('connectionStatus');
466
- const sidebar = document.getElementById('sidebar');
467
-
468
- let currentUserMessage = '';
469
- let currentAiMessage = '';
470
- let currentAiMessageElement = null;
471
- let currentCaseId = crypto.randomUUID();
472
- const activeRole = 'Student'; // LOCKED ROLE
473
- let userLimitReached = false;
474
-
475
- async function checkUserStatus() {
476
- const token = localStorage.getItem('token');
477
- if (!token) return;
478
-
479
- try {
480
- const response = await fetch('/api/user-status', {
481
- headers: { 'Authorization': `Bearer ${token}` }
482
- });
483
- const status = await response.json();
484
-
485
- const indicator = document.getElementById('usageIndicator');
486
- if (status.is_admin) {
487
- indicator.style.display = 'none';
488
- } else {
489
- indicator.style.display = 'block';
490
- const remaining = Math.max(0, 2 - status.question_count);
491
- document.getElementById('remainingCount').innerText = remaining;
492
- if (remaining <= 0) {
493
- userLimitReached = true;
494
- }
495
- }
496
- } catch (err) {
497
- console.error('Failed to fetch user status:', err);
498
- }
499
- }
500
-
501
- function formatText(text) {
502
- // Extract references for Evidence Box
503
- const references = [];
504
- const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
505
- let match;
506
- while ((match = refRegex.exec(text)) !== null) {
507
- references.push({ title: match[1], pages: match[2] });
508
- }
509
-
510
- let formatted = text
511
- .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
512
- .replace(/\*(.*?)\*/g, "<em>$1</em>")
513
- .replace(/### (.*?)\n/g, "<h3>$1</h3>")
514
- .replace(/## (.*?)\n/g, "<h2>$1</h2>")
515
- .replace(/# (.*?)\n/g, "<h1>$1</h1>")
516
- .replace(/\n/g, "<br>")
517
- .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
518
- .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
519
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
520
-
521
- // Add Evidence Box if references found
522
- if (references.length > 0) {
523
- let evidenceHtml = `<div class="evidence-box">
524
- <div class="evidence-title">
525
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
526
- Study Evidence
527
- </div>`;
528
- references.forEach(ref => {
529
- evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
530
- });
531
- evidenceHtml += `</div>`;
532
- formatted += evidenceHtml;
533
- }
534
- return formatted;
535
- }
536
-
537
- function connectWebSocket() {
538
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
539
- ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
540
-
541
- ws.onopen = () => {
542
- console.log('Connected to WebSocket');
543
- connectionStatus.textContent = 'Connected';
544
- connectionStatus.className = 'connection-status connected';
545
- connectionStatus.style.display = 'block';
546
- setTimeout(() => {
547
- connectionStatus.style.display = 'none';
548
- }, 3000);
549
- };
550
-
551
- ws.onclose = () => {
552
- console.log('Disconnected from WebSocket');
553
- connectionStatus.textContent = 'Reconnecting...';
554
- connectionStatus.className = 'connection-status disconnected';
555
- connectionStatus.style.display = 'block';
556
-
557
- // Exponential backoff for reconnection
558
- const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
559
- window._reconnectCount = (window._reconnectCount || 0) + 1;
560
-
561
- setTimeout(() => {
562
- reconnectWebSocket();
563
- loadHistory();
564
- }, backoff);
565
- };
566
-
567
- ws.onerror = (error) => {
568
- console.error('WebSocket Error:', error);
569
- };
570
-
571
-
572
- ws.onmessage = (event) => {
573
- console.log('Received message:', event.data);
574
-
575
- // ✅ Hide typing indicator on response
576
- typingIndicator.style.display = 'none';
577
-
578
- if (event.data === '[DONE]') {
579
- // Save complete chat interaction
580
- saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
581
- checkUserStatus(); // Update remaining count
582
- return;
583
- }
584
-
585
- // Handle usage limit blocks from backend
586
- if (event.data.includes("Free usage limit reached")) {
587
- typingIndicator.style.display = 'none';
588
- userLimitReached = true;
589
- checkUserStatus();
590
- }
591
-
592
- if (!currentAiMessageElement) {
593
- currentAiMessage = event.data;
594
- addMessage(currentAiMessage, 'ai');
595
- } else {
596
- currentAiMessage += event.data;
597
- const formattedMessage = formatText(currentAiMessage);
598
- currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
599
- }
600
- };
601
-
602
- // ✅ Scroll fix (move into sendMessage function if needed)
603
- chatContainer.scrollTop = chatContainer.scrollHeight;
604
- }
605
-
606
- function reconnectWebSocket() {
607
- console.log('Attempting to reconnect...');
608
- connectWebSocket();
609
- loadHistory();
610
- }
611
-
612
- function addMessage(content, type) {
613
- if (type === 'user') {
614
- currentUserMessage = content;
615
- currentAiMessage = '';
616
- currentAiMessageElement = null;
617
- }
618
-
619
- const messageDiv = document.createElement('div');
620
- messageDiv.className = `message ${type}-message`;
621
-
622
- const header = document.createElement('div');
623
- header.className = 'message-header';
624
-
625
- const icon = document.createElement('div');
626
- icon.className = `${type}-icon`;
627
- icon.textContent = type === 'user' ? 'U' : 'L';
628
-
629
- const name = document.createElement('span');
630
- name.textContent = type === 'user' ? 'You' : 'Law Bot';
631
-
632
- header.appendChild(icon);
633
- header.appendChild(name);
634
-
635
- const text = document.createElement('div');
636
- text.className = 'message-content';
637
- text.innerHTML = type === 'ai' ? formatText(content) : content;
638
-
639
- messageDiv.appendChild(header);
640
- messageDiv.appendChild(text);
641
- chatContainer.appendChild(messageDiv);
642
- chatContainer.scrollTop = chatContainer.scrollHeight;
643
-
644
- if (type === 'ai') {
645
- currentAiMessageElement = messageDiv;
646
- }
647
- }
648
-
649
- function sendMessage() {
650
- const message = messageInput.value.trim();
651
- if (!message) return;
652
-
653
- if (userLimitReached) {
654
- addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
655
- return;
656
- }
657
-
658
- if (!activeRole) {
659
- addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
660
- return;
661
- }
662
-
663
- if (ws.readyState === WebSocket.OPEN) {
664
- addMessage(message, 'user');
665
- ws.send(message);
666
- messageInput.value = '';
667
-
668
- // ✅ Show typing indicator after sending
669
- typingIndicator.style.display = 'block';
670
- chatContainer.scrollTop = chatContainer.scrollHeight;
671
- }
672
- }
673
- const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
674
- if (isAtBottom) {
675
- chatContainer.scrollTop = chatContainer.scrollHeight;
676
- }
677
-
678
-
679
- const historyList = document.getElementById('historyList');
680
-
681
- async function loadHistory() {
682
- const token = localStorage.getItem('token');
683
- if (!token) return;
684
-
685
- try {
686
- const response = await fetch(`/api/interactions?role=${activeRole}`, {
687
- headers: { 'Authorization': `Bearer ${token}` }
688
- });
689
- const interactions = await response.json();
690
- historyList.innerHTML = '';
691
- interactions.forEach(item => {
692
- const div = document.createElement('div');
693
- div.className = 'history-item';
694
-
695
- // Readable preview: first sentence or truncated query
696
- const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
697
-
698
- div.innerHTML = `
699
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
700
- <span>${preview}</span>
701
- <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
702
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
703
- </button>
704
- `;
705
- div.onclick = () => loadConversation(item.case_id);
706
- historyList.appendChild(div);
707
- });
708
- } catch (err) {
709
- console.error('Failed to load history:', err);
710
- }
711
- }
712
-
713
- async function loadConversation(caseId) {
714
- const token = localStorage.getItem('token');
715
- try {
716
- const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
717
- headers: { 'Authorization': `Bearer ${token}` }
718
- });
719
- const thread = await response.json();
720
-
721
- currentCaseId = caseId;
722
- chatContainer.innerHTML = ''; // Clear window
723
-
724
- thread.forEach(msg => {
725
- addMessage(msg.query, 'user');
726
- // Render AI Response immediately
727
- const aiMsgDiv = document.createElement('div');
728
- aiMsgDiv.className = 'message ai-message';
729
- aiMsgDiv.innerHTML = `
730
- <div class="message-header">
731
- <div class="ai-icon">L</div>
732
- <span>Law Bot</span>
733
- </div>
734
- <div class="message-content">${formatText(msg.response)}</div>
735
- `;
736
- chatContainer.appendChild(aiMsgDiv);
737
- });
738
- chatContainer.scrollTop = chatContainer.scrollHeight;
739
- } catch (err) {
740
- console.error('Failed to load thread:', err);
741
- }
742
- }
743
-
744
- async function deleteConversation(caseId) {
745
- const token = localStorage.getItem('token');
746
- try {
747
- await fetch(`/api/interactions/${caseId}`, {
748
- method: 'DELETE',
749
- headers: { 'Authorization': `Bearer ${token}` }
750
- });
751
- if (currentCaseId === caseId) {
752
- newChat();
753
- } else {
754
- loadHistory();
755
- }
756
- } catch (err) {
757
- console.error('Failed to delete:', err);
758
- }
759
- }
760
-
761
- function newChat() {
762
- currentCaseId = crypto.randomUUID();
763
- chatContainer.innerHTML = '';
764
- messageInput.value = '';
765
- messageInput.focus();
766
- loadHistory();
767
- }
768
-
769
- // ✅ Modified: Uses Bearer token
770
- const saveChatInteraction = async (caseId, query, response) => {
771
- const token = localStorage.getItem('token');
772
- if (!token) return;
773
-
774
- try {
775
- await fetch('/api/save-interaction', {
776
- method: 'POST',
777
- headers: {
778
- 'Content-Type': 'application/json',
779
- 'Authorization': `Bearer ${token}`
780
- },
781
- body: JSON.stringify({ caseId, query, response, role: activeRole })
782
- });
783
- loadHistory(); // Refresh sidebar history
784
- } catch (error) {
785
- console.error('Error saving chat:', error);
786
- }
787
- };
788
-
789
- sendButton.addEventListener('click', sendMessage);
790
- messageInput.addEventListener('keypress', (e) => {
791
- if (e.key === 'Enter') {
792
- sendMessage();
793
- }
794
- });
795
-
796
-
797
-
798
-
799
- // Theme toggle
800
- const themeToggle = document.getElementById('themeToggle');
801
- const body = document.body;
802
- const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
803
- const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
804
-
805
- function updateTheme(isDark) {
806
- if (isDark) {
807
- body.classList.add('dark');
808
- body.classList.remove('light');
809
- themeToggle.innerHTML = moonIcon;
810
- localStorage.setItem('theme', 'dark');
811
- } else {
812
- body.classList.add('light');
813
- body.classList.remove('dark');
814
- themeToggle.innerHTML = sunIcon;
815
- localStorage.setItem('theme', 'light');
816
- }
817
- }
818
-
819
- themeToggle.addEventListener('click', () => {
820
- const isNowDark = !body.classList.contains('dark');
821
- updateTheme(isNowDark);
822
- });
823
-
824
- // Initialize Theme
825
- const savedTheme = localStorage.getItem('theme') || 'dark';
826
- updateTheme(savedTheme === 'dark');
827
-
828
- // Sidebar Collapse Logic
829
- document.getElementById('collapseSidebar').onclick = () => {
830
- document.body.classList.add('sidebar-collapsed');
831
- };
832
- document.getElementById('floatingToggle').onclick = () => {
833
- document.body.classList.remove('sidebar-collapsed');
834
- };
835
- document.getElementById('newChatBtn').onclick = newChat;
836
-
837
- // Perspective Selection
838
- document.querySelectorAll('.perspective-option').forEach(opt => {
839
- opt.onclick = () => {
840
- document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
841
- opt.classList.add('active');
842
- activeRole = opt.dataset.role;
843
- localStorage.setItem('activeRole', activeRole);
844
- // No session reset needed, just role update for next message
845
- };
846
- });
847
-
848
- checkUserStatus();
849
- connectWebSocket();
850
- loadHistory();
851
-
852
- </script>
853
- </body>
854
-
855
  </html>
 
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>AI Legal Assistant | Student Framework</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-dark: #030617;
11
+ --sidebar-bg: #050a24;
12
+ --accent-blue: #3b82f6;
13
+ --accent-purple: #9333ea;
14
+ --text-primary: #f1f5f9;
15
+ --text-dim: #94a3b8;
16
+ --glass-bg: rgba(255, 255, 255, 0.03);
17
+ --glass-border: rgba(255, 255, 255, 0.08);
18
+ }
19
+
20
+ * {
21
+ margin: 0; padding: 0; box-sizing: border-box;
22
+ font-family: 'Poppins', sans-serif;
23
+ }
24
+
25
+ body {
26
+ background-color: var(--bg-dark);
27
+ color: var(--text-primary);
28
+ height: 100vh;
29
+ display: flex;
30
+ overflow: hidden;
31
+ }
32
+
33
+ /* SIDEBAR */
34
+ .sidebar {
35
+ width: 280px;
36
+ background: var(--sidebar-bg);
37
+ border-right: 1px solid var(--glass-border);
38
+ display: flex; flex-direction: column;
39
+ z-index: 100;
40
+ }
41
+
42
+ .sidebar-header { padding: 30px 20px; }
43
+
44
+ .new-chat-btn {
45
+ width: 100%; padding: 14px;
46
+ background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
47
+ border: none; border-radius: 12px;
48
+ color: white; font-weight: 600;
49
+ display: flex; align-items: center; justify-content: center; gap: 10px;
50
+ cursor: pointer; transition: 0.3s;
51
+ box-shadow: 0 10px 20px rgba(59, 130, 246, 0.2);
52
+ }
53
+
54
+ .new-chat-btn:hover { transform: translateY(-2px); box-shadow: 0 15px 25px rgba(59, 130, 246, 0.3); }
55
+
56
+ .history-label { padding: 0 20px 10px; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 2px; color: var(--text-dim); font-weight: 700; }
57
+ .history-list { flex: 1; overflow-y: auto; padding: 10px; }
58
+ .history-item {
59
+ padding: 12px 15px; border-radius: 10px; margin-bottom: 5px;
60
+ cursor: pointer; transition: 0.2s; font-size: 0.85rem; color: var(--text-dim);
61
+ display: flex; align-items: center; gap: 10px; border: 1px solid transparent;
62
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
63
+ }
64
+ .history-item:hover { background: var(--glass-bg); color: white; border-color: var(--glass-border); }
65
+
66
+ .sidebar-footer { padding: 20px; border-top: 1px solid var(--glass-border); }
67
+ .user-badge { display: flex; align-items: center; gap: 12px; padding: 10px; background: var(--glass-bg); border-radius: 12px; }
68
+ .user-avatar { width: 32px; height: 32px; background: var(--accent-blue); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: 700; color: white; font-size: 0.8rem; }
69
+
70
+ /* MAIN AREA */
71
+ .main-chat { flex: 1; display: flex; flex-direction: column; position: relative; background: radial-gradient(circle at top right, rgba(59, 130, 246, 0.03), transparent); }
72
+
73
+ .top-bar {
74
+ height: 70px; background: rgba(3, 6, 23, 0.82); backdrop-filter: blur(25px);
75
+ border-bottom: 1px solid var(--glass-border);
76
+ display: flex; align-items: center; justify-content: space-between; padding: 0 40px; z-index: 50;
77
+ }
78
+
79
+ .status-pill { display: flex; align-items: center; gap: 8px; font-size: 0.75rem; background: rgba(59, 130, 246, 0.1); color: var(--accent-blue); padding: 4px 12px; border-radius: 20px; font-weight: 600; border: 1px solid rgba(59, 130, 246, 0.2); }
80
+ .status-dot { width: 6px; height: 6px; background: var(--accent-blue); border-radius: 50%; animation: pulse-dot 2s infinite; }
81
+
82
+ @keyframes pulse-dot { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.4; } }
83
+
84
+ .messages-container { flex: 1; overflow-y: auto; padding: 40px 10%; display: flex; flex-direction: column; gap: 30px; scroll-behavior: smooth; }
85
+ .message { max-width: 850px; animation: message-slide 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; }
86
+ @keyframes message-slide { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
87
+
88
+ .user-msg { align-self: flex-end; background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); padding: 16px 24px; border-radius: 20px 20px 4px 20px; color: white; max-width: 70%; line-height: 1.6; }
89
+ .ai-msg { align-self: flex-start; background: var(--glass-bg); border: 1px solid var(--glass-border); padding: 30px; border-radius: 20px 20px 20px 4px; max-width: 85%; line-height: 1.8; color: #e2e8f0; }
90
+ .ai-msg h3 { color: var(--accent-blue); font-size: 1.3rem; margin-bottom: 20px; font-weight: 600; display: flex; align-items: center; gap: 12px; }
91
+ .ai-msg ul { margin: 20px 0; list-style: none; }
92
+ .ai-msg li { position: relative; padding-left: 28px; margin-bottom: 15px; }
93
+ .ai-msg li::before { content: "🎓"; position: absolute; left: 0; font-size: 0.9rem; }
94
+ .highlight { background: rgba(59, 130, 246, 0.08); border-left: 4px solid var(--accent-blue); padding: 22px; margin: 25px 0; border-radius: 0 16px 16px 0; font-weight: 500; color: #fff; }
95
+
96
+ .typing { display: flex; gap: 6px; padding: 18px 28px; background: var(--glass-bg); border-radius: 20px; width: fit-content; margin: 20px 10%; border: 1px solid var(--glass-border); }
97
+ .typing span { width: 8px; height: 8px; background: var(--accent-blue); border-radius: 50%; animation: typing-blink 1.4s infinite ease-in-out; }
98
+ .typing span:nth-child(2) { animation-delay: 0.2s; }
99
+ .typing span:nth-child(3) { animation-delay: 0.4s; }
100
+ @keyframes typing-blink { 0%, 100% { opacity: 0.2; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.1); } }
101
+
102
+ .input-area { padding: 30px 10%; background: linear-gradient(to top, var(--bg-dark) 50%, transparent); border-top: 1px solid var(--glass-border); z-index: 60; }
103
+ .input-box { background: rgba(255, 255, 255, 0.04); border: 1px solid var(--glass-border); border-radius: 18px; padding: 6px; display: flex; align-items: center; transition: 0.3s; }
104
+ .input-box:focus-within { border-color: var(--accent-blue); box-shadow: 0 0 30px rgba(59, 130, 246, 0.2); }
105
+ .input-box input { flex: 1; background: transparent; border: none; padding: 16px 24px; color: white; font-size: 1.05rem; outline: none; }
106
+ .send-btn { background: var(--accent-blue); color: white; border: none; width: 50px; height: 50px; border-radius: 14px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: 0.3s; margin-right: 6px; }
107
+ .send-btn:hover { background: var(--accent-purple); transform: scale(1.1); }
108
+
109
+ .hidden { display: none !important; }
110
+ </style>
111
+ </head>
112
+ <body>
113
+ <aside class="sidebar">
114
+ <div class="sidebar-header">
115
+ <button class="new-chat-btn" id="newChatBtn"><span>+</span> New Research</button>
116
+ </div>
117
+ <div class="history-label">Library Archives</div>
118
+ <div class="history-list" id="historyList"></div>
119
+ <div class="sidebar-footer">
120
+ <div class="user-badge">
121
+ <div class="user-avatar" id="avatarName">ST</div>
122
+ <div>
123
+ <div id="usernameLabel" style="font-size: 0.85rem; font-weight: 600;">Student</div>
124
+ <div style="font-size: 0.7rem; color: var(--text-dim);">Academic Access</div>
125
+ </div>
126
+ </div>
127
+ <div style="display: flex; flex-direction: column; gap: 8px; margin-top: 15px;">
128
+ <a href="/studentdashboard.html" style="color: var(--accent-blue); text-decoration: none; font-size: 0.8rem; text-align: center; font-weight: 600;">Back to Dashboard</a>
129
+ <a href="/role" style="color: var(--text-dim); text-decoration: none; font-size: 0.8rem; text-align: center;">← Change Role</a>
130
+ </div>
131
+ </div>
132
+ </aside>
133
+
134
+ <main class="main-chat">
135
+ <header class="top-bar">
136
+ <div class="system-branding">
137
+ <span style="font-weight: 700; color: white;">⚖️ Law Bot (Student)</span>
138
+ <div class="status-pill"><div class="status-dot"></div>Academic Ready</div>
139
+ </div>
140
+ <div style="font-size: 0.85rem; color: var(--text-dim); font-weight: 500;">Educational Framework v4.0</div>
141
+ </header>
142
+
143
+ <div id="welcomeScreen" style="position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 40px; z-index: 1;">
144
+ <div style="font-size: 4rem; margin-bottom: 25px;">📚</div>
145
+ <h1 style="font-size: 2.8rem; font-weight: 700; margin-bottom: 12px; letter-spacing: -1px;">Empowering your studies.</h1>
146
+ <p style="color: var(--text-dim); max-width: 550px; line-height: 1.7; font-size: 1.1rem;">Expert guidance for law students. Ask about case laws, sections, or get conceptual breakdowns for your exams.</p>
147
+ </div>
148
+
149
+ <div class="messages-container" id="chatContainer"></div>
150
+ <div class="typing hidden" id="typingIndicator"><span></span><span></span><span></span></div>
151
+
152
+ <div class="input-area">
153
+ <div class="input-box">
154
+ <input type="text" id="messageInput" placeholder="Enter your research query..." autocomplete="off">
155
+ <button class="send-btn" id="sendButton">
156
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
157
+ </button>
158
+ </div>
159
+ </div>
160
+ </main>
161
+
162
+ <script>
163
+ let ws;
164
+ const chatContainer = document.getElementById('chatContainer');
165
+ const messageInput = document.getElementById('messageInput');
166
+ const sendButton = document.getElementById('sendButton');
167
+ const typingIndicator = document.getElementById('typingIndicator');
168
+ const welcomeScreen = document.getElementById('welcomeScreen');
169
+ const historyList = document.getElementById('historyList');
170
+
171
+ let currentAiMessage = '';
172
+ let currentAiMessageElement = null;
173
+ let currentCaseId = crypto.randomUUID();
174
+ const activeRole = 'Student';
175
+
176
+ function formatResponse(text) {
177
+ let formatted = text;
178
+ formatted = formatted.replace(/### (.*?)\s*(\n|$)/g, "<h3>📖 $1</h3>");
179
+ formatted = formatted.replace(/^! (.*?)$/gm, '<div class="highlight">$1</div>');
180
+ formatted = formatted.replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>");
181
+ formatted = formatted.replace(/^\*\s(.*?)$/gm, "<li>$1</li>");
182
+ formatted = formatted.replace(/- (.*?)$/gm, "<li>$1</li>");
183
+ formatted = formatted.replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>");
184
+ formatted = formatted.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
185
+ formatted = formatted.replace(/\n/g, "<br>");
186
+ return formatted;
187
+ }
188
+
189
+ function connectWS() {
190
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
191
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
192
+ ws.onmessage = (event) => {
193
+ typingIndicator.classList.add('hidden');
194
+ welcomeScreen.classList.add('hidden');
195
+ if (event.data === '[DONE]') {
196
+ saveInteraction(currentCaseId, messageInput.value, currentAiMessage);
197
+ return;
198
+ }
199
+ if (!currentAiMessageElement) {
200
+ currentAiMessage = event.data;
201
+ createNewMessage('ai', currentAiMessage);
202
+ } else {
203
+ currentAiMessage += event.data;
204
+ currentAiMessageElement.innerHTML = formatResponse(currentAiMessage);
205
+ }
206
+ chatContainer.scrollTop = chatContainer.scrollHeight;
207
+ };
208
+ ws.onclose = () => setTimeout(connectWS, 2000);
209
+ }
210
+
211
+ function createNewMessage(type, content) {
212
+ welcomeScreen.classList.add('hidden');
213
+ const msgDiv = document.createElement('div');
214
+ msgDiv.className = `message ${type}-msg`;
215
+ if (type === 'ai') {
216
+ msgDiv.innerHTML = formatResponse(content);
217
+ currentAiMessageElement = msgDiv;
218
+ } else {
219
+ msgDiv.textContent = content;
220
+ }
221
+ chatContainer.appendChild(msgDiv);
222
+ chatContainer.scrollTop = chatContainer.scrollHeight;
223
+ }
224
+
225
+ function sendMessage() {
226
+ const text = messageInput.value.trim();
227
+ if (!text || ws.readyState !== WebSocket.OPEN) return;
228
+ createNewMessage('user', text);
229
+ ws.send(text);
230
+ messageInput.value = '';
231
+ currentAiMessage = '';
232
+ currentAiMessageElement = null;
233
+ typingIndicator.classList.remove('hidden');
234
+ chatContainer.scrollTop = chatContainer.scrollHeight;
235
+ }
236
+
237
+ async function saveInteraction(caseId, query, response) {
238
+ const token = localStorage.getItem('token');
239
+ if (!token) return;
240
+ try {
241
+ await fetch('/api/save-interaction', {
242
+ method: 'POST',
243
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
244
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
245
+ });
246
+ loadHistory();
247
+ } catch (err) { console.error(err); }
248
+ }
249
+
250
+ async function loadHistory() {
251
+ const token = localStorage.getItem('token');
252
+ if (!token) return;
253
+ try {
254
+ const res = await fetch(`/api/interactions?role=${activeRole}`, {
255
+ headers: { 'Authorization': `Bearer ${token}` }
256
+ });
257
+ const data = await res.json();
258
+ historyList.innerHTML = '';
259
+ data.forEach(item => {
260
+ const div = document.createElement('div');
261
+ div.className = 'history-item';
262
+ const preview = item.query.substring(0, 30) + "...";
263
+ div.innerHTML = `<span>🔖</span> ${preview}`;
264
+ div.onclick = () => loadThread(item.case_id);
265
+ historyList.appendChild(div);
266
+ });
267
+ } catch (err) { console.error(err); }
268
+ }
269
+
270
+ async function loadThread(caseId) {
271
+ const token = localStorage.getItem('token');
272
+ try {
273
+ const res = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
274
+ headers: { 'Authorization': `Bearer ${token}` }
275
+ });
276
+ const thread = await res.json();
277
+ currentCaseId = caseId;
278
+ chatContainer.innerHTML = '';
279
+ welcomeScreen.classList.add('hidden');
280
+ thread.forEach(msg => {
281
+ createNewMessage('user', msg.query);
282
+ createNewMessage('ai', msg.response);
283
+ });
284
+ currentAiMessageElement = null;
285
+ } catch (err) { console.error(err); }
286
+ }
287
+
288
+ async function fetchUserStatus() {
289
+ const token = localStorage.getItem('token');
290
+ if (!token) return;
291
+ try {
292
+ const res = await fetch('/api/user-status', {
293
+ headers: { 'Authorization': `Bearer ${token}` }
294
+ });
295
+ const status = await res.json();
296
+ document.getElementById('usernameLabel').innerText = status.username.charAt(0).toUpperCase() + status.username.slice(1);
297
+ document.getElementById('avatarName').innerText = status.username.substring(0, 2).toUpperCase();
298
+ } catch (err) { console.error(err); }
299
+ }
300
+
301
+ sendButton.onclick = sendMessage;
302
+ messageInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
303
+ document.getElementById('newChatBtn').onclick = () => {
304
+ currentCaseId = crypto.randomUUID();
305
+ chatContainer.innerHTML = '';
306
+ welcomeScreen.classList.remove('hidden');
307
+ };
308
+
309
+ connectWS();
310
+ loadHistory();
311
+ fetchUserStatus();
312
+ </script>
313
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  </html>
src/apps/templates/womanchatbot.html CHANGED
@@ -1,854 +1,575 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Law bot</title>
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
9
- <link rel="stylesheet" href="/static/css/modern-chat.css">
10
- <style>
11
- :root {
12
- --primary-purple: #9b87f5;
13
- --dark-purple: #1A1F2C;
14
- --light-purple: #D6BCFA;
15
- --neutral-gray: #8E9196;
16
- --light-gray: #C8C8C9;
17
- --off-white: #eee;
18
- }
19
-
20
- * {
21
- margin: 0;
22
- padding: 0;
23
- box-sizing: border-box;
24
- font-family: 'Inter', sans-serif;
25
- }
26
-
27
- body {
28
- transition: background-color 0.3s, color 0.3s;
29
- }
30
-
31
- body.dark {
32
- color: var(--off-white);
33
- }
34
-
35
- body.light {
36
- color: var(--dark-purple);
37
- }
38
-
39
- .container {
40
- max-width: 800px;
41
- margin: 0 auto;
42
- padding: 2rem;
43
- min-height: 100vh;
44
- display: flex;
45
- flex-direction: column;
46
- }
47
-
48
- .header {
49
- display: flex;
50
- justify-content: space-between;
51
- align-items: center;
52
- margin-bottom: 2rem;
53
- }
54
-
55
- .header-right {
56
- display: flex;
57
- align-items: center;
58
- gap: 1rem;
59
- }
60
-
61
-
62
- .app-title {
63
- margin-left: 40px;
64
- font-size: 2rem;
65
- font-weight: 600;
66
- background: linear-gradient(135deg, var(--primary-purple), var(--light-purple));
67
- -webkit-background-clip: text;
68
- background-clip: text;
69
- color: transparent;
70
- }
71
-
72
- .theme-toggle {
73
- background: none;
74
- border: none;
75
- cursor: pointer;
76
- padding: 0.5rem;
77
- color: var(--primary-purple);
78
- transition: opacity 0.3s;
79
- }
80
-
81
- .theme-toggle:hover {
82
- opacity: 0.8;
83
- }
84
-
85
- .theme-toggle svg {
86
- width: 24px;
87
- height: 24px;
88
- }
89
-
90
- .chat-container {
91
- flex-grow: 1;
92
- overflow-y: auto;
93
- margin-bottom: 2rem;
94
- padding: 1rem;
95
- }
96
-
97
- .message {
98
- margin-bottom: 1.5rem;
99
- padding: 1rem;
100
- border-radius: 8px;
101
- animation: fadeIn 0.3s ease-in;
102
- }
103
-
104
- .user-message {
105
- background-color: rgba(155, 135, 245, 0.1);
106
- }
107
-
108
- .ai-message {
109
- background-color: rgba(255, 255, 255, 0.05);
110
- border: 1px solid rgba(200, 200, 201, 0.2);
111
- }
112
-
113
- body.light .user-message {
114
- background-color: var(--off-white);
115
- }
116
-
117
- body.light .ai-message {
118
- background-color: #ffffff;
119
- border: 1px solid var(--light-gray);
120
- }
121
-
122
- .message-header {
123
- display: flex;
124
- align-items: center;
125
- margin-bottom: 0.5rem;
126
- font-weight: 500;
127
- }
128
-
129
-
130
- .sidebar-welcome {
131
- padding: 1.5rem;
132
- border-bottom: 1px solid rgba(200, 200, 201, 0.15);
133
- margin-bottom: 1.5rem;
134
- }
135
-
136
- .user-icon,
137
- .ai-icon {
138
- width: 24px;
139
- height: 24px;
140
- border-radius: 50%;
141
- margin-right: 0.5rem;
142
- display: flex;
143
- align-items: center;
144
- justify-content: center;
145
- font-size: 12px;
146
- }
147
-
148
- .user-icon {
149
- background-color: var(--primary-purple);
150
- color: white;
151
- }
152
-
153
- .ai-icon {
154
- background-color: var(--light-purple);
155
- color: var(--dark-purple);
156
- }
157
-
158
- .input-container {
159
- position: fixed;
160
- bottom: 0;
161
- left: 0;
162
- right: 0;
163
- padding: 1rem;
164
- background-color: var(--input-bg);
165
- border-top: 1px solid var(--sidebar-border);
166
- }
167
-
168
- body.light .input-container {
169
- background-color: #ffffff;
170
- border-top: 1px solid var(--light-gray);
171
- }
172
-
173
- .input-wrapper {
174
- max-width: 800px;
175
- margin: 0 auto;
176
- position: relative;
177
- }
178
-
179
- #messageInput {
180
- width: 100%;
181
- padding: 1rem;
182
- padding-right: 3rem;
183
- border: 1px solid rgba(200, 200, 201, 0.3);
184
- border-radius: 8px;
185
- font-size: 1rem;
186
- outline: none;
187
- transition: border-color 0.3s;
188
- background-color: transparent;
189
- color: inherit;
190
- }
191
-
192
- body.light #messageInput {
193
- border: 1px solid var(--light-gray);
194
- }
195
-
196
- #messageInput:focus {
197
- border-color: var(--primary-purple);
198
- }
199
-
200
- #sendButton {
201
- position: absolute;
202
- right: 0.5rem;
203
- top: 50%;
204
- transform: translateY(-50%);
205
- background: none;
206
- border: none;
207
- cursor: pointer;
208
- padding: 0.5rem;
209
- color: var(--primary-purple);
210
- opacity: 0.8;
211
- transition: opacity 0.3s;
212
- }
213
-
214
- #sendButton:hover {
215
- opacity: 1;
216
- }
217
-
218
- .typing-indicator {
219
- padding: 1rem;
220
- color: var(--neutral-gray);
221
- font-style: italic;
222
- display: none;
223
- }
224
-
225
- .welcome-title {
226
- margin: 6rem 0 0.5rem 0;
227
- text-align: center;
228
- color: var(--neutral-gray);
229
- padding: 1rem;
230
- font-size: 1.24rem;
231
- animation: fadeIn 0.3s ease-in;
232
- }
233
-
234
- .welcome-subtitle {
235
- line-height: 1.6;
236
- font-size: 1.1rem;
237
- color: var(--neutral-gray);
238
- max-width: 800px;
239
- margin: 0 auto;
240
- font-weight: 400;
241
- color: var(--primary-purple);
242
- }
243
-
244
- .sidebar-welcome {
245
- margin-bottom: 2rem;
246
- padding: 0 1rem;
247
- border-bottom: none;
248
- }
249
-
250
- .role-selection-link {
251
- display: inline-flex;
252
- align-items: center;
253
- gap: initial;
254
- color: var(--light-purple);
255
- text-decoration: none;
256
- margin-top: auto;
257
- padding: auto;
258
- transition: opacity 0.3s ease;
259
- position: absolute;
260
- top: 550px;
261
- left: 30px;
262
-
263
- }
264
-
265
- .role-selection-link:hover {
266
- opacity: 0.8;
267
- }
268
-
269
- .role-selection-link svg {
270
- width: 16px;
271
- height: 16px;
272
- stroke: currentColor;
273
- }
274
-
275
- /* Add this media query for mobile responsiveness */
276
- @media (max-width: 480px) {
277
- .welcome-message p {
278
- font-size: 1rem;
279
- padding: 0 1rem;
280
- }
281
- }
282
-
283
- @keyframes fadeIn {
284
- from {
285
- opacity: 0;
286
- transform: translateY(10px);
287
- }
288
-
289
- to {
290
- opacity: 1;
291
- transform: translateY(0);
292
- }
293
- }
294
-
295
- .connection-status {
296
- position: fixed;
297
- top: 1rem;
298
- right: 1rem;
299
- padding: 0.5rem 1rem;
300
- border-radius: 4px;
301
- font-size: 0.875rem;
302
- display: none;
303
- }
304
-
305
- .connection-status.connected {
306
- background-color: #4ade80;
307
- color: white;
308
- }
309
-
310
- .connection-status.disconnected {
311
- background-color: #ef4444;
312
- color: white;
313
- }
314
-
315
- /* Styling for formatted text */
316
- .message-content {
317
- line-height: 1.5;
318
- }
319
-
320
- .message-content h1,
321
- .message-content h2,
322
- .message-content h3 {
323
- margin: 1rem 0 0.5rem;
324
- font-weight: 600;
325
- }
326
-
327
- .message-content h1 {
328
- font-size: 1.5rem;
329
- }
330
-
331
- .message-content h2 {
332
- font-size: 1.25rem;
333
- }
334
-
335
- .message-content h3 {
336
- font-size: 1.1rem;
337
- }
338
-
339
- .message-content ul,
340
- .message-content ol {
341
- margin-left: 1.5rem;
342
- margin-bottom: 1rem;
343
- }
344
-
345
- .message-content a {
346
- color: var(--primary-purple);
347
- text-decoration: none;
348
- }
349
-
350
- .message-content a:hover {
351
- text-decoration: underline;
352
- }
353
-
354
- .usage-indicator {
355
- padding: 4px 12px;
356
- background: rgba(155, 135, 245, 0.1);
357
- border: 1px solid rgba(155, 135, 245, 0.3);
358
- border-radius: 20px;
359
- font-size: 0.8rem;
360
- color: var(--primary-purple);
361
- font-weight: 500;
362
- display: none;
363
- /* Hidden by default for Admins */
364
- }
365
- </style>
366
- </head>
367
-
368
- <button class="sidebar-floating-toggle" id="floatingToggle">
369
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
370
- <path d="M3 12h18M3 6h18M3 18h18" />
371
- </svg>
372
- </button>
373
-
374
- <aside class="sidebar" id="sidebar">
375
- <div class="sidebar-header">
376
- <button class="new-chat-btn" id="newChatBtn">
377
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
378
- <path d="M12 5v14M5 12h14" />
379
- </svg>
380
- New Chat
381
- </button>
382
- <button class="collapse-toggle" id="collapseSidebar" title="Collapse sidebar">
383
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
384
- <path d="M15 18l-6-6 6-6" />
385
- </svg>
386
- </button>
387
- </div>
388
- <div class="sidebar-title">Recent Support</div>
389
- <div class="history-list" id="historyList">
390
- <!-- Recent prompts will appear here -->
391
- </div>
392
-
393
- <div class="perspective-container" style="display: none;">
394
- <div class="sidebar-title">Answer Perspective</div>
395
- <div class="perspective-list">
396
- <div class="perspective-option active" data-role="Woman">👩 Woman</div>
397
- </div>
398
- </div>
399
-
400
-
401
- </aside>
402
-
403
- <!-- Existing messages will be added here dynamically -->
404
- <div class="container">
405
- <header class="header">
406
- <h1 class="app-title">Law Bot (Woman)</h1>
407
- <div class="header-right">
408
- <div class="usage-indicator" id="usageIndicator">
409
- Questions Remaining: <span id="remainingCount">--</span>
410
- </div>
411
- <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
412
- <!-- Icon injected by JS -->
413
- </button>
414
-
415
- <a href="/woman.html" class="dashboard-link" title="Back to Dashboard">
416
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
417
- <rect x="3" y="3" width="7" height="7"></rect>
418
- <rect x="14" y="3" width="7" height="7"></rect>
419
- <rect x="14" y="14" width="7" height="7"></rect>
420
- <rect x="3" y="14" width="7" height="7"></rect>
421
- </svg>
422
- </a>
423
- </div>
424
- </header>
425
- <div class="welcome-title">Welcome, Respected Madam!👩‍⚖️</div>
426
- <div class="welcome-subtitle">I’m honored to assist you in navigating legal complexities. How can I help you?
427
- </div>
428
-
429
-
430
- <div class="chat-container" id="chatContainer">
431
- <div class="typing-indicator" id="typingIndicator">
432
- <svg class="gavel-icon" viewBox="0 0 24 24" fill="none" stroke="var(--woman-color)" stroke-width="2">
433
- <path d="M12 21.35C-7 10 6.5 2 12 8.55 17.5 2 31 10 12 21.35Z" />
434
- </svg>
435
- <span>Providing supportive guidance...</span>
436
- </div>
437
- </div>
438
- <div class="input-container">
439
- <div class="input-wrapper">
440
- <input type="text" id="messageInput" placeholder="Ask anything..." autocomplete="off">
441
- <button id="sendButton">
442
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
443
- stroke-linecap="round" stroke-linejoin="round">
444
- <line x1="22" y1="2" x2="11" y2="13"></line>
445
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
446
- </svg>
447
- </button>
448
- </div>
449
- </div>
450
- <footer class="legal-footer">
451
- &copy; 2026 Law Bot AI. Professional Legal Assistant (Woman's Perspective).
452
- <br>Disclaimer: This AI system provides information for educational/assisting purposes and does not
453
- constitute formal legal advice.
454
- </footer>
455
- </div>
456
- <div class="connection-status" id="connectionStatus"></div>
457
-
458
- <script>
459
- let ws;
460
- const chatContainer = document.getElementById('chatContainer');
461
- const messageInput = document.getElementById('messageInput');
462
- const sendButton = document.getElementById('sendButton');
463
- const typingIndicator = document.getElementById('typingIndicator');
464
- const connectionStatus = document.getElementById('connectionStatus');
465
- const sidebar = document.getElementById('sidebar');
466
-
467
- let currentUserMessage = '';
468
- let currentAiMessage = '';
469
- let currentAiMessageElement = null;
470
- let currentCaseId = crypto.randomUUID();
471
- const activeRole = 'Woman'; // LOCKED ROLE
472
- let userLimitReached = false;
473
-
474
- async function checkUserStatus() {
475
- const token = localStorage.getItem('token');
476
- if (!token) return;
477
-
478
- try {
479
- const response = await fetch('/api/user-status', {
480
- headers: { 'Authorization': `Bearer ${token}` }
481
- });
482
- const status = await response.json();
483
-
484
- const indicator = document.getElementById('usageIndicator');
485
- if (status.is_admin) {
486
- indicator.style.display = 'none';
487
- } else {
488
- indicator.style.display = 'block';
489
- const remaining = Math.max(0, 2 - status.question_count);
490
- document.getElementById('remainingCount').innerText = remaining;
491
- if (remaining <= 0) {
492
- userLimitReached = true;
493
- }
494
- }
495
- } catch (err) {
496
- console.error('Failed to fetch user status:', err);
497
- }
498
- }
499
-
500
- function formatText(text) {
501
- // Extract references for Evidence Box
502
- const references = [];
503
- const refRegex = /\*\*Title\*\*:\s*([^\n]+)\s*\*\*Page Numbers\*\*:\s*([^\n]+)/gi;
504
- let match;
505
- while ((match = refRegex.exec(text)) !== null) {
506
- references.push({ title: match[1], pages: match[2] });
507
- }
508
-
509
- let formatted = text
510
- .replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
511
- .replace(/\*(.*?)\*/g, "<em>$1</em>")
512
- .replace(/### (.*?)\n/g, "<h3>$1</h3>")
513
- .replace(/## (.*?)\n/g, "<h2>$1</h2>")
514
- .replace(/# (.*?)\n/g, "<h1>$1</h1>")
515
- .replace(/\n/g, "<br>")
516
- .replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>")
517
- .replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>")
518
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, `<a href="$2" target="_blank">$1</a>`);
519
-
520
- // Add Evidence Box if references found
521
- if (references.length > 0) {
522
- let evidenceHtml = `<div class="evidence-box">
523
- <div class="evidence-title">
524
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
525
- Reference Documents
526
- </div>`;
527
- references.forEach(ref => {
528
- evidenceHtml += `<div class="evidence-item"><strong>${ref.title}</strong> (Page: ${ref.pages})</div>`;
529
- });
530
- evidenceHtml += `</div>`;
531
- formatted += evidenceHtml;
532
- }
533
- return formatted;
534
- }
535
-
536
- function connectWebSocket() {
537
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
538
- ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
539
-
540
- ws.onopen = () => {
541
- console.log('Connected to WebSocket');
542
- connectionStatus.textContent = 'Connected';
543
- connectionStatus.className = 'connection-status connected';
544
- connectionStatus.style.display = 'block';
545
- setTimeout(() => {
546
- connectionStatus.style.display = 'none';
547
- }, 3000);
548
- };
549
-
550
- ws.onclose = () => {
551
- console.log('Disconnected from WebSocket');
552
- connectionStatus.textContent = 'Reconnecting...';
553
- connectionStatus.className = 'connection-status disconnected';
554
- connectionStatus.style.display = 'block';
555
-
556
- // Exponential backoff for reconnection
557
- const backoff = Math.min(30000, (window._reconnectCount || 0) * 2000 + 1000);
558
- window._reconnectCount = (window._reconnectCount || 0) + 1;
559
-
560
- setTimeout(() => {
561
- reconnectWebSocket();
562
- loadHistory();
563
- }, backoff);
564
- };
565
-
566
- ws.onerror = (error) => {
567
- console.error('WebSocket Error:', error);
568
- };
569
-
570
-
571
- ws.onmessage = (event) => {
572
- console.log('Received message:', event.data);
573
-
574
- // ✅ Hide typing indicator on response
575
- typingIndicator.style.display = 'none';
576
-
577
- if (event.data === '[DONE]') {
578
- // Save complete chat interaction
579
- saveChatInteraction(currentCaseId, currentUserMessage, currentAiMessage);
580
- checkUserStatus(); // Update remaining count
581
- return;
582
- }
583
-
584
- // Handle usage limit blocks from backend
585
- if (event.data.includes("Free usage limit reached")) {
586
- typingIndicator.style.display = 'none';
587
- userLimitReached = true;
588
- checkUserStatus();
589
- }
590
-
591
- if (!currentAiMessageElement) {
592
- currentAiMessage = event.data;
593
- addMessage(currentAiMessage, 'ai');
594
- } else {
595
- currentAiMessage += event.data;
596
- const formattedMessage = formatText(currentAiMessage);
597
- currentAiMessageElement.querySelector('.message-content').innerHTML = formattedMessage;
598
- }
599
- };
600
-
601
- // ✅ Scroll fix (move into sendMessage function if needed)
602
- chatContainer.scrollTop = chatContainer.scrollHeight;
603
- }
604
-
605
- function reconnectWebSocket() {
606
- console.log('Attempting to reconnect...');
607
- connectWebSocket();
608
- loadHistory();
609
- }
610
-
611
- function addMessage(content, type) {
612
- if (type === 'user') {
613
- currentUserMessage = content;
614
- currentAiMessage = '';
615
- currentAiMessageElement = null;
616
- }
617
-
618
- const messageDiv = document.createElement('div');
619
- messageDiv.className = `message ${type}-message`;
620
-
621
- const header = document.createElement('div');
622
- header.className = 'message-header';
623
-
624
- const icon = document.createElement('div');
625
- icon.className = `${type}-icon`;
626
- icon.textContent = type === 'user' ? 'U' : 'L';
627
-
628
- const name = document.createElement('span');
629
- name.textContent = type === 'user' ? 'You' : 'Law Bot';
630
-
631
- header.appendChild(icon);
632
- header.appendChild(name);
633
-
634
- const text = document.createElement('div');
635
- text.className = 'message-content';
636
- text.innerHTML = type === 'ai' ? formatText(content) : content;
637
-
638
- messageDiv.appendChild(header);
639
- messageDiv.appendChild(text);
640
- chatContainer.appendChild(messageDiv);
641
- chatContainer.scrollTop = chatContainer.scrollHeight;
642
-
643
- if (type === 'ai') {
644
- currentAiMessageElement = messageDiv;
645
- }
646
- }
647
-
648
- function sendMessage() {
649
- const message = messageInput.value.trim();
650
- if (!message) return;
651
-
652
- if (userLimitReached) {
653
- addMessage("### Free usage limit reached\n\nYou’ve reached the free usage limit (2 questions).\nFurther access is restricted.\n\nPlease contact the administrator for extended access:\nLinkedIn: [https://www.linkedin.com/in/vishwanath77](https://www.linkedin.com/in/vishwanath77)", 'ai');
654
- return;
655
- }
656
-
657
- if (!activeRole) {
658
- addMessage("⚠️ Please select an Answer Perspective in the sidebar to proceed.", 'ai');
659
- return;
660
- }
661
-
662
- if (ws.readyState === WebSocket.OPEN) {
663
- addMessage(message, 'user');
664
- ws.send(message);
665
- messageInput.value = '';
666
-
667
- // ✅ Show typing indicator after sending
668
- typingIndicator.style.display = 'block';
669
- chatContainer.scrollTop = chatContainer.scrollHeight;
670
- }
671
- }
672
- const isAtBottom = chatContainer.scrollHeight - chatContainer.scrollTop === chatContainer.clientHeight;
673
- if (isAtBottom) {
674
- chatContainer.scrollTop = chatContainer.scrollHeight;
675
- }
676
-
677
-
678
- const historyList = document.getElementById('historyList');
679
-
680
- async function loadHistory() {
681
- const token = localStorage.getItem('token');
682
- if (!token) return;
683
-
684
- try {
685
- const response = await fetch(`/api/interactions?role=${activeRole}`, {
686
- headers: { 'Authorization': `Bearer ${token}` }
687
- });
688
- const interactions = await response.json();
689
- historyList.innerHTML = '';
690
- interactions.forEach(item => {
691
- const div = document.createElement('div');
692
- div.className = 'history-item';
693
-
694
- // Readable preview: first sentence or truncated query
695
- const preview = item.query.split('.')[0].substring(0, 40) + (item.query.length > 40 ? '...' : '');
696
-
697
- div.innerHTML = `
698
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"></path></svg>
699
- <span>${preview}</span>
700
- <button class="delete-chat-item" onclick="event.stopPropagation(); deleteConversation('${item.case_id}')">
701
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path></svg>
702
- </button>
703
- `;
704
- div.onclick = () => loadConversation(item.case_id);
705
- historyList.appendChild(div);
706
- });
707
- } catch (err) {
708
- console.error('Failed to load history:', err);
709
- }
710
- }
711
-
712
- async function loadConversation(caseId) {
713
- const token = localStorage.getItem('token');
714
- try {
715
- const response = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
716
- headers: { 'Authorization': `Bearer ${token}` }
717
- });
718
- const thread = await response.json();
719
-
720
- currentCaseId = caseId;
721
- chatContainer.innerHTML = ''; // Clear window
722
-
723
- thread.forEach(msg => {
724
- addMessage(msg.query, 'user');
725
- // Render AI Response immediately
726
- const aiMsgDiv = document.createElement('div');
727
- aiMsgDiv.className = 'message ai-message';
728
- aiMsgDiv.innerHTML = `
729
- <div class="message-header">
730
- <div class="ai-icon">L</div>
731
- <span>Law Bot</span>
732
- </div>
733
- <div class="message-content">${formatText(msg.response)}</div>
734
- `;
735
- chatContainer.appendChild(aiMsgDiv);
736
- });
737
- chatContainer.scrollTop = chatContainer.scrollHeight;
738
- } catch (err) {
739
- console.error('Failed to load thread:', err);
740
- }
741
- }
742
-
743
- async function deleteConversation(caseId) {
744
- const token = localStorage.getItem('token');
745
- try {
746
- await fetch(`/api/interactions/${caseId}`, {
747
- method: 'DELETE',
748
- headers: { 'Authorization': `Bearer ${token}` }
749
- });
750
- if (currentCaseId === caseId) {
751
- newChat();
752
- } else {
753
- loadHistory();
754
- }
755
- } catch (err) {
756
- console.error('Failed to delete:', err);
757
- }
758
- }
759
-
760
- function newChat() {
761
- currentCaseId = crypto.randomUUID();
762
- chatContainer.innerHTML = '';
763
- messageInput.value = '';
764
- messageInput.focus();
765
- loadHistory();
766
- }
767
-
768
- // ✅ Modified: Uses Bearer token
769
- const saveChatInteraction = async (caseId, query, response) => {
770
- const token = localStorage.getItem('token');
771
- if (!token) return;
772
-
773
- try {
774
- await fetch('/api/save-interaction', {
775
- method: 'POST',
776
- headers: {
777
- 'Content-Type': 'application/json',
778
- 'Authorization': `Bearer ${token}`
779
- },
780
- body: JSON.stringify({ caseId, query, response, role: activeRole })
781
- });
782
- loadHistory(); // Refresh sidebar history
783
- } catch (error) {
784
- console.error('Error saving chat:', error);
785
- }
786
- };
787
-
788
- sendButton.addEventListener('click', sendMessage);
789
- messageInput.addEventListener('keypress', (e) => {
790
- if (e.key === 'Enter') {
791
- sendMessage();
792
- }
793
- });
794
-
795
-
796
-
797
-
798
- // Theme toggle
799
- const themeToggle = document.getElementById('themeToggle');
800
- const body = document.body;
801
- const moonIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>`;
802
- const sunIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>`;
803
-
804
- function updateTheme(isDark) {
805
- if (isDark) {
806
- body.classList.add('dark');
807
- body.classList.remove('light');
808
- themeToggle.innerHTML = moonIcon;
809
- localStorage.setItem('theme', 'dark');
810
- } else {
811
- body.classList.add('light');
812
- body.classList.remove('dark');
813
- themeToggle.innerHTML = sunIcon;
814
- localStorage.setItem('theme', 'light');
815
- }
816
- }
817
-
818
- themeToggle.addEventListener('click', () => {
819
- const isNowDark = !body.classList.contains('dark');
820
- updateTheme(isNowDark);
821
- });
822
-
823
- // Initialize Theme
824
- const savedTheme = localStorage.getItem('theme') || 'dark';
825
- updateTheme(savedTheme === 'dark');
826
-
827
- // Sidebar Collapse Logic
828
- document.getElementById('collapseSidebar').onclick = () => {
829
- document.body.classList.add('sidebar-collapsed');
830
- };
831
- document.getElementById('floatingToggle').onclick = () => {
832
- document.body.classList.remove('sidebar-collapsed');
833
- };
834
- document.getElementById('newChatBtn').onclick = newChat;
835
-
836
- // Perspective Selection
837
- document.querySelectorAll('.perspective-option').forEach(opt => {
838
- opt.onclick = () => {
839
- document.querySelectorAll('.perspective-option').forEach(o => o.classList.remove('active'));
840
- opt.classList.add('active');
841
- activeRole = opt.dataset.role;
842
- localStorage.setItem('activeRole', activeRole);
843
- // No session reset needed, just role update for next message
844
- };
845
- });
846
-
847
- checkUserStatus();
848
- connectWebSocket();
849
- loadHistory();
850
-
851
- </script>
852
- </body>
853
-
854
  </html>
 
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>AI Legal Assistant | Woman's Framework</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-dark: #030617;
11
+ --sidebar-bg: #050a24;
12
+ --accent-pink: #f472b6;
13
+ --accent-purple: #9333ea;
14
+ --accent-blue: #3b82f6;
15
+ --text-primary: #f1f5f9;
16
+ --text-dim: #94a3b8;
17
+ --glass-bg: rgba(255, 255, 255, 0.03);
18
+ --glass-border: rgba(255, 255, 255, 0.08);
19
+ }
20
+
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ font-family: 'Poppins', sans-serif;
26
+ }
27
+
28
+ body {
29
+ background-color: var(--bg-dark);
30
+ color: var(--text-primary);
31
+ height: 100vh;
32
+ display: flex;
33
+ overflow: hidden;
34
+ }
35
+
36
+ /* SIDEBAR (ZONE 1) */
37
+ .sidebar {
38
+ width: 280px;
39
+ background: var(--sidebar-bg);
40
+ border-right: 1px solid var(--glass-border);
41
+ display: flex;
42
+ flex-direction: column;
43
+ z-index: 100;
44
+ }
45
+
46
+ .sidebar-header {
47
+ padding: 30px 20px;
48
+ }
49
+
50
+ .new-chat-btn {
51
+ width: 100%;
52
+ padding: 14px;
53
+ background: linear-gradient(135deg, var(--accent-pink), var(--accent-purple));
54
+ border: none;
55
+ border-radius: 12px;
56
+ color: white;
57
+ font-weight: 600;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ gap: 10px;
62
+ cursor: pointer;
63
+ transition: all 0.3s ease;
64
+ box-shadow: 0 10px 20px rgba(244, 114, 182, 0.2);
65
+ }
66
+
67
+ .new-chat-btn:hover {
68
+ transform: translateY(-2px);
69
+ box-shadow: 0 15px 25px rgba(244, 114, 182, 0.3);
70
+ }
71
+
72
+ .history-label {
73
+ padding: 0 20px 10px;
74
+ font-size: 0.75rem;
75
+ text-transform: uppercase;
76
+ letter-spacing: 2px;
77
+ color: var(--text-dim);
78
+ font-weight: 700;
79
+ }
80
+
81
+ .history-list {
82
+ flex: 1;
83
+ overflow-y: auto;
84
+ padding: 10px;
85
+ }
86
+
87
+ .history-item {
88
+ padding: 12px 15px;
89
+ border-radius: 10px;
90
+ margin-bottom: 5px;
91
+ cursor: pointer;
92
+ transition: all 0.2s;
93
+ font-size: 0.85rem;
94
+ color: var(--text-dim);
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 10px;
98
+ border: 1px solid transparent;
99
+ white-space: nowrap;
100
+ overflow: hidden;
101
+ text-overflow: ellipsis;
102
+ }
103
+
104
+ .history-item:hover {
105
+ background: var(--glass-bg);
106
+ color: white;
107
+ border-color: var(--glass-border);
108
+ }
109
+
110
+ .sidebar-footer {
111
+ padding: 20px;
112
+ border-top: 1px solid var(--glass-border);
113
+ }
114
+
115
+ .user-badge {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 12px;
119
+ padding: 10px;
120
+ background: var(--glass-bg);
121
+ border-radius: 12px;
122
+ }
123
+
124
+ .user-avatar {
125
+ width: 32px;
126
+ height: 32px;
127
+ background: linear-gradient(135deg, var(--accent-pink), var(--accent-purple));
128
+ border-radius: 8px;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ font-weight: 700;
133
+ font-size: 0.8rem;
134
+ color: white;
135
+ }
136
+
137
+ /* MAIN CONTENT (ZONE 2) */
138
+ .main-chat {
139
+ flex: 1;
140
+ display: flex;
141
+ flex-direction: column;
142
+ position: relative;
143
+ background: radial-gradient(circle at top right, rgba(244, 114, 182, 0.03), transparent);
144
+ }
145
+
146
+ /* TOP BAR */
147
+ .top-bar {
148
+ height: 70px;
149
+ background: rgba(3, 6, 23, 0.82);
150
+ backdrop-filter: blur(25px);
151
+ -webkit-backdrop-filter: blur(25px);
152
+ border-bottom: 1px solid var(--glass-border);
153
+ display: flex;
154
+ align-items: center;
155
+ justify-content: space-between;
156
+ padding: 0 40px;
157
+ z-index: 50;
158
+ }
159
+
160
+ .system-branding {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 15px;
164
+ }
165
+
166
+ .status-pill {
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 8px;
170
+ font-size: 0.75rem;
171
+ background: rgba(244, 114, 182, 0.1);
172
+ color: var(--accent-pink);
173
+ padding: 4px 12px;
174
+ border-radius: 20px;
175
+ font-weight: 600;
176
+ border: 1px solid rgba(244, 114, 182, 0.2);
177
+ }
178
+
179
+ .status-dot {
180
+ width: 6px;
181
+ height: 6px;
182
+ background: var(--accent-pink);
183
+ border-radius: 50%;
184
+ animation: pulse-dot 2s infinite;
185
+ }
186
+
187
+ @keyframes pulse-dot {
188
+ 0% { transform: scale(1); opacity: 1; }
189
+ 50% { transform: scale(1.5); opacity: 0.4; }
190
+ 100% { transform: scale(1); opacity: 1; }
191
+ }
192
+
193
+ /* MESSAGES AREA */
194
+ .messages-container {
195
+ flex: 1;
196
+ overflow-y: auto;
197
+ padding: 40px 10%;
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 30px;
201
+ scroll-behavior: smooth;
202
+ }
203
+
204
+ .message {
205
+ max-width: 850px;
206
+ animation: message-slide 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
207
+ position: relative;
208
+ }
209
+
210
+ @keyframes message-slide {
211
+ from { opacity: 0; transform: translateY(20px); }
212
+ to { opacity: 1; transform: translateY(0); }
213
+ }
214
+
215
+ .user-msg {
216
+ align-self: flex-end;
217
+ background: linear-gradient(135deg, var(--accent-pink), var(--accent-purple));
218
+ padding: 16px 24px;
219
+ border-radius: 20px 20px 4px 20px;
220
+ max-width: 70%;
221
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
222
+ font-size: 1rem;
223
+ line-height: 1.6;
224
+ color: white;
225
+ }
226
+
227
+ .ai-msg {
228
+ align-self: flex-start;
229
+ background: var(--glass-bg);
230
+ border: 1px solid var(--glass-border);
231
+ padding: 30px;
232
+ border-radius: 20px 20px 20px 4px;
233
+ max-width: 85%;
234
+ font-size: 1.05rem;
235
+ line-height: 1.8;
236
+ color: #e2e8f0;
237
+ }
238
+
239
+ .ai-msg h3 {
240
+ color: var(--accent-pink);
241
+ font-size: 1.3rem;
242
+ margin-bottom: 20px;
243
+ display: flex;
244
+ align-items: center;
245
+ gap: 12px;
246
+ font-weight: 600;
247
+ }
248
+
249
+ .ai-msg p { margin-bottom: 18px; }
250
+ .ai-msg ul { margin: 20px 0; list-style: none; }
251
+ .ai-msg li { position: relative; padding-left: 28px; margin-bottom: 15px; }
252
+
253
+ .ai-msg li::before {
254
+ content: "✦";
255
+ position: absolute;
256
+ left: 0;
257
+ color: var(--accent-pink);
258
+ font-weight: 700;
259
+ }
260
+
261
+ .highlight {
262
+ background: rgba(244, 114, 182, 0.08);
263
+ border-left: 4px solid var(--accent-pink);
264
+ padding: 22px;
265
+ margin: 25px 0;
266
+ border-radius: 0 16px 16px 0;
267
+ font-weight: 500;
268
+ color: #fff;
269
+ }
270
+
271
+ /* TYPING INDICATOR */
272
+ .typing {
273
+ display: flex;
274
+ gap: 6px;
275
+ padding: 18px 28px;
276
+ background: var(--glass-bg);
277
+ border-radius: 20px;
278
+ width: fit-content;
279
+ margin: 20px 10%;
280
+ border: 1px solid var(--glass-border);
281
+ }
282
+
283
+ .typing span {
284
+ width: 8px;
285
+ height: 8px;
286
+ background: var(--accent-pink);
287
+ border-radius: 50%;
288
+ animation: typing-blink 1.4s infinite ease-in-out;
289
+ }
290
+
291
+ .typing span:nth-child(2) { animation-delay: 0.2s; }
292
+ .typing span:nth-child(3) { animation-delay: 0.4s; }
293
+
294
+ @keyframes typing-blink {
295
+ 0%, 100% { opacity: 0.2; transform: scale(0.8); }
296
+ 50% { opacity: 1; transform: scale(1.1); }
297
+ }
298
+
299
+ /* INPUT AREA */
300
+ .input-area {
301
+ padding: 30px 10%;
302
+ background: linear-gradient(to top, var(--bg-dark) 50%, transparent);
303
+ border-top: 1px solid var(--glass-border);
304
+ z-index: 60;
305
+ }
306
+
307
+ .input-box {
308
+ position: relative;
309
+ background: rgba(255, 255, 255, 0.04);
310
+ border: 1px solid var(--glass-border);
311
+ border-radius: 18px;
312
+ padding: 6px;
313
+ display: flex;
314
+ align-items: center;
315
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
316
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
317
+ }
318
+
319
+ .input-box:focus-within {
320
+ border-color: var(--accent-pink);
321
+ box-shadow: 0 0 30px rgba(244, 114, 182, 0.2);
322
+ }
323
+
324
+ .input-box input {
325
+ flex: 1;
326
+ background: transparent;
327
+ border: none;
328
+ padding: 16px 24px;
329
+ color: white;
330
+ font-size: 1.05rem;
331
+ outline: none;
332
+ }
333
+
334
+ .send-btn {
335
+ background: var(--accent-pink);
336
+ color: white;
337
+ border: none;
338
+ width: 50px;
339
+ height: 50px;
340
+ border-radius: 14px;
341
+ display: flex;
342
+ align-items: center;
343
+ justify-content: center;
344
+ cursor: pointer;
345
+ transition: 0.3s;
346
+ margin-right: 6px;
347
+ }
348
+
349
+ .send-btn:hover {
350
+ background: var(--accent-purple);
351
+ transform: scale(1.1);
352
+ }
353
+
354
+ .hidden { display: none !important; }
355
+
356
+ @keyframes float {
357
+ 0%, 100% { transform: translateY(0); }
358
+ 50% { transform: translateY(-15px); }
359
+ }
360
+ </style>
361
+ </head>
362
+ <body>
363
+ <aside class="sidebar">
364
+ <div class="sidebar-header">
365
+ <button class="new-chat-btn" id="newChatBtn">
366
+ <span>+</span> New Case
367
+ </button>
368
+ </div>
369
+
370
+ <div class="history-label">Legal Threads</div>
371
+ <div class="history-list" id="historyList"></div>
372
+
373
+ <div class="sidebar-footer">
374
+ <div class="user-badge">
375
+ <div class="user-avatar" id="avatarName">WM</div>
376
+ <div>
377
+ <div id="usernameLabel" style="font-size: 0.85rem; font-weight: 600;">Madam</div>
378
+ <div style="font-size: 0.7rem; color: var(--text-dim);">Safe Consultation</div>
379
+ </div>
380
+ </div>
381
+ <div style="display: flex; flex-direction: column; gap: 8px; margin-top: 15px;">
382
+ <a href="/woman.html" style="color: var(--accent-pink); text-decoration: none; font-size: 0.8rem; text-align: center; font-weight: 600;">Back to Dashboard</a>
383
+ <a href="/role" style="color: var(--text-dim); text-decoration: none; font-size: 0.8rem; text-align: center;">← Change Role</a>
384
+ </div>
385
+ </div>
386
+ </aside>
387
+
388
+ <main class="main-chat">
389
+ <header class="top-bar">
390
+ <div class="system-branding">
391
+ <span style="font-weight: 700; color: white;">⚖️ Law Bot (Woman)</span>
392
+ <div class="status-pill">
393
+ <div class="status-dot"></div>
394
+ Secure Online
395
+ </div>
396
+ </div>
397
+ <div style="font-size: 0.85rem; color: var(--text-dim); font-weight: 500;">Protective Intelligence v4.0</div>
398
+ </header>
399
+
400
+ <div id="welcomeScreen" style="position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 40px; z-index: 1;">
401
+ <div style="font-size: 4rem; margin-bottom: 25px; animation: float 6s ease-in-out infinite;">🛡️</div>
402
+ <h1 style="font-size: 2.8rem; font-weight: 700; margin-bottom: 12px; letter-spacing: -1px;">Empowerment through Law.</h1>
403
+ <p style="color: var(--text-dim); max-width: 550px; line-height: 1.7; font-size: 1.1rem;">Dedicated legal guidance for your rights and safety. Your consultation is private and powered by expert AI.</p>
404
+ </div>
405
+
406
+ <div class="messages-container" id="chatContainer"></div>
407
+
408
+ <div class="typing hidden" id="typingIndicator">
409
+ <span></span><span></span><span></span>
410
+ </div>
411
+
412
+ <div class="input-area">
413
+ <div class="input-box">
414
+ <input type="text" id="messageInput" placeholder="How can I assist you with your legal rights today?" autocomplete="off">
415
+ <button class="send-btn" id="sendButton">
416
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
417
+ </button>
418
+ </div>
419
+ <p style="text-align: center; font-size: 0.75rem; color: var(--text-dim); margin-top: 18px; opacity: 0.8;">⚠️ AI provide framework for awareness. For judicial action, connect with local authorities or legal counsel.</p>
420
+ </div>
421
+ </main>
422
+
423
+ <script>
424
+ let ws;
425
+ const chatContainer = document.getElementById('chatContainer');
426
+ const messageInput = document.getElementById('messageInput');
427
+ const sendButton = document.getElementById('sendButton');
428
+ const typingIndicator = document.getElementById('typingIndicator');
429
+ const welcomeScreen = document.getElementById('welcomeScreen');
430
+ const historyList = document.getElementById('historyList');
431
+
432
+ let currentAiMessage = '';
433
+ let currentAiMessageElement = null;
434
+ let currentCaseId = crypto.randomUUID();
435
+ const activeRole = 'Woman';
436
+
437
+ function formatResponse(text) {
438
+ let formatted = text;
439
+ formatted = formatted.replace(/### (.*?)\s*(\n|$)/g, "<h3>✨ $1</h3>");
440
+ formatted = formatted.replace(/^! (.*?)$/gm, '<div class="highlight">$1</div>');
441
+ formatted = formatted.replace(/^\d+\.\s(.*?)$/gm, "<li>$1</li>");
442
+ formatted = formatted.replace(/^\*\s(.*?)$/gm, "<li>$1</li>");
443
+ formatted = formatted.replace(/- (.*?)$/gm, "<li>$1</li>");
444
+ formatted = formatted.replace(/<li>(.*?)<\/li>(?!<li>)/g, "<ul><li>$1</li></ul>");
445
+ formatted = formatted.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
446
+ formatted = formatted.replace(/\n/g, "<br>");
447
+ return formatted;
448
+ }
449
+
450
+ function connectWS() {
451
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
452
+ ws = new WebSocket(`${protocol}//${window.location.host}/conversational_chat?role=${activeRole}`);
453
+ ws.onmessage = (event) => {
454
+ typingIndicator.classList.add('hidden');
455
+ welcomeScreen.classList.add('hidden');
456
+ if (event.data === '[DONE]') {
457
+ saveInteraction(currentCaseId, messageInput.value, currentAiMessage);
458
+ return;
459
+ }
460
+ if (!currentAiMessageElement) {
461
+ currentAiMessage = event.data;
462
+ createNewMessage('ai', currentAiMessage);
463
+ } else {
464
+ currentAiMessage += event.data;
465
+ currentAiMessageElement.innerHTML = formatResponse(currentAiMessage);
466
+ }
467
+ chatContainer.scrollTop = chatContainer.scrollHeight;
468
+ };
469
+ ws.onclose = () => setTimeout(connectWS, 2000);
470
+ }
471
+
472
+ function createNewMessage(type, content) {
473
+ welcomeScreen.classList.add('hidden');
474
+ const msgDiv = document.createElement('div');
475
+ msgDiv.className = `message ${type}-msg`;
476
+ if (type === 'ai') {
477
+ msgDiv.innerHTML = formatResponse(content);
478
+ currentAiMessageElement = msgDiv;
479
+ } else {
480
+ msgDiv.textContent = content;
481
+ }
482
+ chatContainer.appendChild(msgDiv);
483
+ chatContainer.scrollTop = chatContainer.scrollHeight;
484
+ }
485
+
486
+ function sendMessage() {
487
+ const text = messageInput.value.trim();
488
+ if (!text || ws.readyState !== WebSocket.OPEN) return;
489
+ createNewMessage('user', text);
490
+ ws.send(text);
491
+ messageInput.value = '';
492
+ currentAiMessage = '';
493
+ currentAiMessageElement = null;
494
+ typingIndicator.classList.remove('hidden');
495
+ chatContainer.scrollTop = chatContainer.scrollHeight;
496
+ }
497
+
498
+ async function saveInteraction(caseId, query, response) {
499
+ const token = localStorage.getItem('token');
500
+ if (!token) return;
501
+ try {
502
+ await fetch('/api/save-interaction', {
503
+ method: 'POST',
504
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
505
+ body: JSON.stringify({ caseId, query, response, role: activeRole })
506
+ });
507
+ loadHistory();
508
+ } catch (err) { console.error(err); }
509
+ }
510
+
511
+ async function loadHistory() {
512
+ const token = localStorage.getItem('token');
513
+ if (!token) return;
514
+ try {
515
+ const res = await fetch(`/api/interactions?role=${activeRole}`, {
516
+ headers: { 'Authorization': `Bearer ${token}` }
517
+ });
518
+ const data = await res.json();
519
+ historyList.innerHTML = '';
520
+ data.forEach(item => {
521
+ const div = document.createElement('div');
522
+ div.className = 'history-item';
523
+ const preview = item.query.substring(0, 30) + "...";
524
+ div.innerHTML = `<span>💬</span> ${preview}`;
525
+ div.onclick = () => loadThread(item.case_id);
526
+ historyList.appendChild(div);
527
+ });
528
+ } catch (err) { console.error(err); }
529
+ }
530
+
531
+ async function loadThread(caseId) {
532
+ const token = localStorage.getItem('token');
533
+ try {
534
+ const res = await fetch(`/api/interactions/${caseId}?role=${activeRole}`, {
535
+ headers: { 'Authorization': `Bearer ${token}` }
536
+ });
537
+ const thread = await res.json();
538
+ currentCaseId = caseId;
539
+ chatContainer.innerHTML = '';
540
+ welcomeScreen.classList.add('hidden');
541
+ thread.forEach(msg => {
542
+ createNewMessage('user', msg.query);
543
+ createNewMessage('ai', msg.response);
544
+ });
545
+ currentAiMessageElement = null;
546
+ } catch (err) { console.error(err); }
547
+ }
548
+
549
+ async function fetchUserStatus() {
550
+ const token = localStorage.getItem('token');
551
+ if (!token) return;
552
+ try {
553
+ const res = await fetch('/api/user-status', {
554
+ headers: { 'Authorization': `Bearer ${token}` }
555
+ });
556
+ const status = await res.json();
557
+ document.getElementById('usernameLabel').innerText = status.username.charAt(0).toUpperCase() + status.username.slice(1);
558
+ document.getElementById('avatarName').innerText = status.username.substring(0, 2).toUpperCase();
559
+ } catch (err) { console.error(err); }
560
+ }
561
+
562
+ sendButton.onclick = sendMessage;
563
+ messageInput.onkeypress = (e) => { if (e.key === 'Enter') sendMessage(); };
564
+ document.getElementById('newChatBtn').onclick = () => {
565
+ currentCaseId = crypto.randomUUID();
566
+ chatContainer.innerHTML = '';
567
+ welcomeScreen.classList.remove('hidden');
568
+ };
569
+
570
+ connectWS();
571
+ loadHistory();
572
+ fetchUserStatus();
573
+ </script>
574
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  </html>