Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/cockpit/e2e/all-examples-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';

/**
* Smoke test that verifies every capability example's Angular app is running
* and can render the chat interface. Requires all 14 Angular apps to be served.
* and can render the chat interface. Requires all 15 Angular apps to be served.
*
* Run with: npx playwright test apps/cockpit/e2e/all-examples-smoke.spec.ts
*
Expand All @@ -26,6 +26,7 @@ const EXAMPLES = [
{ name: 'da-memory', port: 4313, selector: 'app-da-memory' },
{ name: 'skills', port: 4314, selector: 'app-skills' },
{ name: 'sandboxes', port: 4315, selector: 'app-sandboxes' },
{ name: 'c-a2ui', port: 4511, selector: 'app-a2ui' },
] as const;

test.describe('All Examples Smoke Test', () => {
Expand Down
1 change: 1 addition & 0 deletions apps/cockpit/scripts/capability-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const capabilities: readonly Capability[] = [
{ id: 'c-generative-ui', product: 'chat', topic: 'generative-ui', angularProject: 'cockpit-chat-generative-ui-angular', port: 4508, pythonDir: 'cockpit/chat/generative-ui/python', graphName: 'c-generative-ui' },
{ id: 'c-debug', product: 'chat', topic: 'debug', angularProject: 'cockpit-chat-debug-angular', port: 4509, pythonDir: 'cockpit/chat/debug/python', graphName: 'c-debug' },
{ id: 'c-theming', product: 'chat', topic: 'theming', angularProject: 'cockpit-chat-theming-angular', port: 4510, pythonDir: 'cockpit/chat/theming/python', graphName: 'c-theming' },
{ id: 'c-a2ui', product: 'chat', topic: 'a2ui', angularProject: 'cockpit-chat-a2ui-angular', port: 4511, pythonDir: 'cockpit/chat/a2ui/python', graphName: 'c-a2ui' },
] as const;

export function findCapability(id: string): Capability | undefined {
Expand Down
2 changes: 2 additions & 0 deletions apps/cockpit/src/lib/route-resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { chatTimelinePythonModule } from '../../../../cockpit/chat/timeline/pyth
import { chatGenerativeUiPythonModule } from '../../../../cockpit/chat/generative-ui/python/src/index';
import { chatDebugPythonModule } from '../../../../cockpit/chat/debug/python/src/index';
import { chatThemingPythonModule } from '../../../../cockpit/chat/theming/python/src/index';
import { chatA2uiPythonModule } from '../../../../cockpit/chat/a2ui/python/src/index';

export interface ResolveCockpitEntryOptions {
manifest: CockpitManifestEntry[];
Expand Down Expand Up @@ -102,6 +103,7 @@ const capabilityModules = [
chatGenerativeUiPythonModule,
chatDebugPythonModule,
chatThemingPythonModule,
chatA2uiPythonModule,
];

export const toCockpitPath = (entry: CockpitManifestEntry): string =>
Expand Down
41 changes: 19 additions & 22 deletions apps/website/content/docs/render/a2ui/catalog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ For data-driven lists, use the `A2uiChildTemplate` form instead of static `child

## Interactive Components

Interactive components support **two-way data binding** and **button actions**. They use two special props:
Interactive components support **two-way data binding** and **button actions**. They receive two special injected props:

- `_bindings` — a `Record<string, string>` 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.
- `_bindings` — a `Record<string, string>` auto-populated by `surfaceToSpec()` from path references in the component definition. Maps prop names to JSON Pointer paths. When the user changes a bound value, the component emits an `a2ui:datamodel:` event. Agents do **not** write `_bindings` directly — they use path references (e.g., `{"path": "/name"}`) and the render pipeline extracts bindings automatically.
- `emit` — injected by the render engine; components call it to dispatch events back to the chat.

### Button
Expand Down Expand Up @@ -183,22 +183,23 @@ A single-line text input with optional label and placeholder.
| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Input label |
| `value` | `string` | Current value (bind via `_bindings`) |
| `value` | `string` | Current value (resolved from path reference) |
| `placeholder` | `string` | Placeholder text |
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below input when invalid |
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
| `emit` | injected | Event emitter provided by the render engine |

```json
{
"id": "name-field",
"component": "TextField",
"label": "Your name",
"value": {"path": "/name"},
"_bindings": {"value": "/name"}
"value": {"path": "/name"}
}
```

The path reference `{"path": "/name"}` is resolved by `surfaceToSpec()`, which also populates `_bindings` automatically.

### CheckBox

A labeled checkbox with two-way binding for its checked state.
Expand All @@ -210,9 +211,9 @@ A labeled checkbox with two-way binding for its checked state.
| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Checkbox label |
| `checked` | `boolean` | Current checked state (bind via `_bindings`) |
| `checked` | `boolean` | Current checked state (resolved from path reference) |
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below checkbox when invalid |
| `_bindings` | `Record<string, string>` | Bind `checked` to a data model path |
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
| `emit` | injected | Event emitter provided by the render engine |

### ChoicePicker
Expand All @@ -227,9 +228,9 @@ A dropdown select control with a list of string options.
|------|------|-------------|
| `label` | `string` | Select label |
| `options` | `string[]` | List of available options |
| `selected` | `string` | Currently selected value (bind via `_bindings`) |
| `selected` | `string` | Currently selected value (resolved from path reference) |
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below dropdown when invalid |
| `_bindings` | `Record<string, string>` | Bind `selected` to a data model path |
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
| `emit` | injected | Event emitter provided by the render engine |

### DateTimeInput
Expand All @@ -243,12 +244,12 @@ A date, time, or datetime input with two-way binding.
| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Input label |
| `value` | `string` | Current value (bind via `_bindings`) |
| `value` | `string` | Current value (resolved from path reference) |
| `inputType` | `'date' \| 'time' \| 'datetime-local'` | HTML input type. Defaults to `'date'` |
| `min` | `string` | Minimum allowed value |
| `max` | `string` | Maximum allowed value |
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below input when invalid |
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
| `emit` | injected | Event emitter provided by the render engine |

```json
Expand All @@ -257,8 +258,7 @@ A date, time, or datetime input with two-way binding.
"component": "DateTimeInput",
"label": "Appointment date",
"value": {"path": "/appointmentDate"},
"inputType": "date",
"_bindings": {"value": "/appointmentDate"}
"inputType": "date"
}
```

Expand All @@ -278,7 +278,7 @@ A range slider input with two-way binding.
| `max` | `number` | Maximum value |
| `step` | `number` | Step increment |
| `validationResult` | `A2uiValidationResult` | Validation state — shows errors below slider when invalid |
| `_bindings` | `Record<string, string>` | Bind `value` to a data model path |
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
| `emit` | injected | Event emitter provided by the render engine |

```json
Expand All @@ -289,8 +289,7 @@ A range slider input with two-way binding.
"value": {"path": "/volume"},
"min": 0,
"max": 100,
"step": 1,
"_bindings": {"value": "/volume"}
"step": 1
}
```

Expand All @@ -308,7 +307,7 @@ A tabbed container that shows one child panel at a time based on the selected ta
|------|------|-------------|
| `tabs` | `{label: string, childKeys: string[]}[]` | Tab definitions with labels and child component IDs |
| `selected` | `number` | Currently selected tab index. Defaults to `0` |
| `_bindings` | `Record<string, string>` | Bind `selected` to a data model path |
| `_bindings` | `Record<string, string>` | Auto-populated by `surfaceToSpec()` from path references |
| `spec` | `Spec` | Injected automatically by the render engine |
| `emit` | injected | Event emitter provided by the render engine |

Expand All @@ -320,8 +319,7 @@ A tabbed container that shows one child panel at a time based on the selected ta
{"label": "Overview", "childKeys": ["overview-content"]},
{"label": "Details", "childKeys": ["detail-list"]}
],
"selected": 0,
"_bindings": {"selected": "/activeTab"}
"selected": {"path": "/activeTab"}
}
```

Expand Down Expand Up @@ -350,8 +348,7 @@ A dialog overlay that renders child content when open. Supports an optional titl
"title": "Confirm Action",
"open": {"path": "/showConfirm"},
"childKeys": ["confirm-message", "confirm-buttons"],
"dismissible": true,
"_bindings": {"open": "/showConfirm"}
"dismissible": true
}
```

Expand Down
9 changes: 7 additions & 2 deletions cockpit/chat/a2ui/angular/e2e/a2ui.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { expect, test } from '@playwright/test';
test.describe('A2UI Example', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:4511');
await page.waitForSelector('app-a2ui', { state: 'attached' });
await page.waitForSelector('app-a2ui', { state: 'attached', timeout: 10000 });
});

test('renders the chat interface', async ({ page }) => {
await expect(page.locator('chat')).toBeVisible();
await expect(page.locator('chat')).toBeVisible({ timeout: 5000 });
});

test('displays input and send button', async ({ page }) => {
await expect(page.locator('input[name="prompt"]')).toBeVisible({ timeout: 5000 });
await expect(page.locator('button[type="submit"]')).toBeVisible({ timeout: 5000 });
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const environment = {
production: false,
langGraphApiUrl: 'http://localhost:4311/api',
a2uiAssistantId: 'a2ui_form',
langGraphApiUrl: 'http://localhost:4511/api',
a2uiAssistantId: 'c-a2ui',
};
2 changes: 1 addition & 1 deletion cockpit/chat/a2ui/angular/src/environments/environment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const environment = {
production: true,
langGraphApiUrl: '/api',
a2uiAssistantId: 'a2ui_form',
a2uiAssistantId: 'c-a2ui',
};
13 changes: 9 additions & 4 deletions cockpit/chat/a2ui/python/docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,17 @@ Components bind to the data model using path references:

```json
{"id": "name_field", "component": "TextField",
"label": "Name", "value": {"path": "/name"},
"_bindings": {"value": "/name"}}
"label": "Name", "value": {"path": "/name"}}
```

When the user types in the field, the value at `/name` in the data model
updates automatically via the render-lib StateStore.
The `surfaceToSpec` function auto-detects path references and populates
`_bindings` for each input component — agents do not write `_bindings`
directly. When the user changes a bound input, the component emits a
data model update event.

**Known limitation:** Data model updates from user input do not currently
reflect to other components in real time. The agent can refresh state by
sending a new `updateDataModel` message.

</Step>
<Step title="Event routing">
Expand Down
2 changes: 1 addition & 1 deletion cockpit/chat/a2ui/python/langgraph.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"graphs": {
"a2ui_form": "./src/graph.py:graph"
"c-a2ui": "./src/graph.py:graph"
},
"dependencies": ["."],
"python_version": "3.12",
Expand Down
Loading
Loading