diff --git a/src/components/call-desk/ai-chat-panel.tsx b/src/components/call-desk/ai-chat-panel.tsx index 6d73315..2502b0a 100644 --- a/src/components/call-desk/ai-chat-panel.tsx +++ b/src/components/call-desk/ai-chat-panel.tsx @@ -27,7 +27,7 @@ export const AiChatPanel = ({ callerContext, onChatStart }: AiChatPanelProps) => const token = localStorage.getItem('helix_access_token') ?? ''; - const { messages, input, handleSubmit, handleInputChange, isLoading, append } = useChat({ + const { messages, input, handleSubmit, handleInputChange, isLoading, append, setMessages } = useChat({ api: `${API_URL}/api/ai/stream`, streamProtocol: 'text', headers: { @@ -49,6 +49,28 @@ export const AiChatPanel = ({ callerContext, onChatStart }: AiChatPanelProps) => } }, [messages, onChatStart]); + // Auto-fire a patient-summary request when a caller with a leadId appears + // on the panel. Resets whenever the caller changes (new incoming call) so + // each call starts fresh. The sidecar's AI agent inspects the leadId and + // replies with appointment/disposition/notes history when the caller is + // a returning patient, or a brief "net-new caller" ack otherwise. + const autoFiredForLeadRef = useRef(null); + useEffect(() => { + const leadId = callerContext?.leadId ?? null; + if (!leadId) return; + if (autoFiredForLeadRef.current === leadId) return; + + // New caller — clear any prior chat state and fire the summary prompt. + autoFiredForLeadRef.current = leadId; + setMessages([]); + chatStartedRef.current = false; + const name = callerContext?.leadName ?? 'this caller'; + append({ + role: 'user', + content: `Give me a quick summary of ${name} — prior appointments, last disposition, any outstanding notes. If net-new, say so.`, + }); + }, [callerContext?.leadId, callerContext?.leadName, append, setMessages]); + const handleQuickAction = (prompt: string) => { append({ role: 'user', content: prompt }); }; diff --git a/src/components/call-desk/enquiry-form.tsx b/src/components/call-desk/enquiry-form.tsx index ba984c4..b8da10f 100644 --- a/src/components/call-desk/enquiry-form.tsx +++ b/src/components/call-desk/enquiry-form.tsx @@ -287,7 +287,15 @@ export const EnquiryForm = ({ isOpen, onOpenChange, callerPhone, leadName, leadI {followUpNeeded && (
- + setFollowUpDate(e.target.value)} + required + aria-label="Follow-up Date" + className="w-full rounded-lg border border-primary bg-primary px-3 py-2 text-sm text-primary outline-none focus:border-brand-primary" + />
)} diff --git a/src/pages/call-desk.tsx b/src/pages/call-desk.tsx index 4db54f9..618b7cc 100644 --- a/src/pages/call-desk.tsx +++ b/src/pages/call-desk.tsx @@ -19,7 +19,7 @@ import { cx } from '@/utils/cx'; export const CallDeskPage = () => { const { user } = useAuth(); const { leadActivities, calls, followUps: dataFollowUps, patients, appointments } = useData(); - const { callState, callerNumber, callUcid, dialOutbound } = useSip(); + const { callState, callerNumber, callUcid, dialOutbound, isRegistered } = useSip(); const { missedCalls, followUps, marketingLeads, loading } = useWorklist(); const [selectedLead, setSelectedLead] = useState(null); const [contextOpen, setContextOpen] = useState(true); @@ -204,11 +204,11 @@ export const CallDeskPage = () => { )} diff --git a/src/pages/patients.tsx b/src/pages/patients.tsx index 25bb978..0ea1d53 100644 --- a/src/pages/patients.tsx +++ b/src/pages/patients.tsx @@ -6,7 +6,6 @@ import { faIcon } from '@/lib/icon-wrapper'; const SearchLg = faIcon(faMagnifyingGlass); import { Avatar } from '@/components/base/avatar/avatar'; -import { Badge } from '@/components/base/badges/badges'; // Button removed — actions are icon-only now import { Input } from '@/components/base/input/input'; import { Table, TableCard } from '@/components/application/table/table'; @@ -134,7 +133,6 @@ export const PatientsPage = () => { - @@ -196,17 +194,6 @@ export const PatientsPage = () => { - {/* Type */} - - {patient.patientType ? ( - - {patient.patientType} - - ) : ( - - )} - - {/* Gender */} diff --git a/src/providers/sip-provider.tsx b/src/providers/sip-provider.tsx index 55f510c..20eb5dc 100644 --- a/src/providers/sip-provider.tsx +++ b/src/providers/sip-provider.tsx @@ -43,6 +43,8 @@ export const SipProvider = ({ children }: PropsWithChildren) => { const setCallUcid = useSetAtom(sipCallUcidAtom); const setCallDuration = useSetAtom(sipCallDurationAtom); const setCallStartTime = useSetAtom(sipCallStartTimeAtom); + const setIsMutedGlobal = useSetAtom(sipIsMutedAtom); + const setIsOnHoldGlobal = useSetAtom(sipIsOnHoldAtom); // Register Jotai setters so the singleton SIP manager can update atoms useEffect(() => { @@ -51,8 +53,10 @@ export const SipProvider = ({ children }: PropsWithChildren) => { setCallState, setCallerNumber, setCallUcid, + setIsMuted: setIsMutedGlobal, + setIsOnHold: setIsOnHoldGlobal, }); - }, [setConnectionStatus, setCallState, setCallerNumber, setCallUcid]); + }, [setConnectionStatus, setCallState, setCallerNumber, setCallUcid, setIsMutedGlobal, setIsOnHoldGlobal]); // Auto-connect SIP on mount — only if Agent entity has SIP config useEffect(() => { diff --git a/src/state/sip-manager.ts b/src/state/sip-manager.ts index 5190ca7..cb6f51f 100644 --- a/src/state/sip-manager.ts +++ b/src/state/sip-manager.ts @@ -13,6 +13,8 @@ type StateUpdater = { setCallState: (state: CallState) => void; setCallerNumber: (number: string | null) => void; setCallUcid: (ucid: string | null) => void; + setIsMuted: (muted: boolean) => void; + setIsOnHold: (onHold: boolean) => void; }; let stateUpdater: StateUpdater | null = null; @@ -83,7 +85,13 @@ export function connectSip(config: SIPConfig): void { if (ucid) stateUpdater?.setCallUcid(ucid); if (state === 'ended' || state === 'failed') { - sipClient?.unmute(); // clear any mute state so it doesn't persist to next call + // Reset both the SIP track AND the Recoil state — otherwise the + // UI icon + toggle-mute branch logic stay "muted" and the next + // call opens in a confusing half-muted state. + sipClient?.unmute(); + sipClient?.unhold(); + stateUpdater?.setIsMuted(false); + stateUpdater?.setIsOnHold(false); outboundActive = false; outboundPending = false; }