- Reminder flow: send AI self-introduction as message 1 (Twilio REST API), intent response as message 2 (TwiML) so intro always arrives first
- LangGraph reminder graph: classify yes/no/other from patient reply; 'no' now asks 'When would you like to reschedule?' directly
- Reschedule flow: new asked_reschedule_datetime stage replaces multi-step ASAP/next-week flow
- Date-only reply (e.g. '5/18'): ask for time separately, then confirm
- Date+time reply (e.g. '5/18 at 10am'): go straight to confirmation
- new asked_reschedule_time_for_date and asked_reschedule_confirm_datetime stages
- Date/time parsing: regex handles M/D and am/pm formats first; falls back to Gemini for natural language
- Day-level office hours check: if requested day is closed (e.g. Sunday), reply 'Our office is closed on [date]. Choose another day?'
- Time-level office hours check: if requested time is outside working hours (e.g. 12pm during lunch), reply with actual hours (e.g. '9:00 am – 12:00 pm and 1:00 pm – 5:00 pm')
- Slot availability check: verifies no conflicting appointment for same staff member
- After appointment confirmed: patient thank-you reply triggers warm closing with upcoming appointment time
- Schedule page: office hours summary bar above grid showing today's configured hours with link to settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`You are a friendly dental office assistant. Write a short, warm SMS reply (1-2 sentences max) thanking the patient for confirming their appointment and reminding them of the date and time.${apptClause} You MUST reply in ${lang} regardless of the language the patient used. Do not add any formatting or extra text.`,
`You are a friendly dental office assistant. Write a short, warm SMS reply (1-2 sentences max) thanking the patient for confirming their appointment and reminding them of the date and time.${apptClause} You MUST reply in ${lang}. Do not add any formatting or extra text.`,
`You are a friendly dental office assistant. The patient cannot make their appointment. Write a short, empathetic SMS reply (1 sentence max) that says it is understandable and asks if they would like to reschedule. You MUST reply in ${lang} regardless of the language the patient used. Do not add any formatting or extra text.`,
`You are a friendly dental office assistant. The patient cannot make their appointment. Write a short, empathetic SMS reply (1-2 sentences max) acknowledging they can't make it and asking when they would like to reschedule. You MUST reply in ${lang}. Do not add any formatting or extra text.`,
content:`You are a friendly dental office AI assistant named Lisa. The patient wants to schedule an appointment. Ask them in ${lang} whether they are a new patient or an existing patient. One sentence, no formatting.`,
content:`You are a friendly dental office AI assistant. The patient wants to schedule an appointment. Ask them in ${lang} whether they are a new patient or an existing patient. One sentence, no formatting.`,
content:`You are a friendly dental office AI assistant named Lisa. Respond helpfully to the patient's message in ${lang}. Keep it to 1-2 sentences, no formatting. For non-dental questions, let them know our office staff can assist.`,
content:`You are a friendly dental office AI assistant. Respond helpfully to the patient's message in ${lang}. Keep it to 1-2 sentences, no formatting. For non-dental questions, let them know our office staff can assist.`,
{role:"system",content:'Extract the time from the message. Return ONLY a 24-hour time in "HH:MM" format (e.g., "10:00", "14:30"). If no time is mentioned, return "null".'},
{role:"system",content:'Extract the time from the message. Return ONLY a 24-hour time in "HH:MM" format. If no time is mentioned, return "null".'},
English:`What time do you prefer on ${dateLabel}?`,
Spanish:`¿A qué hora prefiere el ${dateLabel}?`,
Portuguese:`Que horário você prefere em ${dateLabel}?`,
Mandarin:`您希望在 ${dateLabel} 几点?`,
Cantonese:`您希望在 ${dateLabel} 幾點?`,
Arabic:`ما الوقت الذي تفضله في ${dateLabel}؟`,
"Haitian Creole":`Ki lè ou prefere nan ${dateLabel}?`,
};
constaskTimeReply=awaitllmReply(
`You are a friendly dental office assistant. The patient wants to reschedule to ${dateLabel} but hasn't given a time. Ask them in ${lang} what time they prefer on that day. 1 sentence, no formatting.`,
English:`Just to confirm — do you prefer ${displayLabel}?`,
Spanish:`Solo para confirmar — ¿prefiere el ${displayLabel}?`,
Portuguese:`Só para confirmar — você prefere ${displayLabel}?`,
Mandarin:`确认一下——您希望的时间是 ${displayLabel} 吗?`,
Cantonese:`確認一下——您希望的時間是 ${displayLabel} 嗎?`,
Arabic:`فقط للتأكيد — هل تفضل ${displayLabel}؟`,
"Haitian Creole":`Jis pou konfime — èske ou prefere ${displayLabel}?`,
};
constconfirmReply=awaitllmReply(
`You are a friendly dental office AI assistant. The patient mentioned a date/time preference that you interpreted as "${displayLabel}". Ask them in ${lang} to confirm: "Do you mean ${displayLabel}?" 1 sentence, natural and friendly. No formatting.`,
English:`Just to confirm — do you prefer ${fullLabel}?`,
Spanish:`Solo para confirmar — ¿prefiere el ${fullLabel}?`,
Portuguese:`Só para confirmar — você prefere ${fullLabel}?`,
Mandarin:`确认一下——您希望的时间是 ${fullLabel} 吗?`,
Cantonese:`確認一下——您希望的時間是 ${fullLabel} 嗎?`,
Arabic:`فقط للتأكيد — هل تفضل ${fullLabel}؟`,
"Haitian Creole":`Jis pou konfime — èske ou prefere ${fullLabel}?`,
};
constconfirmReply=awaitllmReply(
`You are a friendly dental office AI assistant. The patient wants ${fullLabel}. Ask them in ${lang} to confirm: "Do you mean ${fullLabel}?" 1 sentence, natural and friendly. No formatting.`,
constfallback=`Your appointment is moved to ${displayLabel}. Our dental receptionist will confirm it with you tomorrow.`;
constreply=awaitllmReply(
`You are a friendly dental office AI assistant. The patient's appointment has been successfully moved to ${displayLabel}. Write a warm confirmation message in ${lang}: tell them the appointment is moved to ${displayLabel}, and that our dental receptionist will confirm it with them tomorrow. 2 sentences max, no formatting.`,
`Appointment rescheduled to ${displayLabel}.`,
fallback,
apiKey,
);
return{reply,nextStage:"done"};
}
// ── asked_reschedule_confirm: patient answered the reschedule question ────
if(stage==="asked_reschedule_confirm"){
if(no(t)){
constfallbacks: Record<string,string>={
@@ -217,52 +831,28 @@ export async function runRescheduleStep(
`You are a friendly dental office assistant. The patient does not want to reschedule. Write a warm, brief closing message in ${lang}. 1 sentence, no formatting.`,
`Patient said: "${message}"`,fallback,apiKey
`Patient said: "${message}"`,fallback,apiKey,
);
return{reply,nextStage:"done"};
}
// Patient confirmed they want to reschedule — move straight to datetime request
if(yes(t)){
// Check if original appointment was Mon–Thu (days 1–4)
constdow=awaitgetAppointmentDow(patientId);
constisMonToThu=dow>=1&&dow<=4;
if(isMonToThu){
// Offer ASAP or next week
constfallbacks: Record<string,string>={
English:"Would you like to reschedule as soon as possible, or would you prefer next week?",
Spanish:"¿Le gustaría reprogramar lo antes posible, o prefiere la semana que viene?",
Portuguese:"Gostaria de reagendar o mais rápido possível, ou prefere a semana que vem?",
Mandarin:"您想尽快重新安排预约,还是下周更方便?",
Cantonese:"您想盡快重新安排預約,還是下週更方便?",
Arabic:"هل تفضل إعادة الجدولة في أقرب وقت ممكن، أم تفضل الأسبوع القادم؟",
"Haitian Creole":"Èske ou ta renmen repwograme pi vit posib, oswa ou prefere semèn pwochèn?",
English:"What day and time would you like? For example: 'Monday at 10am' or 'next Tuesday afternoon'.",
Spanish:"¿Qué día y hora prefiere? Por ejemplo: 'lunes a las 10am' o 'martes por la tarde'.",
Portuguese:"Que dia e horário você prefere? Por exemplo: 'Segunda às 10h' ou 'terça de tarde'.",
Mandarin:"您希望哪天几点?例如:'星期一上午10点'或'下周二下午'。",
Cantonese:"您希望哪天幾點?例如:'星期一上午10點'或'下週二下午'。",
Arabic:"ما اليوم والوقت الذي تفضله؟ مثلاً: 'الاثنين الساعة 10 صباحاً' أو 'الثلاثاء بعد الظهر'.",
"Haitian Creole":"Ki jou ak ki lè ou prefere? Pa egzanp: 'Lendi 10am' oswa 'Madi apremidi'.",
`You are a friendly dental office assistant. The patient wants to reschedule. Their original appointment was on a weekday. Ask in ${lang} whether they prefer to reschedule as soon as possible or next week. 1 sentence, no formatting.`,
`Patient said: "${message}"`,fallback,apiKey
`You are a friendly dental office assistant. The patient wants to reschedule. Ask them in ${lang} what day and time they prefer. Give 1-2 examples like "Monday at 10am" or "next Tuesday afternoon". 1-2 sentences, no formatting.`,
`You are a friendly dental office assistant. Offer the patient next week's Monday (${mon}), Tuesday (${tue}), or Wednesday (${wed}) in ${lang}. 1-2 sentences, no formatting.`,
`You are a friendly dental office assistant. Offer next week's Monday (${mon}), Tuesday (${tue}), or Wednesday (${wed}) in ${lang}. 1-2 sentences, no formatting.`,
`You are a friendly dental office assistant. The patient confirmed ${label}. Ask in ${lang} whether they prefer morning (9am-12pm) or afternoon (1pm-5pm). 1 sentence, no formatting.`,
`Patient confirmed tomorrow.`,fallback,apiKey
`Patient confirmed tomorrow.`,fallback,apiKey,
);
return{reply,nextStage:"asked_reschedule_time"};
}
if(no(t)){
// Can't make tomorrow — offer next week instead
const{mon,tue,wed}=getNextWeekDays();
constfallbacks: Record<string,string>={
English:`No problem! What about next week? Would ${mon}, ${tue}, or ${wed} work for you?`,
Spanish:`¡Sin problema! ¿Qué le parece la semana que viene? ¿Le vendría bien el ${mon}, ${tue} o el ${wed}?`,
Portuguese:`Sem problema! E na semana que vem? ${mon}, ${tue} ou ${wed} seria bom?`,
English:`No problem! What about next week? Would ${mon}, ${tue}, or ${wed} work?`,
Spanish:`¡Sin problema! ¿Qué le parece la semana que viene? ¿El ${mon}, ${tue} o el ${wed}?`,
Portuguese:`Sem problema! E na semana que vem? ${mon}, ${tue} ou ${wed}?`,
`You are a friendly dental office assistant. The patient cannot come tomorrow. Offer next week: ${mon}, ${tue}, or ${wed} in ${lang}. 1-2 sentences, no formatting.`,
`You are a friendly dental office assistant. The patient chose ${day}. Ask in ${lang} whether they prefer morning (9am-12pm) or afternoon (1pm-5pm). 1 sentence, no formatting.`,
`Patient chose ${day}.`,fallback,apiKey
`Patient chose ${day}.`,fallback,apiKey,
);
return{reply,nextStage:"asked_reschedule_time"};
}
// Day not clearly detected — ask again with the specific options
const{mon,tue,wed}=getNextWeekDays();
constfallbacks: Record<string,string>={
English:`Which day works best — ${mon}, ${tue}, or ${wed}?`,
@@ -394,26 +978,19 @@ export async function runRescheduleStep(
Portuguese:`Qual dia é melhor — ${mon}, ${tue} ou ${wed}?`,
Mandarin:`哪天最方便——${mon}、${tue} 还是 ${wed}?`,
Cantonese:`哪天最方便——${mon}、${tue} 還是 ${wed}?`,
Arabic:`أي يوم هو الأفضل لك — ${mon} أو ${tue} أو ${wed}؟`,
Arabic:`أي يوم هو الأفضل — ${mon} أو ${tue} أو ${wed}؟`,
"Haitian Creole":`Ki jou ki pi bon — ${mon}, ${tue}, oswa ${wed}?`,
constfallback=`Your appointment has been moved to ${apptLabel}. Our dental receptionist will confirm it with you tomorrow.`;
constreply=awaitllmReply(
`You are a friendly dental office assistant. The patient's appointment has been successfully rescheduled to ${apptLabel}. Confirm in ${lang} with enthusiasm. 1 sentence, no formatting.`,
`Appointment moved to ${apptLabel}.`,fallback,apiKey
`You are a friendly dental office assistant. The patient's appointment has been rescheduled to ${apptLabel}. Confirm in ${lang}: say the appointment is moved to ${apptLabel} and that our dental receptionist will confirm it with them tomorrow. 2 sentences, no formatting.`,
`Appointment moved to ${apptLabel}.`,fallback,apiKey,
// ── Stage: reminder_initial → two messages: 1) AI intro, 2) intent response ──
if(stage==="reminder_initial"){
constrawGreeting=chatTemplates.reminderGreeting||
`Hi! My name is Lisa, the dedicated AI assistant at {officeName}. I can confirm or reschedule your appointment and answer general questions 24/7. I will reply your message at any time you need.`;
`Hi! My name is Lisa, the dedicated AI assistant at {officeName}. I can confirm or reschedule your appointment and answer general questions 24/7.`;
content:`You are a friendly dental office AI assistant. The patient just said "${Body}" after completing a conversation. Reply warmly in ${language}, thanking them for choosing the office and reminding them of their upcoming appointment on ${apptDatetime}. 1-2 sentences, no formatting.`,
},
{role:"user",content: Body},
]);
constaiMsg=String(res.content).trim();
if(aiMsg)returnreply(aiMsg,"done");
}catch{/* fall through to fallback */}
}
returnreply(fallback,"done");
}
}
// ── Stage: initial (no active conversation) ───────────────────────────
// Check after-hours: if enabled and currently outside office hours → start new-patient flow
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.