Files
temetro/.agents/skills/ai-elements/scripts/persona-obsidian.tsx
T
Khalid Abdi 929bec8f31 feat: AI-added records save with placeholders + "Added by AI" provenance
Stop blocking AI imports/proposals on missing non-critical fields. Records
the chat agent drafts now save with safe placeholders, auto-generated file
numbers, and a source="ai" marker that surfaces an "Added by AI" badge so a
clinician can review/edit them later.

Backend:
- add `source` (manual|ai) column to patients/appointments/prescriptions
  (migration 0014) + canonical types, services, validation schemas
- relax patient/appointment validation: empty file number allowed, demographic
  + type/provider/initials fall back to placeholders (initials derived from name)
- patients.generateFileNumber() auto-assigns an MRN when one is missing
- proposeAppointment accepts a name when no file number resolves; AI commits +
  /api/ai/import stamp source="ai"

Frontend:
- `source` on Appointment/Patient/Prescription types; AI commits send source="ai"
- reusable <AiBadge> shown on the Patients table/detail and prescriptions list

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:27:17 +03:00

111 lines
2.5 KiB
TypeScript

"use client";
import type { PersonaState } from "@/components/ai-elements/persona";
import { Persona } from "@/components/ai-elements/persona";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import type { LucideIcon } from "lucide-react";
import {
BrainIcon,
CircleIcon,
EyeClosedIcon,
MegaphoneIcon,
MicIcon,
} from "lucide-react";
import { memo, useCallback, useState } from "react";
const states: {
state: PersonaState;
icon: LucideIcon;
label: string;
}[] = [
{
icon: CircleIcon,
label: "Idle",
state: "idle",
},
{
icon: MicIcon,
label: "Listening",
state: "listening",
},
{
icon: BrainIcon,
label: "Thinking",
state: "thinking",
},
{
icon: MegaphoneIcon,
label: "Speaking",
state: "speaking",
},
{
icon: EyeClosedIcon,
label: "Asleep",
state: "asleep",
},
];
interface StateButtonProps {
state: (typeof states)[0];
currentState: PersonaState;
onStateChange: (state: PersonaState) => void;
}
const StateButton = memo(
({ state, currentState, onStateChange }: StateButtonProps) => {
const handleClick = useCallback(
() => onStateChange(state.state),
[onStateChange, state.state]
);
return (
<Tooltip key={state.state}>
<TooltipTrigger asChild>
<Button
onClick={handleClick}
size="icon-sm"
variant={currentState === state.state ? "default" : "outline"}
>
<state.icon className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{state.label}</TooltipContent>
</Tooltip>
);
}
);
StateButton.displayName = "StateButton";
const Example = () => {
const [currentState, setCurrentState] = useState<PersonaState>("idle");
const handleStateChange = useCallback((state: PersonaState) => {
setCurrentState(state);
}, []);
return (
<div className="flex size-full flex-col items-center justify-center gap-4">
<Persona className="size-32" state={currentState} variant="obsidian" />
<ButtonGroup orientation="horizontal">
{states.map((state) => (
<StateButton
currentState={currentState}
key={state.state}
onStateChange={handleStateChange}
state={state}
/>
))}
</ButtonGroup>
</div>
);
};
export default Example;