feat: enhance new-patient AI chat flow with full scheduling and eligibility
- Add 3-message intro (self-intro → empathetic ack → new/existing question) via single TwiML response to guarantee delivery order - Detect reschedule intent from first message; look up existing appointment date - New patient flow: ask insurance type → MassHealth consent → member ID + DOB → Selenium eligibility check - Post-eligibility: active → ask appointment date/time with office-hours validation; inactive → ask other insurance or collect contact info - Date/time collection mirrors reschedule flow: check office day open, ask time, validate against office hours - Auto-create appointment in schedule for known patients on confirmation; use first available staff member - Add openPhoneReply toggle (Settings → AI Chat) to respond to any number at any time - Add 5-minute inactivity timeout: reset conversation to initial stage and clear pending state - Normalize MassHealth DOB to zero-padded MM/DD/YYYY before Selenium submission - Expand isExistingPatient classifier to recognize "old patient", "old", "previous", "prior" - Existing patient confirmation message now acknowledges patient type before asking about insurance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,10 @@ import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
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 } from "lucide-react";
|
||||
import { Bot, CalendarCheck, UserPlus, MessageCircle, Info, GitFork, MessageSquare, Trash2, Plus, Zap } from "lucide-react";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
@@ -546,6 +547,39 @@ export function AiChatSettingsCard() {
|
||||
const [generalFallback, setGeneralFallback] = useState(DEFAULTS.generalFallback);
|
||||
const initialized = useRef(false);
|
||||
|
||||
const [openPhoneReply, setOpenPhoneReply] = useState(false);
|
||||
|
||||
const { data: advancedSettings } = useQuery<{ openPhoneReply: boolean }>({
|
||||
queryKey: ["/api/ai/advanced-settings"],
|
||||
queryFn: async () => {
|
||||
const res = await apiRequest("GET", "/api/ai/advanced-settings");
|
||||
if (!res.ok) return { openPhoneReply: false };
|
||||
return res.json();
|
||||
},
|
||||
staleTime: Infinity,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (advancedSettings) setOpenPhoneReply(advancedSettings.openPhoneReply ?? false);
|
||||
}, [advancedSettings]);
|
||||
|
||||
const openPhoneReplyMutation = useMutation({
|
||||
mutationFn: async (enabled: boolean) => {
|
||||
const res = await apiRequest("PUT", "/api/ai/advanced-settings", { openPhoneReply: enabled });
|
||||
if (!res.ok) throw new Error("Failed to save");
|
||||
return res.json();
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
setOpenPhoneReply(data.openPhoneReply);
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/ai/advanced-settings"] });
|
||||
toast({ title: data.openPhoneReply ? "Open reply enabled" : "Open reply disabled" });
|
||||
},
|
||||
onError: () => {
|
||||
toast({ title: "Error", description: "Failed to save setting.", variant: "destructive" });
|
||||
},
|
||||
});
|
||||
|
||||
// ── SMS template list ──────────────────────────────────────────
|
||||
const [smsTemplates, setSmsTemplates] = useState<SmsTemplate[]>([]);
|
||||
const smsInitialized = useRef(false);
|
||||
@@ -698,6 +732,29 @@ export function AiChatSettingsCard() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
{/* ── Advanced: Open Phone Reply toggle ───────────────────── */}
|
||||
<Card>
|
||||
<CardContent className="py-6">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Zap className="h-5 w-5 text-primary mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm font-semibold leading-snug">Respond to any phone number at any time</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
When enabled, the AI will reply to all incoming texts — including unknown numbers not yet in your patient list — 24/7 regardless of office hours. When disabled, the AI only responds during after-hours.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={openPhoneReply}
|
||||
disabled={openPhoneReplyMutation.isPending}
|
||||
onCheckedChange={(val) => openPhoneReplyMutation.mutate(val)}
|
||||
className="flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* ── Section 0: SMS Templates ─────────────────────────────── */}
|
||||
<Card>
|
||||
<CardContent className="py-6 space-y-5">
|
||||
|
||||
Reference in New Issue
Block a user