import { useState, useEffect, useRef } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { useToast } from "@/hooks/use-toast"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { Bot, CalendarCheck, UserPlus, MessageCircle, Info, GitFork, MessageSquare, Trash2, Plus, Zap, SlidersHorizontal, BookMarked, ChevronDown, ChevronUp, PhoneCall, Stethoscope } from "lucide-react"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; // ─── Types ──────────────────────────────────────────────────────────────────── type SmsTemplate = { id: string; name: string; body: string }; type AiChatTemplates = { reminderGreeting: string; newPatientGreeting: string; generalFallback: string; }; type AiCallTemplates = { greeting: string; reminderGreeting: string; newPatientGreeting: string; generalFallback: string; recallGreeting: string; }; type OfficeContact = { officeName?: string | null; }; // ─── Defaults ───────────────────────────────────────────────────────────────── const DEFAULT_REMINDER_SMS = "Hi {firstName}, this is a reminder from {officeName}. You have an appointment on {appointmentDate} at {appointmentTime}. Please come to our office at {officeAddress}. Please reply YES to confirm or NO to reschedule. Thank you!"; const DEFAULTS = { 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 you message at any time you need.", newPatientGreeting: "Hi! My name is Lisa, the dedicated AI assistant at {officeName}. I can help you schedule an appointment, check your insurance, and answer general questions 24/7. How can I help you today?", generalFallback: "Hi! My name is Lisa, the dedicated AI assistant at {officeName}. How can I help you today?", }; const CALL_DEFAULTS = { greeting: "Hi {firstName}, this is Lisa, the AI assistant at {officeName}. How can I help you today?", reminderGreeting: "Sure, I can help with that.", newPatientGreeting: "No problem, let's get you set up.", generalFallback: "Sure, how can I help?", recallGreeting: "Hi {firstName}, this is Lisa from {officeName}. We wanted to check in — you're due for a routine exam and cleaning. Would you like to make an appointment because your insurance covers it?", }; const DEFAULT_SMS_TEMPLATES = [ { id: "default-appt-reminder", name: "Appointment Reminder SMS", body: DEFAULT_REMINDER_SMS, }, { id: "default-follow-up", name: "Follow up reminder", body: "Hi {firstName}, this is a follow-up from {officeName}. We wanted to check in with you after your recent appointment. Please don't hesitate to call us if you have any questions.", }, ]; function previewTemplate(text: string, officeName: string) { return text.replace(/\{officeName\}/g, officeName || "your dental office"); } function newId() { return Date.now().toString(36) + Math.random().toString(36).slice(2); } // ─── Combined Reminder & Reschedule flow diagram (SVG) ──────────────────────── function LangGraphFlow() { const W = 700; const cx = 350; const nW = 210; const nx = cx - nW / 2; // 245 // ── Triple entry nodes ───────────────────────────────────────────────────── const entryW = 150; const lEntryCx = 130; const mEntryCx = cx; // 350 const rEntryCx = 570; const e1y = 14, e1h = 70; // Merge connector (horizontal line where all three entries meet) const mergeY = e1y + e1h + 14; // 98 // ── Center sequence ─────────────────────────────────────────────────────── const n2y = mergeY + 14, n2h = 52; // Patient replies const n3y = n2y + n2h + 14, n3h = 98; // AI classifies const forkHY = n3y + n3h + 22; // 270 // ── Branch centers ──────────────────────────────────────────────────────── const lcx = 140; // YES (left) const rcx = 490; // NO / Reschedule (right) // ── YES branch ──────────────────────────────────────────────────────────── const yesW = 195; const yes1y = forkHY + 50, yes1h = 102; // y=320 const yes2y = yes1y + yes1h + 12, yes2h = 52; // y=460 // ── NO / Reschedule branch ──────────────────────────────────────────────── const noW = 208; const failX = rcx + noW / 2 + 8; // 602 const failW = 92; const no1y = forkHY + 50, no1h = 84; // y=320 ask date / shortcut const no2y = no1y + no1h + 14, no2h = 82; // y=418 day check const no3y = no2y + no2h + 14, no3h = 64; // y=514 ask time const no4y = no3y + no3h + 14, no4h = 82; // y=592 time check const no5y = no4y + no4h + 14, no5h = 64; // y=688 confirm datetime const no6y = no5y + no5h + 14, no6h = 74; // y=766 patient YES/NO const no7y = no6y + no6h + 14, no7h = 82; // y=854 slot check const no8y = no7y + no7h + 14, no8h = 80; // y=950 move appointment const no9y = no8y + no8h + 14, no9h = 54; // y=1044 patient thanks const totalH = Math.max(yes2y + yes2h, no9y + no9h) + 28; return ( {/* ══ TRIPLE ENTRY NODES ════════════════════════════════════════════ */} {/* Left: Reminder SMS */} Office sends Reminder SMS or Call Staff triggers batch send {/* Middle: Recall call/SMS for an existing patient */} Office call or SMS for an existing patient Recall: free exam & cleaning {/* Right: Reschedule by Office SMS */} Office sends Reschedule SMS "Reschedule by Office" template {/* Converging lines → horizontal merge → Patient replies */} {/* ══ CENTER SEQUENCE ══════════════════════════════════════════════ */} {/* N2: Patient replies */} Patient replies Any SMS triggers the AI {/* N3: AI classifies */} Google AI classifies YES / NO Reminder: intro (MSG 1) + reply (MSG 2) Reschedule: reply sent directly Recall: same as Reschedule flow Date in reply → AI skips to Day Check {/* Fork lines */} {/* YES / NO badges */} YES NO {/* ══ YES BRANCH ══════════════════════════════════════════════════ */} Reminder: Thank you for confirming! Reschedule: What day & time? Recall: What day & time? "See you on [date & time]" date in reply → Day Check ↘ {/* Yes2: Patient thanks */} Patient: "Thank you / OK" AI: "Thank you for choosing us! See you on [date]" {/* Dashed shortcut: YES with date → Day Check */} {/* ══ NO / RESCHEDULE BRANCH ══════════════════════════════════════ */} {/* no1: Understandable / ask date (with date-shortcut note) */} MSG 2: It is understandable! When would you like to reschedule? Patient replies with a preferred date date in reply → skips to Day Check ↓ {/* no2: Day check */} Check: Is office open on that day? (reads Office Hours settings) ✓ Open → ask what time ✗ Closed → "Office closed on [day]. Choose another?" ↩ loops back to ask date {/* no3: What time on [date]? */} AI: "What time do you prefer on [date]?" Patient replies with a time (e.g. "1 pm") {/* no4: Time check */} Check: Is time within office hours? (e.g. not during lunch 12–1pm) ✓ In hours → ask to confirm ✗ → "Not available. Our hours: [AM]–[PM]. Other time?" ↩ loops back to ask time {/* no5: Just to confirm */} AI: "Just to confirm — do you prefer [date at time]?" Patient replies YES or NO {/* no6: Patient YES/NO */} Patient confirms or corrects ✓ YES → check slot availability ✗ NO → "No problem! What day/time?" ↩ returns to date step {/* no7: Slot availability check */} Check: Is slot available for staff? (no overlapping appointments) ✓ Free → move appointment ✗ Taken → "[date] not available. Other time?" ↩ loops back to ask date {/* no8: DB move + AI badge */} DB Update Appointment moved! "Appt moved to [date at time]. Receptionist will confirm tomorrow." 🤖 AI badge shown on schedule grid {/* no9: Patient thanks */} Patient: "Thank you / OK" AI: "Thank you for choosing us! See you on [date]" ); } // ─── New Patient flow diagram (updated) ────────────────────────────────────── function NewPatientFlow() { // ── Canvas ─────────────────────────────────────────────────────────────── const W = 900; const cx = 450; // ── Coordinates ────────────────────────────────────────────────────────── const nW = 240; const nx = cx - nW / 2; // Top sequence const n1y = 14; const n1h = 84; const n2y = n1y + n1h + 14; const n2h = 52; const forkHY = n2y + n2h + 18; // Main branch centres const lcx = 210; // New Patient const rcx = 690; // Existing Patient const brW = 210; // ── NEW PATIENT layout ──────────────────────────────────────────────────── // insurance question const npInsY = forkHY + 44; const npInsH = 52; // sub-fork 1: has-ins / no-ins const npSf1Y = npInsY + npInsH + 18; const lhcx = 140; // has insurance const lncx = 325; // no insurance // no-ins leaf const npNoInsY = npSf1Y + 20; const npNoInsH = 76; const npNoInsW = 130; // what kind? const npWkY = npSf1Y + 20; const npWkH = 52; const npWkW = 192; // sub-fork 2: masshealth / other const npSf2Y = npWkY + npWkH + 18; const mhcx = 82; // masshealth const othcx = 210; // other // other ins leaf const npOthY = npSf2Y + 20; const npOthH = 76; const npOthW = 130; // masshealth consent node const npMhY = npSf2Y + 20; const npMhH = 76; const npMhW = 155; // selenium const npSelY = npMhY + npMhH + 18; const npSelH = 66; const npSelW = 148; // active / inactive fork const npResHY = npSelY + npSelH + 14; const npActCx = 40; const npInaCx = 140; const npResY = npResHY + 22; // active result const npActH = 76; const npActW = 110; // inactive → other insurance? const npInaH = 52; const npInaW = 110; // other-insurance-after-inactive const npOins2Y = npResY + Math.max(npActH, npInaH) + 16; const npOins2H = 52; const npOins2W = 150; // final: yes=transfer / no=contact info const npFinalY = npOins2Y + npOins2H + 18; const npTxCx = npInaCx - 44; const npCiCx = npInaCx + 46; const npTxH = 44; const npTxW = 86; const npCiH = 64; const npCiW = 132; const totalH_np = npFinalY + npCiH + 18; // ── EXISTING PATIENT layout ─────────────────────────────────────────────── const epInsY = forkHY + 44; const epInsH = 64; const epInsW = 210; const epSf1Y = epInsY + epInsH + 18; const epSameCx = 638; const epDiffCx = 760; // no-same (transfer) const epDiffY = epSf1Y + 20; const epDiffH = 52; const epDiffW = 130; // same confirmed const epSameY = epSf1Y + 20; const epSameH = 52; const epSameW = 192; // sub-fork 2: masshealth / other const epSf2Y = epSameY + epSameH + 18; const epMhCx = 586; const epOthCx = 706; // other ins leaf const epOthY = epSf2Y + 20; const epOthH = 76; const epOthW = 130; // mh auto-check const epMhY = epSf2Y + 20; const epMhH = 62; const epMhW = 175; // selenium const epSelY = epMhY + epMhH + 18; const epSelH = 66; const epSelW = 148; // active / inactive const epResHY = epSelY + epSelH + 14; const epActCx = 534; const epInaCx = 648; const epResY = epResHY + 22; const epActH = 76; const epActW = 110; const epInaH = 64; const epInaW = 120; const totalH_ep = epResY + Math.max(epActH, epInaH) + 18; const totalH = Math.max(totalH_np, totalH_ep) + 10; return ( {/* ═══════════ ENTRY — 3-message flow ═════════════════════════ */} Patient texts the office number AI sends 3 messages (TwiML, guaranteed order): ① Self-intro (New Patient Greeting template) ② Empathetic ack · ③ "New or existing patient?" AI classifies patient reply "new patient" / "existing" / "old patient" / … {/* ═══════════ MAIN FORK ══════════════════════════════════════ */} New Patient Existing Patient {/* ═══════════ NEW PATIENT BRANCH ═════════════════════════════ */} {/* Insurance? */} Do you have dental insurance? "Yes" / "No" / "MassHealth" / … {/* Sub-fork 1 */} Yes No ins. {/* No insurance → schedule */} No insurance Ask preferred date & time Office hours check → Appointment created {/* What kind? */} What kind of insurance? "MassHealth" / "Blue Cross" / … {/* Sub-fork 2 */} MassHealth Other {/* Other ins → schedule */} Other insurance Ask preferred date & time Office hours check → Appointment created {/* MassHealth consent + ID+DOB */} Check MassHealth now? Yes → send Member ID + DOB (MM/DD/YYYY) No → ask date preference {/* NP Selenium */} Selenium MassHealth Portal Auto-checks eligibility Result sent via SMS {/* NP ACTIVE / INACTIVE fork */} ACTIVE INACTIVE {/* NP ACTIVE result */} MassHealth active! Ask date & time Office hours check → Appt created {/* NP INACTIVE → other insurance? */} Coverage inactive or not verified Other insurance? "Yes" or "No" {/* Final fork: yes=transfer, no=contact info */} Yes No Transfer to staff Leave name & phone Receptionist will contact you shortly {/* ═══════════ EXISTING PATIENT BRANCH ════════════════════════ */} {/* Same insurance? */} Got it — existing patient! Same insurance on file? "Yes" / "No" / "Changed" {/* Sub-fork 1 */} YES NO {/* Changed → transfer */} Insurance changed Transfer to staff {/* Same confirmed */} Insurance confirmed MassHealth or Other? {/* Sub-fork 2 */} MassHealth Other {/* Other ins → schedule */} Other insurance Ask preferred date & time Office hours check → Appointment created {/* EP MassHealth auto-check */} MassHealth auto-check Using stored ID + DOB Please wait 30-60 secs… {/* EP Selenium */} Selenium MassHealth Portal Auto-checks eligibility Result sent via SMS {/* EP ACTIVE / INACTIVE */} ACTIVE INACTIVE Coverage active! Ask date & time Office hours check → Appt created Coverage inactive Self-pay offer? YES → schedule / NO → close ); } // ─── Users AI Chat (internal staff assistant) config ───────────────────────── const INTERNAL_CHAT_PLACEHOLDER = `Examples of custom context you can add: • "This office primarily treats MassHealth patients — always prefer MassHealth eligibility checks." • "When a patient name is typed, also show their upcoming appointment if available." • "Default provider is Mary Scannell."`; function InternalChatSettingsCard() { const { toast } = useToast(); const [systemPrompt, setSystemPrompt] = useState(""); const initialized = useRef(false); const { data, isLoading } = useQuery<{ systemPrompt: string }>({ queryKey: ["/api/ai/internal-chat-settings"], queryFn: async () => { const res = await apiRequest("GET", "/api/ai/internal-chat-settings"); if (!res.ok) return { systemPrompt: "" }; return res.json(); }, staleTime: Infinity, refetchOnWindowFocus: false, }); useEffect(() => { if (data && !initialized.current) { initialized.current = true; setSystemPrompt(data.systemPrompt ?? ""); } }, [data]); const saveMutation = useMutation({ mutationFn: async (prompt: string) => { const res = await apiRequest("PUT", "/api/ai/internal-chat-settings", { systemPrompt: prompt }); if (!res.ok) throw new Error("Failed to save"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/ai/internal-chat-settings"] }); toast({ title: "Users AI Chat settings saved" }); }, onError: () => { toast({ title: "Error", description: "Failed to save settings.", variant: "destructive" }); }, }); return (

Users AI Chat

Configure the AI assistant available to staff via the{" "} bot icon in the top-right corner. Staff can type natural language commands such as{" "} "check MARIA GONZALES"{" "} to search for a patient and pre-fill their eligibility check, or{" "} "open claims"{" "} to navigate directly to a page.

{/* Built-in workflows */}

Built-in Workflows

{/* Eligibility by name */}
🏥

Eligibility by patient name

Looks up the patient in the database, resolves their insurance, and opens the eligibility page pre-filled.

{["check Maria Jesus", "verify insurance for John Smith"].map((ex) => ( {ex} ))}
{/* Eligibility by member ID */}
🔢

Eligibility by Member ID + DOB

Provide a member ID and date of birth. Insurance is resolved from the patient record, or from what you state in the message.

{["check masshealth for 100xxxx, 10/10/1988"].map((ex) => ( {ex} ))}
{/* Check & Claim */}
⚡

Check eligibility + claim procedures

Resolves the patient, maps procedure names to CDT codes using the fee schedule, and opens the eligibility page ready to check and claim. Procedure names or CDT codes both work.

{[ "check masshealth for 100xxxx, 10/10/1988 and claim perio exam and adult cleaning", "check Maria Jesus and claim D0120 D1110", ].map((ex) => ( {ex} ))}

CDT codes are looked up from the fee schedule — no AI translation needed. If insurance is unknown the assistant will ask which one to use.

{/* Navigation */}
🗺️

Navigation

Opens any page in the app.

{["open claims", "go to schedule", "find patient GONZALES"].map((ex) => ( {ex} ))}
{/* CDT Aliases */} {/* Additional context / system prompt */}
Additional AI Context

Optional instructions injected into the AI's system prompt. Use this to describe your practice's preferences, common shortcuts, or patient demographics.

{isLoading ? (

Loading...

) : (