Files
temetro/.agents/skills/ai-elements/references/persona.md
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

167 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Persona
An animated AI visual component powered by Rive that responds to different states like listening, thinking, and speaking.
The `Persona` component displays an animated AI visual that responds to different conversational states. Built with Rive WebGL2, it provides smooth, high-performance animations for various AI interaction states including idle, listening, thinking, speaking, and asleep. The component supports multiple visual variants to match different design aesthetics.
See `scripts/persona-obsidian.tsx` for this example.
## Installation
```bash
npx ai-elements@latest add persona
```
## Features
- Smooth state-based animations powered by Rive
- Multiple visual variants (obsidian, mana, opal, halo, glint, command)
- Responsive to five distinct states: idle, listening, thinking, speaking, and asleep
- WebGL2-accelerated rendering for optimal performance
- Customizable size and styling
- Lifecycle callbacks for load, ready, pause, play, and stop events
- TypeScript support with full type definitions
## Variants
The Persona component comes with 6 distinct visual variants, each with its own unique aesthetic:
### Obsidian (Default)
See `scripts/persona-obsidian.tsx` for this example.
### Mana
See `scripts/persona-mana.tsx` for this example.
### Opal
See `scripts/persona-opal.tsx` for this example.
### Halo
See `scripts/persona-halo.tsx` for this example.
### Glint
See `scripts/persona-glint.tsx` for this example.
### Command
See `scripts/persona-command.tsx` for this example.
## Props
### `<Persona />`
The root component that renders the animated AI visual.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `state` | `unknown` | - | The current state of the AI persona. Controls which animation is displayed. |
| `variant` | `unknown` | - | The visual style variant to display. |
| `className` | `string` | - | Additional CSS classes to apply to the component. |
| `onLoad` | `RiveParameters[` | - | Callback fired when the Rive file starts loading. |
| `onLoadError` | `RiveParameters[` | - | Callback fired if the Rive file fails to load. |
| `onReady` | `() => void` | - | Callback fired when the Rive animation is ready to play. |
| `onPause` | `RiveParameters[` | - | Callback fired when the animation is paused. |
| `onPlay` | `RiveParameters[` | - | Callback fired when the animation starts playing. |
| `onStop` | `RiveParameters[` | - | Callback fired when the animation is stopped. |
## States
The Persona component responds to five distinct states, each triggering different animations:
- **idle**: The default resting state when the AI is not active
- **listening**: Displayed when the AI is actively listening to user input (e.g., during voice recording)
- **thinking**: Shown when the AI is processing or generating a response
- **speaking**: Active when the AI is delivering a response (e.g., text-to-speech output)
- **asleep**: A dormant state for when the AI is inactive or in low-power mode
## React Strict Mode (Vite)
The Persona component uses WebGL2 for rendering. Browsers limit the number of active WebGL2 contexts (~816), and React Strict Mode (enabled by default in Vite dev) double-mounts components, which can exhaust that limit and crash the page.
The component includes a built-in guard that defers WebGL2 initialization by one frame, preventing context creation during Strict Mode's throw-away mount. This means the component works in Vite dev mode out of the box — no configuration needed.
If you still experience crashes (for example, when rendering many Persona instances simultaneously), reduce the number of concurrent Persona components on screen.
## Usage Examples
### Basic Usage
```tsx
import { Persona } from "@repo/elements/persona";
export default function App() {
return <Persona state="listening" variant="opal" />;
}
```
### With State Management
```tsx
import { Persona } from "@repo/elements/persona";
import { useState } from "react";
export default function App() {
const [state, setState] = useState<
"idle" | "listening" | "thinking" | "speaking" | "asleep"
>("idle");
const startListening = () => setState("listening");
const startThinking = () => setState("thinking");
const startSpeaking = () => setState("speaking");
const reset = () => setState("idle");
return (
<div>
<Persona state={state} variant="opal" className="size-32" />
<div>
<button onClick={startListening}>Listen</button>
<button onClick={startThinking}>Think</button>
<button onClick={startSpeaking}>Speak</button>
<button onClick={reset}>Reset</button>
</div>
</div>
);
}
```
### With Custom Styling
```tsx
import { Persona } from "@repo/elements/persona";
export default function App() {
return (
<Persona
state="thinking"
variant="halo"
className="size-64 rounded-full border border-border"
/>
);
}
```
### With Lifecycle Callbacks
```tsx
import { Persona } from "@repo/elements/persona";
export default function App() {
return (
<Persona
state="listening"
variant="glint"
onReady={() => console.log("Animation ready")}
onLoad={() => console.log("Starting to load")}
onLoadError={(error) => console.error("Failed to load:", error)}
onPlay={() => console.log("Animation playing")}
onPause={() => console.log("Animation paused")}
onStop={() => console.log("Animation stopped")}
/>
);
}
```