LiamKhoaLe commited on
Commit
c20f696
Β·
1 Parent(s): 3058d07

Readd submodules JS func to app.js since FastAPI cannot render modules script type. Refactored all js in wrapped section

Browse files
static/js/app.js CHANGED
@@ -1,10 +1,12 @@
1
  // Medical AI Assistant - Main Application JavaScript
2
- import { attachUIHandlers } from './ui/handlers.js';
3
- import { attachDoctorUI } from './ui/doctor.js';
4
- import { attachPatientUI } from './ui/patient.js';
5
- import { attachSettingsUI } from './ui/settings.js';
6
- import { attachSessionsUI } from './chat/sessions.js';
7
- import { attachMessagingUI } from './chat/messaging.js';
 
 
8
 
9
  class MedicalChatbotApp {
10
  constructor() {
@@ -20,14 +22,14 @@ class MedicalChatbotApp {
20
  }
21
 
22
  async init() {
23
- // Attach shared UI helpers once
24
- attachUIHandlers(this);
25
- // Attach specialized UIs
26
- attachDoctorUI(this);
27
- attachPatientUI(this);
28
- attachSettingsUI(this);
29
- attachSessionsUI(this);
30
- attachMessagingUI(this);
31
  this.setupEventListeners();
32
  this.loadUserPreferences();
33
  this.initializeUser();
@@ -45,6 +47,7 @@ class MedicalChatbotApp {
45
  // Bind patient handlers
46
  console.log('[DEBUG] Binding patient handlers');
47
  this.bindPatientHandlers();
 
48
  // Apply saved theme immediately
49
  const prefs = JSON.parse(localStorage.getItem('medicalChatbotPreferences') || '{}');
50
  this.setTheme(prefs.theme || 'auto');
@@ -313,17 +316,13 @@ class MedicalChatbotApp {
313
 
314
  getWelcomeMessage() {
315
  return `πŸ‘‹ Welcome to Medical AI Assistant
316
-
317
  I'm here to help you with medical questions, diagnosis assistance, and healthcare information. I can:
318
-
319
  πŸ” Answer medical questions and provide information
320
  πŸ“‹ Help with symptom analysis and differential diagnosis
321
  πŸ’Š Provide medication and treatment information
322
  πŸ“š Explain medical procedures and conditions
323
  ⚠️ Offer general health advice (not medical diagnosis)
324
-
325
  **Important:** This is for informational purposes only. Always consult with qualified healthcare professionals for medical advice.
326
-
327
  How can I assist you today?`;
328
  }
329
 
@@ -332,15 +331,6 @@ How can I assist you today?`;
332
  chatMessages.innerHTML = '';
333
  }
334
 
335
- updateChatTitle() {
336
- const titleElement = document.getElementById('chatTitle');
337
- if (this.currentSession) {
338
- titleElement.textContent = this.currentSession.title;
339
- } else {
340
- titleElement.textContent = 'Medical AI Assistant';
341
- }
342
- }
343
-
344
  showModal(modalId) {
345
  console.log('[DEBUG] showModal called with ID:', modalId);
346
  const modal = document.getElementById(modalId);
@@ -352,27 +342,6 @@ How can I assist you today?`;
352
  }
353
  }
354
 
355
- hideModal(modalId) {
356
- console.log('[DEBUG] hideModal called with ID:', modalId);
357
- const modal = document.getElementById(modalId);
358
- if (modal) {
359
- modal.classList.remove('show');
360
- console.log('[DEBUG] Modal hidden:', modalId);
361
- } else {
362
- console.error('[DEBUG] Modal not found:', modalId);
363
- }
364
- }
365
-
366
- showUserModal() {
367
- console.log('[DEBUG] showUserModal called');
368
- this.showModal('userModal');
369
- }
370
-
371
- showSettingsModal() {
372
- console.log('[DEBUG] showSettingsModal called');
373
- this.showModal('settingsModal');
374
- }
375
-
376
  saveSettings() {
377
  const theme = document.getElementById('themeSelect').value;
378
  const fontSize = document.getElementById('fontSize').value;
@@ -409,16 +378,107 @@ How can I assist you today?`;
409
  return Date.now().toString(36) + Math.random().toString(36).substr(2);
410
  }
411
 
 
 
412
  // ----------------------------------------------------------
413
  // Additional UI setup START
414
- // Including session.js and settings.js from ui/ and chat/
415
  // -----------------------------
416
  // Our submodules aren't lodaed on app.js, so we need to add them here
417
  // Perhaps this is FastAPI limitation, remove this when proper deploy this
418
  // On UI specific hosting site.
419
  // ----------------------------------------------------------
420
 
421
- // Additional methods that are called by the modules
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  loadUserPreferences() {
423
  const prefs = JSON.parse(localStorage.getItem('medicalChatbotPreferences') || '{}');
424
  if (prefs.theme) this.setTheme(prefs.theme);
@@ -461,139 +521,180 @@ How can I assist you today?`;
461
  localStorage.setItem('medicalChatbotPreferences', JSON.stringify(preferences));
462
  }
463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  getChatSessions() {
465
- const sessions = localStorage.getItem('medicalChatbotSessions');
466
  return sessions ? JSON.parse(sessions) : [];
467
  }
468
 
469
  saveCurrentSession() {
470
  if (!this.currentSession) return;
 
471
  const sessions = this.getChatSessions();
472
  const existingIndex = sessions.findIndex(s => s.id === this.currentSession.id);
473
  if (existingIndex >= 0) {
474
- sessions[existingIndex] = this.currentSession;
475
  } else {
476
  sessions.unshift(this.currentSession);
477
  }
478
- localStorage.setItem('medicalChatbotSessions', JSON.stringify(sessions));
479
  }
480
 
481
  loadChatSessions() {
482
  const sessionsContainer = document.getElementById('chatSessions');
483
- if (!sessionsContainer) return;
484
-
485
- // Combine backend and local sessions
486
- const allSessions = [...this.backendSessions, ...this.getChatSessions()];
487
-
488
- // Remove duplicates and sort by last activity
489
- const uniqueSessions = allSessions.reduce((acc, session) => {
490
- const existing = acc.find(s => s.id === session.id);
491
- if (!existing) {
492
- acc.push(session);
493
- } else if (session.lastActivity > existing.lastActivity) {
494
- const index = acc.indexOf(existing);
495
- acc[index] = session;
496
- }
497
- return acc;
498
- }, []);
499
-
500
- uniqueSessions.sort((a, b) => new Date(b.lastActivity) - new Date(a.lastActivity));
501
-
502
  sessionsContainer.innerHTML = '';
503
- uniqueSessions.forEach(session => {
 
 
 
 
 
504
  const sessionElement = document.createElement('div');
505
- sessionElement.className = 'chat-session';
506
- if (this.currentSession && this.currentSession.id === session.id) {
507
- sessionElement.classList.add('active');
508
- }
509
-
510
- const timeAgo = this.formatTime(session.lastActivity);
 
 
 
 
511
  sessionElement.innerHTML = `
512
  <div class="chat-session-row">
513
  <div class="chat-session-meta">
514
  <div class="chat-session-title">${session.title}</div>
515
- <div class="chat-session-time">${timeAgo}</div>
516
  </div>
517
  <div class="chat-session-actions">
518
- <button class="chat-session-menu" onclick="event.stopPropagation(); this.nextElementSibling.classList.toggle('show')">
519
- <i class="fas fa-ellipsis-v"></i>
520
  </button>
521
- <div class="chat-session-menu-popover">
522
- <div class="chat-session-menu-item" onclick="window.medicalChatbot.renameChatSession('${session.id}')">
523
- <i class="fas fa-edit"></i> Rename
524
- </div>
525
- <div class="chat-session-menu-item" onclick="window.medicalChatbot.deleteChatSession('${session.id}')">
526
- <i class="fas fa-trash"></i> Delete
527
- </div>
528
- </div>
529
  </div>
530
  </div>
531
  `;
532
-
533
- sessionElement.addEventListener('click', () => {
534
- this.loadChatSession(session.id);
535
- });
536
-
537
  sessionsContainer.appendChild(sessionElement);
 
 
 
 
 
 
 
 
 
 
 
538
  });
539
  }
540
 
 
 
 
 
 
 
 
 
 
541
  loadChatSession(sessionId) {
542
- const allSessions = [...this.backendSessions, ...this.getChatSessions()];
543
- const session = allSessions.find(s => s.id === sessionId);
544
  if (!session) return;
545
-
546
  this.currentSession = session;
547
  this.clearChatMessages();
548
-
549
- if (session.source === 'backend') {
550
- this.hydrateMessagesForSession(sessionId);
551
- } else {
552
- session.messages.forEach(m => this.displayMessage(m));
553
- }
554
-
555
  this.updateChatTitle();
556
  this.loadChatSessions();
557
  }
558
 
559
  renameChatSession(sessionId, newTitle) {
560
- const allSessions = [...this.backendSessions, ...this.getChatSessions()];
561
- const session = allSessions.find(s => s.id === sessionId);
562
- if (session) {
563
- session.title = newTitle;
564
- if (session.source === 'backend') {
565
- // Update backend session
566
- this.updateBackendSession(sessionId, { title: newTitle });
567
- } else {
568
- // Update local session
569
- this.saveCurrentSession();
570
- }
571
- this.loadChatSessions();
572
  this.updateChatTitle();
573
  }
 
574
  }
575
 
576
  deleteChatSession(sessionId) {
577
- if (confirm('Are you sure you want to delete this chat session?')) {
578
- const allSessions = [...this.backendSessions, ...this.getChatSessions()];
579
- const session = allSessions.find(s => s.id === sessionId);
580
-
581
- if (session && session.source === 'backend') {
582
- // Delete from backend
583
- this.deleteBackendSession(sessionId);
584
- } else {
585
- // Delete from local storage
586
- const sessions = this.getChatSessions();
587
- const filtered = sessions.filter(s => s.id !== sessionId);
588
- localStorage.setItem('medicalChatbotSessions', JSON.stringify(filtered));
589
- }
590
-
591
- if (this.currentSession && this.currentSession.id === sessionId) {
592
- this.startNewChat();
593
  } else {
594
- this.loadChatSessions();
 
 
595
  }
596
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  }
598
 
599
  updateBackendSession(sessionId, updates) {
@@ -606,36 +707,508 @@ How can I assist you today?`;
606
  console.log('Deleting backend session:', sessionId);
607
  }
608
 
609
- showLoading(show) {
610
- const overlay = document.getElementById('loadingOverlay');
611
- if (overlay) {
612
- if (show) {
613
- overlay.classList.add('show');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  } else {
615
- overlay.classList.remove('show');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  }
 
 
617
  }
618
- this.isLoading = show;
619
  }
620
 
621
- updateCurrentSession() {
622
- if (this.currentSession) {
623
- this.currentSession.lastActivity = new Date().toISOString();
624
- this.saveCurrentSession();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  }
626
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  }
 
 
 
 
628
 
629
  // Initialize the app when DOM is loaded
630
  document.addEventListener('DOMContentLoaded', () => {
631
  window.medicalChatbot = new MedicalChatbotApp();
632
  });
633
 
634
-
635
- // ----------------------------------------------------------
636
- // Additional UI setup END
637
- // ----------------------------------------------------------
638
-
639
  // Handle system theme changes
640
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
641
  const themeSelect = document.getElementById('themeSelect');
@@ -651,7 +1224,7 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e)
651
  const toggle = document.getElementById('sidebarToggle');
652
  if (toggle && sidebar) {
653
  toggle.addEventListener('click', () => {
654
- sidebar.classList.toggle('show');
655
  });
656
  }
657
  });
@@ -675,4 +1248,20 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e)
675
  }
676
  }
677
  });
678
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  // Medical AI Assistant - Main Application JavaScript
2
+
3
+ // TEMPORARILY DISABLED SUBMODULES
4
+ // import { attachUIHandlers } from './ui/handlers.js';
5
+ // import { attachDoctorUI } from './ui/doctor.js';
6
+ // import { attachPatientUI } from './ui/patient.js';
7
+ // import { attachSettingsUI } from './ui/settings.js';
8
+ // import { attachSessionsUI } from './chat/sessions.js';
9
+ // import { attachMessagingUI } from './chat/messaging.js';
10
 
11
  class MedicalChatbotApp {
12
  constructor() {
 
22
  }
23
 
24
  async init() {
25
+ // // TEMPORARILY DISABLED SUBMODULES ATTACHMENT
26
+ // attachUIHandlers(this);
27
+ // // Attach specialized UIs
28
+ // attachDoctorUI(this);
29
+ // attachPatientUI(this);
30
+ // attachSettingsUI(this);
31
+ // attachSessionsUI(this);
32
+ // attachMessagingUI(this);
33
  this.setupEventListeners();
34
  this.loadUserPreferences();
35
  this.initializeUser();
 
47
  // Bind patient handlers
48
  console.log('[DEBUG] Binding patient handlers');
49
  this.bindPatientHandlers();
50
+ this.setupPatientModal();
51
  // Apply saved theme immediately
52
  const prefs = JSON.parse(localStorage.getItem('medicalChatbotPreferences') || '{}');
53
  this.setTheme(prefs.theme || 'auto');
 
316
 
317
  getWelcomeMessage() {
318
  return `πŸ‘‹ Welcome to Medical AI Assistant
 
319
  I'm here to help you with medical questions, diagnosis assistance, and healthcare information. I can:
 
320
  πŸ” Answer medical questions and provide information
321
  πŸ“‹ Help with symptom analysis and differential diagnosis
322
  πŸ’Š Provide medication and treatment information
323
  πŸ“š Explain medical procedures and conditions
324
  ⚠️ Offer general health advice (not medical diagnosis)
 
325
  **Important:** This is for informational purposes only. Always consult with qualified healthcare professionals for medical advice.
 
326
  How can I assist you today?`;
327
  }
328
 
 
331
  chatMessages.innerHTML = '';
332
  }
333
 
 
 
 
 
 
 
 
 
 
334
  showModal(modalId) {
335
  console.log('[DEBUG] showModal called with ID:', modalId);
336
  const modal = document.getElementById(modalId);
 
342
  }
343
  }
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  saveSettings() {
346
  const theme = document.getElementById('themeSelect').value;
347
  const fontSize = document.getElementById('fontSize').value;
 
378
  return Date.now().toString(36) + Math.random().toString(36).substr(2);
379
  }
380
 
381
+
382
+
383
  // ----------------------------------------------------------
384
  // Additional UI setup START
 
385
  // -----------------------------
386
  // Our submodules aren't lodaed on app.js, so we need to add them here
387
  // Perhaps this is FastAPI limitation, remove this when proper deploy this
388
  // On UI specific hosting site.
389
  // ----------------------------------------------------------
390
 
391
+
392
+ // ================================================================================
393
+ // HANDLERS.JS FUNCTIONALITY
394
+ // ================================================================================
395
+ toggleSidebar() {
396
+ const sidebar = document.getElementById('sidebar');
397
+ console.log('[DEBUG] toggleSidebar called');
398
+ if (sidebar) {
399
+ const wasOpen = sidebar.classList.contains('show');
400
+ sidebar.classList.toggle('show');
401
+ const isNowOpen = sidebar.classList.contains('show');
402
+ console.log('[DEBUG] Sidebar toggled - was open:', wasOpen, 'now open:', isNowOpen);
403
+ } else {
404
+ console.error('[DEBUG] Sidebar element not found');
405
+ }
406
+ }
407
+
408
+ autoResizeTextarea(textarea) {
409
+ if (!textarea) return;
410
+ textarea.style.height = 'auto';
411
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
412
+ }
413
+
414
+ exportChat() {
415
+ if (!this.currentSession || this.currentSession.messages.length === 0) {
416
+ alert('No chat to export.');
417
+ return;
418
+ }
419
+ const chatData = {
420
+ user: this.currentUser?.name || 'Unknown',
421
+ session: this.currentSession.title,
422
+ date: new Date().toISOString(),
423
+ messages: this.currentSession.messages
424
+ };
425
+ const blob = new Blob([JSON.stringify(chatData, null, 2)], { type: 'application/json' });
426
+ const url = URL.createObjectURL(blob);
427
+ const a = document.createElement('a');
428
+ a.href = url;
429
+ a.download = `medical-chat-${this.currentSession.title.replace(/[^a-z0-9]/gi, '-')}.json`;
430
+ document.body.appendChild(a);
431
+ a.click();
432
+ document.body.removeChild(a);
433
+ URL.revokeObjectURL(url);
434
+ }
435
+
436
+ clearChat() {
437
+ if (confirm('Are you sure you want to clear this chat? This action cannot be undone.')) {
438
+ this.clearChatMessages();
439
+ if (this.currentSession) {
440
+ this.currentSession.messages = [];
441
+ this.currentSession.title = 'New Chat';
442
+ this.updateChatTitle();
443
+ }
444
+ }
445
+ }
446
+
447
+ hideModal(modalId) {
448
+ console.log('[DEBUG] hideModal called with ID:', modalId);
449
+ const modal = document.getElementById(modalId);
450
+ if (modal) {
451
+ modal.classList.remove('show');
452
+ console.log('[DEBUG] Modal hidden:', modalId);
453
+ } else {
454
+ console.error('[DEBUG] Modal not found:', modalId);
455
+ }
456
+ }
457
+
458
+ showUserModal() {
459
+ this.populateDoctorSelect();
460
+ const sel = document.getElementById('profileNameSelect');
461
+ if (sel && sel.options.length === 0) {
462
+ const createOpt = document.createElement('option');
463
+ createOpt.value = '__create__';
464
+ createOpt.textContent = 'Create doctor user...';
465
+ sel.appendChild(createOpt);
466
+ }
467
+ if (sel && !sel.value) sel.value = this.currentUser?.name || '__create__';
468
+ document.getElementById('profileRole').value = this.currentUser.role;
469
+ document.getElementById('profileSpecialty').value = this.currentUser.specialty || '';
470
+ this.showModal('userModal');
471
+ }
472
+
473
+ showSettingsModal() {
474
+ console.log('[DEBUG] showSettingsModal called');
475
+ this.showModal('settingsModal');
476
+ }
477
+
478
+
479
+ // ================================================================================
480
+ // SETTINGS.JS FUNCTIONALITY
481
+ // ================================================================================
482
  loadUserPreferences() {
483
  const prefs = JSON.parse(localStorage.getItem('medicalChatbotPreferences') || '{}');
484
  if (prefs.theme) this.setTheme(prefs.theme);
 
521
  localStorage.setItem('medicalChatbotPreferences', JSON.stringify(preferences));
522
  }
523
 
524
+ showLoading(show) {
525
+ const overlay = document.getElementById('loadingOverlay');
526
+ if (overlay) {
527
+ if (show) {
528
+ overlay.classList.add('show');
529
+ } else {
530
+ overlay.classList.remove('show');
531
+ }
532
+ }
533
+ this.isLoading = show;
534
+ }
535
+
536
+
537
+ // ================================================================================
538
+ // SESSIONS.JS FUNCTIONALITY
539
+ // ================================================================================
540
  getChatSessions() {
541
+ const sessions = localStorage.getItem(`chatSessions_${this.currentUser.id}`);
542
  return sessions ? JSON.parse(sessions) : [];
543
  }
544
 
545
  saveCurrentSession() {
546
  if (!this.currentSession) return;
547
+ if (this.currentSession.source === 'backend') return; // do not persist backend sessions locally here
548
  const sessions = this.getChatSessions();
549
  const existingIndex = sessions.findIndex(s => s.id === this.currentSession.id);
550
  if (existingIndex >= 0) {
551
+ sessions[existingIndex] = { ...this.currentSession };
552
  } else {
553
  sessions.unshift(this.currentSession);
554
  }
555
+ localStorage.setItem(`chatSessions_${this.currentUser.id}`, JSON.stringify(sessions));
556
  }
557
 
558
  loadChatSessions() {
559
  const sessionsContainer = document.getElementById('chatSessions');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  sessionsContainer.innerHTML = '';
561
+ const sessions = (this.backendSessions && this.backendSessions.length > 0) ? this.backendSessions : this.getChatSessions();
562
+ if (sessions.length === 0) {
563
+ sessionsContainer.innerHTML = '<div class="no-sessions">No chat sessions yet</div>';
564
+ return;
565
+ }
566
+ sessions.forEach(session => {
567
  const sessionElement = document.createElement('div');
568
+ sessionElement.className = `chat-session ${session.id === this.currentSession?.id ? 'active' : ''}`;
569
+ sessionElement.addEventListener('click', async () => {
570
+ if (session.source === 'backend') {
571
+ this.currentSession = { ...session };
572
+ await this.hydrateMessagesForSession(session.id);
573
+ } else {
574
+ this.loadChatSession(session.id);
575
+ }
576
+ });
577
+ const time = this.formatTime(session.lastActivity);
578
  sessionElement.innerHTML = `
579
  <div class="chat-session-row">
580
  <div class="chat-session-meta">
581
  <div class="chat-session-title">${session.title}</div>
582
+ <div class="chat-session-time">${time}</div>
583
  </div>
584
  <div class="chat-session-actions">
585
+ <button class="chat-session-menu" title="Options" aria-label="Options" data-session-id="${session.id}">
586
+ <i class="fas fa-ellipsis-vertical"></i>
587
  </button>
 
 
 
 
 
 
 
 
588
  </div>
589
  </div>
590
  `;
 
 
 
 
 
591
  sessionsContainer.appendChild(sessionElement);
592
+ const menuBtn = sessionElement.querySelector('.chat-session-menu');
593
+ if (session.source !== 'backend') {
594
+ menuBtn.addEventListener('click', (e) => {
595
+ e.stopPropagation();
596
+ this.showSessionMenu(e.currentTarget, session.id);
597
+ });
598
+ } else {
599
+ menuBtn.disabled = true;
600
+ menuBtn.style.opacity = 0.5;
601
+ menuBtn.title = 'Options available for local sessions only';
602
+ }
603
  });
604
  }
605
 
606
+ updateChatTitle() {
607
+ const titleElement = document.getElementById('chatTitle');
608
+ if (this.currentSession) {
609
+ titleElement.textContent = this.currentSession.title;
610
+ } else {
611
+ titleElement.textContent = 'Medical AI Assistant';
612
+ }
613
+ }
614
+
615
  loadChatSession(sessionId) {
616
+ const sessions = this.getChatSessions();
617
+ const session = sessions.find(s => s.id === sessionId);
618
  if (!session) return;
 
619
  this.currentSession = session;
620
  this.clearChatMessages();
621
+ session.messages.forEach(message => this.displayMessage(message));
 
 
 
 
 
 
622
  this.updateChatTitle();
623
  this.loadChatSessions();
624
  }
625
 
626
  renameChatSession(sessionId, newTitle) {
627
+ const sessions = this.getChatSessions();
628
+ const idx = sessions.findIndex(s => s.id === sessionId);
629
+ if (idx === -1) return;
630
+ sessions[idx] = { ...sessions[idx], title: newTitle };
631
+ localStorage.setItem(`chatSessions_${this.currentUser.id}`, JSON.stringify(sessions));
632
+ if (this.currentSession && this.currentSession.id === sessionId) {
633
+ this.currentSession.title = newTitle;
 
 
 
 
 
634
  this.updateChatTitle();
635
  }
636
+ this.loadChatSessions();
637
  }
638
 
639
  deleteChatSession(sessionId) {
640
+ const sessions = this.getChatSessions();
641
+ const index = sessions.findIndex(s => s.id === sessionId);
642
+ if (index === -1) return;
643
+ const confirmDelete = confirm('Delete this chat session? This cannot be undone.');
644
+ if (!confirmDelete) return;
645
+ sessions.splice(index, 1);
646
+ localStorage.setItem(`chatSessions_${this.currentUser.id}`, JSON.stringify(sessions));
647
+ if (this.currentSession && this.currentSession.id === sessionId) {
648
+ if (sessions.length > 0) {
649
+ this.currentSession = sessions[0];
650
+ this.clearChatMessages();
651
+ this.currentSession.messages.forEach(m => this.displayMessage(m));
652
+ this.updateChatTitle();
 
 
 
653
  } else {
654
+ this.currentSession = null;
655
+ this.clearChatMessages();
656
+ this.updateChatTitle();
657
  }
658
  }
659
+ this.loadChatSessions();
660
+ }
661
+
662
+ showSessionMenu(anchorEl, sessionId) {
663
+ // Remove existing popover
664
+ document.querySelectorAll('.chat-session-menu-popover').forEach(p => p.remove());
665
+ const rect = anchorEl.getBoundingClientRect();
666
+ const pop = document.createElement('div');
667
+ pop.className = 'chat-session-menu-popover show';
668
+ pop.innerHTML = `
669
+ <div class="chat-session-menu-item" data-action="edit" data-session-id="${sessionId}"><i class="fas fa-pen"></i> Edit Name</div>
670
+ <div class="chat-session-menu-item" data-action="delete" data-session-id="${sessionId}"><i class="fas fa-trash"></i> Delete</div>
671
+ `;
672
+ document.body.appendChild(pop);
673
+ pop.style.top = `${rect.bottom + window.scrollY + 6}px`;
674
+ pop.style.left = `${rect.right + window.scrollX - pop.offsetWidth}px`;
675
+ const onDocClick = (ev) => {
676
+ if (!pop.contains(ev.target) && ev.target !== anchorEl) {
677
+ pop.remove();
678
+ document.removeEventListener('click', onDocClick);
679
+ }
680
+ };
681
+ setTimeout(() => document.addEventListener('click', onDocClick), 0);
682
+ pop.querySelectorAll('.chat-session-menu-item').forEach(item => {
683
+ item.addEventListener('click', (e) => {
684
+ const action = item.getAttribute('data-action');
685
+ const id = item.getAttribute('data-session-id');
686
+ if (action === 'delete') this.deleteChatSession(id);
687
+ else if (action === 'edit') {
688
+ this._pendingEditSessionId = id;
689
+ const sessions = this.getChatSessions();
690
+ const s = sessions.find(x => x.id === id);
691
+ const input = document.getElementById('editSessionTitleInput');
692
+ if (input) input.value = s ? s.title : '';
693
+ this.showModal('editTitleModal');
694
+ }
695
+ pop.remove();
696
+ });
697
+ });
698
  }
699
 
700
  updateBackendSession(sessionId, updates) {
 
707
  console.log('Deleting backend session:', sessionId);
708
  }
709
 
710
+ updateCurrentSession() {
711
+ if (this.currentSession) {
712
+ this.currentSession.lastActivity = new Date().toISOString();
713
+ this.saveCurrentSession();
714
+ }
715
+ }
716
+
717
+
718
+ // ================================================================================
719
+ // DOCTOR.JS FUNCTIONALITY
720
+ // ================================================================================
721
+ loadDoctors() {
722
+ try {
723
+ const raw = localStorage.getItem('medicalChatbotDoctors');
724
+ const arr = raw ? JSON.parse(raw) : [];
725
+ const seen = new Set();
726
+ return arr.filter(x => x && x.name && !seen.has(x.name) && seen.add(x.name));
727
+ } catch { return []; }
728
+ }
729
+
730
+ saveDoctors() {
731
+ localStorage.setItem('medicalChatbotDoctors', JSON.stringify(this.doctors));
732
+ }
733
+
734
+ populateDoctorSelect() {
735
+ const sel = document.getElementById('profileNameSelect');
736
+ const newSec = document.getElementById('newDoctorSection');
737
+ if (!sel) return;
738
+ sel.innerHTML = '';
739
+ const createOpt = document.createElement('option');
740
+ createOpt.value = '__create__';
741
+ createOpt.textContent = 'Create doctor user...';
742
+ sel.appendChild(createOpt);
743
+ // Ensure no duplicates, include current doctor
744
+ const names = new Set(this.doctors.map(d => d.name));
745
+ if (this.currentUser?.name && !names.has(this.currentUser.name)) {
746
+ this.doctors.unshift({ name: this.currentUser.name });
747
+ names.add(this.currentUser.name);
748
+ this.saveDoctors();
749
+ }
750
+ this.doctors.forEach(d => {
751
+ const opt = document.createElement('option');
752
+ opt.value = d.name;
753
+ opt.textContent = d.name;
754
+ if (this.currentUser?.name === d.name) opt.selected = true;
755
+ sel.appendChild(opt);
756
+ });
757
+ sel.addEventListener('change', () => {
758
+ if (sel.value === '__create__') {
759
+ newSec.style.display = '';
760
+ const input = document.getElementById('newDoctorName');
761
+ if (input) input.value = '';
762
  } else {
763
+ newSec.style.display = 'none';
764
+ }
765
+ });
766
+ const cancelBtn = document.getElementById('cancelNewDoctor');
767
+ const confirmBtn = document.getElementById('confirmNewDoctor');
768
+ if (cancelBtn) cancelBtn.onclick = () => { newSec.style.display = 'none'; sel.value = this.currentUser?.name || ''; };
769
+ if (confirmBtn) confirmBtn.onclick = () => {
770
+ const name = (document.getElementById('newDoctorName').value || '').trim();
771
+ if (!name) return;
772
+ if (!this.doctors.find(d => d.name === name)) {
773
+ this.doctors.unshift({ name });
774
+ this.saveDoctors();
775
+ }
776
+ this.populateDoctorSelect();
777
+ sel.value = name;
778
+ newSec.style.display = 'none';
779
+ };
780
+ }
781
+
782
+ saveUserProfile() {
783
+ const nameSel = document.getElementById('profileNameSelect');
784
+ const name = nameSel ? nameSel.value : '';
785
+ const role = document.getElementById('profileRole').value;
786
+ const specialty = document.getElementById('profileSpecialty').value.trim();
787
+
788
+ if (!name || name === '__create__') {
789
+ alert('Please select or create a doctor name.');
790
+ return;
791
+ }
792
+
793
+ if (!this.doctors.find(d => d.name === name)) {
794
+ this.doctors.unshift({ name });
795
+ this.saveDoctors();
796
+ }
797
+
798
+ this.currentUser.name = name;
799
+ this.currentUser.role = role;
800
+ this.currentUser.specialty = specialty;
801
+
802
+ this.saveUser();
803
+ this.updateUserDisplay();
804
+ this.hideModal('userModal');
805
+ }
806
+
807
+ // ================================================================================
808
+ // PATIENT.JS FUNCTIONALITY
809
+ // ================================================================================
810
+ loadSavedPatientId() {
811
+ const pid = localStorage.getItem('medicalChatbotPatientId');
812
+ if (pid && /^\d{8}$/.test(pid)) {
813
+ this.currentPatientId = pid;
814
+ const status = document.getElementById('patientStatus');
815
+ if (status) {
816
+ status.textContent = `Patient: ${pid}`;
817
+ status.style.color = 'var(--text-secondary)';
818
  }
819
+ const input = document.getElementById('patientIdInput');
820
+ if (input) input.value = pid;
821
  }
 
822
  }
823
 
824
+ savePatientId() {
825
+ if (this.currentPatientId) localStorage.setItem('medicalChatbotPatientId', this.currentPatientId);
826
+ else localStorage.removeItem('medicalChatbotPatientId');
827
+ }
828
+
829
+ loadPatient = async function () {
830
+ console.log('[DEBUG] loadPatient called');
831
+ const input = document.getElementById('patientIdInput');
832
+ const status = document.getElementById('patientStatus');
833
+ const id = (input?.value || '').trim();
834
+ console.log('[DEBUG] Patient ID from input:', id);
835
+ if (!/^\d{8}$/.test(id)) {
836
+ console.log('[DEBUG] Invalid patient ID format');
837
+ if (status) { status.textContent = 'Invalid patient ID. Use 8 digits.'; status.style.color = 'var(--warning-color)'; }
838
+ return;
839
+ }
840
+ console.log('[DEBUG] Setting current patient ID:', id);
841
+ this.currentPatientId = id;
842
+ this.savePatientId();
843
+ if (status) { status.textContent = `Patient: ${id}`; status.style.color = 'var(--text-secondary)'; }
844
+ await this.fetchAndRenderPatientSessions();
845
+ }
846
+
847
+ fetchAndRenderPatientSessions = async function () {
848
+ if (!this.currentPatientId) return;
849
+ try {
850
+ const resp = await fetch(`/patients/${this.currentPatientId}/sessions`);
851
+ if (resp.ok) {
852
+ const data = await resp.json();
853
+ const sessions = Array.isArray(data.sessions) ? data.sessions : [];
854
+ this.backendSessions = sessions.map(s => ({
855
+ id: s.session_id,
856
+ title: s.title || 'New Chat',
857
+ messages: [],
858
+ createdAt: s.created_at || new Date().toISOString(),
859
+ lastActivity: s.last_activity || new Date().toISOString(),
860
+ source: 'backend'
861
+ }));
862
+ if (this.backendSessions.length > 0) {
863
+ this.currentSession = this.backendSessions[0];
864
+ await this.hydrateMessagesForSession(this.currentSession.id);
865
+ }
866
+ } else {
867
+ console.warn('Failed to fetch patient sessions', resp.status);
868
+ this.backendSessions = [];
869
+ }
870
+ } catch (e) {
871
+ console.error('Failed to load patient sessions', e);
872
+ this.backendSessions = [];
873
+ }
874
+ this.loadChatSessions();
875
+ }
876
+
877
+ hydrateMessagesForSession = async function (sessionId) {
878
+ try {
879
+ const resp = await fetch(`/sessions/${sessionId}/messages?limit=1000`);
880
+ if (!resp.ok) return;
881
+ const data = await resp.json();
882
+ const msgs = Array.isArray(data.messages) ? data.messages : [];
883
+ const normalized = msgs.map(m => ({
884
+ id: m._id || this.generateId(),
885
+ role: m.role,
886
+ content: m.content,
887
+ timestamp: m.timestamp
888
+ }));
889
+ if (this.currentSession && this.currentSession.id === sessionId) {
890
+ this.currentSession.messages = normalized;
891
+ this.clearChatMessages();
892
+ this.currentSession.messages.forEach(m => this.displayMessage(m));
893
+ this.updateChatTitle();
894
+ }
895
+ } catch (e) {
896
+ console.error('Failed to hydrate session messages', e);
897
+ }
898
+ }
899
+
900
+ bindPatientHandlers() {
901
+ console.log('[DEBUG] bindPatientHandlers called');
902
+ const loadBtn = document.getElementById('loadPatientBtn');
903
+ console.log('[DEBUG] Load button found:', !!loadBtn);
904
+ if (loadBtn) loadBtn.addEventListener('click', () => this.loadPatient());
905
+ const patientInput = document.getElementById('patientIdInput');
906
+ const suggestionsEl = document.getElementById('patientSuggestions');
907
+ console.log('[DEBUG] Patient input found:', !!patientInput);
908
+ console.log('[DEBUG] Suggestions element found:', !!suggestionsEl);
909
+ if (!patientInput) return;
910
+ let debounceTimer;
911
+ const hideSuggestions = () => { if (suggestionsEl) suggestionsEl.style.display = 'none'; };
912
+ const renderSuggestions = (items) => {
913
+ if (!suggestionsEl) return;
914
+ if (!items || items.length === 0) { hideSuggestions(); return; }
915
+ suggestionsEl.innerHTML = '';
916
+ items.forEach(p => {
917
+ const div = document.createElement('div');
918
+ div.className = 'patient-suggestion';
919
+ div.textContent = `${p.name || 'Unknown'} (${p.patient_id})`;
920
+ div.addEventListener('click', async () => {
921
+ this.currentPatientId = p.patient_id;
922
+ this.savePatientId();
923
+ patientInput.value = p.patient_id;
924
+ hideSuggestions();
925
+ const status = document.getElementById('patientStatus');
926
+ if (status) { status.textContent = `Patient: ${p.patient_id}`; status.style.color = 'var(--text-secondary)'; }
927
+ await this.fetchAndRenderPatientSessions();
928
+ });
929
+ suggestionsEl.appendChild(div);
930
+ });
931
+ suggestionsEl.style.display = 'block';
932
+ };
933
+ patientInput.addEventListener('input', () => {
934
+ const q = patientInput.value.trim();
935
+ console.log('[DEBUG] Patient input changed:', q);
936
+ clearTimeout(debounceTimer);
937
+ if (!q) { hideSuggestions(); return; }
938
+ debounceTimer = setTimeout(async () => {
939
+ try {
940
+ console.log('[DEBUG] Searching patients with query:', q);
941
+ const resp = await fetch(`/patients/search?q=${encodeURIComponent(q)}&limit=8`, { headers: { 'Accept': 'application/json' } });
942
+ console.log('[DEBUG] Search response status:', resp.status);
943
+ if (resp.ok) {
944
+ const data = await resp.json();
945
+ console.log('[DEBUG] Search results:', data);
946
+ renderSuggestions(data.results || []);
947
+ } else {
948
+ console.warn('Search request failed', resp.status);
949
+ }
950
+ } catch (e) {
951
+ console.error('[DEBUG] Search error:', e);
952
+ }
953
+ }, 200);
954
+ });
955
+ patientInput.addEventListener('keydown', async (e) => {
956
+ if (e.key === 'Enter') {
957
+ const value = patientInput.value.trim();
958
+ console.log('[DEBUG] Patient input Enter pressed with value:', value);
959
+ if (/^\d{8}$/.test(value)) {
960
+ console.log('[DEBUG] Loading patient with 8-digit ID');
961
+ await this.loadPatient();
962
+ hideSuggestions();
963
+ } else {
964
+ console.log('[DEBUG] Searching for patient by name/partial ID');
965
+ try {
966
+ const resp = await fetch(`/patients/search?q=${encodeURIComponent(value)}&limit=1`);
967
+ console.log('[DEBUG] Search response status:', resp.status);
968
+ if (resp.ok) {
969
+ const data = await resp.json();
970
+ console.log('[DEBUG] Search results for Enter:', data);
971
+ const first = (data.results || [])[0];
972
+ if (first) {
973
+ console.log('[DEBUG] Found patient, setting as current:', first);
974
+ this.currentPatientId = first.patient_id;
975
+ this.savePatientId();
976
+ patientInput.value = first.patient_id;
977
+ hideSuggestions();
978
+ const status = document.getElementById('patientStatus');
979
+ if (status) { status.textContent = `Patient: ${first.patient_id}`; status.style.color = 'var(--text-secondary)'; }
980
+ await this.fetchAndRenderPatientSessions();
981
+ return;
982
+ }
983
+ }
984
+ } catch (e) {
985
+ console.error('[DEBUG] Search error on Enter:', e);
986
+ }
987
+ const status = document.getElementById('patientStatus');
988
+ if (status) { status.textContent = 'No matching patient found'; status.style.color = 'var(--warning-color)'; }
989
+ }
990
+ }
991
+ });
992
+ document.addEventListener('click', (ev) => {
993
+ if (!suggestionsEl) return;
994
+ if (!suggestionsEl.contains(ev.target) && ev.target !== patientInput) hideSuggestions();
995
+ });
996
+ }
997
+
998
+ // Patient modal functionality
999
+ setupPatientModal() {
1000
+ const profileBtn = document.getElementById('patientMenuBtn');
1001
+ const modal = document.getElementById('patientModal');
1002
+ const closeBtn = document.getElementById('patientModalClose');
1003
+ const logoutBtn = document.getElementById('patientLogoutBtn');
1004
+ const createBtn = document.getElementById('patientCreateBtn');
1005
+
1006
+ if (profileBtn && modal) {
1007
+ profileBtn.addEventListener('click', async () => {
1008
+ const pid = this?.currentPatientId;
1009
+ if (pid) {
1010
+ try {
1011
+ const resp = await fetch(`/patients/${pid}`);
1012
+ if (resp.ok) {
1013
+ const p = await resp.json();
1014
+ const name = p.name || 'Unknown';
1015
+ const age = typeof p.age === 'number' ? p.age : '-';
1016
+ const sex = p.sex || '-';
1017
+ const meds = Array.isArray(p.medications) && p.medications.length > 0 ? p.medications.join(', ') : '-';
1018
+ document.getElementById('patientSummary').textContent = `${name} β€” ${sex}, ${age}`;
1019
+ document.getElementById('patientMedications').textContent = meds;
1020
+ document.getElementById('patientAssessment').textContent = p.past_assessment_summary || '-';
1021
+ }
1022
+ } catch (e) {
1023
+ console.error('Failed to load patient profile', e);
1024
+ }
1025
+ }
1026
+ modal.classList.add('show');
1027
+ });
1028
+ }
1029
+
1030
+ if (closeBtn && modal) {
1031
+ closeBtn.addEventListener('click', () => modal.classList.remove('show'));
1032
+ modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('show'); });
1033
+ }
1034
+
1035
+ if (logoutBtn) {
1036
+ logoutBtn.addEventListener('click', () => {
1037
+ if (confirm('Log out current patient?')) {
1038
+ this.currentPatientId = null;
1039
+ localStorage.removeItem('medicalChatbotPatientId');
1040
+ const status = document.getElementById('patientStatus');
1041
+ if (status) { status.textContent = 'No patient selected'; status.style.color = 'var(--text-secondary)'; }
1042
+ const input = document.getElementById('patientIdInput');
1043
+ if (input) input.value = '';
1044
+ modal.classList.remove('show');
1045
+ }
1046
+ });
1047
+ }
1048
+
1049
+ if (createBtn) createBtn.addEventListener('click', () => modal.classList.remove('show'));
1050
+ }
1051
+
1052
+ // ================================================================================
1053
+ // MESSAGING.JS FUNCTIONALITY
1054
+ // ================================================================================
1055
+ sendMessage = async function () {
1056
+ const input = document.getElementById('chatInput');
1057
+ const message = input.value.trim();
1058
+ if (!message || this.isLoading) return;
1059
+ if (!this.currentPatientId) {
1060
+ const status = document.getElementById('patientStatus');
1061
+ if (status) { status.textContent = 'Select a patient before chatting.'; status.style.color = 'var(--warning-color)'; }
1062
+ return;
1063
+ }
1064
+ input.value = '';
1065
+ this.autoResizeTextarea(input);
1066
+ this.addMessage('user', message);
1067
+ this.showLoading(true);
1068
+ try {
1069
+ const response = await this.callMedicalAPI(message);
1070
+ this.addMessage('assistant', response);
1071
+ this.updateCurrentSession();
1072
+ } catch (error) {
1073
+ console.error('Error sending message:', error);
1074
+ let errorMessage = 'I apologize, but I encountered an error processing your request.';
1075
+ if (error.message.includes('500')) errorMessage = 'The server encountered an internal error. Please try again in a moment.';
1076
+ else if (error.message.includes('404')) errorMessage = 'The requested service was not found. Please check your connection.';
1077
+ else if (error.message.includes('fetch')) errorMessage = 'Unable to connect to the server. Please check your internet connection.';
1078
+ this.addMessage('assistant', errorMessage);
1079
+ } finally {
1080
+ this.showLoading(false);
1081
+ }
1082
+ }
1083
+
1084
+ callMedicalAPI = async function (message) {
1085
+ try {
1086
+ const response = await fetch('/chat', {
1087
+ method: 'POST',
1088
+ headers: { 'Content-Type': 'application/json' },
1089
+ body: JSON.stringify({
1090
+ user_id: this.currentUser.id,
1091
+ patient_id: this.currentPatientId,
1092
+ doctor_id: this.currentUser.id,
1093
+ session_id: this.currentSession?.id || 'default',
1094
+ message: message,
1095
+ user_role: this.currentUser.role,
1096
+ user_specialty: this.currentUser.specialty,
1097
+ title: this.currentSession?.title || 'New Chat'
1098
+ })
1099
+ });
1100
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
1101
+ const data = await response.json();
1102
+ return data.response || 'I apologize, but I received an empty response. Please try again.';
1103
+ } catch (error) {
1104
+ console.error('API call failed:', error);
1105
+ console.error('Error details:', {
1106
+ message: error.message,
1107
+ stack: error.stack,
1108
+ user: this.currentUser,
1109
+ session: this.currentSession,
1110
+ patientId: this.currentPatientId
1111
+ });
1112
+ if (error.name === 'TypeError' && error.message.includes('fetch')) return this.generateMockResponse(message);
1113
+ throw error;
1114
+ }
1115
+ }
1116
+
1117
+ generateMockResponse(message) {
1118
+ const responses = [
1119
+ "Based on your question about medical topics, I can provide general information. However, please remember that this is for educational purposes only and should not replace professional medical advice.",
1120
+ "That's an interesting medical question. While I can offer some general insights, it's important to consult with healthcare professionals for personalized medical advice.",
1121
+ "I understand your medical inquiry. For accurate diagnosis and treatment recommendations, please consult with qualified healthcare providers who can assess your specific situation.",
1122
+ "Thank you for your medical question. I can provide educational information, but medical decisions should always be made in consultation with healthcare professionals.",
1123
+ "I appreciate your interest in medical topics. Remember that medical information found online should be discussed with healthcare providers for proper evaluation."
1124
+ ];
1125
+ return responses[Math.floor(Math.random() * responses.length)];
1126
+ }
1127
+
1128
+ addMessage(role, content) {
1129
+ if (!this.currentSession) this.startNewChat();
1130
+ const message = { id: this.generateId(), role, content, timestamp: new Date().toISOString() };
1131
+ this.currentSession.messages.push(message);
1132
+ this.displayMessage(message);
1133
+ if (role === 'user' && this.currentSession.messages.length === 2) this.summariseAndSetTitle(content);
1134
+ }
1135
+
1136
+ summariseAndSetTitle = async function (text) {
1137
+ try {
1138
+ const resp = await fetch('/summarise', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, max_words: 5 }) });
1139
+ if (resp.ok) {
1140
+ const data = await resp.json();
1141
+ const title = (data.title || 'New Chat').trim();
1142
+ this.currentSession.title = title;
1143
+ this.updateCurrentSession();
1144
+ this.updateChatTitle();
1145
+ this.loadChatSessions();
1146
+ } else {
1147
+ const fallback = text.length > 50 ? text.substring(0, 50) + '...' : text;
1148
+ this.currentSession.title = fallback;
1149
+ this.updateCurrentSession();
1150
+ this.updateChatTitle();
1151
+ this.loadChatSessions();
1152
+ }
1153
+ } catch (e) {
1154
+ const fallback = text.length > 50 ? text.substring(0, 50) + '...' : text;
1155
+ this.currentSession.title = fallback;
1156
+ this.updateCurrentSession();
1157
+ this.updateChatTitle();
1158
+ this.loadChatSessions();
1159
  }
1160
  }
1161
+
1162
+ displayMessage(message) {
1163
+ const chatMessages = document.getElementById('chatMessages');
1164
+ const messageElement = document.createElement('div');
1165
+ messageElement.className = `message ${message.role}-message fade-in`;
1166
+ messageElement.id = `message-${message.id}`;
1167
+ const avatar = message.role === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>';
1168
+ const time = this.formatTime(message.timestamp);
1169
+ messageElement.innerHTML = `
1170
+ <div class="message-avatar">${avatar}</div>
1171
+ <div class="message-content">
1172
+ <div class="message-text">${this.formatMessageContent(message.content)}</div>
1173
+ <div class="message-time">${time}</div>
1174
+ </div>`;
1175
+ chatMessages.appendChild(messageElement);
1176
+ chatMessages.scrollTop = chatMessages.scrollHeight;
1177
+ if (this.currentSession) this.currentSession.lastActivity = new Date().toISOString();
1178
+ }
1179
+
1180
+ formatMessageContent(content) {
1181
+ return content
1182
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
1183
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
1184
+ .replace(/\n/g, '<br>')
1185
+ .replace(/πŸ”/g, '<span style="color: var(--primary-color);">πŸ”</span>')
1186
+ .replace(/πŸ“‹/g, '<span style="color: var(--secondary-color);">πŸ“‹</span>')
1187
+ .replace(/πŸ’Š/g, '<span style="color: var(--accent-color);">πŸ’Š</span>')
1188
+ .replace(/πŸ“š/g, '<span style="color: var(--success-color);">πŸ“š</span>')
1189
+ .replace(/⚠️/g, '<span style="color: var(--warning-color);">⚠️</span>');
1190
+ }
1191
+
1192
+ formatTime(timestamp) {
1193
+ const date = new Date(timestamp);
1194
+ const now = new Date();
1195
+ const diff = now - date;
1196
+ if (diff < 60000) return 'Just now';
1197
+ if (diff < 3600000) { const minutes = Math.floor(diff / 60000); return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; }
1198
+ if (diff < 86400000) { const hours = Math.floor(diff / 3600000); return `${hours} hour${hours > 1 ? 's' : ''} ago`; }
1199
+ return date.toLocaleDateString();
1200
+ }
1201
  }
1202
+ // ----------------------------------------------------------
1203
+ // Additional UI setup END
1204
+ // ----------------------------------------------------------
1205
+
1206
 
1207
  // Initialize the app when DOM is loaded
1208
  document.addEventListener('DOMContentLoaded', () => {
1209
  window.medicalChatbot = new MedicalChatbotApp();
1210
  });
1211
 
 
 
 
 
 
1212
  // Handle system theme changes
1213
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
1214
  const themeSelect = document.getElementById('themeSelect');
 
1224
  const toggle = document.getElementById('sidebarToggle');
1225
  if (toggle && sidebar) {
1226
  toggle.addEventListener('click', () => {
1227
+ sidebar.classList.toggle('show');
1228
  });
1229
  }
1230
  });
 
1248
  }
1249
  }
1250
  });
1251
+ })();
1252
+
1253
+ // Doctor modal open/close wiring (from doctor.js)
1254
+ document.addEventListener('DOMContentLoaded', () => {
1255
+ const doctorCard = document.getElementById('userProfile');
1256
+ const userModal = document.getElementById('userModal');
1257
+ const closeBtn = document.getElementById('userModalClose');
1258
+ const cancelBtn = document.getElementById('userModalCancel');
1259
+ if (doctorCard && userModal) {
1260
+ doctorCard.addEventListener('click', () => userModal.classList.add('show'));
1261
+ }
1262
+ if (closeBtn) closeBtn.addEventListener('click', () => userModal.classList.remove('show'));
1263
+ if (cancelBtn) cancelBtn.addEventListener('click', () => userModal.classList.remove('show'));
1264
+ if (userModal) {
1265
+ userModal.addEventListener('click', (e) => { if (e.target === userModal) userModal.classList.remove('show'); });
1266
+ }
1267
+ });
static/js/chat/sessions.js CHANGED
@@ -161,6 +161,4 @@ export function attachSessionsUI(app) {
161
  }
162
  });
163
  };
164
- }
165
-
166
-
 
161
  }
162
  });
163
  };
164
+ }
 
 
static/js/ui/handlers.js CHANGED
@@ -65,6 +65,4 @@ export function attachUIHandlers(app) {
65
  app.hideModal = function (modalId) {
66
  document.getElementById(modalId)?.classList.remove('show');
67
  };
68
- }
69
-
70
-
 
65
  app.hideModal = function (modalId) {
66
  document.getElementById(modalId)?.classList.remove('show');
67
  };
68
+ }
 
 
static/js/ui/settings.js CHANGED
@@ -71,6 +71,4 @@ export function attachSettingsUI(app) {
71
  if (cancelBtn) cancelBtn.addEventListener('click', () => modal.classList.remove('show'));
72
  if (modal) modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('show'); });
73
  });
74
- }
75
-
76
-
 
71
  if (cancelBtn) cancelBtn.addEventListener('click', () => modal.classList.remove('show'));
72
  if (modal) modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('show'); });
73
  });
74
+ }