mirror of
https://github.com/temetro/temetro.git
synced 2026-08-30 12:19:07 +00:00
feat: live AI chat (thinking + inline Veil) and display/add actions
Make the chat feel alive and let the agent act on the clinic — safely. UX (frontend): - Stream `data-step` parts from each tool into an inline Chain-of-Thought trace, plus a "Thinking…" shimmer while a request is in flight (works even on the non-streamed external+Veil path). - Replace the modal Veil consent Dialog with an inline, once-per-session "Veil" confirmation above the input (with a "Use local model" option). - Render new list + action-preview cards; chat-input now uses COSS tokens. Agent (backend): - Add display tools (listAppointments / listTasks / listPrescriptions) and propose tools (proposeAppointment / proposeTask / proposePrescription) that validate as a dry run and stream an approval card — nothing is written until the clinician approves, via the existing RBAC-gated create endpoints. - previewImport now also covers single-patient add + migration. - System prompt: display + add only, never edit/delete or alter the schema; stronger migration guidance. ToolContext carries the viewer for task scoping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import type { UIMessage } from "ai";
|
||||
|
||||
import type { Appointment } from "@/lib/appointments";
|
||||
import type { Lab, Patient, Trend } from "@/lib/patients";
|
||||
import type { Prescription } from "@/lib/prescriptions";
|
||||
import type { Task } from "@/lib/tasks";
|
||||
|
||||
// Custom data parts the backend agent streams alongside its text. They carry
|
||||
// REAL (un-redacted) record data straight to the clinician's screen for rich
|
||||
@@ -25,6 +28,28 @@ export type VeilNoticeData = {
|
||||
level: string;
|
||||
};
|
||||
|
||||
// A Chain-of-Thought step the agent emits as it works (one per tool action).
|
||||
export type StepData = {
|
||||
id: string;
|
||||
label: string;
|
||||
status: "active" | "complete";
|
||||
};
|
||||
|
||||
// Read-only list cards (display actions).
|
||||
export type AppointmentListData = { appointments: Appointment[] };
|
||||
export type TaskListData = { tasks: Task[] };
|
||||
export type PrescriptionListData = { prescriptions: Prescription[] };
|
||||
|
||||
// An add proposed by the agent, awaiting one-click clinician approval. `record`
|
||||
// is the validated, ready-to-commit input for the matching create endpoint.
|
||||
export type ActionPreviewKind = "appointment" | "task" | "prescription";
|
||||
export type ActionPreviewData = {
|
||||
token: string;
|
||||
kind: ActionPreviewKind;
|
||||
record: unknown;
|
||||
issues?: string[];
|
||||
};
|
||||
|
||||
// Maps each data part name → its payload. Part `type` strings are the key
|
||||
// prefixed with `data-` (e.g. `data-patientCard`), per the AI SDK convention.
|
||||
export type TemetroDataParts = {
|
||||
@@ -32,6 +57,11 @@ export type TemetroDataParts = {
|
||||
labCard: LabCardData;
|
||||
importPreview: ImportPreviewData;
|
||||
veilNotice: VeilNoticeData;
|
||||
step: StepData;
|
||||
appointmentList: AppointmentListData;
|
||||
taskList: TaskListData;
|
||||
prescriptionList: PrescriptionListData;
|
||||
actionPreview: ActionPreviewData;
|
||||
};
|
||||
|
||||
export type TemetroUIMessage = UIMessage<never, TemetroDataParts>;
|
||||
|
||||
@@ -361,6 +361,31 @@
|
||||
"in-stock": "In stock",
|
||||
"low": "Low stock",
|
||||
"out": "Out of stock"
|
||||
},
|
||||
"addItem": "Add item",
|
||||
"dialog": {
|
||||
"title": "Add inventory item",
|
||||
"description": "Add a medication to the pharmacy's stock. Only the name is required.",
|
||||
"name": "Medication",
|
||||
"namePlaceholder": "e.g. Amoxicillin",
|
||||
"form": "Form",
|
||||
"formPlaceholder": "e.g. Tablet",
|
||||
"strength": "Strength",
|
||||
"strengthPlaceholder": "e.g. 500mg",
|
||||
"stock": "In stock",
|
||||
"unit": "Unit",
|
||||
"unitPlaceholder": "e.g. tablets",
|
||||
"reorder": "Reorder at",
|
||||
"location": "Location",
|
||||
"locationPlaceholder": "e.g. A3",
|
||||
"expires": "Expires",
|
||||
"cancel": "Cancel",
|
||||
"submit": "Add item",
|
||||
"nameRequiredTitle": "Medication name required",
|
||||
"nameRequiredBody": "Enter a medication name before adding the item.",
|
||||
"addedTitle": "Item added",
|
||||
"failedTitle": "Could not add item",
|
||||
"failedBody": "Something went wrong. Please try again."
|
||||
}
|
||||
},
|
||||
"lab": {
|
||||
@@ -643,12 +668,44 @@
|
||||
}
|
||||
},
|
||||
"patientNotFound": "I couldn't find a patient with file number {{fileNumber}}.",
|
||||
"consent": {
|
||||
"title": "Send to external AI provider?",
|
||||
"body": "This message will be processed by {{provider}}, an external provider.",
|
||||
"veilNote": "Veil de-identifies patient information (names, MRNs, providers) before it leaves your infrastructure, and restores it in the answer. Local Ollama models avoid this entirely.",
|
||||
"thinking": "Thinking…",
|
||||
"steps": "Steps",
|
||||
"veil": {
|
||||
"title": "Veil",
|
||||
"body": "This message will be processed by {{provider}}, an external provider. Veil de-identifies patient information (names, MRNs, providers) before it leaves your infrastructure, and restores it in the answer.",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "De-identify & send"
|
||||
"useLocal": "Use local model",
|
||||
"confirm": "De-identify & send",
|
||||
"activeChip": "Veil active · {{provider}}"
|
||||
},
|
||||
"actionCard": {
|
||||
"title": {
|
||||
"appointment": "Proposed appointment",
|
||||
"task": "Proposed task",
|
||||
"prescription": "Proposed prescription"
|
||||
},
|
||||
"kind": {
|
||||
"appointment": "Appointment added.",
|
||||
"task": "Task added.",
|
||||
"prescription": "Prescription added."
|
||||
},
|
||||
"approve": "Add",
|
||||
"adding": "Adding…",
|
||||
"discard": "Discard",
|
||||
"added": "Added.",
|
||||
"discarded": "Discarded — nothing was saved.",
|
||||
"addedTitle": "Added",
|
||||
"failedTitle": "Could not add",
|
||||
"failedBody": "Something went wrong, or you don't have permission. Please try again."
|
||||
},
|
||||
"lists": {
|
||||
"appointments": "Appointments",
|
||||
"tasks": "Tasks",
|
||||
"prescriptions": "Prescriptions",
|
||||
"done": "Done",
|
||||
"noAppointments": "No appointments.",
|
||||
"noTasks": "No tasks.",
|
||||
"noPrescriptions": "No prescriptions."
|
||||
},
|
||||
"labCard": {
|
||||
"flags": {
|
||||
|
||||
Reference in New Issue
Block a user