"use client"; import { nanoid } from "nanoid"; import type { ChatStatus } from "ai"; import { useSearchParams } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Conversation, ConversationContent, ConversationScrollButton, } from "@/components/ai-elements/conversation"; import { Message, MessageContent, MessageResponse, } from "@/components/ai-elements/message"; import { ChatInput } from "@/components/chat/chat-input"; import { PatientResult } from "@/components/chat/patient-cards"; import { getPatient, type Patient } from "@/lib/patients"; type ChatMessage = | { id: string; role: "user"; text: string } | { id: string; role: "assistant"; kind: "text"; text: string } | { id: string; role: "assistant"; kind: "patient"; fileNumber: string; status: "loading" | "ready" | "not-found"; patient?: Patient; }; // Trigger: `/patient 10293` or just `/10293` pulls up records. const PATIENT_COMMAND = /^\/(?:patient\s+)?(\d+)$/i; export function ChatPanel() { const { t } = useTranslation(); const [messages, setMessages] = useState([]); const [status, setStatus] = useState("ready"); const send = useCallback(async (text: string) => { const trimmed = text.trim(); if (!trimmed) { return; } setMessages((prev) => [ ...prev, { id: nanoid(), role: "user", text: trimmed }, ]); const match = trimmed.match(PATIENT_COMMAND); // Patient lookup: append a loading card set, then fill it in once the // (mock) record comes back. if (match) { const fileNumber = match[1]; const resultId = nanoid(); setStatus("submitted"); setMessages((prev) => [ ...prev, { id: resultId, role: "assistant", kind: "patient", fileNumber, status: "loading", }, ]); let patient: Patient | null = null; try { patient = await getPatient(fileNumber); } catch { // Network / auth errors fall through as "not found"; a 401 will have // already redirected to /login via the API client. patient = null; } setMessages((prev) => prev.map((message) => message.id === resultId && message.role === "assistant" && message.kind === "patient" ? { ...message, status: patient ? "ready" : "not-found", patient: patient ?? undefined, } : message ) ); setStatus("ready"); return; } // UI-only: mock assistant reply for anything that isn't a command. setStatus("submitted"); window.setTimeout(() => { setStatus("streaming"); setMessages((prev) => [ ...prev, { id: nanoid(), role: "assistant", kind: "text", text: `This is a **UI-only preview** of temetro. Try \`/patient 10293\` to pull up a patient's records.\n\nYou asked:\n\n> ${trimmed}`, }, ]); window.setTimeout(() => setStatus("ready"), 250); }, 500); }, []); const handleStop = useCallback(() => setStatus("ready"), []); // Opening a patient from the Patients page lands here as `/?patient=`; // run the lookup once on arrival. const searchParams = useSearchParams(); const requestedPatient = searchParams.get("patient"); const handledPatientRef = useRef(null); useEffect(() => { if (requestedPatient && handledPatientRef.current !== requestedPatient) { handledPatientRef.current = requestedPatient; send(`/patient ${requestedPatient}`); } }, [requestedPatient, send]); const promptInput = ( ); if (messages.length === 0) { return (

{t("chat.heading")}

{promptInput}
); } return (
{messages.map((message) => { if (message.role === "assistant" && message.kind === "patient") { return ( setMessages((prev) => prev.map((m) => m.id === message.id && m.role === "assistant" && m.kind === "patient" ? { ...m, patient: updated, status: "ready" } : m ) ) } patient={message.patient} status={message.status} /> ); } return ( {message.role === "user" ? ( {message.text} ) : ( {message.text} )} ); })}
{promptInput}
); }