diff --git a/docs/guides/authoring-components.md b/docs/guides/authoring-components.md new file mode 100644 index 000000000..67b43a101 --- /dev/null +++ b/docs/guides/authoring-components.md @@ -0,0 +1,243 @@ +# Authoring Custom Components + +Learn how to define, implement, and register custom components in A2UI using the `rizzcharts` sample as an example. This guide focuses on authoring a component around your Angular code. + +## Overview + +Authoring a new component involves four main steps: + +1. **Define the Catalog Schema**: Specify the component's properties and types in a JSON Schema. +2. **Define the Component (Client)**: Implement the UI using your framework (e.g., Angular). +3. **Register with the Renderer (Client)**: Add the component to your client-side catalog. +4. **Invoke from the Agent**: Instruct the agent to use the component via `send_a2ui_json_to_client`. + +--- + +## 1. Defining the Catalog Schema + +The catalog schema defines the API of your catalog. It lists available components and their properties, which the agent uses to construct UI payloads. + +**This schema acts as a contract between the client and the server (agent).** Both must agree on this schema for rendering to work. The client advertises what catalogs it supports, and the server selects a compatible one. For details on how this handshake works, see [A2UI Catalog Negotiation](../concepts/catalogs.md#a2ui-catalog-negotiation). + +In the [`rizzcharts`](../../samples/agent/adk/rizzcharts/README.md) example, the catalog schema is defined in [`rizzcharts_catalog_definition.json`](../../samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json). + +Here is the schema for the `Chart` component: + +```json +"Chart": { + "type": "object", + "description": "An interactive chart that uses a hierarchical list of objects for its data.", + "properties": { + "type": { + "type": "string", + "description": "The type of chart to render.", + "enum": [ + "doughnut", + "pie" + ] + }, + "title": { + "type": "object", + "description": "The title of the chart. Can be a literal string or a data model path.", + "properties": { + "literalString": { + "type": "string" + }, + "path": { + "type": "string" + } + } + }, + "chartData": { + "type": "object", + "description": "The data for the chart, provided as a list of items. Can be a literal array or a data model path.", + "properties": { + "literalArray": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "number" + }, + "drillDown": { + "type": "array", + "description": "An optional list of items for the next level of data.", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "number" + } + }, + "required": [ + "label", + "value" + ] + } + } + }, + "required": [ + "label", + "value" + ] + } + }, + "path": { + "type": "string" + } + } + } + }, + "required": [ + "type", + "chartData" + ] +} +``` + +--- + +## 2. Implementing the Component (Client) + +Implement your component using your client-side framework. For Angular, your component should extend `DynamicComponent` provided by `@a2ui/angular`. + +In the [`rizzcharts`](../../samples/client/angular/projects/rizzcharts/README.md) example, the `Chart` component is defined in [`chart.ts`](../../samples/client/angular/projects/rizzcharts/src/a2ui-catalog/chart.ts). + +```typescript +import { DynamicComponent } from '@a2ui/angular'; +import * as Primitives from '@a2ui/web_core/types/primitives'; +import * as Types from '@a2ui/web_core/types/types'; +import { Component, computed, input, Signal, signal } from '@angular/core'; + +@Component({ + selector: 'a2ui-chart', + template: ` +
+

{{ resolvedTitle() }}

+ +
+ `, +}) +export class Chart extends DynamicComponent { + readonly type = input.required(); + protected readonly chartType = computed(() => this.type() as ChartType); + + readonly title = input(); + protected readonly resolvedTitle = computed(() => super.resolvePrimitive(this.title() ?? null)); + + readonly chartData = input.required(); + // ... data resolution logic using super.resolvePrimitive for data paths +} +``` + +Key points: +- **Extend `DynamicComponent`**: This gives you access to `resolvePrimitive` for data binding resolution. +- **Use Angular Inputs**: Map properties from the schema to Angular inputs. + +--- + +## 3. Registering with the Renderer (Client) + +Once the component is implemented, register it in your client catalog. This maps the component name (used by agents) to the implementation class. + +In the [`rizzcharts`](../../samples/agent/adk/rizzcharts/README.md) example, this is done in [`catalog.ts`](../../samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts). + +```typescript +import { Catalog, DEFAULT_CATALOG } from '@a2ui/angular'; +import { inputBinding } from '@angular/core'; + +export const RIZZ_CHARTS_CATALOG = { + ...DEFAULT_CATALOG, + Chart: { + type: () => import('./chart').then((r) => r.Chart), + bindings: ({ properties }) => [ + inputBinding('type', () => ('type' in properties && properties['type']) || undefined), + inputBinding('title', () => ('title' in properties && properties['title']) || undefined), + inputBinding( + 'chartData', + () => ('chartData' in properties && properties['chartData']) || undefined, + ), + ], + }, +} as Catalog; +``` + +Key points: +- **Lazy Loading**: Use `import()` to lazy-load the component code. +- **Input Bindings**: Use `inputBinding` to map properties from the schema to Angular inputs. + +--- + +## 4. Invoking from the Agent + +To use the custom component, you initialize the agent with tools from the A2UI SDK that understand your catalog. The SDK handles resolving the catalog and providing examples to the model. + +Here is how the flow wires up: + +### 4.1 Session Preparation (Executor) + +The execution layer (e.g., `RizzchartsAgentExecutor`) intercepts the incoming message to detect if A2UI is enabled and what catalogs the client supports. It resolves the catalog and saves it to the session state. + +```python +# In agent_executor.py + +use_ui = try_activate_a2ui_extension(context) +if use_ui: + # Resolve catalog based on client capabilities + a2ui_catalog = self.schema_manager.get_selected_catalog( + client_ui_capabilities=capabilities + ) + examples = self.schema_manager.load_examples(a2ui_catalog, validate=True) + + # Save to session (Event contains state_delta) + await runner.session_service.append_event( + session, + Event( + actions=EventActions( + state_delta={ + _A2UI_ENABLED_KEY: True, + _A2UI_CATALOG_KEY: a2ui_catalog, + _A2UI_EXAMPLES_KEY: examples, + } + ), + ), + ) +``` + +### 4.2 Agent Tool Setup + +The Agent uses [SendA2uiToClientToolset](../../agent_sdks/python/src/a2ui/adk/a2a_extension/send_a2ui_to_client_toolset.py) to give the agent a tool that it can use to send A2UI to the client. + +```python +from a2ui.adk.a2a_extension.send_a2ui_to_client_toolset import SendA2uiToClientToolset + +a2ui_catalog = self.schema_manager.get_selected_catalog( + client_ui_capabilities=capabilities +) +agent.tools = [ + SendA2uiToClientToolset( + a2ui_catalog=a2ui_catalog, + a2ui_enabled=True, + ) +] +``` + +### 4.3 Tool Execution + +Invocations of the tool in [SendA2uiToClientToolset](../../agent_sdks/python/src/a2ui/adk/a2a_extension/send_a2ui_to_client_toolset.py) by the LLM are intercepted in the A2A Agent Executor using the [A2uiEventConverter](../../agent_sdks/python/src/a2ui/adk/a2a_extension/send_a2ui_to_client_toolset.py). This automatically translates tool calls into A2A Dataparts with the A2UI payload. + +```python +from a2ui.adk.a2a_extension.send_a2ui_to_client_toolset import ( + A2uiEventConverter, +) + +config = A2aAgentExecutorConfig(event_converter=A2uiEventConverter()) +``` + diff --git a/docs/guides/custom-components.md b/docs/guides/custom-components.md index 9915fa400..4f1c4a022 100644 --- a/docs/guides/custom-components.md +++ b/docs/guides/custom-components.md @@ -31,9 +31,10 @@ TODO: Add detailed guide for defining custom catalogs for each platform. **Web (Lit / Angular):** -- How to define a catalog with both standard and custom components -- How to register the catalog with the A2UI client -- How to implement custom component classes +- [Authoring Custom Components](authoring-components.md): A detailed guide using Angular and rizzcharts as an example. +- How to define a catalog with both standard and custom components in Lit +- How to register the catalog with the A2UI client in Lit + **Flutter:** diff --git a/mkdocs.yaml b/mkdocs.yaml index e2403424d..0d0486b6b 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -53,7 +53,9 @@ nav: - Agent Development: guides/agent-development.md - Renderer Development: guides/renderer-development.md - Custom Components: guides/custom-components.md + - Authoring Custom Components: guides/authoring-components.md - Theming & Styling: guides/theming.md + - A2UI over MCP: guides/a2ui_over_mcp.md - Reference: - Component Gallery: reference/components.md