diff --git a/apps/website/content/docs/chat/a2ui/catalog.mdx b/apps/website/content/docs/chat/a2ui/catalog.mdx new file mode 100644 index 000000000..3a07571c2 --- /dev/null +++ b/apps/website/content/docs/chat/a2ui/catalog.mdx @@ -0,0 +1,281 @@ +# Component Catalog + +The built-in A2UI catalog provides 12 Angular components covering display, layout, and interactive controls. It is included automatically when using `ChatComponent`, and can be instantiated directly for custom rendering setups. + +**Import:** + +```typescript +import { a2uiBasicCatalog } from '@cacheplane/chat'; +``` + +## Signature + +```typescript +function a2uiBasicCatalog(): ViewRegistry +``` + +Returns a `ViewRegistry` mapping 12 A2UI type names to their Angular component implementations. Pass the result to `A2uiSurfaceComponent` or use it as a starting point for a custom registry. + +```typescript +const catalog = a2uiBasicCatalog(); +``` + +## Display Components + +### Text + +Renders a span of text. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Text` | `A2uiTextComponent` | `a2ui-text` | + +| Prop | Type | Description | +|------|------|-------------| +| `text` | `string` | The text content to display | + +```json +{"id": "greeting", "component": "Text", "text": "Hello, world!"} +``` + +### Image + +Renders an `` element. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Image` | `A2uiImageComponent` | `a2ui-image` | + +| Prop | Type | Description | +|------|------|-------------| +| `url` | `string` | Image source URL | +| `alt` | `string` | Alt text for accessibility | + +### Icon + +Renders an icon glyph as a text span. The `name` prop is displayed directly — use an emoji or a Unicode symbol. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Icon` | `A2uiIconComponent` | `a2ui-icon` | + +| Prop | Type | Description | +|------|------|-------------| +| `name` | `string` | Glyph to render (e.g., `"✓"`, `"⚠️"`) | + +### Divider + +Renders a horizontal rule. Has no props. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Divider` | `A2uiDividerComponent` | `a2ui-divider` | + +## Layout Components + +Layout components receive `childKeys` — an array of component IDs — and render each child via json-render's `RenderElementComponent`. They also receive the full `spec` so that child elements can be looked up by key. + +### Row + +Arranges children horizontally with a flex row layout. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Row` | `A2uiRowComponent` | `a2ui-row` | + +| Prop | Type | Description | +|------|------|-------------| +| `childKeys` | `string[]` | Ordered list of child component IDs | +| `spec` | `Spec` | Injected automatically by the render engine | + +### Column + +Arranges children vertically with a flex column layout. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Column` | `A2uiColumnComponent` | `a2ui-column` | + +| Prop | Type | Description | +|------|------|-------------| +| `childKeys` | `string[]` | Ordered list of child component IDs | +| `spec` | `Spec` | Injected automatically by the render engine | + +### Card + +Renders children inside a rounded bordered card container with an optional title. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Card` | `A2uiCardComponent` | `a2ui-card` | + +| Prop | Type | Description | +|------|------|-------------| +| `title` | `string` | Optional card heading | +| `childKeys` | `string[]` | Ordered list of child component IDs | +| `spec` | `Spec` | Injected automatically by the render engine | + +### List + +Renders children in a scrollable vertical list (max height 24rem). + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `List` | `A2uiListComponent` | `a2ui-list` | + +| Prop | Type | Description | +|------|------|-------------| +| `childKeys` | `string[]` | Ordered list of child component IDs | +| `spec` | `Spec` | Injected automatically by the render engine | + + +For data-driven lists, use the `A2uiChildTemplate` form instead of static `childKeys`. Set `children` to `{"path": "/items", "componentId": "item-template"}` and the surface component will expand the template once per array item. See the [Surface Component](/docs/chat/a2ui/surface-component) page for details. + + +## Interactive Components + +Interactive components support **two-way data binding** and **button actions**. They use two special props: + +- `_bindings` — a `Record` mapping prop names to JSON Pointer paths in the data model. When the user changes the value, the component emits an `a2ui:datamodel:` event to update the model. +- `emit` — injected by the render engine; components call it to dispatch events back to the chat. + +### Button + +Renders a button that dispatches an action when clicked. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `Button` | `A2uiButtonComponent` | `a2ui-button` | + +| Prop | Type | Description | +|------|------|-------------| +| `label` | `string` | Button label text | +| `variant` | `'primary' \| 'borderless'` | Visual style. Defaults to `'primary'` | +| `disabled` | `boolean` | Disables the button when `true` | +| `action` | `A2uiAction` | Action to execute on click (event or function call) | +| `checks` | `A2uiCheck[]` | Validation checks — button is disabled if any fail | +| `emit` | injected | Event emitter provided by the render engine | + +**Action types:** + +```json +// Emit a named event (sent back to the agent) +{"action": {"event": {"name": "submit", "context": {"formId": "contact"}}}} + +// Execute a local function (e.g., open a URL) +{"action": {"functionCall": {"call": "openUrl", "args": {"url": "https://example.com"}}}} +``` + +**Validation checks** reference built-in validators by name. If any check fails, the button is automatically disabled: + +```json +{"checks": [{"call": "required", "args": {"value": {"path": "/email"}}, "message": "Email is required"}]} +``` + +### TextField + +A single-line text input with optional label and placeholder. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `TextField` | `A2uiTextFieldComponent` | `a2ui-text-field` | + +| Prop | Type | Description | +|------|------|-------------| +| `label` | `string` | Input label | +| `value` | `string` | Current value (bind via `_bindings`) | +| `placeholder` | `string` | Placeholder text | +| `_bindings` | `Record` | Bind `value` to a data model path | +| `emit` | injected | Event emitter provided by the render engine | + +```json +{ + "id": "name-field", + "component": "TextField", + "label": "Your name", + "value": {"path": "/name"}, + "_bindings": {"value": "/name"} +} +``` + +### CheckBox + +A labeled checkbox with two-way binding for its checked state. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `CheckBox` | `A2uiCheckBoxComponent` | `a2ui-check-box` | + +| Prop | Type | Description | +|------|------|-------------| +| `label` | `string` | Checkbox label | +| `checked` | `boolean` | Current checked state (bind via `_bindings`) | +| `_bindings` | `Record` | Bind `checked` to a data model path | +| `emit` | injected | Event emitter provided by the render engine | + +### ChoicePicker + +A dropdown select control with a list of string options. + +| A2UI type | Angular component | Selector | +|-----------|-------------------|----------| +| `ChoicePicker` | `A2uiChoicePickerComponent` | `a2ui-choice-picker` | + +| Prop | Type | Description | +|------|------|-------------| +| `label` | `string` | Select label | +| `options` | `string[]` | List of available options | +| `selected` | `string` | Currently selected value (bind via `_bindings`) | +| `_bindings` | `Record` | Bind `selected` to a data model path | +| `emit` | injected | Event emitter provided by the render engine | + +## Built-in Functions + +Props that use `A2uiFunctionCall` can reference these built-in functions: + +| Function | Args | Description | +|----------|------|-------------| +| `formatNumber` | `value`, `precision`, `grouping` | Formats a number with optional decimal places and thousands grouping | +| `formatCurrency` | `value`, `locale`, `currency` | Formats a number as a currency string (default: `en-US` / `USD`) | +| `formatDate` | `value`, `locale` | Formats an ISO date string using `toLocaleDateString` | +| `pluralize` | `count`, `singular`, `plural` | Returns singular or plural form based on count | +| `openUrl` | `url` | Opens a URL in a new tab (also usable as a button action) | +| `and` | any named args | Returns `true` if all arg values are truthy | +| `or` | any named args | Returns `true` if any arg value is truthy | +| `not` | `value` | Negates a boolean value | + +## Component Summary + +| A2UI Type | Angular Component | Category | +|-----------|-------------------|----------| +| `Text` | `A2uiTextComponent` | Display | +| `Image` | `A2uiImageComponent` | Display | +| `Icon` | `A2uiIconComponent` | Display | +| `Divider` | `A2uiDividerComponent` | Display | +| `Row` | `A2uiRowComponent` | Layout | +| `Column` | `A2uiColumnComponent` | Layout | +| `Card` | `A2uiCardComponent` | Layout | +| `List` | `A2uiListComponent` | Layout | +| `Button` | `A2uiButtonComponent` | Interactive | +| `TextField` | `A2uiTextFieldComponent` | Interactive | +| `CheckBox` | `A2uiCheckBoxComponent` | Interactive | +| `ChoicePicker` | `A2uiChoicePickerComponent` | Interactive | + +## What's Next + + + + How the A2UI protocol works end-to-end. + + + Render surfaces and understand dynamic value resolution. + + diff --git a/apps/website/content/docs/chat/a2ui/overview.mdx b/apps/website/content/docs/chat/a2ui/overview.mdx new file mode 100644 index 000000000..0292dd3a3 --- /dev/null +++ b/apps/website/content/docs/chat/a2ui/overview.mdx @@ -0,0 +1,147 @@ +# A2UI Overview + +A2UI is an open standard for agent-driven user interfaces. It lets an AI agent describe a structured UI — components, layout, and live data — using a simple JSON protocol, and have that UI rendered automatically inside a chat session. + +The `ChatComponent` includes built-in A2UI support with no extra configuration. When an agent response begins with the `---a2ui_JSON---` sentinel, the chat switches to A2UI rendering mode automatically. + + +The implementation follows the A2UI v0.9 specification. For the full protocol reference, see [a2ui.org](https://a2ui.org). + + +## How It Works in ChatComponent + +When content arrives with the `---a2ui_JSON---` prefix, the streaming pipeline switches to JSONL mode. Each newline-delimited JSON object is parsed as an A2UI message and handed to the surface store. + +``` +AI response starts with ---a2ui_JSON--- + → ContentClassifier sets type = 'a2ui' + → createA2uiMessageParser() parses JSONL line-by-line + → Each A2uiMessage is applied to A2uiSurfaceStore + → Surfaces signal updates reactively + → A2uiSurfaceComponent renders each surface via json-render +``` + +The surface store maintains a `Map` keyed by surface ID. Each surface holds a flat component map, a data model, theme metadata, and the catalog ID used to resolve components. + +The `ChatComponent` provides the built-in `a2uiBasicCatalog` automatically — you do not need to configure a catalog to start using A2UI. + +## The Four Message Types + +The A2UI protocol is built on four message types. Agents compose entire interfaces by sending sequences of these messages. + +| Message | Purpose | +|---------|---------| +| `createSurface` | Creates a new named surface with a catalog ID and optional theme | +| `updateComponents` | Adds or replaces components on a surface (merged by component ID) | +| `updateDataModel` | Sets or patches a value in the surface data model via JSON Pointer | +| `deleteSurface` | Removes a surface and all its components | + +### createSurface + +Declares a new surface. Must be sent before any other message for that surface ID. + +```json +{"createSurface": {"surfaceId": "order-status", "catalogId": "basic"}} +``` + +### updateComponents + +Sends one or more component definitions. Each component has a unique `id`, a `component` type name, and any component-specific props. + +```json +{"updateComponents": { + "surfaceId": "order-status", + "components": [ + {"id": "root", "component": "Column", "children": ["title", "status"]}, + {"id": "title", "component": "Text", "text": "Your Order"}, + {"id": "status", "component": "Text", "text": {"path": "/state"}} + ] +}} +``` + +### updateDataModel + +Sets values at a JSON Pointer path in the surface's data model. Components whose props reference these paths re-render automatically. + +```json +{"updateDataModel": {"surfaceId": "order-status", "path": "/state", "value": "Shipped"}} +``` + +Omit `path` (or use `/`) to replace the entire data model at once. + +### deleteSurface + +Removes a surface from the store and dismisses its rendered output. + +```json +{"deleteSurface": {"surfaceId": "order-status"}} +``` + +## A2UI vs json-render + +Both A2UI and json-render produce rendered UIs inside chat messages, but they serve different purposes. + +| | A2UI | json-render | +|-|------|-------------| +| Protocol | Multi-message JSONL stream | Single JSON object | +| State | Surfaces with live data models | Stateless spec | +| Interactivity | Two-way bindings, button actions, validation | Read-only display | +| Detection | `---a2ui_JSON---` prefix | First character is `{` | +| Use case | Agent-driven dynamic UIs | Rich content cards | + +Use **A2UI** when the agent needs to build an interface incrementally, bind it to live data, or respond to user interactions. Use **json-render** for rich one-shot content cards — formatted results, structured data displays, and similar read-only output. + +## Quick Setup + +No configuration is required. Add `ChatComponent` to your template and A2UI content is detected and rendered automatically: + +```typescript +import { ChatComponent } from '@cacheplane/chat'; + +@Component({ + template: ``, + imports: [ChatComponent], +}) +export class AppComponent { + stream = /* your AI stream */; +} +``` + +When the AI response starts with `---a2ui_JSON---`, the chat renders the surfaces using the built-in `a2uiBasicCatalog`. No inputs to set, no catalog to register. + + +To use your own components or extend the built-in set, pass a `ViewRegistry` to the chat. See the [Catalog reference](/docs/chat/a2ui/catalog) for details. + + +## What's Next + + + + API reference for A2uiSurfaceComponent — render a surface outside ChatComponent. + + + API reference for createA2uiSurfaceStore() and the apply/surfaces/surface interface. + + + Reference for all 12 built-in components and their props. + + + How the streaming pipeline detects and routes A2UI content. + + diff --git a/apps/website/content/docs/chat/a2ui/surface-component.mdx b/apps/website/content/docs/chat/a2ui/surface-component.mdx new file mode 100644 index 000000000..187fecef7 --- /dev/null +++ b/apps/website/content/docs/chat/a2ui/surface-component.mdx @@ -0,0 +1,116 @@ +# A2uiSurfaceComponent + +Angular component that renders a single A2UI surface. It converts the surface's component map and data model into a json-render `Spec`, then delegates rendering to `RenderSpecComponent`. + +**Import:** + +```typescript +import { A2uiSurfaceComponent } from '@cacheplane/chat'; +``` + +**Selector:** `a2ui-surface` + +## Inputs + +| Input | Type | Required | Description | +|-------|------|----------|-------------| +| `surface` | `A2uiSurface` | Yes | The surface to render | +| `catalog` | `ViewRegistry` | Yes | Component registry used to resolve A2UI type names to Angular components | + +## How It Works + +`A2uiSurfaceComponent` bridges the A2UI surface model to the json-render engine in three steps: + +**1. Convert surface to Spec (`surfaceToSpec`)** + +The `surfaceToSpec()` function walks the flat component map and produces a json-render `Spec`. It requires a component with `id: 'root'` to be present — if no root exists, nothing renders. + +**2. Resolve dynamic values** + +Before the spec is emitted, each component prop is evaluated against the surface's data model. A prop can be: + +- A literal value — passed through as-is +- A path reference `{ path: '/some/pointer' }` — resolved via JSON Pointer against `dataModel` +- A function call `{ call: 'formatCurrency', args: { ... } }` — executed by the built-in function registry +- A template string `"Hello ${/name}"` — interpolated with values from `dataModel` + +**3. Expand template children** + +When a component's `children` field is an `A2uiChildTemplate` (`{ path, componentId }`), the surface component expands it over the array at `path` in the data model. Each array item gets its own cloned element with props resolved in that item's scope. + +**4. Render via RenderSpecComponent** + +The final `Spec` is passed to `RenderSpecComponent` along with the `ViewRegistry` (converted to an Angular registry via `toRenderRegistry`). Rendering is fully reactive — when the surface signal updates, the spec recomputes and only affected elements re-render. + +## Usage Outside ChatComponent + +`A2uiSurfaceComponent` is used internally by `ChatComponent`, but you can also use it directly to embed a surface in any Angular template. + +```typescript +import { Component, signal } from '@angular/core'; +import { A2uiSurfaceComponent, createA2uiSurfaceStore, a2uiBasicCatalog } from '@cacheplane/chat'; +import { createA2uiMessageParser } from '@cacheplane/a2ui'; + +@Component({ + selector: 'app-agent-panel', + standalone: true, + imports: [A2uiSurfaceComponent], + template: ` + @if (store.surface('dashboard')() as surface) { + + } + `, +}) +export class AgentPanelComponent { + readonly catalog = a2uiBasicCatalog(); + readonly store = createA2uiSurfaceStore(); + private readonly parser = createA2uiMessageParser(); + + receiveChunk(chunk: string): void { + for (const msg of this.parser.push(chunk)) { + this.store.apply(msg); + } + } +} +``` + + +The surface must contain a component with `id: 'root'`. If no root component has been received yet (for example, while the agent is still streaming), `A2uiSurfaceComponent` renders nothing until one arrives. + + +## surfaceToSpec() + +The conversion function is exported for testing or custom rendering pipelines. + +**Import:** + +```typescript +import { surfaceToSpec } from '@cacheplane/chat'; +``` + +**Signature:** + +```typescript +function surfaceToSpec(surface: A2uiSurface): Spec | null +``` + +Returns `null` when the surface has no `root` component. Otherwise returns a complete json-render `Spec` with all dynamic values resolved against the current `dataModel`. + +## What's Next + + + + Manage surfaces and apply A2UI messages reactively. + + + All 12 built-in components and their props. + + diff --git a/apps/website/content/docs/chat/a2ui/surface-store.mdx b/apps/website/content/docs/chat/a2ui/surface-store.mdx new file mode 100644 index 000000000..3d0174a61 --- /dev/null +++ b/apps/website/content/docs/chat/a2ui/surface-store.mdx @@ -0,0 +1,148 @@ +# createA2uiSurfaceStore() + +Factory function that creates an `A2uiSurfaceStore` — a reactive store that accumulates A2UI messages into a live `Map` of surfaces. + +**Import:** + +```typescript +import { createA2uiSurfaceStore } from '@cacheplane/chat'; +``` + +## Signature + +```typescript +function createA2uiSurfaceStore(): A2uiSurfaceStore +``` + +**Returns:** `A2uiSurfaceStore` — a stateful store backed by Angular signals. Can be created outside an injection context. + +## A2uiSurfaceStore Interface + +```typescript +interface A2uiSurfaceStore { + /** Apply an A2UI message, updating surfaces reactively. */ + apply(message: A2uiMessage): void; + + /** Signal containing all current surfaces, keyed by surfaceId. */ + readonly surfaces: Signal>; + + /** Returns a computed signal for a single surface by ID. */ + surface(surfaceId: string): Signal; +} +``` + +### `apply(message)` + +Processes one `A2uiMessage` and updates the internal surfaces signal. All four message types are handled: + +| Message type | Behavior | +|--------------|----------| +| `createSurface` | Creates a new `A2uiSurface` entry with an empty component map and data model | +| `updateComponents` | Merges the provided components into the surface's component map by `id` — existing components are replaced, others are kept | +| `updateDataModel` | Applies a JSON Pointer patch to the surface's data model (see below) | +| `deleteSurface` | Removes the surface from the map entirely | + +Messages for unknown surface IDs are silently ignored (except `createSurface`, which registers the surface). + +### `surfaces` + +A readonly `Signal>` containing all active surfaces. Each map operation produces a new `Map` reference so that Angular's change detection triggers correctly. + +### `surface(surfaceId)` + +Returns a `computed` signal for a single surface. The signal emits `undefined` until a `createSurface` message registers it, and `undefined` again after `deleteSurface` removes it. + +```typescript +const dashboard = store.surface('dashboard'); +// dashboard() is A2uiSurface | undefined +``` + +## updateDataModel Semantics + +The `updateDataModel` message uses JSON Pointer (RFC 6901) paths to address values in the data model. + +| `path` | `value` | Effect | +|--------|---------|--------| +| `undefined` or `'/'` | Object | Replaces the entire data model | +| `/some/path` | Any value | Sets `dataModel[some][path]` to `value` | +| `/some/path` | `undefined` | Deletes the value at that path | + +```json +// Replace entire model +{"updateDataModel": {"surfaceId": "s1", "value": {"name": "Alice", "score": 42}}} + +// Set a single field +{"updateDataModel": {"surfaceId": "s1", "path": "/score", "value": 99}} + +// Delete a field +{"updateDataModel": {"surfaceId": "s1", "path": "/score"}} +``` + +## Usage with createA2uiMessageParser + +The surface store is designed to work with `createA2uiMessageParser`, which parses raw JSONL chunks into typed `A2uiMessage` objects. + +```typescript +import { createA2uiSurfaceStore } from '@cacheplane/chat'; +import { createA2uiMessageParser } from '@cacheplane/a2ui'; +import { effect } from '@angular/core'; + +const store = createA2uiSurfaceStore(); +const parser = createA2uiMessageParser(); + +// Feed raw JSONL chunks as they arrive from the stream +function onChunk(chunk: string): void { + const messages = parser.push(chunk); + for (const msg of messages) { + store.apply(msg); + } +} + +// React to surface changes +effect(() => { + const surface = store.surface('dashboard')(); + if (surface) { + console.log('Components:', [...surface.components.keys()]); + console.log('Data model:', surface.dataModel); + } +}); +``` + + +The parser expects each line to be wrapped in an envelope object: `{"createSurface": {...}}`, `{"updateComponents": {...}}`, etc. The envelope key determines the message type; its value is the message payload. + + +## A2uiSurface Shape + +Each surface stored in the map has the following structure: + +```typescript +interface A2uiSurface { + surfaceId: string; + catalogId: string; + theme?: A2uiTheme; + components: Map; + dataModel: Record; +} +``` + +The `components` map is keyed by component ID. The `dataModel` is a plain object that components reference via JSON Pointer paths in their props. + +## What's Next + + + + Render a surface using A2uiSurfaceComponent. + + + All 12 built-in components and their props. + + diff --git a/apps/website/content/docs/chat/api/content-classifier.mdx b/apps/website/content/docs/chat/api/content-classifier.mdx index 7db44ec53..e5cf4387c 100644 --- a/apps/website/content/docs/chat/api/content-classifier.mdx +++ b/apps/website/content/docs/chat/api/content-classifier.mdx @@ -39,6 +39,12 @@ interface ContentClassifier { /** True while content is still arriving. */ readonly streaming: Signal; + /** A2UI surfaces parsed from the stream. */ + readonly a2uiSurfaces: Signal>; + + /** Parse errors encountered (non-fatal). */ + readonly errors: Signal; + /** Clean up resources. */ dispose(): void; } @@ -55,8 +61,16 @@ type ContentType = 'undetermined' | 'markdown' | 'json-render' | 'a2ui' | 'mixed | `undetermined` | No content received yet | | `markdown` | Plain text / markdown prose | | `json-render` | JSON spec detected (first non-whitespace is `{`) | -| `a2ui` | A2UI payload detected (future) | -| `mixed` | Markdown followed by structured content (future) | +| `a2ui` | A2UI payload detected via `---a2ui_JSON---` prefix, parsed as JSONL messages | +| `mixed` | Markdown followed by structured content | + +### a2uiSurfaces + +When the content type is `a2ui`, this signal contains all parsed A2UI surfaces with their components and data models. The map is keyed by surface ID and updates incrementally as JSONL lines arrive during streaming. + +### errors + +Parse errors are captured but don't stop the classifier. Partial results continue to render. Useful for diagnostics — check this signal when debugging unexpected rendering behavior or malformed agent output. ## Usage diff --git a/apps/website/content/docs/chat/components/chat.mdx b/apps/website/content/docs/chat/components/chat.mdx index 76f0c9a27..78c3c1de4 100644 --- a/apps/website/content/docs/chat/components/chat.mdx +++ b/apps/website/content/docs/chat/components/chat.mdx @@ -145,6 +145,12 @@ const myViews = views({ AI messages containing JSON are parsed character-by-character as tokens stream. Components render incrementally — string props grow visibly as tokens arrive. See the [Generative UI guide](/docs/chat/guides/generative-ui) for full setup. +## A2UI Rendering + +When AI messages contain A2UI content (prefixed with `---a2ui_JSON---`), the component auto-detects and renders surfaces using the built-in `a2uiBasicCatalog()`. This includes 12 components from the A2UI v0.9 basic catalog: Text, Image, Icon, Divider, Row, Column, Card, List, Button, TextField, CheckBox, and ChoicePicker. + +A2UI surfaces support two-way data binding, button actions, template expansion over collections, and validation. See the [A2UI guide](/docs/chat/a2ui/overview) for details. + ## Auto-Scroll Behavior The component tracks message count and loading state to auto-scroll: diff --git a/apps/website/content/docs/chat/getting-started/introduction.mdx b/apps/website/content/docs/chat/getting-started/introduction.mdx index 26178cffe..881261a65 100644 --- a/apps/website/content/docs/chat/getting-started/introduction.mdx +++ b/apps/website/content/docs/chat/getting-started/introduction.mdx @@ -60,7 +60,7 @@ LangGraph Platform - **`@cacheplane/agent`** provides the `agent()` function and the `AgentRef` type. Every chat component accepts an `AgentRef` as its primary input. The ref exposes reactive Signals for `messages()`, `isLoading()`, `error()`, `interrupt()`, `toolCalls()`, `history()`, and more. -- **`@cacheplane/render`** provides `RenderSpecComponent` and view registries for rendering JSON UI specs as Angular components. The `ChatComponent` auto-detects JSON specs in AI messages and renders them through `@cacheplane/render` — pass a view registry via the `[views]` input. +- **`@cacheplane/render`** provides `RenderSpecComponent` and view registries for rendering JSON UI specs as Angular components. The `ChatComponent` auto-detects JSON specs in AI messages and renders them through `@cacheplane/render` — pass a view registry via the `[views]` input. The `ChatComponent` also auto-detects A2UI v0.9 payloads and renders them using a built-in 12-component catalog. ## When to Use `ChatComponent` vs. Custom Assembly diff --git a/apps/website/content/docs/chat/guides/generative-ui.mdx b/apps/website/content/docs/chat/guides/generative-ui.mdx index 5c6fab8ce..a7307d13a 100644 --- a/apps/website/content/docs/chat/guides/generative-ui.mdx +++ b/apps/website/content/docs/chat/guides/generative-ui.mdx @@ -131,6 +131,10 @@ export class InteractiveChatComponent { The store enables two-way data binding between generative UI components and your application via `$state` and `$bindState` prop expressions in specs. +## A2UI Protocol + +For agents that implement Google's A2UI v0.9 protocol, `ChatComponent` auto-detects A2UI payloads (prefixed with `---a2ui_JSON---`) and renders them using the built-in A2UI catalog. See the [A2UI guide](/docs/chat/a2ui/overview) for details. + ## What's Next @@ -148,4 +152,7 @@ The store enables two-way data binding between generative UI components and your > Full reference for the JSON spec format, prop expressions, and element types. + + Render A2UI v0.9 surfaces with the built-in 12-component catalog. + diff --git a/apps/website/content/docs/chat/guides/streaming.mdx b/apps/website/content/docs/chat/guides/streaming.mdx index cc55e6135..fd31c7aad 100644 --- a/apps/website/content/docs/chat/guides/streaming.mdx +++ b/apps/website/content/docs/chat/guides/streaming.mdx @@ -104,6 +104,33 @@ interface ElementAccumulationState { } ``` +## A2UI Content Detection + +A2UI content uses a different detection trigger than JSON-render specs. Instead of detecting the first non-whitespace `{` character, the classifier looks for the `---a2ui_JSON---` prefix at the start of the message. + +Once detected, the classifier switches to A2UI mode and parses the remaining content as JSONL — one JSON object per line — rather than a single JSON object. Each line represents an A2UI message that builds up surfaces with components and data models. + +The resulting surfaces are available via `classifier.a2uiSurfaces()`, which returns a `Map` keyed by surface ID. See the [A2UI guide](/docs/chat/a2ui/overview) for full details on the A2UI protocol and surface structure. + +## Error Handling + +Parse errors are captured in the `errors` signal and do not crash the rendering pipeline. When a malformed token arrives, the classifier records the error and continues processing subsequent tokens — partial results keep rendering. + +```typescript +const classifier = createContentClassifier(); + +// Feed content (errors are captured internally) +classifier.update(content); + +// Check for non-fatal parse errors +const parseErrors = classifier.errors(); +if (parseErrors.length > 0) { + console.warn('Parse errors encountered:', parseErrors); +} +``` + +This makes the `errors` signal useful for diagnostics and debugging without disrupting the user-facing chat experience. + ## What's Next diff --git a/apps/website/src/lib/docs-config.ts b/apps/website/src/lib/docs-config.ts index 7ee5b0a21..6865dcd55 100644 --- a/apps/website/src/lib/docs-config.ts +++ b/apps/website/src/lib/docs-config.ts @@ -167,6 +167,17 @@ export const docsConfig: DocsLibrary[] = [ { title: 'createParseTreeStore()', slug: 'parse-tree-store', section: 'api' }, ], }, + { + title: 'A2UI', + id: 'a2ui', + color: 'red', + pages: [ + { title: 'Overview', slug: 'overview', section: 'a2ui' }, + { title: 'A2uiSurfaceComponent', slug: 'surface-component', section: 'a2ui' }, + { title: 'createA2uiSurfaceStore()', slug: 'surface-store', section: 'a2ui' }, + { title: 'Component Catalog', slug: 'catalog', section: 'a2ui' }, + ], + }, ], }, ];