dylanglenister commited on
Commit
2e2a4b0
·
1 Parent(s): e8cda17

Fixing whitespace

Browse files
Files changed (2) hide show
  1. static/css/styles.css +10 -10
  2. static/js/app.js +146 -146
static/css/styles.css CHANGED
@@ -707,12 +707,12 @@ body {
707
  .wave-bar:nth-child(8) { animation-delay: 0.7s; }
708
 
709
  @keyframes wave {
710
- 0%, 100% {
711
- height: 20px;
712
  background-color: var(--primary-color);
713
  }
714
- 50% {
715
- height: 40px;
716
  background-color: var(--accent-color);
717
  }
718
  }
@@ -1184,12 +1184,12 @@ body {
1184
  .patient-load-btn:hover {
1185
  background: var(--primary-hover);
1186
  }
1187
- .patient-create-link {
1188
- display:inline-flex; align-items:center;
1189
- justify-content:center; padding:8px 10px;
1190
- border: 1px solid var(--border-color);
1191
- border-radius:6px; color: var(--text-secondary);
1192
- text-decoration:none;
1193
  }
1194
  .patient-create-link:hover { background: var(--bg-tertiary); }
1195
 
 
707
  .wave-bar:nth-child(8) { animation-delay: 0.7s; }
708
 
709
  @keyframes wave {
710
+ 0%, 100% {
711
+ height: 20px;
712
  background-color: var(--primary-color);
713
  }
714
+ 50% {
715
+ height: 40px;
716
  background-color: var(--accent-color);
717
  }
718
  }
 
1184
  .patient-load-btn:hover {
1185
  background: var(--primary-hover);
1186
  }
1187
+ .patient-create-link {
1188
+ display:inline-flex; align-items:center;
1189
+ justify-content:center; padding:8px 10px;
1190
+ border: 1px solid var(--border-color);
1191
+ border-radius:6px; color: var(--text-secondary);
1192
+ text-decoration:none;
1193
  }
1194
  .patient-create-link:hover { background: var(--bg-tertiary); }
1195
 
static/js/app.js CHANGED
@@ -49,7 +49,7 @@ class MedicalChatbotApp {
49
  // Ensure a session exists and is displayed immediately if nothing to show
50
  this.ensureStartupSession();
51
  this.loadChatSessions();
52
-
53
  // Bind patient handlers
54
  console.log('[DEBUG] Binding patient handlers');
55
  this.bindPatientHandlers();
@@ -58,7 +58,7 @@ class MedicalChatbotApp {
58
  const prefs = JSON.parse(localStorage.getItem('medicalChatbotPreferences') || '{}');
59
  this.setTheme(prefs.theme || 'auto');
60
  this.setupTheme();
61
-
62
  // Initialize audio recording (guarded if module not present)
63
  try {
64
  if (typeof AudioRecordingUI !== 'undefined') {
@@ -79,7 +79,7 @@ class MedicalChatbotApp {
79
  this.toggleSidebar();
80
  });
81
  }
82
-
83
  // Click outside sidebar to close (mobile/overlay behavior)
84
  const overlay = document.getElementById('appOverlay');
85
  console.log('[DEBUG] Overlay element found:', !!overlay);
@@ -97,27 +97,27 @@ class MedicalChatbotApp {
97
  }
98
  }
99
  };
100
-
101
  // Keep overlay synced when toggling
102
  const origToggle = this.toggleSidebar.bind(this);
103
- this.toggleSidebar = () => {
104
  console.log('[DEBUG] Wrapped toggleSidebar called');
105
- origToggle();
106
- updateOverlay();
107
  };
108
-
109
  // Initialize overlay state - ensure it's hidden on startup
110
  if (overlay) {
111
  overlay.classList.remove('show');
112
  }
113
  updateOverlay();
114
-
115
  // Handle window resize for responsive behavior
116
  window.addEventListener('resize', () => {
117
  console.log('[DEBUG] Window resized, updating overlay');
118
  updateOverlay();
119
  });
120
-
121
  // Click outside to close sidebar
122
  document.addEventListener('click', (e) => {
123
  const sidebar = document.getElementById('sidebar');
@@ -127,9 +127,9 @@ class MedicalChatbotApp {
127
  const isOpen = sidebar.classList.contains('show');
128
  const clickInside = sidebar.contains(e.target) || (toggleBtn && toggleBtn.contains(e.target));
129
  const clickOnOverlay = overlay && overlay.contains(e.target);
130
-
131
  console.log('[DEBUG] Click event - sidebar open:', isOpen, 'click inside:', clickInside, 'click on overlay:', clickOnOverlay);
132
-
133
  if (isOpen && !clickInside) {
134
  if (clickOnOverlay) {
135
  console.log('[DEBUG] Clicked on overlay, closing sidebar');
@@ -145,7 +145,7 @@ class MedicalChatbotApp {
145
  }
146
  updateOverlay();
147
  }, true);
148
-
149
  if (overlay) {
150
  overlay.addEventListener('click', () => {
151
  console.log('[DEBUG] Overlay clicked directly');
@@ -239,7 +239,7 @@ class MedicalChatbotApp {
239
  const userModalClose = document.getElementById('userModalClose');
240
  const userModalCancel = document.getElementById('userModalCancel');
241
  const userModalSave = document.getElementById('userModalSave');
242
-
243
  if (userModalClose) {
244
  userModalClose.addEventListener('click', () => {
245
  this.hideModal('userModal');
@@ -262,7 +262,7 @@ class MedicalChatbotApp {
262
  const settingsModalClose = document.getElementById('settingsModalClose');
263
  const settingsModalCancel = document.getElementById('settingsModalCancel');
264
  const settingsModalSave = document.getElementById('settingsModalSave');
265
-
266
  if (settingsModalClose) {
267
  settingsModalClose.addEventListener('click', () => {
268
  this.hideModal('settingsModal');
@@ -297,7 +297,7 @@ class MedicalChatbotApp {
297
  const editTitleModalClose = document.getElementById('editTitleModalClose');
298
  const editTitleModalCancel = document.getElementById('editTitleModalCancel');
299
  const editTitleModalSave = document.getElementById('editTitleModalSave');
300
-
301
  if (editTitleModalClose) editTitleModalClose.addEventListener('click', closeEdit);
302
  if (editTitleModalCancel) editTitleModalCancel.addEventListener('click', closeEdit);
303
  if (editTitleModalSave) {
@@ -485,7 +485,7 @@ How can I assist you today?`;
485
  // Our submodules aren't lodaed on app.js, so we need to add them here
486
  // Perhaps this is FastAPI limitation, remove this when proper deploy this
487
  // On UI specific hosting site.
488
- // ----------------------------------------------------------
489
 
490
 
491
  // ================================================================================
@@ -564,16 +564,16 @@ How can I assist you today?`;
564
  sel.appendChild(createOpt);
565
  }
566
  if (sel && !sel.value) sel.value = this.currentUser?.name || '__create__';
567
-
568
  // Safely set role and specialty with null checks
569
  const roleEl = document.getElementById('profileRole');
570
  const specialtyEl = document.getElementById('profileSpecialty');
571
  if (roleEl) roleEl.value = (this.currentUser && this.currentUser.role) ? this.currentUser.role : 'Medical Professional';
572
  if (specialtyEl) specialtyEl.value = (this.currentUser && this.currentUser.specialty) ? this.currentUser.specialty : '';
573
-
574
  // Add event listener for doctor selection changes
575
  this.setupDoctorSelectionHandler();
576
-
577
  this.showModal('userModal');
578
  }
579
 
@@ -581,24 +581,24 @@ How can I assist you today?`;
581
  const sel = document.getElementById('profileNameSelect');
582
  const roleEl = document.getElementById('profileRole');
583
  const specialtyEl = document.getElementById('profileSpecialty');
584
-
585
  if (!sel || !roleEl || !specialtyEl) return;
586
-
587
  // Remove existing listeners to avoid duplicates
588
  sel.removeEventListener('change', this.handleDoctorSelection);
589
-
590
  // Add new listener
591
  this.handleDoctorSelection = async (event) => {
592
  const selectedName = event.target.value;
593
  console.log('[DEBUG] Doctor selected:', selectedName);
594
-
595
  if (selectedName === '__create__') {
596
  // Reset to default values for new doctor
597
  roleEl.value = 'Medical Professional';
598
  specialtyEl.value = '';
599
  return;
600
  }
601
-
602
  // Find the selected doctor in our doctors list
603
  const selectedDoctor = this.doctors.find(d => d.name === selectedName);
604
  if (selectedDoctor) {
@@ -632,7 +632,7 @@ How can I assist you today?`;
632
  }
633
  }
634
  };
635
-
636
  sel.addEventListener('change', this.handleDoctorSelection);
637
  }
638
 
@@ -640,7 +640,7 @@ How can I assist you today?`;
640
  console.log('[DEBUG] showSettingsModal called');
641
  this.showModal('settingsModal');
642
  }
643
-
644
 
645
  // ================================================================================
646
  // SETTINGS.JS FUNCTIONALITY
@@ -791,14 +791,14 @@ How can I assist you today?`;
791
 
792
  async switchToSession(session) {
793
  console.log('[DEBUG] Switching to session:', session.id, session.source);
794
-
795
  // Clear current session and messages first
796
  this.currentSession = null;
797
  this.clearChatMessages();
798
-
799
  // Set new session
800
  this.currentSession = { ...session };
801
-
802
  if (session.source === 'backend') {
803
  // For backend sessions, always fetch fresh messages
804
  console.log('[DEBUG] Fetching messages for backend session:', session.id);
@@ -823,7 +823,7 @@ How can I assist you today?`;
823
  console.log('[DEBUG] No messages found for local session:', session.id);
824
  }
825
  }
826
-
827
  this.updateChatTitle();
828
  this.loadChatSessions(); // Re-render to update active state
829
  }
@@ -851,28 +851,28 @@ How can I assist you today?`;
851
  async deleteChatSession(sessionId) {
852
  const confirmDelete = confirm('Delete this chat session? This cannot be undone.');
853
  if (!confirmDelete) return;
854
-
855
  try {
856
  // Check if it's a backend session
857
  const isBackendSession = this.backendSessions && this.backendSessions.some(s => s.id === sessionId);
858
-
859
  if (isBackendSession) {
860
  // Delete from backend (MongoDB + memory system)
861
  const resp = await fetch(`/sessions/${sessionId}`, {
862
  method: 'DELETE',
863
  headers: { 'Content-Type': 'application/json' }
864
  });
865
-
866
  if (!resp.ok) {
867
  throw new Error(`HTTP ${resp.status}`);
868
  }
869
-
870
  const result = await resp.json();
871
  console.log('[DEBUG] Backend deletion result:', result);
872
-
873
  // Remove from backend sessions
874
  this.backendSessions = this.backendSessions.filter(s => s.id !== sessionId);
875
-
876
  // Invalidate caches
877
  this.invalidateSessionCache(this.currentPatientId);
878
  this.invalidateMessageCache(this.currentPatientId, sessionId);
@@ -881,11 +881,11 @@ How can I assist you today?`;
881
  const sessions = this.getChatSessions();
882
  const index = sessions.findIndex(s => s.id === sessionId);
883
  if (index === -1) return;
884
-
885
  sessions.splice(index, 1);
886
  localStorage.setItem(`chatSessions_${this.currentUser.id}`, JSON.stringify(sessions));
887
  }
888
-
889
  // Handle current session cleanup
890
  if (this.currentSession && this.currentSession.id === sessionId) {
891
  if (isBackendSession) {
@@ -911,9 +911,9 @@ How can I assist you today?`;
911
  }
912
  this.updateChatTitle();
913
  }
914
-
915
  this.loadChatSessions();
916
-
917
  } catch (error) {
918
  console.error('Error deleting session:', error);
919
  alert('Failed to delete session. Please try again.');
@@ -1057,10 +1057,10 @@ How can I assist you today?`;
1057
  const sel = document.getElementById('profileNameSelect');
1058
  const newSec = document.getElementById('newDoctorSection');
1059
  if (!sel) return;
1060
-
1061
  // Load doctors from MongoDB
1062
  await this.loadDoctors();
1063
-
1064
  sel.innerHTML = '';
1065
  const createOpt = document.createElement('option');
1066
  createOpt.value = '__create__';
@@ -1103,23 +1103,23 @@ How can I assist you today?`;
1103
  // Get current role and specialty from the form
1104
  const role = document.getElementById('profileRole').value || 'Medical Professional';
1105
  const specialty = document.getElementById('profileSpecialty').value.trim() || '';
1106
-
1107
  // Create doctor in MongoDB
1108
- const result = await this.createDoctor({
1109
- name,
1110
- role,
1111
  specialty,
1112
  medical_roles: [role]
1113
  });
1114
  if (result) {
1115
- this.doctors.unshift({
1116
- name,
1117
- role,
1118
- specialty,
1119
- _id: result.doctor_id
1120
  });
1121
  this.saveDoctors();
1122
-
1123
  // Update current user profile
1124
  this.currentUser.name = name;
1125
  this.currentUser.role = role;
@@ -1165,10 +1165,10 @@ How can I assist you today?`;
1165
  const existingDoctorIndex = this.doctors.findIndex(d => d.name === name);
1166
  if (existingDoctorIndex === -1) {
1167
  // Add new doctor to local list
1168
- this.doctors.unshift({
1169
- name,
1170
- role,
1171
- specialty
1172
  });
1173
  } else {
1174
  // Update existing doctor in local list
@@ -1188,18 +1188,18 @@ How can I assist you today?`;
1188
  specialty: specialty || null,
1189
  medical_roles: [role]
1190
  };
1191
-
1192
  try {
1193
  const resp = await fetch('/doctors', {
1194
  method: 'POST',
1195
  headers: { 'Content-Type': 'application/json' },
1196
  body: JSON.stringify(doctorPayload)
1197
  });
1198
-
1199
  if (!resp.ok) throw new Error('Failed to create doctor in backend');
1200
  const data = await resp.json();
1201
  console.log('[Doctor] Created new doctor in backend:', data);
1202
-
1203
  // Update local doctor with the ID from backend
1204
  const localDoctor = this.doctors.find(d => d.name === name);
1205
  if (localDoctor) {
@@ -1219,7 +1219,7 @@ How can I assist you today?`;
1219
  // ================================================================================
1220
  // PATIENT.JS FUNCTIONALITY
1221
  // ================================================================================
1222
-
1223
  async getLocalStorageSuggestions(query) {
1224
  try {
1225
  const storedPatients = JSON.parse(localStorage.getItem('medicalChatbotPatients') || '[]');
@@ -1243,19 +1243,19 @@ How can I assist you today?`;
1243
  combinePatientResults(mongoResults, localResults) {
1244
  // Create a map to deduplicate by patient_id, with MongoDB results taking priority
1245
  const resultMap = new Map();
1246
-
1247
  // Add MongoDB results first (they take priority)
1248
  mongoResults.forEach(patient => {
1249
  resultMap.set(patient.patient_id, patient);
1250
  });
1251
-
1252
  // Add localStorage results only if not already present
1253
  localResults.forEach(patient => {
1254
  if (!resultMap.has(patient.patient_id)) {
1255
  resultMap.set(patient.patient_id, patient);
1256
  }
1257
  });
1258
-
1259
  return Array.from(resultMap.values());
1260
  }
1261
 
@@ -1282,7 +1282,7 @@ How can I assist you today?`;
1282
  const status = document.getElementById('patientStatus');
1283
  const actions = document.getElementById('patientActions');
1284
  const emrLink = document.getElementById('emrLink');
1285
-
1286
  if (status) {
1287
  // Try to fetch patient name
1288
  try {
@@ -1298,11 +1298,11 @@ How can I assist you today?`;
1298
  }
1299
  status.style.color = 'var(--text-secondary)';
1300
  }
1301
-
1302
  // Show EMR link
1303
  if (actions) actions.style.display = 'block';
1304
  if (emrLink) emrLink.href = `/static/emr.html?patient_id=${pid}`;
1305
-
1306
  const input = document.getElementById('patientIdInput');
1307
  if (input) input.value = pid;
1308
  }
@@ -1317,7 +1317,7 @@ How can I assist you today?`;
1317
  const status = document.getElementById('patientStatus');
1318
  const actions = document.getElementById('patientActions');
1319
  const emrLink = document.getElementById('emrLink');
1320
-
1321
  if (status) {
1322
  if (patientName) {
1323
  status.textContent = `Patient: ${patientName} (${patientId})`;
@@ -1326,7 +1326,7 @@ How can I assist you today?`;
1326
  }
1327
  status.style.color = 'var(--text-secondary)';
1328
  }
1329
-
1330
  // Show EMR link
1331
  if (actions) actions.style.display = 'block';
1332
  if (emrLink) emrLink.href = `/static/emr.html?patient_id=${patientId}`;
@@ -1338,13 +1338,13 @@ How can I assist you today?`;
1338
  const status = document.getElementById('patientStatus');
1339
  const value = (input?.value || '').trim();
1340
  console.log('[DEBUG] Patient input value:', value);
1341
-
1342
  if (!value) {
1343
  console.log('[DEBUG] No input provided');
1344
  if (status) { status.textContent = 'Please enter patient ID or name.'; status.style.color = 'var(--warning-color)'; }
1345
  return;
1346
  }
1347
-
1348
  // If it's a complete 8-digit ID, use it directly
1349
  if (/^\d{8}$/.test(value)) {
1350
  console.log('[DEBUG] Valid 8-digit ID provided');
@@ -1365,7 +1365,7 @@ How can I assist you today?`;
1365
  await this.fetchAndRenderPatientSessions();
1366
  return;
1367
  }
1368
-
1369
  // Otherwise, search for patient by name or partial ID
1370
  console.log('[DEBUG] Searching for patient by name/partial ID');
1371
  try {
@@ -1388,7 +1388,7 @@ How can I assist you today?`;
1388
  } catch (e) {
1389
  console.error('[DEBUG] Search error:', e);
1390
  }
1391
-
1392
  // No patient found
1393
  console.log('[DEBUG] No patient found');
1394
  if (status) { status.textContent = 'No patient found. Try a different search.'; status.style.color = 'var(--warning-color)'; }
@@ -1396,12 +1396,12 @@ How can I assist you today?`;
1396
 
1397
  fetchAndRenderPatientSessions = async function () {
1398
  if (!this.currentPatientId) return;
1399
-
1400
  // Check localStorage cache first
1401
  const cacheKey = `sessions_${this.currentPatientId}`;
1402
  const cached = localStorage.getItem(cacheKey);
1403
  let sessions = [];
1404
-
1405
  if (cached) {
1406
  try {
1407
  const cachedData = JSON.parse(cached);
@@ -1416,7 +1416,7 @@ How can I assist you today?`;
1416
  console.warn('Failed to parse cached sessions:', e);
1417
  }
1418
  }
1419
-
1420
  // If no cache or cache is stale, fetch from backend
1421
  if (sessions.length === 0) {
1422
  try {
@@ -1424,7 +1424,7 @@ How can I assist you today?`;
1424
  if (resp.ok) {
1425
  const data = await resp.json();
1426
  sessions = Array.isArray(data.sessions) ? data.sessions : [];
1427
-
1428
  // Cache the sessions
1429
  localStorage.setItem(cacheKey, JSON.stringify({
1430
  sessions: sessions,
@@ -1438,7 +1438,7 @@ How can I assist you today?`;
1438
  console.error('Failed to load patient sessions', e);
1439
  }
1440
  }
1441
-
1442
  // Process sessions
1443
  this.backendSessions = sessions.map(s => ({
1444
  id: s.session_id,
@@ -1448,12 +1448,12 @@ How can I assist you today?`;
1448
  lastActivity: s.last_activity || new Date().toISOString(),
1449
  source: 'backend'
1450
  }));
1451
-
1452
  if (this.backendSessions.length > 0) {
1453
  this.currentSession = this.backendSessions[0];
1454
  await this.hydrateMessagesForSession(this.currentSession.id);
1455
  }
1456
-
1457
  this.loadChatSessions();
1458
  }
1459
 
@@ -1463,7 +1463,7 @@ How can I assist you today?`;
1463
  const cacheKey = `messages_${this.currentPatientId}_${sessionId}`;
1464
  const cached = localStorage.getItem(cacheKey);
1465
  let messages = [];
1466
-
1467
  if (cached) {
1468
  try {
1469
  const cachedData = JSON.parse(cached);
@@ -1478,7 +1478,7 @@ How can I assist you today?`;
1478
  console.warn('Failed to parse cached messages:', e);
1479
  }
1480
  }
1481
-
1482
  // If no cache or cache is stale, fetch from backend
1483
  if (messages.length === 0) {
1484
  const resp = await fetch(`/sessions/${sessionId}/messages?patient_id=${this.currentPatientId}&limit=1000`);
@@ -1494,7 +1494,7 @@ How can I assist you today?`;
1494
  content: m.content,
1495
  timestamp: m.timestamp
1496
  }));
1497
-
1498
  // Cache the messages
1499
  localStorage.setItem(cacheKey, JSON.stringify({
1500
  messages: messages,
@@ -1502,14 +1502,14 @@ How can I assist you today?`;
1502
  }));
1503
  console.log('[DEBUG] Cached messages for session:', sessionId, 'count:', messages.length);
1504
  }
1505
-
1506
  // Sort messages by timestamp (ascending order for display)
1507
  const sortedMessages = messages.sort((a, b) => {
1508
  const timeA = new Date(a.timestamp || 0).getTime();
1509
  const timeB = new Date(b.timestamp || 0).getTime();
1510
  return timeA - timeB;
1511
  });
1512
-
1513
  if (this.currentSession && this.currentSession.id === sessionId) {
1514
  this.currentSession.messages = sortedMessages;
1515
  this.clearChatMessages();
@@ -1570,7 +1570,7 @@ How can I assist you today?`;
1570
  console.log('[DEBUG] Search URL:', url);
1571
  const resp = await fetch(url);
1572
  console.log('[DEBUG] Search response status:', resp.status);
1573
-
1574
  let mongoResults = [];
1575
  if (resp.ok) {
1576
  const data = await resp.json();
@@ -1579,16 +1579,16 @@ How can I assist you today?`;
1579
  } else {
1580
  console.warn('MongoDB search request failed', resp.status);
1581
  }
1582
-
1583
  // Get localStorage suggestions as fallback/additional results
1584
  const localResults = await this.getLocalStorageSuggestions(q);
1585
-
1586
  // Combine and deduplicate results (MongoDB results take priority)
1587
  const combinedResults = this.combinePatientResults(mongoResults, localResults);
1588
  console.log('[DEBUG] Combined search results:', combinedResults);
1589
  renderSuggestions(combinedResults);
1590
-
1591
- } catch (e) {
1592
  console.error('[DEBUG] Search error:', e);
1593
  // Fallback for network errors
1594
  console.log('[DEBUG] Trying fallback search after error');
@@ -1617,7 +1617,7 @@ How can I assist you today?`;
1617
  const closeBtn = document.getElementById('patientModalClose');
1618
  const logoutBtn = document.getElementById('patientLogoutBtn');
1619
  const createBtn = document.getElementById('patientCreateBtn');
1620
-
1621
  if (profileBtn && modal) {
1622
  profileBtn.addEventListener('click', async () => {
1623
  const pid = this?.currentPatientId;
@@ -1641,12 +1641,12 @@ How can I assist you today?`;
1641
  modal.classList.add('show');
1642
  });
1643
  }
1644
-
1645
  if (closeBtn && modal) {
1646
  closeBtn.addEventListener('click', () => modal.classList.remove('show'));
1647
  modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('show'); });
1648
  }
1649
-
1650
  if (logoutBtn) {
1651
  logoutBtn.addEventListener('click', () => {
1652
  if (confirm('Log out current patient?')) {
@@ -1660,7 +1660,7 @@ How can I assist you today?`;
1660
  }
1661
  });
1662
  }
1663
-
1664
  if (createBtn) createBtn.addEventListener('click', () => modal.classList.remove('show'));
1665
  }
1666
 
@@ -1671,7 +1671,7 @@ How can I assist you today?`;
1671
  try {
1672
  this.audioRecorder = new AudioRecordingUI(this);
1673
  const success = await this.audioRecorder.initialize();
1674
-
1675
  if (success) {
1676
  console.log('[Audio] Audio recording initialized successfully');
1677
  // Make globally accessible for voice detection callback
@@ -1729,7 +1729,7 @@ How can I assist you today?`;
1729
  const response = await this.callMedicalAPI(message);
1730
  this.addMessage('assistant', response);
1731
  this.updateCurrentSession();
1732
-
1733
  // Invalidate caches after successful message exchange
1734
  if (this.currentSession && this.currentSession.id) {
1735
  this.invalidateMessageCache(this.currentPatientId, this.currentSession.id);
@@ -1802,10 +1802,10 @@ How can I assist you today?`;
1802
  // Check if session needs title generation after messages are loaded
1803
  checkAndGenerateSessionTitle() {
1804
  if (!this.currentSession || !this.currentSession.messages) return;
1805
-
1806
  // Check if this is a new session that needs a title (exactly 2 messages: user + assistant)
1807
- if (this.currentSession.messages.length === 2 &&
1808
- this.currentSession.title === 'New Chat' &&
1809
  this.currentSession.messages[0].role === 'user') {
1810
  const firstMessage = this.currentSession.messages[0].content;
1811
  this.summariseAndSetTitle(firstMessage);
@@ -1889,7 +1889,7 @@ How can I assist you today?`;
1889
  }
1890
  // ----------------------------------------------------------
1891
  // Additional UI setup END
1892
- // ----------------------------------------------------------
1893
 
1894
 
1895
  // Initialize the app when DOM is loaded
@@ -1998,13 +1998,13 @@ class AudioRecorder {
1998
  this.isRecording = true;
1999
  this.recordingStartTime = Date.now();
2000
  console.log('Audio recording started');
2001
-
2002
  // Start timer
2003
  this.startTimer();
2004
-
2005
  // Start voice detection
2006
  this.startVoiceDetection();
2007
-
2008
  return true;
2009
  } catch (error) {
2010
  console.error('Failed to start recording:', error);
@@ -2021,11 +2021,11 @@ class AudioRecorder {
2021
  this.mediaRecorder.stop();
2022
  this.isRecording = false;
2023
  console.log('Audio recording stopped');
2024
-
2025
  // Stop timer and voice detection
2026
  this.stopTimer();
2027
  this.stopVoiceDetection();
2028
-
2029
  return true;
2030
  } catch (error) {
2031
  console.error('Failed to stop recording:', error);
@@ -2040,7 +2040,7 @@ class AudioRecorder {
2040
  const minutes = Math.floor(elapsed / 60);
2041
  const seconds = elapsed % 60;
2042
  const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
2043
-
2044
  const timerElement = document.getElementById('recordingTimer');
2045
  if (timerElement) {
2046
  timerElement.textContent = timeString;
@@ -2059,24 +2059,24 @@ class AudioRecorder {
2059
  startVoiceDetection() {
2060
  const checkVoice = () => {
2061
  if (!this.isRecording || !this.analyser) return;
2062
-
2063
  const bufferLength = this.analyser.frequencyBinCount;
2064
  const dataArray = new Uint8Array(bufferLength);
2065
  this.analyser.getByteFrequencyData(dataArray);
2066
-
2067
  // Calculate average volume
2068
  const average = dataArray.reduce((sum, value) => sum + value, 0) / bufferLength;
2069
  const threshold = 20; // Adjust this value to change sensitivity
2070
-
2071
  const container = document.querySelector('.recording-container');
2072
  const statusElement = document.getElementById('recordingStatus');
2073
-
2074
  if (average > threshold) {
2075
  // Voice detected
2076
  container.classList.remove('silent');
2077
  container.classList.add('listening');
2078
  if (statusElement) statusElement.textContent = 'Listening...';
2079
-
2080
  // Reset silence timer
2081
  this.resetSilenceTimer();
2082
  } else {
@@ -2085,10 +2085,10 @@ class AudioRecorder {
2085
  container.classList.add('silent');
2086
  if (statusElement) statusElement.textContent = 'Silence detected...';
2087
  }
2088
-
2089
  requestAnimationFrame(checkVoice);
2090
  };
2091
-
2092
  checkVoice();
2093
  }
2094
 
@@ -2100,7 +2100,7 @@ class AudioRecorder {
2100
  if (this.silenceTimer) {
2101
  clearTimeout(this.silenceTimer);
2102
  }
2103
-
2104
  // Auto-stop after 3 seconds of silence
2105
  this.silenceTimer = setTimeout(() => {
2106
  if (this.isRecording) {
@@ -2123,10 +2123,10 @@ class AudioRecorder {
2123
  try {
2124
  // Create audio blob
2125
  const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
2126
-
2127
  // Transcribe audio
2128
  const transcribedText = await this.transcribeAudio(audioBlob);
2129
-
2130
  return transcribedText;
2131
  } catch (error) {
2132
  console.error('Failed to process recording:', error);
@@ -2163,19 +2163,19 @@ class AudioRecorder {
2163
  this.audioStream.getTracks().forEach(track => track.stop());
2164
  this.audioStream = null;
2165
  }
2166
-
2167
  if (this.audioContext) {
2168
  this.audioContext.close();
2169
  this.audioContext = null;
2170
  }
2171
-
2172
  if (this.silenceTimer) {
2173
  clearTimeout(this.silenceTimer);
2174
  this.silenceTimer = null;
2175
  }
2176
-
2177
  this.stopTimer();
2178
-
2179
  this.mediaRecorder = null;
2180
  this.audioChunks = [];
2181
  this.isRecording = false;
@@ -2217,7 +2217,7 @@ class AudioRecordingUI {
2217
  setupUI() {
2218
  this.microphoneBtn = document.getElementById('microphoneBtn');
2219
  this.modal = document.getElementById('audioRecordingModal');
2220
-
2221
  if (!this.microphoneBtn) {
2222
  console.error('Microphone button not found');
2223
  return;
@@ -2230,15 +2230,15 @@ class AudioRecordingUI {
2230
 
2231
  // Set up event listeners
2232
  this.microphoneBtn.addEventListener('click', (e) => this.startRecording(e));
2233
-
2234
  // Modal close handlers
2235
  const closeBtn = document.getElementById('audioRecordingModalClose');
2236
  const stopBtn = document.getElementById('stopRecordingBtn');
2237
-
2238
  if (closeBtn) {
2239
  closeBtn.addEventListener('click', () => this.closeModal());
2240
  }
2241
-
2242
  if (stopBtn) {
2243
  stopBtn.addEventListener('click', () => this.stopRecording());
2244
  }
@@ -2260,11 +2260,11 @@ class AudioRecordingUI {
2260
  }
2261
 
2262
  event.preventDefault();
2263
-
2264
  try {
2265
  // Show modal
2266
  this.showModal();
2267
-
2268
  // Start recording
2269
  const success = this.recorder.startRecording();
2270
  if (success) {
@@ -2302,14 +2302,14 @@ class AudioRecordingUI {
2302
  try {
2303
  // Process the recording
2304
  const transcribedText = await this.recorder.processRecording();
2305
-
2306
  if (transcribedText) {
2307
  this.insertTranscribedText(transcribedText);
2308
  this.showSuccess('Audio transcribed successfully!');
2309
  } else {
2310
  this.showError('No speech detected. Please try again.');
2311
  }
2312
-
2313
  this.closeModal();
2314
  } catch (error) {
2315
  console.error('Failed to process recording:', error);
@@ -2329,12 +2329,12 @@ class AudioRecordingUI {
2329
  if (this.modal) {
2330
  this.modal.classList.remove('show');
2331
  document.body.style.overflow = ''; // Restore scrolling
2332
-
2333
  // Stop recording if still active
2334
  if (this.recorder.isRecording) {
2335
  this.recorder.stopRecording();
2336
  }
2337
-
2338
  // Reset modal state
2339
  this.updateModalState('ready');
2340
  }
@@ -2344,12 +2344,12 @@ class AudioRecordingUI {
2344
  const container = document.querySelector('.recording-container');
2345
  const statusElement = document.getElementById('recordingStatus');
2346
  const stopBtn = document.getElementById('stopRecordingBtn');
2347
-
2348
  if (!container) return;
2349
 
2350
  // Remove all state classes
2351
  container.classList.remove('listening', 'silent', 'processing');
2352
-
2353
  switch (state) {
2354
  case 'ready':
2355
  container.classList.add('listening');
@@ -2379,23 +2379,23 @@ class AudioRecordingUI {
2379
  // Append transcribed text to existing content
2380
  const currentText = chatInput.value.trim();
2381
  const newText = currentText ? `${currentText} ${text}` : text;
2382
-
2383
  chatInput.value = newText;
2384
-
2385
  // Add visual feedback for transcribed text
2386
  chatInput.classList.add('transcribed');
2387
-
2388
  // Remove the highlighting after a few seconds
2389
  setTimeout(() => {
2390
  chatInput.classList.remove('transcribed');
2391
  }, 3000);
2392
-
2393
  // Trigger input event to update UI
2394
  chatInput.dispatchEvent(new Event('input', { bubbles: true }));
2395
-
2396
  // Focus the input
2397
  chatInput.focus();
2398
-
2399
  // Auto-resize if needed
2400
  if (this.app && this.app.autoResizeTextarea) {
2401
  this.app.autoResizeTextarea(chatInput);
@@ -2407,7 +2407,7 @@ class AudioRecordingUI {
2407
 
2408
  // Remove all state classes
2409
  this.microphoneBtn.classList.remove('recording-ready', 'recording-active', 'recording-processing');
2410
-
2411
  // Add appropriate state class
2412
  switch (state) {
2413
  case 'ready':
@@ -2432,16 +2432,16 @@ class AudioRecordingUI {
2432
  errorMsg = document.createElement('div');
2433
  errorMsg.id = 'audioError';
2434
  errorMsg.className = 'audio-error-message';
2435
-
2436
  const chatInputContainer = document.querySelector('.chat-input-container');
2437
  if (chatInputContainer) {
2438
  chatInputContainer.appendChild(errorMsg);
2439
  }
2440
  }
2441
-
2442
  errorMsg.textContent = message;
2443
  errorMsg.style.display = 'block';
2444
-
2445
  // Hide after 5 seconds
2446
  setTimeout(() => {
2447
  errorMsg.style.display = 'none';
@@ -2455,16 +2455,16 @@ class AudioRecordingUI {
2455
  successMsg = document.createElement('div');
2456
  successMsg.id = 'audioSuccess';
2457
  successMsg.className = 'audio-success-message';
2458
-
2459
  const chatInputContainer = document.querySelector('.chat-input-container');
2460
  if (chatInputContainer) {
2461
  chatInputContainer.appendChild(successMsg);
2462
  }
2463
  }
2464
-
2465
  successMsg.textContent = message;
2466
  successMsg.style.display = 'block';
2467
-
2468
  // Hide after 3 seconds
2469
  setTimeout(() => {
2470
  successMsg.style.display = 'none';
@@ -2555,4 +2555,4 @@ document.addEventListener('DOMContentLoaded', () => {
2555
  if (userModal) {
2556
  userModal.addEventListener('click', (e) => { if (e.target === userModal) userModal.classList.remove('show'); });
2557
  }
2558
- });
 
49
  // Ensure a session exists and is displayed immediately if nothing to show
50
  this.ensureStartupSession();
51
  this.loadChatSessions();
52
+
53
  // Bind patient handlers
54
  console.log('[DEBUG] Binding patient handlers');
55
  this.bindPatientHandlers();
 
58
  const prefs = JSON.parse(localStorage.getItem('medicalChatbotPreferences') || '{}');
59
  this.setTheme(prefs.theme || 'auto');
60
  this.setupTheme();
61
+
62
  // Initialize audio recording (guarded if module not present)
63
  try {
64
  if (typeof AudioRecordingUI !== 'undefined') {
 
79
  this.toggleSidebar();
80
  });
81
  }
82
+
83
  // Click outside sidebar to close (mobile/overlay behavior)
84
  const overlay = document.getElementById('appOverlay');
85
  console.log('[DEBUG] Overlay element found:', !!overlay);
 
97
  }
98
  }
99
  };
100
+
101
  // Keep overlay synced when toggling
102
  const origToggle = this.toggleSidebar.bind(this);
103
+ this.toggleSidebar = () => {
104
  console.log('[DEBUG] Wrapped toggleSidebar called');
105
+ origToggle();
106
+ updateOverlay();
107
  };
108
+
109
  // Initialize overlay state - ensure it's hidden on startup
110
  if (overlay) {
111
  overlay.classList.remove('show');
112
  }
113
  updateOverlay();
114
+
115
  // Handle window resize for responsive behavior
116
  window.addEventListener('resize', () => {
117
  console.log('[DEBUG] Window resized, updating overlay');
118
  updateOverlay();
119
  });
120
+
121
  // Click outside to close sidebar
122
  document.addEventListener('click', (e) => {
123
  const sidebar = document.getElementById('sidebar');
 
127
  const isOpen = sidebar.classList.contains('show');
128
  const clickInside = sidebar.contains(e.target) || (toggleBtn && toggleBtn.contains(e.target));
129
  const clickOnOverlay = overlay && overlay.contains(e.target);
130
+
131
  console.log('[DEBUG] Click event - sidebar open:', isOpen, 'click inside:', clickInside, 'click on overlay:', clickOnOverlay);
132
+
133
  if (isOpen && !clickInside) {
134
  if (clickOnOverlay) {
135
  console.log('[DEBUG] Clicked on overlay, closing sidebar');
 
145
  }
146
  updateOverlay();
147
  }, true);
148
+
149
  if (overlay) {
150
  overlay.addEventListener('click', () => {
151
  console.log('[DEBUG] Overlay clicked directly');
 
239
  const userModalClose = document.getElementById('userModalClose');
240
  const userModalCancel = document.getElementById('userModalCancel');
241
  const userModalSave = document.getElementById('userModalSave');
242
+
243
  if (userModalClose) {
244
  userModalClose.addEventListener('click', () => {
245
  this.hideModal('userModal');
 
262
  const settingsModalClose = document.getElementById('settingsModalClose');
263
  const settingsModalCancel = document.getElementById('settingsModalCancel');
264
  const settingsModalSave = document.getElementById('settingsModalSave');
265
+
266
  if (settingsModalClose) {
267
  settingsModalClose.addEventListener('click', () => {
268
  this.hideModal('settingsModal');
 
297
  const editTitleModalClose = document.getElementById('editTitleModalClose');
298
  const editTitleModalCancel = document.getElementById('editTitleModalCancel');
299
  const editTitleModalSave = document.getElementById('editTitleModalSave');
300
+
301
  if (editTitleModalClose) editTitleModalClose.addEventListener('click', closeEdit);
302
  if (editTitleModalCancel) editTitleModalCancel.addEventListener('click', closeEdit);
303
  if (editTitleModalSave) {
 
485
  // Our submodules aren't lodaed on app.js, so we need to add them here
486
  // Perhaps this is FastAPI limitation, remove this when proper deploy this
487
  // On UI specific hosting site.
488
+ // ----------------------------------------------------------
489
 
490
 
491
  // ================================================================================
 
564
  sel.appendChild(createOpt);
565
  }
566
  if (sel && !sel.value) sel.value = this.currentUser?.name || '__create__';
567
+
568
  // Safely set role and specialty with null checks
569
  const roleEl = document.getElementById('profileRole');
570
  const specialtyEl = document.getElementById('profileSpecialty');
571
  if (roleEl) roleEl.value = (this.currentUser && this.currentUser.role) ? this.currentUser.role : 'Medical Professional';
572
  if (specialtyEl) specialtyEl.value = (this.currentUser && this.currentUser.specialty) ? this.currentUser.specialty : '';
573
+
574
  // Add event listener for doctor selection changes
575
  this.setupDoctorSelectionHandler();
576
+
577
  this.showModal('userModal');
578
  }
579
 
 
581
  const sel = document.getElementById('profileNameSelect');
582
  const roleEl = document.getElementById('profileRole');
583
  const specialtyEl = document.getElementById('profileSpecialty');
584
+
585
  if (!sel || !roleEl || !specialtyEl) return;
586
+
587
  // Remove existing listeners to avoid duplicates
588
  sel.removeEventListener('change', this.handleDoctorSelection);
589
+
590
  // Add new listener
591
  this.handleDoctorSelection = async (event) => {
592
  const selectedName = event.target.value;
593
  console.log('[DEBUG] Doctor selected:', selectedName);
594
+
595
  if (selectedName === '__create__') {
596
  // Reset to default values for new doctor
597
  roleEl.value = 'Medical Professional';
598
  specialtyEl.value = '';
599
  return;
600
  }
601
+
602
  // Find the selected doctor in our doctors list
603
  const selectedDoctor = this.doctors.find(d => d.name === selectedName);
604
  if (selectedDoctor) {
 
632
  }
633
  }
634
  };
635
+
636
  sel.addEventListener('change', this.handleDoctorSelection);
637
  }
638
 
 
640
  console.log('[DEBUG] showSettingsModal called');
641
  this.showModal('settingsModal');
642
  }
643
+
644
 
645
  // ================================================================================
646
  // SETTINGS.JS FUNCTIONALITY
 
791
 
792
  async switchToSession(session) {
793
  console.log('[DEBUG] Switching to session:', session.id, session.source);
794
+
795
  // Clear current session and messages first
796
  this.currentSession = null;
797
  this.clearChatMessages();
798
+
799
  // Set new session
800
  this.currentSession = { ...session };
801
+
802
  if (session.source === 'backend') {
803
  // For backend sessions, always fetch fresh messages
804
  console.log('[DEBUG] Fetching messages for backend session:', session.id);
 
823
  console.log('[DEBUG] No messages found for local session:', session.id);
824
  }
825
  }
826
+
827
  this.updateChatTitle();
828
  this.loadChatSessions(); // Re-render to update active state
829
  }
 
851
  async deleteChatSession(sessionId) {
852
  const confirmDelete = confirm('Delete this chat session? This cannot be undone.');
853
  if (!confirmDelete) return;
854
+
855
  try {
856
  // Check if it's a backend session
857
  const isBackendSession = this.backendSessions && this.backendSessions.some(s => s.id === sessionId);
858
+
859
  if (isBackendSession) {
860
  // Delete from backend (MongoDB + memory system)
861
  const resp = await fetch(`/sessions/${sessionId}`, {
862
  method: 'DELETE',
863
  headers: { 'Content-Type': 'application/json' }
864
  });
865
+
866
  if (!resp.ok) {
867
  throw new Error(`HTTP ${resp.status}`);
868
  }
869
+
870
  const result = await resp.json();
871
  console.log('[DEBUG] Backend deletion result:', result);
872
+
873
  // Remove from backend sessions
874
  this.backendSessions = this.backendSessions.filter(s => s.id !== sessionId);
875
+
876
  // Invalidate caches
877
  this.invalidateSessionCache(this.currentPatientId);
878
  this.invalidateMessageCache(this.currentPatientId, sessionId);
 
881
  const sessions = this.getChatSessions();
882
  const index = sessions.findIndex(s => s.id === sessionId);
883
  if (index === -1) return;
884
+
885
  sessions.splice(index, 1);
886
  localStorage.setItem(`chatSessions_${this.currentUser.id}`, JSON.stringify(sessions));
887
  }
888
+
889
  // Handle current session cleanup
890
  if (this.currentSession && this.currentSession.id === sessionId) {
891
  if (isBackendSession) {
 
911
  }
912
  this.updateChatTitle();
913
  }
914
+
915
  this.loadChatSessions();
916
+
917
  } catch (error) {
918
  console.error('Error deleting session:', error);
919
  alert('Failed to delete session. Please try again.');
 
1057
  const sel = document.getElementById('profileNameSelect');
1058
  const newSec = document.getElementById('newDoctorSection');
1059
  if (!sel) return;
1060
+
1061
  // Load doctors from MongoDB
1062
  await this.loadDoctors();
1063
+
1064
  sel.innerHTML = '';
1065
  const createOpt = document.createElement('option');
1066
  createOpt.value = '__create__';
 
1103
  // Get current role and specialty from the form
1104
  const role = document.getElementById('profileRole').value || 'Medical Professional';
1105
  const specialty = document.getElementById('profileSpecialty').value.trim() || '';
1106
+
1107
  // Create doctor in MongoDB
1108
+ const result = await this.createDoctor({
1109
+ name,
1110
+ role,
1111
  specialty,
1112
  medical_roles: [role]
1113
  });
1114
  if (result) {
1115
+ this.doctors.unshift({
1116
+ name,
1117
+ role,
1118
+ specialty,
1119
+ _id: result.doctor_id
1120
  });
1121
  this.saveDoctors();
1122
+
1123
  // Update current user profile
1124
  this.currentUser.name = name;
1125
  this.currentUser.role = role;
 
1165
  const existingDoctorIndex = this.doctors.findIndex(d => d.name === name);
1166
  if (existingDoctorIndex === -1) {
1167
  // Add new doctor to local list
1168
+ this.doctors.unshift({
1169
+ name,
1170
+ role,
1171
+ specialty
1172
  });
1173
  } else {
1174
  // Update existing doctor in local list
 
1188
  specialty: specialty || null,
1189
  medical_roles: [role]
1190
  };
1191
+
1192
  try {
1193
  const resp = await fetch('/doctors', {
1194
  method: 'POST',
1195
  headers: { 'Content-Type': 'application/json' },
1196
  body: JSON.stringify(doctorPayload)
1197
  });
1198
+
1199
  if (!resp.ok) throw new Error('Failed to create doctor in backend');
1200
  const data = await resp.json();
1201
  console.log('[Doctor] Created new doctor in backend:', data);
1202
+
1203
  // Update local doctor with the ID from backend
1204
  const localDoctor = this.doctors.find(d => d.name === name);
1205
  if (localDoctor) {
 
1219
  // ================================================================================
1220
  // PATIENT.JS FUNCTIONALITY
1221
  // ================================================================================
1222
+
1223
  async getLocalStorageSuggestions(query) {
1224
  try {
1225
  const storedPatients = JSON.parse(localStorage.getItem('medicalChatbotPatients') || '[]');
 
1243
  combinePatientResults(mongoResults, localResults) {
1244
  // Create a map to deduplicate by patient_id, with MongoDB results taking priority
1245
  const resultMap = new Map();
1246
+
1247
  // Add MongoDB results first (they take priority)
1248
  mongoResults.forEach(patient => {
1249
  resultMap.set(patient.patient_id, patient);
1250
  });
1251
+
1252
  // Add localStorage results only if not already present
1253
  localResults.forEach(patient => {
1254
  if (!resultMap.has(patient.patient_id)) {
1255
  resultMap.set(patient.patient_id, patient);
1256
  }
1257
  });
1258
+
1259
  return Array.from(resultMap.values());
1260
  }
1261
 
 
1282
  const status = document.getElementById('patientStatus');
1283
  const actions = document.getElementById('patientActions');
1284
  const emrLink = document.getElementById('emrLink');
1285
+
1286
  if (status) {
1287
  // Try to fetch patient name
1288
  try {
 
1298
  }
1299
  status.style.color = 'var(--text-secondary)';
1300
  }
1301
+
1302
  // Show EMR link
1303
  if (actions) actions.style.display = 'block';
1304
  if (emrLink) emrLink.href = `/static/emr.html?patient_id=${pid}`;
1305
+
1306
  const input = document.getElementById('patientIdInput');
1307
  if (input) input.value = pid;
1308
  }
 
1317
  const status = document.getElementById('patientStatus');
1318
  const actions = document.getElementById('patientActions');
1319
  const emrLink = document.getElementById('emrLink');
1320
+
1321
  if (status) {
1322
  if (patientName) {
1323
  status.textContent = `Patient: ${patientName} (${patientId})`;
 
1326
  }
1327
  status.style.color = 'var(--text-secondary)';
1328
  }
1329
+
1330
  // Show EMR link
1331
  if (actions) actions.style.display = 'block';
1332
  if (emrLink) emrLink.href = `/static/emr.html?patient_id=${patientId}`;
 
1338
  const status = document.getElementById('patientStatus');
1339
  const value = (input?.value || '').trim();
1340
  console.log('[DEBUG] Patient input value:', value);
1341
+
1342
  if (!value) {
1343
  console.log('[DEBUG] No input provided');
1344
  if (status) { status.textContent = 'Please enter patient ID or name.'; status.style.color = 'var(--warning-color)'; }
1345
  return;
1346
  }
1347
+
1348
  // If it's a complete 8-digit ID, use it directly
1349
  if (/^\d{8}$/.test(value)) {
1350
  console.log('[DEBUG] Valid 8-digit ID provided');
 
1365
  await this.fetchAndRenderPatientSessions();
1366
  return;
1367
  }
1368
+
1369
  // Otherwise, search for patient by name or partial ID
1370
  console.log('[DEBUG] Searching for patient by name/partial ID');
1371
  try {
 
1388
  } catch (e) {
1389
  console.error('[DEBUG] Search error:', e);
1390
  }
1391
+
1392
  // No patient found
1393
  console.log('[DEBUG] No patient found');
1394
  if (status) { status.textContent = 'No patient found. Try a different search.'; status.style.color = 'var(--warning-color)'; }
 
1396
 
1397
  fetchAndRenderPatientSessions = async function () {
1398
  if (!this.currentPatientId) return;
1399
+
1400
  // Check localStorage cache first
1401
  const cacheKey = `sessions_${this.currentPatientId}`;
1402
  const cached = localStorage.getItem(cacheKey);
1403
  let sessions = [];
1404
+
1405
  if (cached) {
1406
  try {
1407
  const cachedData = JSON.parse(cached);
 
1416
  console.warn('Failed to parse cached sessions:', e);
1417
  }
1418
  }
1419
+
1420
  // If no cache or cache is stale, fetch from backend
1421
  if (sessions.length === 0) {
1422
  try {
 
1424
  if (resp.ok) {
1425
  const data = await resp.json();
1426
  sessions = Array.isArray(data.sessions) ? data.sessions : [];
1427
+
1428
  // Cache the sessions
1429
  localStorage.setItem(cacheKey, JSON.stringify({
1430
  sessions: sessions,
 
1438
  console.error('Failed to load patient sessions', e);
1439
  }
1440
  }
1441
+
1442
  // Process sessions
1443
  this.backendSessions = sessions.map(s => ({
1444
  id: s.session_id,
 
1448
  lastActivity: s.last_activity || new Date().toISOString(),
1449
  source: 'backend'
1450
  }));
1451
+
1452
  if (this.backendSessions.length > 0) {
1453
  this.currentSession = this.backendSessions[0];
1454
  await this.hydrateMessagesForSession(this.currentSession.id);
1455
  }
1456
+
1457
  this.loadChatSessions();
1458
  }
1459
 
 
1463
  const cacheKey = `messages_${this.currentPatientId}_${sessionId}`;
1464
  const cached = localStorage.getItem(cacheKey);
1465
  let messages = [];
1466
+
1467
  if (cached) {
1468
  try {
1469
  const cachedData = JSON.parse(cached);
 
1478
  console.warn('Failed to parse cached messages:', e);
1479
  }
1480
  }
1481
+
1482
  // If no cache or cache is stale, fetch from backend
1483
  if (messages.length === 0) {
1484
  const resp = await fetch(`/sessions/${sessionId}/messages?patient_id=${this.currentPatientId}&limit=1000`);
 
1494
  content: m.content,
1495
  timestamp: m.timestamp
1496
  }));
1497
+
1498
  // Cache the messages
1499
  localStorage.setItem(cacheKey, JSON.stringify({
1500
  messages: messages,
 
1502
  }));
1503
  console.log('[DEBUG] Cached messages for session:', sessionId, 'count:', messages.length);
1504
  }
1505
+
1506
  // Sort messages by timestamp (ascending order for display)
1507
  const sortedMessages = messages.sort((a, b) => {
1508
  const timeA = new Date(a.timestamp || 0).getTime();
1509
  const timeB = new Date(b.timestamp || 0).getTime();
1510
  return timeA - timeB;
1511
  });
1512
+
1513
  if (this.currentSession && this.currentSession.id === sessionId) {
1514
  this.currentSession.messages = sortedMessages;
1515
  this.clearChatMessages();
 
1570
  console.log('[DEBUG] Search URL:', url);
1571
  const resp = await fetch(url);
1572
  console.log('[DEBUG] Search response status:', resp.status);
1573
+
1574
  let mongoResults = [];
1575
  if (resp.ok) {
1576
  const data = await resp.json();
 
1579
  } else {
1580
  console.warn('MongoDB search request failed', resp.status);
1581
  }
1582
+
1583
  // Get localStorage suggestions as fallback/additional results
1584
  const localResults = await this.getLocalStorageSuggestions(q);
1585
+
1586
  // Combine and deduplicate results (MongoDB results take priority)
1587
  const combinedResults = this.combinePatientResults(mongoResults, localResults);
1588
  console.log('[DEBUG] Combined search results:', combinedResults);
1589
  renderSuggestions(combinedResults);
1590
+
1591
+ } catch (e) {
1592
  console.error('[DEBUG] Search error:', e);
1593
  // Fallback for network errors
1594
  console.log('[DEBUG] Trying fallback search after error');
 
1617
  const closeBtn = document.getElementById('patientModalClose');
1618
  const logoutBtn = document.getElementById('patientLogoutBtn');
1619
  const createBtn = document.getElementById('patientCreateBtn');
1620
+
1621
  if (profileBtn && modal) {
1622
  profileBtn.addEventListener('click', async () => {
1623
  const pid = this?.currentPatientId;
 
1641
  modal.classList.add('show');
1642
  });
1643
  }
1644
+
1645
  if (closeBtn && modal) {
1646
  closeBtn.addEventListener('click', () => modal.classList.remove('show'));
1647
  modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('show'); });
1648
  }
1649
+
1650
  if (logoutBtn) {
1651
  logoutBtn.addEventListener('click', () => {
1652
  if (confirm('Log out current patient?')) {
 
1660
  }
1661
  });
1662
  }
1663
+
1664
  if (createBtn) createBtn.addEventListener('click', () => modal.classList.remove('show'));
1665
  }
1666
 
 
1671
  try {
1672
  this.audioRecorder = new AudioRecordingUI(this);
1673
  const success = await this.audioRecorder.initialize();
1674
+
1675
  if (success) {
1676
  console.log('[Audio] Audio recording initialized successfully');
1677
  // Make globally accessible for voice detection callback
 
1729
  const response = await this.callMedicalAPI(message);
1730
  this.addMessage('assistant', response);
1731
  this.updateCurrentSession();
1732
+
1733
  // Invalidate caches after successful message exchange
1734
  if (this.currentSession && this.currentSession.id) {
1735
  this.invalidateMessageCache(this.currentPatientId, this.currentSession.id);
 
1802
  // Check if session needs title generation after messages are loaded
1803
  checkAndGenerateSessionTitle() {
1804
  if (!this.currentSession || !this.currentSession.messages) return;
1805
+
1806
  // Check if this is a new session that needs a title (exactly 2 messages: user + assistant)
1807
+ if (this.currentSession.messages.length === 2 &&
1808
+ this.currentSession.title === 'New Chat' &&
1809
  this.currentSession.messages[0].role === 'user') {
1810
  const firstMessage = this.currentSession.messages[0].content;
1811
  this.summariseAndSetTitle(firstMessage);
 
1889
  }
1890
  // ----------------------------------------------------------
1891
  // Additional UI setup END
1892
+ // ----------------------------------------------------------
1893
 
1894
 
1895
  // Initialize the app when DOM is loaded
 
1998
  this.isRecording = true;
1999
  this.recordingStartTime = Date.now();
2000
  console.log('Audio recording started');
2001
+
2002
  // Start timer
2003
  this.startTimer();
2004
+
2005
  // Start voice detection
2006
  this.startVoiceDetection();
2007
+
2008
  return true;
2009
  } catch (error) {
2010
  console.error('Failed to start recording:', error);
 
2021
  this.mediaRecorder.stop();
2022
  this.isRecording = false;
2023
  console.log('Audio recording stopped');
2024
+
2025
  // Stop timer and voice detection
2026
  this.stopTimer();
2027
  this.stopVoiceDetection();
2028
+
2029
  return true;
2030
  } catch (error) {
2031
  console.error('Failed to stop recording:', error);
 
2040
  const minutes = Math.floor(elapsed / 60);
2041
  const seconds = elapsed % 60;
2042
  const timeString = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
2043
+
2044
  const timerElement = document.getElementById('recordingTimer');
2045
  if (timerElement) {
2046
  timerElement.textContent = timeString;
 
2059
  startVoiceDetection() {
2060
  const checkVoice = () => {
2061
  if (!this.isRecording || !this.analyser) return;
2062
+
2063
  const bufferLength = this.analyser.frequencyBinCount;
2064
  const dataArray = new Uint8Array(bufferLength);
2065
  this.analyser.getByteFrequencyData(dataArray);
2066
+
2067
  // Calculate average volume
2068
  const average = dataArray.reduce((sum, value) => sum + value, 0) / bufferLength;
2069
  const threshold = 20; // Adjust this value to change sensitivity
2070
+
2071
  const container = document.querySelector('.recording-container');
2072
  const statusElement = document.getElementById('recordingStatus');
2073
+
2074
  if (average > threshold) {
2075
  // Voice detected
2076
  container.classList.remove('silent');
2077
  container.classList.add('listening');
2078
  if (statusElement) statusElement.textContent = 'Listening...';
2079
+
2080
  // Reset silence timer
2081
  this.resetSilenceTimer();
2082
  } else {
 
2085
  container.classList.add('silent');
2086
  if (statusElement) statusElement.textContent = 'Silence detected...';
2087
  }
2088
+
2089
  requestAnimationFrame(checkVoice);
2090
  };
2091
+
2092
  checkVoice();
2093
  }
2094
 
 
2100
  if (this.silenceTimer) {
2101
  clearTimeout(this.silenceTimer);
2102
  }
2103
+
2104
  // Auto-stop after 3 seconds of silence
2105
  this.silenceTimer = setTimeout(() => {
2106
  if (this.isRecording) {
 
2123
  try {
2124
  // Create audio blob
2125
  const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
2126
+
2127
  // Transcribe audio
2128
  const transcribedText = await this.transcribeAudio(audioBlob);
2129
+
2130
  return transcribedText;
2131
  } catch (error) {
2132
  console.error('Failed to process recording:', error);
 
2163
  this.audioStream.getTracks().forEach(track => track.stop());
2164
  this.audioStream = null;
2165
  }
2166
+
2167
  if (this.audioContext) {
2168
  this.audioContext.close();
2169
  this.audioContext = null;
2170
  }
2171
+
2172
  if (this.silenceTimer) {
2173
  clearTimeout(this.silenceTimer);
2174
  this.silenceTimer = null;
2175
  }
2176
+
2177
  this.stopTimer();
2178
+
2179
  this.mediaRecorder = null;
2180
  this.audioChunks = [];
2181
  this.isRecording = false;
 
2217
  setupUI() {
2218
  this.microphoneBtn = document.getElementById('microphoneBtn');
2219
  this.modal = document.getElementById('audioRecordingModal');
2220
+
2221
  if (!this.microphoneBtn) {
2222
  console.error('Microphone button not found');
2223
  return;
 
2230
 
2231
  // Set up event listeners
2232
  this.microphoneBtn.addEventListener('click', (e) => this.startRecording(e));
2233
+
2234
  // Modal close handlers
2235
  const closeBtn = document.getElementById('audioRecordingModalClose');
2236
  const stopBtn = document.getElementById('stopRecordingBtn');
2237
+
2238
  if (closeBtn) {
2239
  closeBtn.addEventListener('click', () => this.closeModal());
2240
  }
2241
+
2242
  if (stopBtn) {
2243
  stopBtn.addEventListener('click', () => this.stopRecording());
2244
  }
 
2260
  }
2261
 
2262
  event.preventDefault();
2263
+
2264
  try {
2265
  // Show modal
2266
  this.showModal();
2267
+
2268
  // Start recording
2269
  const success = this.recorder.startRecording();
2270
  if (success) {
 
2302
  try {
2303
  // Process the recording
2304
  const transcribedText = await this.recorder.processRecording();
2305
+
2306
  if (transcribedText) {
2307
  this.insertTranscribedText(transcribedText);
2308
  this.showSuccess('Audio transcribed successfully!');
2309
  } else {
2310
  this.showError('No speech detected. Please try again.');
2311
  }
2312
+
2313
  this.closeModal();
2314
  } catch (error) {
2315
  console.error('Failed to process recording:', error);
 
2329
  if (this.modal) {
2330
  this.modal.classList.remove('show');
2331
  document.body.style.overflow = ''; // Restore scrolling
2332
+
2333
  // Stop recording if still active
2334
  if (this.recorder.isRecording) {
2335
  this.recorder.stopRecording();
2336
  }
2337
+
2338
  // Reset modal state
2339
  this.updateModalState('ready');
2340
  }
 
2344
  const container = document.querySelector('.recording-container');
2345
  const statusElement = document.getElementById('recordingStatus');
2346
  const stopBtn = document.getElementById('stopRecordingBtn');
2347
+
2348
  if (!container) return;
2349
 
2350
  // Remove all state classes
2351
  container.classList.remove('listening', 'silent', 'processing');
2352
+
2353
  switch (state) {
2354
  case 'ready':
2355
  container.classList.add('listening');
 
2379
  // Append transcribed text to existing content
2380
  const currentText = chatInput.value.trim();
2381
  const newText = currentText ? `${currentText} ${text}` : text;
2382
+
2383
  chatInput.value = newText;
2384
+
2385
  // Add visual feedback for transcribed text
2386
  chatInput.classList.add('transcribed');
2387
+
2388
  // Remove the highlighting after a few seconds
2389
  setTimeout(() => {
2390
  chatInput.classList.remove('transcribed');
2391
  }, 3000);
2392
+
2393
  // Trigger input event to update UI
2394
  chatInput.dispatchEvent(new Event('input', { bubbles: true }));
2395
+
2396
  // Focus the input
2397
  chatInput.focus();
2398
+
2399
  // Auto-resize if needed
2400
  if (this.app && this.app.autoResizeTextarea) {
2401
  this.app.autoResizeTextarea(chatInput);
 
2407
 
2408
  // Remove all state classes
2409
  this.microphoneBtn.classList.remove('recording-ready', 'recording-active', 'recording-processing');
2410
+
2411
  // Add appropriate state class
2412
  switch (state) {
2413
  case 'ready':
 
2432
  errorMsg = document.createElement('div');
2433
  errorMsg.id = 'audioError';
2434
  errorMsg.className = 'audio-error-message';
2435
+
2436
  const chatInputContainer = document.querySelector('.chat-input-container');
2437
  if (chatInputContainer) {
2438
  chatInputContainer.appendChild(errorMsg);
2439
  }
2440
  }
2441
+
2442
  errorMsg.textContent = message;
2443
  errorMsg.style.display = 'block';
2444
+
2445
  // Hide after 5 seconds
2446
  setTimeout(() => {
2447
  errorMsg.style.display = 'none';
 
2455
  successMsg = document.createElement('div');
2456
  successMsg.id = 'audioSuccess';
2457
  successMsg.className = 'audio-success-message';
2458
+
2459
  const chatInputContainer = document.querySelector('.chat-input-container');
2460
  if (chatInputContainer) {
2461
  chatInputContainer.appendChild(successMsg);
2462
  }
2463
  }
2464
+
2465
  successMsg.textContent = message;
2466
  successMsg.style.display = 'block';
2467
+
2468
  // Hide after 3 seconds
2469
  setTimeout(() => {
2470
  successMsg.style.display = 'none';
 
2555
  if (userModal) {
2556
  userModal.addEventListener('click', (e) => { if (e.target === userModal) userModal.classList.remove('show'); });
2557
  }
2558
+ });