const state = {
mode: "idle",
step: null,
booking: {},
cancellationId: "",
feedbackRating: null,
};
const doctors = {
Cardiology: ["Dr. Asha Patel", "Dr. Michael Reed"],
Orthopedics: ["Dr. Daniel Brooks", "Dr. Priya Nair"],
Dermatology: ["Dr. Sara Kim", "Dr. Omar Khan"],
Pediatrics: ["Dr. Neha Shah", "Dr. Luis Moreno"],
"Internal Medicine": ["Dr. Rachel Adams", "Dr. Vikram Rao"],
};
const symptomDepartments = [
{ terms: ["chest", "heart", "palpitation", "blood pressure"], department: "Cardiology" },
{ terms: ["knee", "bone", "joint", "back", "fracture", "shoulder"], department: "Orthopedics" },
{ terms: ["rash", "skin", "acne", "itch"], department: "Dermatology" },
{ terms: ["child", "baby", "pediatric", "fever"], department: "Pediatrics" },
];
const faqs = [
{
terms: ["visiting", "hours"],
answer: "General visiting hours are 9:00 AM to 7:00 PM. Intensive care and specialty units may use separate schedules.",
},
{
terms: ["document", "bring", "id", "insurance"],
answer: "Please bring a photo ID, insurance card if applicable, referral letter if required, medication list, and previous reports.",
},
{
terms: ["emergency", "er", "urgent"],
answer: "The emergency department is open 24/7. If this is a medical emergency, call emergency services or go to the nearest emergency department.",
},
{
terms: ["parking"],
answer: "Patient parking is available near the main entrance. Accessible parking is located beside the reception entrance.",
},
{
terms: ["telehealth", "online", "video"],
answer: "Telehealth appointments are available for selected departments. The care team confirms eligibility during booking.",
},
];
const chatLog = document.querySelector("[data-chat-log]");
const form = document.querySelector("[data-chat-form]");
const input = document.querySelector("[data-chat-input]");
const quickActions = document.querySelector("[data-quick-actions]");
const memoryPanel = document.querySelector("[data-memory]");
const appointmentPanel = document.querySelector("[data-appointments]");
const resetButton = document.querySelector("[data-reset]");
function getMemory() {
return JSON.parse(localStorage.getItem("mediflow-memory") || "{}");
}
function setMemory(nextMemory) {
localStorage.setItem("mediflow-memory", JSON.stringify(nextMemory));
renderPanels();
}
function getAppointments() {
return JSON.parse(localStorage.getItem("mediflow-appointments") || "[]");
}
function setAppointments(appointments) {
localStorage.setItem("mediflow-appointments", JSON.stringify(appointments));
renderPanels();
}
function addMessage(sender, text, options = {}) {
const message = document.createElement("div");
message.className = `message ${sender}`;
if (options.card) {
message.classList.add("card-message");
}
message.innerHTML = text;
chatLog.appendChild(message);
chatLog.scrollTop = chatLog.scrollHeight;
}
function addBot(text, options) {
addMessage("bot", text, options);
}
function addUser(text) {
addMessage("user", escapeHtml(text));
}
function escapeHtml(value) {
return value.replace(/[&<>"']/g, (char) => ({
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
}[char]));
}
function renderQuickActions(actions = []) {
quickActions.innerHTML = "";
actions.forEach((action) => {
const button = document.createElement("button");
button.type = "button";
button.className = "choice";
button.textContent = action.label;
button.addEventListener("click", () => handleInput(action.value || action.label, true));
quickActions.appendChild(button);
});
}
function renderPanels() {
const memory = getMemory();
const appointments = getAppointments();
memoryPanel.innerHTML = `
Name${memory.fullName || "Not saved"}
Department${memory.preferredDepartment || "Not saved"}
Doctor${memory.preferredDoctor || "Not saved"}
`;
if (!appointments.length) {
appointmentPanel.innerHTML = "No active prototype appointments.
";
return;
}
appointmentPanel.innerHTML = appointments.map((appointment) => `
${appointment.id}
${appointment.doctor}
${appointment.department} · ${appointment.date} · ${appointment.time}
`).join("");
}
function start() {
const memory = getMemory();
const name = memory.fullName ? ` Welcome back, ${memory.fullName}.` : "";
addBot(`Hello, I am MediFlow Assistant.${name} I can help you book a doctor appointment, cancel an appointment, answer hospital questions, or collect feedback.`);
renderQuickActions([
{ label: "Book appointment" },
{ label: "Cancel appointment" },
{ label: "Ask hospital question" },
{ label: "Give feedback" },
]);
renderPanels();
}
function routeIntent(text) {
const lower = text.toLowerCase();
if (lower.includes("cancel")) return beginCancellation();
if (lower.includes("feedback") || lower.includes("rate")) return beginFeedback();
if (lower.includes("hour") || lower.includes("insurance") || lower.includes("parking") || lower.includes("document") || lower.includes("emergency") || lower.includes("telehealth")) return answerFaq(text);
return beginBooking(text);
}
function beginBooking(initialText = "") {
const memory = getMemory();
state.mode = "booking";
state.step = memory.fullName ? "reuseName" : "name";
state.booking = {};
if (initialText) {
const department = detectDepartment(initialText);
if (department) {
state.booking.department = department;
state.booking.symptoms = initialText;
}
}
if (memory.fullName) {
addBot(`Should I book this appointment for ${memory.fullName}?`);
renderQuickActions([{ label: "Yes, use saved name" }, { label: "No, enter another name" }]);
return;
}
addBot("Please enter the patient's full name.");
renderQuickActions([]);
}
function continueBooking(text) {
const memory = getMemory();
if (state.step === "reuseName") {
if (text.toLowerCase().startsWith("yes")) {
state.booking.patientName = memory.fullName;
state.step = "contact";
addBot("Please enter a phone number or email for appointment updates.");
renderQuickActions([]);
return;
}
state.step = "name";
addBot("Please enter the patient's full name.");
renderQuickActions([]);
return;
}
if (state.step === "name") {
state.booking.patientName = text;
setMemory({ ...memory, fullName: text });
state.step = "contact";
addBot("Please enter a phone number or email for appointment updates.");
return;
}
if (state.step === "contact") {
state.booking.contact = text;
if (state.booking.department) {
state.step = "doctor";
askDoctor();
return;
}
state.step = "department";
addBot("Which department, doctor, or symptom should I use for this appointment?");
renderQuickActions([
{ label: "Cardiology" },
{ label: "Orthopedics" },
{ label: "Dermatology" },
{ label: "Pediatrics" },
]);
return;
}
if (state.step === "department") {
const department = detectDepartment(text);
state.booking.symptoms = text;
state.booking.department = department || "Internal Medicine";
setMemory({ ...getMemory(), preferredDepartment: state.booking.department });
state.step = "doctor";
askDoctor();
return;
}
if (state.step === "doctor") {
state.booking.doctor = text === "No preference" ? doctors[state.booking.department][0] : text;
setMemory({ ...getMemory(), preferredDoctor: state.booking.doctor });
state.step = "date";
addBot("What date would you prefer? Example: May 20, 2026.");
renderQuickActions([]);
return;
}
if (state.step === "date") {
state.booking.date = text;
state.step = "time";
addBot("What time would you prefer?");
renderQuickActions([
{ label: "9:30 AM" },
{ label: "11:00 AM" },
{ label: "2:30 PM" },
{ label: "4:00 PM" },
]);
return;
}
if (state.step === "time") {
state.booking.time = text;
state.step = "confirm";
addConfirmationPreview();
renderQuickActions([{ label: "Confirm" }, { label: "Change details" }, { label: "Cancel booking" }]);
return;
}
if (state.step === "confirm") {
if (text === "Confirm") return confirmBooking();
if (text === "Change details") return beginBooking();
state.mode = "idle";
state.step = null;
addBot("No problem. I cancelled this draft booking.");
renderQuickActions([{ label: "Book appointment" }, { label: "Ask hospital question" }]);
}
}
function detectDepartment(text) {
const lower = text.toLowerCase();
const exact = Object.keys(doctors).find((department) => lower.includes(department.toLowerCase()));
if (exact) return exact;
const mapped = symptomDepartments.find((item) => item.terms.some((term) => lower.includes(term)));
return mapped?.department || null;
}
function askDoctor() {
const department = state.booking.department;
const options = [...doctors[department], "No preference"];
addBot(`I mapped that request to ${department}. Please choose a doctor.`);
addBot(`
${department.slice(0, 1)}
${doctors[department][0]}
${department} · Available Mon/Wed/Fri
Caption: Doctor profile card showing specialty and available appointment days.
`, { card: true });
renderQuickActions(options.map((label) => ({ label })));
}
function addConfirmationPreview() {
const booking = state.booking;
addBot(`
Appointment draft
${booking.patientName}
${booking.department} with ${booking.doctor}
${booking.date} at ${booking.time}
Caption: Appointment confirmation card showing doctor, department, date, time, and patient name.
`, { card: true });
}
function confirmBooking() {
const id = `MF-${Date.now().toString().slice(-6)}`;
const appointment = { ...state.booking, id };
setAppointments([...getAppointments(), appointment]);
state.mode = "idle";
state.step = null;
addBot(`Your appointment is confirmed. Appointment ID: ${id}.`);
addBot("Would you like to leave feedback or ask another hospital question?");
renderQuickActions([{ label: "Give feedback" }, { label: "Ask hospital question" }, { label: "Book another appointment" }]);
}
function beginCancellation() {
state.mode = "cancellation";
state.step = "identify";
addBot("Please enter your appointment ID.");
renderQuickActions(getAppointments().map((appointment) => ({ label: appointment.id })));
}
function continueCancellation(text) {
const appointment = getAppointments().find((item) => item.id.toLowerCase() === text.toLowerCase());
if (!appointment) {
addBot("I could not find that prototype appointment ID. Please check the ID or start a new booking.");
renderQuickActions([{ label: "Book appointment" }, { label: "Cancel appointment" }]);
state.mode = "idle";
return;
}
if (state.step === "identify") {
state.cancellationId = appointment.id;
state.step = "confirmCancel";
addBot(`I found ${appointment.id}: ${appointment.department} with ${appointment.doctor} on ${appointment.date} at ${appointment.time}. Do you want to cancel it?`);
renderQuickActions([{ label: "Yes, cancel" }, { label: "No, keep appointment" }]);
return;
}
}
function finishCancellation(text) {
if (text === "Yes, cancel") {
setAppointments(getAppointments().filter((appointment) => appointment.id !== state.cancellationId));
addBot("Your appointment has been cancelled. You can book another appointment if needed.");
} else {
addBot("Okay, I kept the appointment active.");
}
state.mode = "idle";
state.step = null;
renderQuickActions([{ label: "Book appointment" }, { label: "Give feedback" }]);
}
function answerFaq(text) {
state.mode = "idle";
state.step = null;
const lower = text.toLowerCase();
const match = faqs.find((faq) => faq.terms.some((term) => lower.includes(term)));
addBot(match ? match.answer : "I can help with visiting hours, required documents, emergency care, parking, and telehealth.");
renderQuickActions([
{ label: "Visiting hours" },
{ label: "Required documents" },
{ label: "Emergency department" },
{ label: "Book appointment" },
]);
}
function beginFeedback() {
state.mode = "feedback";
state.step = "rating";
addBot("How would you rate this experience from 1 to 5?");
renderQuickActions(["1", "2", "3", "4", "5"].map((label) => ({ label })));
}
function continueFeedback(text) {
if (state.step === "rating") {
state.feedbackRating = text;
state.step = "comment";
addBot("Please share one thing we could improve.");
renderQuickActions([]);
return;
}
addBot(`Thank you. I saved your prototype feedback rating as ${state.feedbackRating}.`);
state.mode = "idle";
state.step = null;
renderQuickActions([{ label: "Book appointment" }, { label: "Ask hospital question" }]);
}
function handleInput(rawText, fromQuickAction = false) {
const text = rawText.trim();
if (!text) return;
addUser(text);
input.value = "";
if (text === "Book appointment" || text === "Book another appointment") return beginBooking();
if (text === "Cancel appointment") return beginCancellation();
if (text === "Ask hospital question") {
state.mode = "faq";
addBot("What hospital question can I help with?");
renderQuickActions([{ label: "Visiting hours" }, { label: "Required documents" }, { label: "Parking" }, { label: "Telehealth" }]);
return;
}
if (text === "Give feedback") return beginFeedback();
if (state.mode === "booking") return continueBooking(text);
if (state.mode === "cancellation" && state.step === "confirmCancel") return finishCancellation(text);
if (state.mode === "cancellation") return continueCancellation(text);
if (state.mode === "feedback") return continueFeedback(text);
if (state.mode === "faq" || fromQuickAction) return answerFaq(text);
return routeIntent(text);
}
form.addEventListener("submit", (event) => {
event.preventDefault();
handleInput(input.value);
});
resetButton.addEventListener("click", () => {
localStorage.removeItem("mediflow-memory");
localStorage.removeItem("mediflow-appointments");
chatLog.innerHTML = "";
state.mode = "idle";
state.step = null;
state.booking = {};
start();
});
start();