mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
929bec8f31
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>
167 lines
5.4 KiB
Markdown
167 lines
5.4 KiB
Markdown
# 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 (~8–16), 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")}
|
||
/>
|
||
);
|
||
}
|
||
```
|