// Core UI & Modal Elements
const modal = document.getElementById("consultationModal");
const addMedBtn = document.getElementById("addMed");
const medList = document.getElementById("medicineList");
const sendRxBtn = document.getElementById("sendRx");
const closeModal = document.querySelector(".rx-modal .close-modal");
const cancelRxBtn = document.getElementById("cancelRx");
const startRecordBtn = document.getElementById("startRecording");
const recordingStatus = document.getElementById("recordingStatus");
const aiInsightsBox = document.getElementById("aiInsights");
const insightText = document.getElementById("insightText");
const queueDateInput = document.getElementById("queueDate");
const newWalkinBtn = document.getElementById("newWalkinBtn");
const labList = document.getElementById("labTestList");
const addLabBtn = document.getElementById("addLabTest");
// 1. App State & Stores
let isScribing = false;
let currentPatientId = "#012";
let recognition;
let mediaRecorder;
let audioChunks = [];
const patientDataStore = {
"#012": { notes: "", summary: "" },
"#013": { notes: "", summary: "" },
"#014": { notes: "", summary: "" }
};
const SECTION_META = {
overview: {
target: 'overview-section',
title: 'Clinic Overview',
subtitle: 'Live queue, stats, and AI insights for today.'
},
queue: {
target: 'overview-section',
title: 'Live Queue',
subtitle: 'Patients waiting and in consultation.'
},
patients: {
target: 'patients-section',
title: 'Patient Records',
subtitle: 'Search history, visits, and contact details.'
},
pharmacy: {
target: 'pharmacy-section',
title: 'Pharmacy',
subtitle: 'Inventory, prescriptions, and stock alerts.'
},
lab: {
target: 'lab-section',
title: 'Laboratory',
subtitle: 'Orders, sample collection, and results.'
},
zen: {
target: 'doctor-zen-section',
title: 'Clinical Workspace',
subtitle: 'Document visits, prescribe, and review vitals.'
},
settings: {
target: 'settings-section',
title: 'Preferences',
subtitle: 'Manage your profile and clinical tool settings.'
}
};
const ROLE_CONFIG = {
DOCTOR: {
hideSections: ['overview', 'billing', 'staff'],
defaultSection: 'zen',
displayName: 'Dr. Sharma'
},
OWNER: {
hideSections: [],
defaultSection: 'overview',
displayName: null
},
RECEPTIONIST: {
hideSections: ['pharmacy', 'lab'],
defaultSection: 'queue',
displayName: 'Reception'
}
};
function getUserRole() {
return localStorage.getItem('userRole');
}
function resolveSectionTarget(sectionId) {
const role = getUserRole();
const config = ROLE_CONFIG[role] || ROLE_CONFIG.OWNER;
if (sectionId === 'queue' && role === 'DOCTOR') return 'doctor-zen-section';
if (sectionId === 'zen') return 'doctor-zen-section';
const meta = SECTION_META[sectionId];
return meta ? meta.target : 'overview-section';
}
function updateSectionPageHeader(sectionId) {
const header = document.getElementById('section-page-header');
const titleEl = document.getElementById('sectionPageTitle');
const subtitleEl = document.getElementById('sectionPageSubtitle');
if (!header || !titleEl || !subtitleEl) return;
const isDoctor = getUserRole() === 'DOCTOR';
const isClinicalView = sectionId === 'zen' || sectionId === 'queue';
const meta = SECTION_META[sectionId];
if (isDoctor && !isClinicalView && meta) {
titleEl.textContent = meta.title;
subtitleEl.textContent = meta.subtitle;
header.classList.remove('hidden');
} else {
header.classList.add('hidden');
}
}
function setActiveNavLink(sectionId) {
const isDoctor = getUserRole() === 'DOCTOR';
const activeKey = (sectionId === 'zen' || (sectionId === 'queue' && isDoctor)) ? 'queue' : sectionId;
document.querySelectorAll('.side-nav a[data-section]').forEach(link => {
link.classList.toggle('active', link.dataset.section === activeKey);
});
}
function applyRoleNav() {
const role = getUserRole();
const config = ROLE_CONFIG[role] || ROLE_CONFIG.OWNER;
document.querySelectorAll('.side-nav a[data-section]').forEach(link => {
const hidden = config.hideSections.includes(link.dataset.section);
link.style.display = hidden ? 'none' : '';
});
}
// 2. Initialization & Speech Recognition Setup
document.addEventListener("DOMContentLoaded", () => {
const payload = requireLogin(['OWNER', 'DOCTOR', 'RECEPTIONIST']);
if (!payload) return;
const userRole = payload.role || getUserRole();
const userEmail = localStorage.getItem('userEmail') || 'admin@curio.app';
const config = ROLE_CONFIG[userRole] || ROLE_CONFIG.OWNER;
const roleDisplay = document.querySelector(".user-profile .info strong");
const emailDisplay = document.querySelector(".user-profile .info span");
if (roleDisplay) {
roleDisplay.innerText = config.displayName || (userRole.charAt(0) + userRole.slice(1).toLowerCase());
}
if (emailDisplay) emailDisplay.innerText = userEmail;
const sidebar = document.getElementById('sidebar');
if (userRole === 'DOCTOR') {
document.body.classList.add('role-doctor');
sidebar?.classList.add('compact-rail');
} else {
document.body.classList.remove('role-doctor');
sidebar?.classList.remove('compact-rail');
}
applyRoleNav();
document.getElementById('sideNav')?.addEventListener('click', (e) => {
const link = e.target.closest('a[data-section]');
if (!link || link.hasAttribute('data-external')) return;
e.preventDefault();
showSection(link.dataset.section);
});
showSection(config.defaultSection === 'zen' ? 'zen' : config.defaultSection);
// Zen 4.0 Macros & Nudge Logic
const MACROS = {
".fever": "Patient presents with elevated body temperature. Antipyretics advised. Rest and hydration recommended.",
".cough": "Patient reports persistent cough. Chest sounds clear bilaterally. Recommended symptomatic relief.",
".cold": "Patient reports nasal congestion and rhinorrhea. Viral etiology suspected. Supportive care advised."
};
const nudgeText = document.getElementById("aiNudgeText");
const tagsContainer = document.querySelector(".suggested-tags");
document.addEventListener("input", (e) => {
if (e.target && e.target.tagName === 'TEXTAREA' && e.target.id.startsWith('soap-')) {
let val = e.target.value;
let modified = false;
for (const [macro, text] of Object.entries(MACROS)) {
if (val.includes(macro)) {
val = val.replace(macro, text);
modified = true;
}
}
if (modified) {
e.target.value = val;
// Dispatch event so other listeners (if any) know the value changed
e.target.dispatchEvent(new Event('input', { bubbles: true }));
return; // Prevent recursive trigger on this pass
}
// Nudge Logic update
if (nudgeText && tagsContainer) {
const lowerVal = val.toLowerCase();
if (lowerVal.includes("fever") || lowerVal.includes("temperature")) {
nudgeText.innerText = "β¨ AI suggests: Patient has elevated temp. Consider prescribing Paracetamol and checking for throat infection.";
tagsContainer.innerHTML = '#ViralFever#InfectionCheck';
} else if (lowerVal.includes("cough") || lowerVal.includes("breath")) {
nudgeText.innerText = "β¨ AI suggests: Respiratory symptoms detected. Check SpO2 and listen for chest congestion.";
tagsContainer.innerHTML = '#AsthmaProtocol#Bronchitis';
}
}
}
});
// Zen 4.0 Global Hotkeys
document.addEventListener("keydown", (e) => {
// Ctrl + Q to toggle queue sidebar
if (e.ctrlKey && e.key.toLowerCase() === 'q') {
e.preventDefault();
const layout = document.querySelector('.zen-layout-3col');
const pipeline = document.getElementById('zenPipeline');
if (layout && pipeline) {
layout.classList.toggle('collapsed-queue');
pipeline.classList.toggle('collapsed');
}
}
// Ctrl + Shift + S to toggle Scribe
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 's') {
e.preventDefault();
window.toggleZenScribe();
}
});
// Initialize Recognition
if ('webkitSpeechRecognition' in window) {
recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 3;
recognition.onresult = (event) => {
for (let i = event.resultIndex; i < event.results.length; ++i) {
if (event.results[i].isFinal) {
const transcript = event.results[i][0].transcript;
// Default streaming to Subjective section or active field
const activeField = document.activeElement;
if (activeField && activeField.tagName === 'TEXTAREA' && activeField.id.startsWith('soap-')) {
activeField.value += transcript + '. ';
} else {
const defaultField = document.getElementById("soap-s");
if (defaultField) defaultField.value += transcript + '. ';
}
}
}
};
recognition.onerror = () => { isScribing = false; updateScribeUI(); };
}
});
// 3. Section Switching Logic
window.showSection = (sectionId) => {
const sections = ['overview-section', 'patients-section', 'pharmacy-section', 'lab-section', 'doctor-zen-section', 'settings-section'];
sections.forEach(id => {
const el = document.getElementById(id);
if (el) el.classList.add('hidden');
});
const role = getUserRole();
if (sectionId === 'settings') {
const isDoctor = role === 'DOCTOR';
document.querySelector('#settings-section .doctor-only')?.classList.toggle('hidden', !isDoctor);
document.querySelector('#settings-section .admin-only')?.classList.toggle('hidden', isDoctor);
if (typeof loadSettings === 'function') loadSettings();
}
const target = resolveSectionTarget(sectionId);
const targetEl = document.getElementById(target);
if (targetEl) targetEl.classList.remove('hidden');
if (target === 'doctor-zen-section') {
document.body.classList.add('zen-mode-active');
} else {
document.body.classList.remove('zen-mode-active');
}
if (sectionId === 'queue' && target === 'overview-section') {
document.getElementById('queue-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
const headerSection = (sectionId === 'queue' && getUserRole() === 'DOCTOR') ? 'zen' : sectionId;
updateSectionPageHeader(headerSection);
setActiveNavLink(sectionId);
if (window.innerWidth <= 768) {
const sidebar = document.getElementById('sidebar');
sidebar?.classList.remove('open');
document.getElementById('sidebarBackdrop')?.classList.remove('visible');
document.getElementById('sidebarMenuBtn')?.setAttribute('aria-expanded', 'false');
}
return false;
};
// 4. Clinical Context & Scribe Logic
window.toggleZenScribe = async () => {
if (!recognition) { alert("Speech Recognition not supported."); return; }
isScribing = !isScribing;
updateScribeUI();
if (isScribing) {
recognition.lang = document.getElementById("scribeLang")?.value || 'en-US';
recognition.start();
startDeepCapture();
} else {
recognition.stop();
if (mediaRecorder) mediaRecorder.stop();
}
};
function updateScribeUI() {
const btn = document.getElementById("zenScribeBtn");
const status = document.getElementById("scribeStatus");
if (!btn || !status) return;
if (isScribing) {
btn.innerHTML = "βΈοΈ Pause Scribe";
btn.style.background = "#EF4444";
btn.style.color = "white";
status.classList.remove("hidden");
} else {
btn.innerHTML = "π΄ Start Recording";
btn.style.background = ""; // Resets to CSS default
btn.style.color = "";
status.classList.add("hidden");
}
}
async function startDeepCapture() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
audioChunks = [];
mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data);
mediaRecorder.onstop = () => {
const blob = new Blob(audioChunks, { type: 'audio/wav' });
processWithWhisper(blob);
};
mediaRecorder.start();
} catch (err) { console.warn("Mic access denied for Deep Scribe"); }
}
async function processWithWhisper(blob) {
const formData = new FormData();
formData.append("file", blob, "audio.wav");
formData.append("model", "tiny"); // Explicitly tell the server which model to use
try {
const res = await fetch("https://curio.applaude.net/api/whisper/v1/audio/transcriptions", {
method: "POST",
body: formData
});
const data = await res.json();
if (data.text) {
const activeField = document.activeElement;
if (activeField && activeField.tagName === 'TEXTAREA' && activeField.id.startsWith('soap-')) {
activeField.value += "\n[DEEP SCRIBE]: " + data.text;
} else {
const defaultField = document.getElementById("soap-s");
if (defaultField) defaultField.value += "\n[DEEP SCRIBE]: " + data.text;
}
generateSummary(true);
}
} catch (e) {
console.warn("Deep Scribe error, using fallback summary.", e);
generateSummary(document.getElementById("scribeLang")?.value === 'te-IN');
}
}
function generateSummary(isTelugu = false) {
const soapS = document.getElementById("soap-s")?.value || "";
const soapO = document.getElementById("soap-o")?.value || "";
const soapA = document.getElementById("soap-a")?.value || "";
const soapP = document.getElementById("soap-p")?.value || "";
const combinedNotes = [soapS, soapO, soapA, soapP].filter(Boolean).join("\n");
const notesEl = document.getElementById("consultationNotes");
if (notesEl) notesEl.value = combinedNotes;
const summary = isTelugu
? "π [TRANSLATED] CLINICAL SUMMARY: Patient presents in Telugu. Dry cough and fatigue noted. Recommended fluids and rest."
: "π CLINICAL SUMMARY: Patient presents with persistent dry cough. Recommended hydration and follow-up.";
const summaryTextEl = document.getElementById("summaryText");
if (summaryTextEl) summaryTextEl.innerText = summary;
const summaryCard = document.getElementById("aiSummaryCard");
if (summaryCard) summaryCard.classList.remove("hidden");
if (currentPatientId) {
patientDataStore[currentPatientId] = {
notes: combinedNotes,
soapS,
soapO,
soapA,
soapP,
summary
};
}
}
window.switchPatientContext = (name, token, triage) => {
if (currentPatientId) {
const notesVal = document.getElementById("consultationNotes")?.value || "";
const soapS = document.getElementById("soap-s")?.value || "";
const soapO = document.getElementById("soap-o")?.value || "";
const soapA = document.getElementById("soap-a")?.value || "";
const soapP = document.getElementById("soap-p")?.value || "";
const summary = document.getElementById("summaryText")?.innerText || "";
patientDataStore[currentPatientId] = {
notes: notesVal,
soapS,
soapO,
soapA,
soapP,
summary
};
}
isScribing = false;
updateScribeUI();
currentPatientId = token;
const data = patientDataStore[token] || { notes: "", soapS: "", soapO: "", soapA: "", soapP: "", summary: "" };
const notesEl = document.getElementById("consultationNotes");
if (notesEl) notesEl.value = data.notes || "";
const sField = document.getElementById("soap-s");
const oField = document.getElementById("soap-o");
const aField = document.getElementById("soap-a");
const pField = document.getElementById("soap-p");
if (sField) sField.value = data.soapS || "";
if (oField) oField.value = data.soapO || "";
if (aField) aField.value = data.soapA || "";
if (pField) pField.value = data.soapP || "";
const summaryTextEl = document.getElementById("summaryText");
if (summaryTextEl) summaryTextEl.innerText = data.summary || "";
const summaryCard = document.getElementById("aiSummaryCard");
if (summaryCard) {
if (data.summary) summaryCard.classList.remove("hidden");
else summaryCard.classList.add("hidden");
}
const patientHeading = document.querySelector(".patient-identity-row h1");
if (patientHeading) patientHeading.innerHTML = `${name} (32y, Female)`;
document.querySelectorAll('.pipeline-item').forEach(item => {
const itemToken = item.querySelector('.p-token')?.textContent?.trim();
item.classList.toggle('next', itemToken === token);
item.classList.toggle('active', itemToken === token);
});
};
// 5. Global Helpers & Modal Handlers
window.openHistory = () => {
const heading = document.querySelector(".patient-identity-row h1, .patient-identity h1");
const name = heading ? heading.innerText.split(' (')[0] : 'Patient';
alert(`π Loading History for ${name}...\n\n- Visit (05/12/26): Chronic Gastritis`);
};
window.openConsultation = () => {
const triagePills = document.querySelectorAll(".patient-identity .meta-pills .pill.time");
const triage = triagePills[triagePills.length - 1];
const rxField = document.getElementById("rxDiagnosis");
if (rxField && triage) rxField.value = triage.innerText;
if (modal) modal.classList.add('open');
};
window.shareSummary = () => alert("π€ Summary sent to Patient's WhatsApp.");
window.printSummary = () => alert("π¨οΈ Printing Handover Note...");
window.toggleSidebar = () => {
const sidebar = document.getElementById('sidebar');
const backdrop = document.getElementById('sidebarBackdrop');
const menuBtn = document.getElementById('sidebarMenuBtn');
const isOpen = sidebar?.classList.toggle('open');
backdrop?.classList.toggle('visible', !!isOpen);
if (menuBtn) menuBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
};
function closeRxModal() {
if (modal) modal.classList.remove('open');
}
if (addMedBtn && medList) {
addMedBtn.addEventListener('click', () => {
const row = medList.querySelector('.med-row');
if (row) medList.appendChild(row.cloneNode(true));
});
}
if (addLabBtn && labList) {
addLabBtn.addEventListener('click', () => {
const row = labList.querySelector('.lab-row');
if (row) {
const clone = row.cloneNode(true);
const input = clone.querySelector('input');
if (input) input.value = '';
labList.appendChild(clone);
}
});
}
window.shareTip = (type) => {
const tips = {
diabetes: 'Maintain a balanced diet low in refined sugars.',
hypertension: 'Reduce salt intake and practice daily breathing exercises.',
viral: 'Stay hydrated and get adequate rest.'
};
alert(`π€ Health tip sent: ${tips[type] || 'General wellness advice.'}`);
};
window.printTips = () => alert('π¨οΈ Printing patient education handoutβ¦');
// Global Listeners
if (closeModal) closeModal.onclick = closeRxModal;
if (cancelRxBtn) cancelRxBtn.onclick = closeRxModal;
window.onclick = (e) => { if (e.target === modal) closeRxModal(); };
// AI Scribe Polling Logic
const submitAiBtn = document.getElementById("submitAiScribeBtn");
if (submitAiBtn) {
submitAiBtn.addEventListener("click", async () => {
const dictationInput = document.getElementById("aiDictationInput");
const statusDiv = document.getElementById("aiScribeStatus");
const statusText = document.getElementById("aiScribeStatusText");
if (!dictationInput || !dictationInput.value.trim()) {
alert("Please enter some dictation first.");
return;
}
// UI Reset
submitAiBtn.disabled = true;
statusDiv.classList.remove("hidden");
statusText.innerText = "Submitting to CuraFlow AI Engine...";
try {
// Start Job
const res = await fetch("/api/ai/scribe/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dictation: dictationInput.value })
});
const data = await res.json();
if (!data.success) throw new Error(data.error);
const jobId = data.jobId;
statusText.innerText = "AI Scribe analyzing dictation (this may take a minute)...";
// Poll Status
const pollInterval = setInterval(async () => {
try {
const statusRes = await fetch(`/api/ai/scribe/status/${jobId}`);
const statusData = await statusRes.json();
if (statusData.status === 'COMPLETED') {
clearInterval(pollInterval);
statusText.innerText = "Analysis Complete! Populating fields...";
// Populate Fields from AI Output
const result = statusData.result;
if (result && result.clinical_note) {
const rxDiagnosis = document.getElementById("rxDiagnosis");
if (rxDiagnosis && result.clinical_note.assessment) {
rxDiagnosis.value = result.clinical_note.assessment;
}
// Log full output to insights box
const insightsBox = document.getElementById("aiInsights");
const insightText = document.getElementById("insightText");
if (insightsBox && insightText) {
insightsBox.classList.remove("hidden");
insightText.innerHTML = `
Symptoms: ${result.clinical_note.chief_complaint?.symptoms?.join(', ')}
Billing Codes: ${result.billing_codes?.icd_10_codes?.map(c => c.code).join(', ') || 'None'}
(Check console for full JSON structure)
`;
console.log("AI Scribe Full Output:", result);
}
}
setTimeout(() => {
statusDiv.classList.add("hidden");
submitAiBtn.disabled = false;
}, 2000);
} else if (statusData.status === 'FAILED') {
clearInterval(pollInterval);
statusText.innerText = "AI Analysis Failed. Please try again.";
submitAiBtn.disabled = false;
}
} catch (err) {
console.error("Polling error", err);
}
}, 5000); // Poll every 5 seconds
} catch (err) {
console.error(err);
statusText.innerText = "Error submitting dictation.";
submitAiBtn.disabled = false;
}
});
}
// ==========================================
// AI Lab Anomaly Watcher Logic
// ==========================================
const startWatcherBtn = document.getElementById("startWatcherBtn");
const submitLabBtn = document.getElementById("submitLabBtn");
const labWatcherStatusBadge = document.getElementById("labWatcherStatusBadge");
const labResultInputSection = document.getElementById("labResultInputSection");
const aiAlertsContainer = document.getElementById("aiAlertsContainer");
let activeWatcherPatientId = null;
let alertPollingInterval = null;
if (startWatcherBtn) {
startWatcherBtn.addEventListener("click", async () => {
const patientId = document.getElementById("labPatientId").value.trim();
const baseline = document.getElementById("labBaseline").value.trim();
if (!patientId) return alert("Please enter a Patient ID");
startWatcherBtn.disabled = true;
startWatcherBtn.innerText = "Starting...";
try {
const res = await fetch("/api/ai/lab/watch/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
patientId: patientId,
baselineData: {
baseline: baseline || "Patient is healthy",
medications: []
}
})
});
const data = await res.json();
if (data.success) {
activeWatcherPatientId = patientId;
labWatcherStatusBadge.innerText = "Active (Monitoring)";
labWatcherStatusBadge.classList.add('badge-active');
labResultInputSection.classList.remove("hidden");
startWatcherBtn.innerText = "Watcher Running";
// Start polling for alerts
if (alertPollingInterval) clearInterval(alertPollingInterval);
alertPollingInterval = setInterval(pollLabAlerts, 5000);
} else {
alert("Failed: " + data.error);
startWatcherBtn.disabled = false;
startWatcherBtn.innerText = "Start Watcher";
}
} catch (err) {
console.error(err);
alert("Error starting watcher");
startWatcherBtn.disabled = false;
startWatcherBtn.innerText = "Start Watcher";
}
});
}
if (submitLabBtn) {
submitLabBtn.addEventListener("click", async () => {
if (!activeWatcherPatientId) return alert("Start watcher first");
const resultInput = document.getElementById("newLabResult");
const result = resultInput.value.trim();
if (!result) return alert("Enter a lab result");
submitLabBtn.disabled = true;
submitLabBtn.innerText = "Sending...";
try {
const res = await fetch(`/api/ai/lab/result/${activeWatcherPatientId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ result })
});
const data = await res.json();
if (data.success) {
resultInput.value = "";
setTimeout(() => {
submitLabBtn.innerText = "Send to AI Watcher";
submitLabBtn.disabled = false;
}, 1000);
} else {
alert("Error: " + data.error);
submitLabBtn.disabled = false;
submitLabBtn.innerText = "Send to AI Watcher";
}
} catch (err) {
console.error(err);
alert("Failed to submit result");
submitLabBtn.disabled = false;
submitLabBtn.innerText = "Send to AI Watcher";
}
});
}
async function pollLabAlerts() {
if (!activeWatcherPatientId) return;
try {
const res = await fetch(`/api/ai/lab/alerts/${activeWatcherPatientId}`);
const data = await res.json();
if (data.success && data.alerts && data.alerts.length > 0) {
renderAlerts(data.alerts);
}
} catch (err) {
console.error("Error polling alerts:", err);
}
}
function renderAlerts(alerts) {
if (!aiAlertsContainer) return;
let html = `
${alert.danger_explanation}
Action: ${alert.recommended_action}