Normalized for Mintlify from
knowledge-base/neurigraph-memory-architecture/neurigraph-tool-references/07-AI-Chat-UI-Component-Library.mdx.Clean-Room Specification: AI Chat UI Component Library
Purpose of This Document
This document specifies the architecture, component hierarchy, runtime system, and implementation patterns for a headless React component library purpose-built for conversational AI interfaces. The library provides 20+ unstyled composable primitives organized around threads, messages, composers, and branching — with a protocol-driven runtime that abstracts over multiple AI provider backends. This specification enables full independent implementation from scratch.1. Architecture Overview
1.1 Three-Layer Architecture
The system is organized into three distinct layers:<div>, <p>, <form>, <button>) and rely entirely on the consumer to provide CSS/Tailwind classes. Each primitive is a namespace object containing sub-components (e.g., ThreadPrimitive.Root, ThreadPrimitive.Messages).
React Bindings: Hooks and context providers that bridge the runtime layer to React’s rendering model. These use a custom reactivity system (not React state) to minimize re-renders.
Core Runtime: Pure TypeScript classes that manage conversation state, message trees, streaming, tool execution, and provider communication. Provider-agnostic — adapters translate between specific AI SDK protocols and the internal representation.
1.2 Package Structure
The library is organized as a monorepo with these key packages:2. Core Runtime System
2.1 AssistantRuntime
The root runtime that owns the entire conversation state tree.2.2 ThreadRuntime
Manages a single conversation thread with message tree, branching, and streaming.2.3 Message Tree (Branching Model)
Messages are stored in a tree structure that supports branching (editing a user message creates a new branch, preserving the old one):getMessages() always returns a linear sequence — the currently active path through the tree. Switching branches changes which path is active but preserves all other branches.
2.4 ThreadMessage Types
2.5 Content Part Types (9 Types)
2.6 Message Status
2.7 Streaming Protocol
The runtime consumes a stream of chunks that incrementally build the assistant message:
Stream consumption: The runtime maintains a mutable draft message. Each chunk mutates the draft in place, then notifies subscribers. On stream completion (when
status chunk arrives or stream closes), the draft is finalized into an immutable ThreadMessage and added to the repository.
3. Tap-Based Micro-Reactivity System
3.1 Problem Statement
Standard React state management (useState/useReducer) causes entire component subtrees to re-render when any state changes. For chat UIs with hundreds of messages, each containing multiple content parts that stream character-by-character, this creates catastrophic re-rendering.3.2 Solution: Tap Subscriptions
The library implements a tap-based reactivity system that bypasses React’s state model entirely. Runtime objects are observable stores. React hooks subscribe to specific slices of state and only trigger re-renders when those specific slices change.3.3 useAui / useAuiState / useAuiEvent Hooks
useAuiState(useThreadRuntime, t => t.isRunning) only causes a re-render when isRunning transitions between true/false, even though the thread’s messages, status, and other properties may be changing constantly during streaming.
4. React Primitives
4.1 ThreadPrimitive
Namespace object for thread-level components.4.2 MessagePrimitive
4.3 ComposerPrimitive
4.4 BranchPickerPrimitive
Allows navigating between message branches (when user edits a message, creating alternate conversation paths):4.5 ActionBarPrimitive
Context-sensitive action buttons for messages:4.6 AttachmentPrimitive
4.7 Conditional Rendering with If Components
Every primitive namespace includes an If component for declarative conditional rendering:
5. Context System
5.1 Context Hierarchy
Contexts nest to provide scoped access to runtime instances:5.2 Context Hooks
5.3 AssistantRuntimeProvider
Top-level provider that wraps the entire chat UI:6. Vercel AI SDK Adapter
6.1 Bridge Hook
The primary adapter bridges Vercel AI SDK’suseChat hook to the runtime system:
6.2 Message Format Conversion
7. Tool Execution and Approval Flow
7.1 Tool Definition
7.2 Tool Approval (Human-in-the-Loop)
When a tool call requires human approval before execution:- UI renders approval component — detected by checking
message.status.type === "requires-action" - User approves/rejects → calls
threadRuntime.addToolResult({ toolCallId, result })where result is either the execution output or an error/rejection message - Runtime updates the ToolCallContentPart with the result and continues
8. Attachment System
8.1 Attachment Adapters
Attachments are handled via an adapter pattern that supports different upload strategies:8.2 Built-in Adapters
9. Markdown Rendering
9.1 MarkdownText Component
The@assistant-ui/react-markdown package provides a component that renders assistant text content as rich markdown:
9.2 Smooth Streaming Animation
Whensmooth is enabled, text doesn’t appear all at once — instead characters are revealed progressively:
10. Tailwind CSS Integration
10.1 aui-* Variant Selectors
The Tailwind plugin provides custom variants that map to component states:
Usage example:
11. Speech (Text-to-Speech) Integration
12. Thread Management and Persistence
12.1 Multi-Thread Support
12.2 External Store Adapter
For persisting threads to a backend:13. Putting It All Together — Full Composition Example
14. Behavioral Test Cases
Thread Operations
- Empty thread renders empty state: When messages array is empty,
ThreadPrimitive.Emptychildren are rendered,ThreadPrimitive.Messagesrenders nothing. - Message ordering: Messages render in the order returned by
MessageRepository.getMessages()(linear active path). - Auto-scroll on new content: When user is scrolled to bottom and new streaming text arrives, viewport scrolls to keep bottom visible.
- Auto-scroll disengage: When user scrolls up manually, auto-scroll stops. New messages do NOT force scroll.
- Auto-scroll re-engage: When user scrolls back to bottom, auto-scroll re-engages for subsequent messages.
Message Branching
- Edit creates branch: Editing a user message creates a new child of the same parent, preserving the original branch.
- Branch navigation:
BranchPickerPrimitive.Previous/Nextcycle through sibling branches at the branching point. - Branch count accuracy:
BranchPickerPrimitive.Countshows total siblings,Numbershows 1-indexed current. - Branch isolation: Switching branches replaces all messages after the branching point with the alternate path.
- Nested branches: Branches can exist at multiple depths — each operates independently.
Streaming
- Text delta accumulation: Multiple
text-deltachunks for the same part index concatenate correctly. - Tool call streaming:
tool-call-beginfollowed bytool-call-deltachunks produces incrementally parsed args. - Mixed content streaming: Text, tool calls, and reasoning parts can arrive interleaved — each routed to correct part index.
- Stream cancellation:
ComposerPrimitive.CancelcallsthreadRuntime.cancelRun(), which aborts the stream and sets status toincomplete/cancelled. - Status finalization: Stream ending with
statuschunk finalizes the message; without it, status defaults tocomplete/unknown.
Composer
- Submit on Enter: Pressing Enter (without Shift) triggers form submission when text is non-empty.
- Newline on Shift+Enter: Shift+Enter inserts a newline without submitting.
- Disabled while running: Send button is disabled when
thread.isRunning === true. - Auto-resize: Textarea height grows with content up to max-height, then scrolls internally.
- Attachment flow: Adding file creates PendingAttachment → displayed in composer → on send, adapter.send() converts to CompleteAttachment.
Tool Execution
- Tool approval flow: Message with status
requires-actionrenders approval UI;addToolResultresolves it and continues generation. - Tool rejection: Calling
addToolResultwithisError: truesends rejection to model. - Custom tool renderers:
by_namecomponent map renders specific components for named tools. - Fallback tool renderer: Unknown tool names render with the
Fallbackcomponent.
Reactivity
- Selective re-rendering: Changing
thread.isRunningdoes NOT re-render message components that only subscribe to message content. - Streaming efficiency: During text streaming, only the active TextContentPart component re-renders — not sibling parts or other messages.
- useAuiState equality: Custom equality functions prevent re-renders when selector output is structurally identical.
Action Bar
- Copy to clipboard:
ActionBarPrimitive.Copyextracts all text content parts, joins them, copies to clipboard via navigator.clipboard.writeText. - Autohide behavior: With
autohide="not-last", only the last message’s action bar is visible; others appear on hover. - Reload regenerates:
ActionBarPrimitive.Reloadremoves the assistant message and callsstartRunto regenerate. - Feedback submission: Positive/Negative feedback updates
message.metadata.feedbackand emits event.
Markdown Rendering
- GFM support: Tables, strikethrough, task lists render correctly via remark-gfm.
- Code highlighting: Fenced code blocks with language annotation get syntax highlighting.
- LaTeX rendering: Inline
$...$and display$$...$$render as mathematical notation. - Smooth streaming: With
smoothenabled, text reveals character-by-character via requestAnimationFrame. - Link safety: External links render with
target="_blank" rel="noopener".
Attachments
- Image preview: Image attachments show thumbnail in composer before sending.
- Text file reading:
.txt,.md,.jsonfiles are read as text and included in message content. - Remove before send: Clicking remove on a pending attachment calls adapter.remove() and removes from composer.
- Accept filter: File picker only shows files matching adapter’s accept filter.
Thread Persistence
- Export/Import roundtrip:
thread.export()produces a serializable repository;thread.import()restores the full tree structure including branches. - Thread switching:
switchToThread(id)loads messages from external store and sets as active. - New thread creation:
switchToNewThread()creates an empty thread and sets as active.
Provider Adapter
- Vercel message conversion: Vercel AI SDK messages with toolInvocations correctly convert to ToolCallContentParts.
- Vercel stream mapping: Vercel’s streaming protocol chunks map to internal AssistantStreamChunks.
- Bidirectional sync: Adding a message via the runtime is reflected back to Vercel’s useChat state.