feat(cortex): update chat/nudge flow, deepgram streaming and agenda form
Refines chat panel, nudge messages and artifact rendering, reworks deepgram token/streaming handling, and adds test tooling deps (playwright, cypress) to package.json. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ import { FallbackPicker } from '../blocks/fallback-picker';
|
||||
import { IntakeStatusBlock } from '../blocks/intake-status-block';
|
||||
import { RisicoBlock } from '../blocks/risico-block';
|
||||
import { DiagnoseBlock } from '../blocks/diagnose-block';
|
||||
// No Show casus
|
||||
import { NoShowDocumentBlock } from '../blocks/noshow-document-block';
|
||||
import { APPOINTMENT_TYPES, type AppointmentTypeCode, type LocationClassCode } from '@/app/epd/agenda/types';
|
||||
import type { Artifact, BlockType } from '@/stores/cortex-store';
|
||||
import { parseRelativeDate, isDateRange } from '@/lib/cortex/date-time-parser';
|
||||
@@ -205,6 +207,17 @@ function renderArtifactBlock(artifact: Artifact, onCloseArtifact: (id: string) =
|
||||
return <RisicoBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||
case 'diagnose_query':
|
||||
return <DiagnoseBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||
// No Show casus
|
||||
case 'register_no_show': {
|
||||
const nsPrefill = artifact.prefill as {
|
||||
documentId: string;
|
||||
content: string;
|
||||
title: string;
|
||||
originalContent?: string;
|
||||
rescriptWarning?: string;
|
||||
};
|
||||
return <NoShowDocumentBlock key={artifact.id} prefill={nsPrefill} />;
|
||||
}
|
||||
default:
|
||||
return (
|
||||
<div className="p-4 text-slate-500">
|
||||
@@ -250,6 +263,9 @@ export function getArtifactTitle(type: BlockType, prefill?: any): string {
|
||||
return 'Risicotaxatie';
|
||||
case 'diagnose_query':
|
||||
return 'Diagnoses';
|
||||
// No Show casus
|
||||
case 'register_no_show':
|
||||
return prefill?.title ? `Brief — ${prefill.title}` : 'Huisartsbrief';
|
||||
default:
|
||||
return 'Artifact';
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { ToastAction } from '@/components/ui/toast';
|
||||
import { createEncounter } from '@/app/epd/agenda/actions';
|
||||
import { useCortexStore } from '@/stores/cortex-store';
|
||||
import { formatPatientName as formatPatientNameFromDb } from '@/lib/fhir/patient-mapper';
|
||||
import {
|
||||
APPOINTMENT_TYPES,
|
||||
LOCATION_CLASSES,
|
||||
@@ -47,7 +49,12 @@ interface PatientResult {
|
||||
birthDate?: string;
|
||||
}
|
||||
|
||||
function normalizePatientName(name: string) {
|
||||
return name.toLowerCase().trim().replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps) {
|
||||
const activePatient = useCortexStore((s) => s.activePatient);
|
||||
// Form State
|
||||
const [patientId, setPatientId] = useState<string>(prefillData?.patient?.id || '');
|
||||
const [patientName, setPatientName] = useState<string>(prefillData?.patient?.name || '');
|
||||
@@ -69,6 +76,7 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [showResults, setShowResults] = useState(false);
|
||||
const searchRef = useRef<HTMLDivElement>(null);
|
||||
const autoResolvedPatientRef = useRef<string | null>(null);
|
||||
|
||||
// Initialize search query if patient is prefilled but we want to allow editing
|
||||
useEffect(() => {
|
||||
@@ -77,6 +85,69 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
}
|
||||
}, [prefillData]);
|
||||
|
||||
// If Cortex only extracted a name, resolve it once so the appointment form
|
||||
// can submit without forcing the user to retype and select the same client.
|
||||
useEffect(() => {
|
||||
const prefilledPatient = prefillData?.patient;
|
||||
if (!prefilledPatient?.name || prefilledPatient.id || patientId) return;
|
||||
|
||||
const normalizedPrefill = normalizePatientName(prefilledPatient.name);
|
||||
if (autoResolvedPatientRef.current === normalizedPrefill) return;
|
||||
|
||||
autoResolvedPatientRef.current = normalizedPrefill;
|
||||
|
||||
if (activePatient) {
|
||||
const activePatientName = formatPatientNameFromDb(activePatient);
|
||||
if (normalizePatientName(activePatientName) === normalizedPrefill) {
|
||||
setPatientId(activePatient.id);
|
||||
setPatientName(activePatientName);
|
||||
setSearchQuery(activePatientName);
|
||||
setShowResults(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
async function resolvePrefilledPatient() {
|
||||
setIsSearching(true);
|
||||
try {
|
||||
const res = await fetch(`/api/cortex/patients/search?q=${encodeURIComponent(prefilledPatient.name)}`);
|
||||
if (!res.ok || cancelled) return;
|
||||
|
||||
const data = await res.json();
|
||||
const patients = (data.patients || []) as PatientResult[];
|
||||
const exactMatch = patients.find(
|
||||
(patient) => normalizePatientName(patient.name) === normalizedPrefill
|
||||
);
|
||||
const match = exactMatch || (patients.length === 1 ? patients[0] : null);
|
||||
|
||||
if (match) {
|
||||
setPatientId(match.id);
|
||||
setPatientName(match.name);
|
||||
setSearchQuery(match.name);
|
||||
setSearchResults([]);
|
||||
setShowResults(false);
|
||||
} else {
|
||||
setSearchResults(patients);
|
||||
setShowResults(patients.length > 0);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to resolve prefilled patient', err);
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setIsSearching(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolvePrefilledPatient();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [prefillData, patientId, activePatient]);
|
||||
|
||||
// Handle outside click to close search results
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
|
||||
Reference in New Issue
Block a user