Spaces:
Runtime error
Runtime error
dylanglenister
commited on
Commit
·
7fbfb89
1
Parent(s):
1faabaf
Updating app js file
Browse files- static/js/app.js +122 -135
static/js/app.js
CHANGED
|
@@ -599,10 +599,10 @@ How can I assist you today?`;
|
|
| 599 |
} else {
|
| 600 |
// If doctor not found in local list, try to fetch from backend
|
| 601 |
try {
|
| 602 |
-
const resp = await fetch(`/account
|
| 603 |
if (resp.ok) {
|
| 604 |
const data = await resp.json();
|
| 605 |
-
const doctor = data
|
| 606 |
if (doctor) {
|
| 607 |
if (doctor.role) {
|
| 608 |
roleEl.value = doctor.role;
|
|
@@ -730,12 +730,10 @@ How can I assist you today?`;
|
|
| 730 |
const sessionElement = document.createElement('div');
|
| 731 |
sessionElement.className = `chat-session ${session.id === this.currentSession?.id ? 'active' : ''}`;
|
| 732 |
sessionElement.addEventListener('click', async () => {
|
| 733 |
-
if
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
this.loadChatSession(session.id);
|
| 738 |
-
}
|
| 739 |
});
|
| 740 |
const time = this.formatTime(session.lastActivity);
|
| 741 |
sessionElement.innerHTML = `
|
|
@@ -853,9 +851,6 @@ How can I assist you today?`;
|
|
| 853 |
throw new Error(`HTTP ${resp.status}`);
|
| 854 |
}
|
| 855 |
|
| 856 |
-
const result = await resp.json();
|
| 857 |
-
console.log('[DEBUG] Backend deletion result:', result);
|
| 858 |
-
|
| 859 |
// Remove from backend sessions
|
| 860 |
this.backendSessions = this.backendSessions.filter(s => s.id !== sessionId);
|
| 861 |
|
|
@@ -971,13 +966,13 @@ How can I assist you today?`;
|
|
| 971 |
const resp = await fetch('/account');
|
| 972 |
if (resp.ok) {
|
| 973 |
const data = await resp.json();
|
| 974 |
-
this.doctors = data
|
| 975 |
// Ensure each doctor has role and specialty information
|
| 976 |
this.doctors = this.doctors.map(doctor => ({
|
| 977 |
name: doctor.name,
|
| 978 |
role: doctor.role || 'Medical Professional',
|
| 979 |
specialty: doctor.specialty || '',
|
| 980 |
-
|
| 981 |
}));
|
| 982 |
// Also save to localStorage for offline access
|
| 983 |
localStorage.setItem('medicalChatbotDoctors', JSON.stringify(this.doctors));
|
|
@@ -1004,10 +999,10 @@ How can I assist you today?`;
|
|
| 1004 |
|
| 1005 |
async searchDoctors(query) {
|
| 1006 |
try {
|
| 1007 |
-
const resp = await fetch(`/account
|
| 1008 |
if (resp.ok) {
|
| 1009 |
const data = await resp.json();
|
| 1010 |
-
return data
|
| 1011 |
}
|
| 1012 |
} catch (e) {
|
| 1013 |
console.warn('Doctor search failed:', e);
|
|
@@ -1025,8 +1020,7 @@ How can I assist you today?`;
|
|
| 1025 |
if (resp.ok) {
|
| 1026 |
const data = await resp.json();
|
| 1027 |
// Add to local doctors list
|
| 1028 |
-
|
| 1029 |
-
this.doctors.push({ name: data.name, _id: data.account_id });
|
| 1030 |
this.saveDoctors();
|
| 1031 |
return data;
|
| 1032 |
}
|
|
@@ -1089,26 +1083,25 @@ How can I assist you today?`;
|
|
| 1089 |
if (!this.doctors.find(d => d.name === name)) {
|
| 1090 |
// Get current role and specialty from the form
|
| 1091 |
const role = document.getElementById('profileRole').value || 'Medical Professional';
|
| 1092 |
-
const specialty = document.getElementById('profileSpecialty').value.trim() ||
|
| 1093 |
|
| 1094 |
// Create doctor in MongoDB
|
| 1095 |
const result = await this.createDoctor({
|
| 1096 |
name,
|
| 1097 |
role,
|
| 1098 |
-
specialty
|
| 1099 |
-
medical_roles: [role]
|
| 1100 |
});
|
| 1101 |
-
if (result && result.
|
| 1102 |
this.doctors.unshift({
|
| 1103 |
name,
|
| 1104 |
role,
|
| 1105 |
specialty,
|
| 1106 |
-
|
| 1107 |
});
|
| 1108 |
this.saveDoctors();
|
| 1109 |
|
| 1110 |
// Update current user profile
|
| 1111 |
-
this.currentUser.id = result.
|
| 1112 |
this.currentUser.name = name;
|
| 1113 |
this.currentUser.role = role;
|
| 1114 |
this.currentUser.specialty = specialty;
|
|
@@ -1134,10 +1127,13 @@ How can I assist you today?`;
|
|
| 1134 |
}
|
| 1135 |
|
| 1136 |
// Check if doctor exists in MongoDB first
|
| 1137 |
-
let
|
| 1138 |
try {
|
| 1139 |
-
const resp = await fetch(`/account
|
| 1140 |
-
|
|
|
|
|
|
|
|
|
|
| 1141 |
} catch (e) {
|
| 1142 |
console.warn('Failed to check doctor existence:', e);
|
| 1143 |
}
|
|
@@ -1148,12 +1144,11 @@ How can I assist you today?`;
|
|
| 1148 |
this.currentUser.specialty = specialty;
|
| 1149 |
|
| 1150 |
// Only create new doctor in MongoDB if it doesn't exist
|
| 1151 |
-
if (!
|
| 1152 |
const doctorPayload = {
|
| 1153 |
name: name,
|
| 1154 |
role: role,
|
| 1155 |
-
specialty: specialty || null
|
| 1156 |
-
medical_roles: [role]
|
| 1157 |
};
|
| 1158 |
|
| 1159 |
try {
|
|
@@ -1167,17 +1162,16 @@ How can I assist you today?`;
|
|
| 1167 |
const data = await resp.json();
|
| 1168 |
console.log('[Doctor] Created new doctor in backend:', data);
|
| 1169 |
|
| 1170 |
-
|
| 1171 |
-
|
| 1172 |
-
this.currentUser.id = data.account_id;
|
| 1173 |
}
|
| 1174 |
|
| 1175 |
// Update local doctor list with the ID from backend
|
| 1176 |
const existingDoctorIndex = this.doctors.findIndex(d => d.name === name);
|
| 1177 |
if (existingDoctorIndex === -1) {
|
| 1178 |
-
this.doctors.unshift({ name, role, specialty,
|
| 1179 |
} else {
|
| 1180 |
-
this.doctors[existingDoctorIndex].
|
| 1181 |
}
|
| 1182 |
|
| 1183 |
} catch (err) {
|
|
@@ -1185,9 +1179,8 @@ How can I assist you today?`;
|
|
| 1185 |
}
|
| 1186 |
} else {
|
| 1187 |
// If doctor exists, find their ID and update currentUser
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
this.currentUser.id = existingDoctor._id;
|
| 1191 |
}
|
| 1192 |
console.log('[Doctor] Doctor already exists in backend, no creation needed');
|
| 1193 |
}
|
|
@@ -1208,8 +1201,7 @@ How can I assist you today?`;
|
|
| 1208 |
const storedPatients = JSON.parse(localStorage.getItem('medicalChatbotPatients') || '[]');
|
| 1209 |
return storedPatients.filter(p => {
|
| 1210 |
const nameMatch = p.name.toLowerCase().includes(query.toLowerCase());
|
| 1211 |
-
|
| 1212 |
-
const idMatch = p._id && p._id.includes(query);
|
| 1213 |
return nameMatch || idMatch;
|
| 1214 |
});
|
| 1215 |
} catch (e) {
|
|
@@ -1222,14 +1214,12 @@ How can I assist you today?`;
|
|
| 1222 |
const resultMap = new Map();
|
| 1223 |
// Add MongoDB results first (they take priority)
|
| 1224 |
mongoResults.forEach(patient => {
|
| 1225 |
-
|
| 1226 |
-
if(patient._id) resultMap.set(patient._id, patient);
|
| 1227 |
});
|
| 1228 |
// Add localStorage results only if not already present
|
| 1229 |
localResults.forEach(patient => {
|
| 1230 |
-
|
| 1231 |
-
|
| 1232 |
-
resultMap.set(patient._id, patient);
|
| 1233 |
}
|
| 1234 |
});
|
| 1235 |
return Array.from(resultMap.values());
|
|
@@ -1265,7 +1255,7 @@ How can I assist you today?`;
|
|
| 1265 |
const resp = await fetch(`/patient/${pid}`);
|
| 1266 |
if (resp.ok) {
|
| 1267 |
const patient = await resp.json();
|
| 1268 |
-
status.textContent = `Patient: ${patient.name || 'Unknown'} (${patient.
|
| 1269 |
} else {
|
| 1270 |
status.textContent = `Patient: ${pid}`;
|
| 1271 |
}
|
|
@@ -1324,18 +1314,18 @@ How can I assist you today?`;
|
|
| 1324 |
// Search for patient by name or ID
|
| 1325 |
console.log('[DEBUG] Searching for patient');
|
| 1326 |
try {
|
| 1327 |
-
const resp = await fetch(`/patient
|
| 1328 |
console.log('[DEBUG] Search response status:', resp.status);
|
| 1329 |
if (resp.ok) {
|
| 1330 |
const data = await resp.json();
|
| 1331 |
console.log('[DEBUG] Search results:', data);
|
| 1332 |
-
const first = (data
|
| 1333 |
-
if (first && first.
|
| 1334 |
console.log('[DEBUG] Found patient, setting as current:', first);
|
| 1335 |
-
this.currentPatientId = first.
|
| 1336 |
this.savePatientId();
|
| 1337 |
-
input.value = first.
|
| 1338 |
-
this.updatePatientDisplay(first.
|
| 1339 |
await this.fetchAndRenderPatientSessions();
|
| 1340 |
return;
|
| 1341 |
}
|
|
@@ -1378,7 +1368,7 @@ How can I assist you today?`;
|
|
| 1378 |
const resp = await fetch(`/patient/${this.currentPatientId}/session`);
|
| 1379 |
if (resp.ok) {
|
| 1380 |
const data = await resp.json();
|
| 1381 |
-
sessions = Array.isArray(data
|
| 1382 |
|
| 1383 |
// Cache the sessions
|
| 1384 |
localStorage.setItem(cacheKey, JSON.stringify({
|
|
@@ -1396,12 +1386,11 @@ How can I assist you today?`;
|
|
| 1396 |
|
| 1397 |
// Process sessions
|
| 1398 |
this.backendSessions = sessions.map(s => ({
|
| 1399 |
-
|
| 1400 |
-
id: s.session_id || s._id,
|
| 1401 |
title: s.title || 'New Chat',
|
| 1402 |
messages: [],
|
| 1403 |
createdAt: s.created_at || new Date().toISOString(),
|
| 1404 |
-
lastActivity: s.
|
| 1405 |
source: 'backend'
|
| 1406 |
}));
|
| 1407 |
|
|
@@ -1414,7 +1403,6 @@ How can I assist you today?`;
|
|
| 1414 |
}
|
| 1415 |
|
| 1416 |
hydrateMessagesForSession = async function (sessionId) {
|
| 1417 |
-
// FIX: Add a guard clause to prevent calling the API with an invalid ID
|
| 1418 |
if (!sessionId || sessionId === 'undefined') {
|
| 1419 |
console.error('[DEBUG] hydrateMessagesForSession was called with an invalid session ID:', sessionId);
|
| 1420 |
return;
|
|
@@ -1442,16 +1430,16 @@ How can I assist you today?`;
|
|
| 1442 |
|
| 1443 |
// If no cache or cache is stale, fetch from backend
|
| 1444 |
if (messages.length === 0) {
|
| 1445 |
-
const resp = await fetch(`/session/${sessionId}/messages
|
| 1446 |
if (!resp.ok) {
|
| 1447 |
console.warn(`Failed to fetch messages for session ${sessionId}:`, resp.status);
|
| 1448 |
return;
|
| 1449 |
}
|
| 1450 |
const data = await resp.json();
|
| 1451 |
-
const msgs = Array.isArray(data
|
| 1452 |
messages = msgs.map(m => ({
|
| 1453 |
-
id: m.
|
| 1454 |
-
role: m.
|
| 1455 |
content: m.content,
|
| 1456 |
timestamp: m.timestamp
|
| 1457 |
}));
|
|
@@ -1506,15 +1494,13 @@ How can I assist you today?`;
|
|
| 1506 |
items.forEach(p => {
|
| 1507 |
const div = document.createElement('div');
|
| 1508 |
div.className = 'patient-suggestion';
|
| 1509 |
-
|
| 1510 |
-
div.textContent = `${p.name || 'Unknown'} (${p._id})`;
|
| 1511 |
div.addEventListener('click', async () => {
|
| 1512 |
-
|
| 1513 |
-
this.currentPatientId = p._id;
|
| 1514 |
this.savePatientId();
|
| 1515 |
-
patientInput.value = p.
|
| 1516 |
hideSuggestions();
|
| 1517 |
-
this.updatePatientDisplay(p.
|
| 1518 |
await this.fetchAndRenderPatientSessions();
|
| 1519 |
});
|
| 1520 |
suggestionsEl.appendChild(div);
|
|
@@ -1529,7 +1515,7 @@ How can I assist you today?`;
|
|
| 1529 |
debounceTimer = setTimeout(async () => {
|
| 1530 |
try {
|
| 1531 |
console.log('[DEBUG] Searching patients with query:', q);
|
| 1532 |
-
const url = `/patient
|
| 1533 |
console.log('[DEBUG] Search URL:', url);
|
| 1534 |
const resp = await fetch(url);
|
| 1535 |
console.log('[DEBUG] Search response status:', resp.status);
|
|
@@ -1537,7 +1523,7 @@ How can I assist you today?`;
|
|
| 1537 |
let mongoResults = [];
|
| 1538 |
if (resp.ok) {
|
| 1539 |
const data = await resp.json();
|
| 1540 |
-
mongoResults = data
|
| 1541 |
console.log('[DEBUG] MongoDB search results:', mongoResults);
|
| 1542 |
} else {
|
| 1543 |
console.warn('MongoDB search request failed', resp.status);
|
|
@@ -1689,23 +1675,25 @@ How can I assist you today?`;
|
|
| 1689 |
this.addMessage('user', message);
|
| 1690 |
this.showLoading(true);
|
| 1691 |
try {
|
| 1692 |
-
const isNewSession = this.currentSession
|
| 1693 |
-
const responseData = await this.callMedicalAPI(message);
|
| 1694 |
|
| 1695 |
if (isNewSession && responseData.session_id) {
|
| 1696 |
-
console.log(`[DEBUG] New session created on backend. Updating session ID
|
| 1697 |
-
const oldId = this.currentSession.id;
|
| 1698 |
-
this.currentSession.id = responseData.session_id;
|
| 1699 |
|
| 1700 |
-
//
|
| 1701 |
-
|
| 1702 |
-
|
| 1703 |
-
|
| 1704 |
-
|
| 1705 |
-
localStorage.setItem(`chatSessions_${this.currentUser.id}`, JSON.stringify(sessions));
|
| 1706 |
}
|
| 1707 |
-
|
| 1708 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1709 |
}
|
| 1710 |
|
| 1711 |
this.addMessage('assistant', responseData.response || 'I apologize, but I received an empty response. Please try again.');
|
|
@@ -1728,27 +1716,46 @@ How can I assist you today?`;
|
|
| 1728 |
}
|
| 1729 |
}
|
| 1730 |
|
| 1731 |
-
callMedicalAPI = async function (message) {
|
| 1732 |
try {
|
| 1733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1734 |
account_id: this.currentUser.id,
|
| 1735 |
patient_id: this.currentPatientId,
|
| 1736 |
message: message
|
| 1737 |
};
|
| 1738 |
|
| 1739 |
-
|
| 1740 |
-
if (this.currentSession?.id && this.currentSession.id !== 'default') {
|
| 1741 |
-
payload.session_id = this.currentSession.id;
|
| 1742 |
-
}
|
| 1743 |
-
|
| 1744 |
-
const response = await fetch('/chat', {
|
| 1745 |
method: 'POST',
|
| 1746 |
headers: { 'Content-Type': 'application/json' },
|
| 1747 |
-
body: JSON.stringify(
|
| 1748 |
});
|
| 1749 |
-
|
| 1750 |
-
|
|
|
|
| 1751 |
return data;
|
|
|
|
| 1752 |
} catch (error) {
|
| 1753 |
console.error('API call failed:', error);
|
| 1754 |
console.error('Error details:', {
|
|
@@ -1830,15 +1837,15 @@ How can I assist you today?`;
|
|
| 1830 |
messageElement.id = `message-${message.id}`;
|
| 1831 |
const avatar = message.role === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>';
|
| 1832 |
const time = this.formatTime(message.timestamp);
|
| 1833 |
-
|
| 1834 |
// Add EMR icon for assistant messages (system-generated)
|
| 1835 |
-
const emrIcon = message.role === 'assistant' ?
|
| 1836 |
`<div class="message-actions">
|
| 1837 |
<button class="emr-extract-btn" onclick="app.extractEMR('${message.id}')" title="Extract to EMR" data-message-id="${message.id}">
|
| 1838 |
<i class="fas fa-file-medical"></i>
|
| 1839 |
</button>
|
| 1840 |
</div>` : '';
|
| 1841 |
-
|
| 1842 |
messageElement.innerHTML = `
|
| 1843 |
<div class="message-avatar">${avatar}</div>
|
| 1844 |
<div class="message-content">
|
|
@@ -1849,11 +1856,6 @@ How can I assist you today?`;
|
|
| 1849 |
chatMessages.appendChild(messageElement);
|
| 1850 |
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 1851 |
if (this.currentSession) this.currentSession.lastActivity = new Date().toISOString();
|
| 1852 |
-
|
| 1853 |
-
// Check EMR status for assistant messages
|
| 1854 |
-
if (message.role === 'assistant' && this.currentPatientId) {
|
| 1855 |
-
this.checkEMRStatus(message.id);
|
| 1856 |
-
}
|
| 1857 |
}
|
| 1858 |
|
| 1859 |
formatMessageContent(content) {
|
|
@@ -1915,25 +1917,28 @@ How can I assist you today?`;
|
|
| 1915 |
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
| 1916 |
}
|
| 1917 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1918 |
// Call EMR extraction API
|
| 1919 |
-
const response = await fetch(
|
| 1920 |
method: 'POST',
|
| 1921 |
headers: {
|
| 1922 |
-
'Content-Type': 'application/json',
|
| 1923 |
-
}
|
| 1924 |
-
body: JSON.stringify({
|
| 1925 |
-
patient_id: this.currentPatientId,
|
| 1926 |
-
doctor_id: this.currentUser.id || 'default-doctor',
|
| 1927 |
-
message_id: messageId,
|
| 1928 |
-
session_id: this.currentSession?.id || 'default-session',
|
| 1929 |
-
message: message.content
|
| 1930 |
-
})
|
| 1931 |
});
|
| 1932 |
|
| 1933 |
if (response.ok) {
|
| 1934 |
const result = await response.json();
|
| 1935 |
console.log('EMR extraction successful:', result);
|
| 1936 |
-
|
| 1937 |
// Show success message
|
| 1938 |
if (button) {
|
| 1939 |
button.innerHTML = '<i class="fas fa-check"></i>';
|
|
@@ -1944,7 +1949,7 @@ How can I assist you today?`;
|
|
| 1944 |
button.disabled = false;
|
| 1945 |
}, 2000);
|
| 1946 |
}
|
| 1947 |
-
|
| 1948 |
// Show notification
|
| 1949 |
this.showNotification('EMR data extracted successfully!', 'success');
|
| 1950 |
} else {
|
|
@@ -1955,37 +1960,19 @@ How can I assist you today?`;
|
|
| 1955 |
|
| 1956 |
} catch (error) {
|
| 1957 |
console.error('Error extracting EMR:', error);
|
| 1958 |
-
|
| 1959 |
// Reset button state
|
| 1960 |
const button = document.querySelector(`[onclick="app.extractEMR('${messageId}')"]`);
|
| 1961 |
if (button) {
|
| 1962 |
button.innerHTML = '<i class="fas fa-file-medical"></i>';
|
| 1963 |
button.disabled = false;
|
| 1964 |
}
|
| 1965 |
-
|
| 1966 |
// Show error message
|
| 1967 |
this.showNotification('Failed to extract EMR data. Please try again.', 'error');
|
| 1968 |
}
|
| 1969 |
}
|
| 1970 |
|
| 1971 |
-
async checkEMRStatus(messageId) {
|
| 1972 |
-
try {
|
| 1973 |
-
const response = await fetch(`/emr/check/${messageId}`);
|
| 1974 |
-
if (response.ok) {
|
| 1975 |
-
const result = await response.json();
|
| 1976 |
-
const button = document.querySelector(`[data-message-id="${messageId}"]`);
|
| 1977 |
-
if (button && result.emr_exists) {
|
| 1978 |
-
button.innerHTML = '<i class="fas fa-check"></i>';
|
| 1979 |
-
button.style.color = 'var(--success-color)';
|
| 1980 |
-
button.title = 'EMR data already extracted';
|
| 1981 |
-
button.disabled = true;
|
| 1982 |
-
}
|
| 1983 |
-
}
|
| 1984 |
-
} catch (error) {
|
| 1985 |
-
console.warn('Could not check EMR status:', error);
|
| 1986 |
-
}
|
| 1987 |
-
}
|
| 1988 |
-
|
| 1989 |
showNotification(message, type = 'info') {
|
| 1990 |
// Create notification element
|
| 1991 |
const notification = document.createElement('div');
|
|
@@ -1996,13 +1983,13 @@ How can I assist you today?`;
|
|
| 1996 |
<span>${message}</span>
|
| 1997 |
</div>
|
| 1998 |
`;
|
| 1999 |
-
|
| 2000 |
// Add to page
|
| 2001 |
document.body.appendChild(notification);
|
| 2002 |
-
|
| 2003 |
// Show notification
|
| 2004 |
setTimeout(() => notification.classList.add('show'), 100);
|
| 2005 |
-
|
| 2006 |
// Remove after 3 seconds
|
| 2007 |
setTimeout(() => {
|
| 2008 |
notification.classList.remove('show');
|
|
|
|
| 599 |
} else {
|
| 600 |
// If doctor not found in local list, try to fetch from backend
|
| 601 |
try {
|
| 602 |
+
const resp = await fetch(`/account?q=${encodeURIComponent(selectedName)}&limit=1`);
|
| 603 |
if (resp.ok) {
|
| 604 |
const data = await resp.json();
|
| 605 |
+
const doctor = data && data[0];
|
| 606 |
if (doctor) {
|
| 607 |
if (doctor.role) {
|
| 608 |
roleEl.value = doctor.role;
|
|
|
|
| 730 |
const sessionElement = document.createElement('div');
|
| 731 |
sessionElement.className = `chat-session ${session.id === this.currentSession?.id ? 'active' : ''}`;
|
| 732 |
sessionElement.addEventListener('click', async () => {
|
| 733 |
+
// Avoid re-loading if the session is already active
|
| 734 |
+
if (session.id === this.currentSession?.id) return;
|
| 735 |
+
|
| 736 |
+
await this.switchToSession(session);
|
|
|
|
|
|
|
| 737 |
});
|
| 738 |
const time = this.formatTime(session.lastActivity);
|
| 739 |
sessionElement.innerHTML = `
|
|
|
|
| 851 |
throw new Error(`HTTP ${resp.status}`);
|
| 852 |
}
|
| 853 |
|
|
|
|
|
|
|
|
|
|
| 854 |
// Remove from backend sessions
|
| 855 |
this.backendSessions = this.backendSessions.filter(s => s.id !== sessionId);
|
| 856 |
|
|
|
|
| 966 |
const resp = await fetch('/account');
|
| 967 |
if (resp.ok) {
|
| 968 |
const data = await resp.json();
|
| 969 |
+
this.doctors = data || [];
|
| 970 |
// Ensure each doctor has role and specialty information
|
| 971 |
this.doctors = this.doctors.map(doctor => ({
|
| 972 |
name: doctor.name,
|
| 973 |
role: doctor.role || 'Medical Professional',
|
| 974 |
specialty: doctor.specialty || '',
|
| 975 |
+
id: doctor.id
|
| 976 |
}));
|
| 977 |
// Also save to localStorage for offline access
|
| 978 |
localStorage.setItem('medicalChatbotDoctors', JSON.stringify(this.doctors));
|
|
|
|
| 999 |
|
| 1000 |
async searchDoctors(query) {
|
| 1001 |
try {
|
| 1002 |
+
const resp = await fetch(`/account?q=${encodeURIComponent(query)}&limit=10`);
|
| 1003 |
if (resp.ok) {
|
| 1004 |
const data = await resp.json();
|
| 1005 |
+
return data || [];
|
| 1006 |
}
|
| 1007 |
} catch (e) {
|
| 1008 |
console.warn('Doctor search failed:', e);
|
|
|
|
| 1020 |
if (resp.ok) {
|
| 1021 |
const data = await resp.json();
|
| 1022 |
// Add to local doctors list
|
| 1023 |
+
this.doctors.push({ name: data.name, id: data.id });
|
|
|
|
| 1024 |
this.saveDoctors();
|
| 1025 |
return data;
|
| 1026 |
}
|
|
|
|
| 1083 |
if (!this.doctors.find(d => d.name === name)) {
|
| 1084 |
// Get current role and specialty from the form
|
| 1085 |
const role = document.getElementById('profileRole').value || 'Medical Professional';
|
| 1086 |
+
const specialty = document.getElementById('profileSpecialty').value.trim() || null;
|
| 1087 |
|
| 1088 |
// Create doctor in MongoDB
|
| 1089 |
const result = await this.createDoctor({
|
| 1090 |
name,
|
| 1091 |
role,
|
| 1092 |
+
specialty
|
|
|
|
| 1093 |
});
|
| 1094 |
+
if (result && result.id) {
|
| 1095 |
this.doctors.unshift({
|
| 1096 |
name,
|
| 1097 |
role,
|
| 1098 |
specialty,
|
| 1099 |
+
id: result.id
|
| 1100 |
});
|
| 1101 |
this.saveDoctors();
|
| 1102 |
|
| 1103 |
// Update current user profile
|
| 1104 |
+
this.currentUser.id = result.id;
|
| 1105 |
this.currentUser.name = name;
|
| 1106 |
this.currentUser.role = role;
|
| 1107 |
this.currentUser.specialty = specialty;
|
|
|
|
| 1127 |
}
|
| 1128 |
|
| 1129 |
// Check if doctor exists in MongoDB first
|
| 1130 |
+
let existingDoctor = null;
|
| 1131 |
try {
|
| 1132 |
+
const resp = await fetch(`/account?q=${encodeURIComponent(name)}`);
|
| 1133 |
+
if(resp.ok) {
|
| 1134 |
+
const accounts = await resp.json();
|
| 1135 |
+
existingDoctor = accounts.find(acc => acc.name === name);
|
| 1136 |
+
}
|
| 1137 |
} catch (e) {
|
| 1138 |
console.warn('Failed to check doctor existence:', e);
|
| 1139 |
}
|
|
|
|
| 1144 |
this.currentUser.specialty = specialty;
|
| 1145 |
|
| 1146 |
// Only create new doctor in MongoDB if it doesn't exist
|
| 1147 |
+
if (!existingDoctor) {
|
| 1148 |
const doctorPayload = {
|
| 1149 |
name: name,
|
| 1150 |
role: role,
|
| 1151 |
+
specialty: specialty || null
|
|
|
|
| 1152 |
};
|
| 1153 |
|
| 1154 |
try {
|
|
|
|
| 1162 |
const data = await resp.json();
|
| 1163 |
console.log('[Doctor] Created new doctor in backend:', data);
|
| 1164 |
|
| 1165 |
+
if (data.id) {
|
| 1166 |
+
this.currentUser.id = data.id;
|
|
|
|
| 1167 |
}
|
| 1168 |
|
| 1169 |
// Update local doctor list with the ID from backend
|
| 1170 |
const existingDoctorIndex = this.doctors.findIndex(d => d.name === name);
|
| 1171 |
if (existingDoctorIndex === -1) {
|
| 1172 |
+
this.doctors.unshift({ name, role, specialty, id: data.id });
|
| 1173 |
} else {
|
| 1174 |
+
this.doctors[existingDoctorIndex].id = data.id;
|
| 1175 |
}
|
| 1176 |
|
| 1177 |
} catch (err) {
|
|
|
|
| 1179 |
}
|
| 1180 |
} else {
|
| 1181 |
// If doctor exists, find their ID and update currentUser
|
| 1182 |
+
if (existingDoctor && existingDoctor.id) {
|
| 1183 |
+
this.currentUser.id = existingDoctor.id;
|
|
|
|
| 1184 |
}
|
| 1185 |
console.log('[Doctor] Doctor already exists in backend, no creation needed');
|
| 1186 |
}
|
|
|
|
| 1201 |
const storedPatients = JSON.parse(localStorage.getItem('medicalChatbotPatients') || '[]');
|
| 1202 |
return storedPatients.filter(p => {
|
| 1203 |
const nameMatch = p.name.toLowerCase().includes(query.toLowerCase());
|
| 1204 |
+
const idMatch = p.id && p.id.includes(query);
|
|
|
|
| 1205 |
return nameMatch || idMatch;
|
| 1206 |
});
|
| 1207 |
} catch (e) {
|
|
|
|
| 1214 |
const resultMap = new Map();
|
| 1215 |
// Add MongoDB results first (they take priority)
|
| 1216 |
mongoResults.forEach(patient => {
|
| 1217 |
+
if(patient.id) resultMap.set(patient.id, patient);
|
|
|
|
| 1218 |
});
|
| 1219 |
// Add localStorage results only if not already present
|
| 1220 |
localResults.forEach(patient => {
|
| 1221 |
+
if (patient.id && !resultMap.has(patient.id)) {
|
| 1222 |
+
resultMap.set(patient.id, patient);
|
|
|
|
| 1223 |
}
|
| 1224 |
});
|
| 1225 |
return Array.from(resultMap.values());
|
|
|
|
| 1255 |
const resp = await fetch(`/patient/${pid}`);
|
| 1256 |
if (resp.ok) {
|
| 1257 |
const patient = await resp.json();
|
| 1258 |
+
status.textContent = `Patient: ${patient.name || 'Unknown'} (${patient.id})`;
|
| 1259 |
} else {
|
| 1260 |
status.textContent = `Patient: ${pid}`;
|
| 1261 |
}
|
|
|
|
| 1314 |
// Search for patient by name or ID
|
| 1315 |
console.log('[DEBUG] Searching for patient');
|
| 1316 |
try {
|
| 1317 |
+
const resp = await fetch(`/patient?q=${encodeURIComponent(value)}&limit=1`);
|
| 1318 |
console.log('[DEBUG] Search response status:', resp.status);
|
| 1319 |
if (resp.ok) {
|
| 1320 |
const data = await resp.json();
|
| 1321 |
console.log('[DEBUG] Search results:', data);
|
| 1322 |
+
const first = (data || [])[0];
|
| 1323 |
+
if (first && first.id) {
|
| 1324 |
console.log('[DEBUG] Found patient, setting as current:', first);
|
| 1325 |
+
this.currentPatientId = first.id;
|
| 1326 |
this.savePatientId();
|
| 1327 |
+
input.value = first.id;
|
| 1328 |
+
this.updatePatientDisplay(first.id, first.name || 'Unknown');
|
| 1329 |
await this.fetchAndRenderPatientSessions();
|
| 1330 |
return;
|
| 1331 |
}
|
|
|
|
| 1368 |
const resp = await fetch(`/patient/${this.currentPatientId}/session`);
|
| 1369 |
if (resp.ok) {
|
| 1370 |
const data = await resp.json();
|
| 1371 |
+
sessions = Array.isArray(data) ? data : [];
|
| 1372 |
|
| 1373 |
// Cache the sessions
|
| 1374 |
localStorage.setItem(cacheKey, JSON.stringify({
|
|
|
|
| 1386 |
|
| 1387 |
// Process sessions
|
| 1388 |
this.backendSessions = sessions.map(s => ({
|
| 1389 |
+
id: s.id,
|
|
|
|
| 1390 |
title: s.title || 'New Chat',
|
| 1391 |
messages: [],
|
| 1392 |
createdAt: s.created_at || new Date().toISOString(),
|
| 1393 |
+
lastActivity: s.updated_at || new Date().toISOString(),
|
| 1394 |
source: 'backend'
|
| 1395 |
}));
|
| 1396 |
|
|
|
|
| 1403 |
}
|
| 1404 |
|
| 1405 |
hydrateMessagesForSession = async function (sessionId) {
|
|
|
|
| 1406 |
if (!sessionId || sessionId === 'undefined') {
|
| 1407 |
console.error('[DEBUG] hydrateMessagesForSession was called with an invalid session ID:', sessionId);
|
| 1408 |
return;
|
|
|
|
| 1430 |
|
| 1431 |
// If no cache or cache is stale, fetch from backend
|
| 1432 |
if (messages.length === 0) {
|
| 1433 |
+
const resp = await fetch(`/session/${sessionId}/messages`);
|
| 1434 |
if (!resp.ok) {
|
| 1435 |
console.warn(`Failed to fetch messages for session ${sessionId}:`, resp.status);
|
| 1436 |
return;
|
| 1437 |
}
|
| 1438 |
const data = await resp.json();
|
| 1439 |
+
const msgs = Array.isArray(data) ? data : [];
|
| 1440 |
messages = msgs.map(m => ({
|
| 1441 |
+
id: m.id || this.generateId(),
|
| 1442 |
+
role: m.sent_by_user ? 'user' : 'assistant',
|
| 1443 |
content: m.content,
|
| 1444 |
timestamp: m.timestamp
|
| 1445 |
}));
|
|
|
|
| 1494 |
items.forEach(p => {
|
| 1495 |
const div = document.createElement('div');
|
| 1496 |
div.className = 'patient-suggestion';
|
| 1497 |
+
div.textContent = `${p.name || 'Unknown'} (${p.id})`;
|
|
|
|
| 1498 |
div.addEventListener('click', async () => {
|
| 1499 |
+
this.currentPatientId = p.id;
|
|
|
|
| 1500 |
this.savePatientId();
|
| 1501 |
+
patientInput.value = p.id;
|
| 1502 |
hideSuggestions();
|
| 1503 |
+
this.updatePatientDisplay(p.id, p.name || 'Unknown');
|
| 1504 |
await this.fetchAndRenderPatientSessions();
|
| 1505 |
});
|
| 1506 |
suggestionsEl.appendChild(div);
|
|
|
|
| 1515 |
debounceTimer = setTimeout(async () => {
|
| 1516 |
try {
|
| 1517 |
console.log('[DEBUG] Searching patients with query:', q);
|
| 1518 |
+
const url = `/patient?q=${encodeURIComponent(q)}&limit=8`;
|
| 1519 |
console.log('[DEBUG] Search URL:', url);
|
| 1520 |
const resp = await fetch(url);
|
| 1521 |
console.log('[DEBUG] Search response status:', resp.status);
|
|
|
|
| 1523 |
let mongoResults = [];
|
| 1524 |
if (resp.ok) {
|
| 1525 |
const data = await resp.json();
|
| 1526 |
+
mongoResults = data || [];
|
| 1527 |
console.log('[DEBUG] MongoDB search results:', mongoResults);
|
| 1528 |
} else {
|
| 1529 |
console.warn('MongoDB search request failed', resp.status);
|
|
|
|
| 1675 |
this.addMessage('user', message);
|
| 1676 |
this.showLoading(true);
|
| 1677 |
try {
|
| 1678 |
+
const isNewSession = !this.currentSession || this.currentSession.id === 'default' || this.currentSession.source !== 'backend';
|
| 1679 |
+
const responseData = await this.callMedicalAPI(message, isNewSession);
|
| 1680 |
|
| 1681 |
if (isNewSession && responseData.session_id) {
|
| 1682 |
+
console.log(`[DEBUG] New session created on backend. Updating session ID to '${responseData.session_id}'`);
|
|
|
|
|
|
|
| 1683 |
|
| 1684 |
+
// If it was a local session, remove it
|
| 1685 |
+
if (this.currentSession.id !== 'default' && this.currentSession.source !== 'backend') {
|
| 1686 |
+
const localSessions = this.getChatSessions();
|
| 1687 |
+
const updatedLocalSessions = localSessions.filter(s => s.id !== this.currentSession.id);
|
| 1688 |
+
localStorage.setItem(`chatSessions_${this.currentUser.id}`, JSON.stringify(updatedLocalSessions));
|
|
|
|
| 1689 |
}
|
| 1690 |
+
|
| 1691 |
+
// Update current session to reflect backend reality
|
| 1692 |
+
this.currentSession.id = responseData.session_id;
|
| 1693 |
+
this.currentSession.source = 'backend';
|
| 1694 |
+
|
| 1695 |
+
// Add to backend sessions list and re-render
|
| 1696 |
+
await this.fetchAndRenderPatientSessions();
|
| 1697 |
}
|
| 1698 |
|
| 1699 |
this.addMessage('assistant', responseData.response || 'I apologize, but I received an empty response. Please try again.');
|
|
|
|
| 1716 |
}
|
| 1717 |
}
|
| 1718 |
|
| 1719 |
+
callMedicalAPI = async function (message, isNewSession) {
|
| 1720 |
try {
|
| 1721 |
+
let sessionId = this.currentSession?.id;
|
| 1722 |
+
|
| 1723 |
+
// Step 1: Create a new session if required
|
| 1724 |
+
if (isNewSession) {
|
| 1725 |
+
const createSessionPayload = {
|
| 1726 |
+
account_id: this.currentUser.id,
|
| 1727 |
+
patient_id: this.currentPatientId,
|
| 1728 |
+
title: "New Chat" // Title can be updated later
|
| 1729 |
+
};
|
| 1730 |
+
const sessionResponse = await fetch('/session', {
|
| 1731 |
+
method: 'POST',
|
| 1732 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1733 |
+
body: JSON.stringify(createSessionPayload)
|
| 1734 |
+
});
|
| 1735 |
+
if (!sessionResponse.ok) throw new Error(`HTTP error! status: ${sessionResponse.status}`);
|
| 1736 |
+
const newSessionData = await sessionResponse.json();
|
| 1737 |
+
sessionId = newSessionData.id;
|
| 1738 |
+
this.currentSession.id = sessionId; // Update current session immediately
|
| 1739 |
+
this.currentSession.source = 'backend';
|
| 1740 |
+
}
|
| 1741 |
+
|
| 1742 |
+
// Step 2: Post the message to the (potentially new) session
|
| 1743 |
+
const messagePayload = {
|
| 1744 |
account_id: this.currentUser.id,
|
| 1745 |
patient_id: this.currentPatientId,
|
| 1746 |
message: message
|
| 1747 |
};
|
| 1748 |
|
| 1749 |
+
const messageResponse = await fetch(`/session/${sessionId}/messages`, {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1750 |
method: 'POST',
|
| 1751 |
headers: { 'Content-Type': 'application/json' },
|
| 1752 |
+
body: JSON.stringify(messagePayload)
|
| 1753 |
});
|
| 1754 |
+
|
| 1755 |
+
if (!messageResponse.ok) throw new Error(`HTTP error! status: ${messageResponse.status}`);
|
| 1756 |
+
const data = await messageResponse.json();
|
| 1757 |
return data;
|
| 1758 |
+
|
| 1759 |
} catch (error) {
|
| 1760 |
console.error('API call failed:', error);
|
| 1761 |
console.error('Error details:', {
|
|
|
|
| 1837 |
messageElement.id = `message-${message.id}`;
|
| 1838 |
const avatar = message.role === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>';
|
| 1839 |
const time = this.formatTime(message.timestamp);
|
| 1840 |
+
|
| 1841 |
// Add EMR icon for assistant messages (system-generated)
|
| 1842 |
+
const emrIcon = message.role === 'assistant' ?
|
| 1843 |
`<div class="message-actions">
|
| 1844 |
<button class="emr-extract-btn" onclick="app.extractEMR('${message.id}')" title="Extract to EMR" data-message-id="${message.id}">
|
| 1845 |
<i class="fas fa-file-medical"></i>
|
| 1846 |
</button>
|
| 1847 |
</div>` : '';
|
| 1848 |
+
|
| 1849 |
messageElement.innerHTML = `
|
| 1850 |
<div class="message-avatar">${avatar}</div>
|
| 1851 |
<div class="message-content">
|
|
|
|
| 1856 |
chatMessages.appendChild(messageElement);
|
| 1857 |
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 1858 |
if (this.currentSession) this.currentSession.lastActivity = new Date().toISOString();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1859 |
}
|
| 1860 |
|
| 1861 |
formatMessageContent(content) {
|
|
|
|
| 1917 |
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
| 1918 |
}
|
| 1919 |
|
| 1920 |
+
// Build URL with query parameters
|
| 1921 |
+
const params = new URLSearchParams({
|
| 1922 |
+
patient_id: this.currentPatientId,
|
| 1923 |
+
doctor_id: this.currentUser.id || 'default-doctor',
|
| 1924 |
+
message_id: messageId,
|
| 1925 |
+
session_id: this.currentSession?.id || 'default-session',
|
| 1926 |
+
message: message.content
|
| 1927 |
+
});
|
| 1928 |
+
const url = `/emr/extract?${params.toString()}`;
|
| 1929 |
+
|
| 1930 |
// Call EMR extraction API
|
| 1931 |
+
const response = await fetch(url, {
|
| 1932 |
method: 'POST',
|
| 1933 |
headers: {
|
| 1934 |
+
'Content-Type': 'application/json', // Header might still be needed by middleware
|
| 1935 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1936 |
});
|
| 1937 |
|
| 1938 |
if (response.ok) {
|
| 1939 |
const result = await response.json();
|
| 1940 |
console.log('EMR extraction successful:', result);
|
| 1941 |
+
|
| 1942 |
// Show success message
|
| 1943 |
if (button) {
|
| 1944 |
button.innerHTML = '<i class="fas fa-check"></i>';
|
|
|
|
| 1949 |
button.disabled = false;
|
| 1950 |
}, 2000);
|
| 1951 |
}
|
| 1952 |
+
|
| 1953 |
// Show notification
|
| 1954 |
this.showNotification('EMR data extracted successfully!', 'success');
|
| 1955 |
} else {
|
|
|
|
| 1960 |
|
| 1961 |
} catch (error) {
|
| 1962 |
console.error('Error extracting EMR:', error);
|
| 1963 |
+
|
| 1964 |
// Reset button state
|
| 1965 |
const button = document.querySelector(`[onclick="app.extractEMR('${messageId}')"]`);
|
| 1966 |
if (button) {
|
| 1967 |
button.innerHTML = '<i class="fas fa-file-medical"></i>';
|
| 1968 |
button.disabled = false;
|
| 1969 |
}
|
| 1970 |
+
|
| 1971 |
// Show error message
|
| 1972 |
this.showNotification('Failed to extract EMR data. Please try again.', 'error');
|
| 1973 |
}
|
| 1974 |
}
|
| 1975 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1976 |
showNotification(message, type = 'info') {
|
| 1977 |
// Create notification element
|
| 1978 |
const notification = document.createElement('div');
|
|
|
|
| 1983 |
<span>${message}</span>
|
| 1984 |
</div>
|
| 1985 |
`;
|
| 1986 |
+
|
| 1987 |
// Add to page
|
| 1988 |
document.body.appendChild(notification);
|
| 1989 |
+
|
| 1990 |
// Show notification
|
| 1991 |
setTimeout(() => notification.classList.add('show'), 100);
|
| 1992 |
+
|
| 1993 |
// Remove after 3 seconds
|
| 1994 |
setTimeout(() => {
|
| 1995 |
notification.classList.remove('show');
|