Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
37c3c26
feat(chat): add Message.reasoning + Message.reasoningDurationMs
blove May 4, 2026
9cb53ad
feat(chat): add formatDuration utility
blove May 4, 2026
fe5551d
feat(chat): chat-reasoning styles
blove May 4, 2026
dfbcc2a
feat(chat): chat-reasoning primitive
blove May 4, 2026
ff24010
feat(chat): export ChatReasoningComponent + formatDuration
blove May 4, 2026
0e732b4
fix(chat): chat-reasoning auto-resets on streaming re-engage + spec a…
blove May 4, 2026
226d407
feat(chat): chatToolCallTemplate directive
blove May 4, 2026
d065763
feat(chat): export ChatToolCallTemplateDirective + context type
blove May 4, 2026
0401ed4
refactor(chat): test chatToolCallTemplate via viewChildren signal
blove May 4, 2026
03a89fd
feat(chat): chat-tool-calls grouping + per-tool template registry
blove May 4, 2026
cb9c3a7
feat(chat): chat-tool-call-card status pill + default-collapsed
blove May 4, 2026
c3a8e24
feat(langgraph): extractReasoning + accumulateReasoning helpers
blove May 4, 2026
da87bcb
feat(langgraph): bridge accumulates reasoning + tracks per-message ti…
blove May 4, 2026
cf4adfd
feat(langgraph): toMessage populates Message.reasoning + reasoningDur…
blove May 4, 2026
331bcbd
feat(ag-ui): handle REASONING_MESSAGE_* events
blove May 4, 2026
e2c1f38
feat(ag-ui): FakeAgent reasoningTokens option
blove May 4, 2026
c4f1a66
feat(chat/testing): add provider-neutral reasoning fixture
blove May 4, 2026
0f91772
test(ag-ui): reasoning-fixture conformance
blove May 4, 2026
92247f3
test(langgraph): reasoning-fixture conformance
blove May 4, 2026
50732ef
fix(langgraph): wrap toMessage in arrow fn to avoid map index collision
blove May 4, 2026
967fe8c
feat(chat): <chat> renders <chat-reasoning> + forwards chatToolCallTe…
blove May 4, 2026
d5349e6
docs(chat): chat-reasoning + tool-call template references + 0.0.19 c…
blove May 4, 2026
3d90254
chore: bump chat 0.0.19, langgraph 0.0.11, ag-ui 0.0.3
blove May 4, 2026
8a6f1ff
fix(chat): satisfy @nx/dependency-checks for vite-only @analogjs plugin
blove May 4, 2026
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
57 changes: 57 additions & 0 deletions apps/website/content/docs/chat/components/chat-reasoning.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ChatReasoningComponent

`ChatReasoningComponent` renders an assistant's reasoning content as a compact pill that expands to reveal the underlying text. The `<chat>` composition automatically renders this primitive above the assistant response when `Message.reasoning` is populated by the adapter — most consumers don't need to use it directly.

**Selector:** `chat-reasoning`

**Import:**

```typescript
import { ChatReasoningComponent, formatDuration } from '@ngaf/chat';
```

## Visual states

| State | Pill label | Behavior |
|---|---|---|
| `[isStreaming]="true"` | "Thinking…" with pulsing dot | Auto-expanded; body streams in |
| Idle, `[durationMs]` set | "Thought for Ns" | Collapsed by default; click to expand |
| Idle, no `[durationMs]` | "Show reasoning" | Collapsed by default; click to expand |

## Inputs

| Input | Type | Default | Description |
|---|---|---|---|
| `[content]` | `string` | `''` | The reasoning text to render |
| `[isStreaming]` | `boolean` | `false` | True while the model is mid-reasoning |
| `[durationMs]` | `number \| undefined` | `undefined` | Wall-clock duration of the reasoning phase |
| `[label]` | `string \| undefined` | `undefined` | Override the auto-derived label |
| `[defaultExpanded]` | `boolean` | `false` | Open the panel by default when idle |

## Standalone usage

```html
<chat-reasoning
[content]="reasoningText"
[isStreaming]="isStillThinking"
[durationMs]="thoughtFor"
/>
```

## formatDuration utility

Use `formatDuration(ms)` to render the duration string yourself (e.g. for a sidebar):

```typescript
formatDuration(0) // "<1s"
formatDuration(4_000) // "4s"
formatDuration(72_000) // "1m 12s"
```

## Behavior

- The component hides itself entirely (`display: none`) when `[content]` is empty.
- `[isStreaming]="true"` force-expands the panel so streaming content is visible.
- A user click on the pill toggles the panel; the user choice persists across `[isStreaming]` transitions for the lifetime of the instance.
- When `isStreaming` re-engages on a follow-up turn (a new reasoning phase begins after a prior idle period), the panel resets to expanded.
- The body re-uses `<chat-streaming-md>` so reasoning content gets the same markdown rendering pipeline as the response (lists, code blocks, headings render).
132 changes: 25 additions & 107 deletions apps/website/content/docs/chat/components/chat-tool-call-card.mdx
Original file line number Diff line number Diff line change
@@ -1,139 +1,57 @@
# ChatToolCallCardComponent

`ChatToolCallCardComponent` is a composition that renders an expandable card for a single tool call. It displays the tool name in the header, shows a completion badge when done, and expands to reveal the tool's input arguments and output result.
`ChatToolCallCardComponent` renders a single tool call as an expandable card with a status pill (running / complete / error), inputs, and output.

**Selector:** `chat-tool-call-card`

**Import:**

```typescript
import { ChatToolCallCardComponent } from '@ngaf/chat';
import type { ToolCallInfo } from '@ngaf/chat';
import { ChatToolCallCardComponent, type ToolCallInfo } from '@ngaf/chat';
```

## Basic Usage
## Status pill

```html
<chat-tool-call-card [toolCall]="myToolCall" />
```
| Status | Visual | aria-label |
|---|---|---|
| `running` | spinner (animated) | "Running" |
| `complete` | check (success color) | "Completed" |
| `error` | exclamation (error color) | "Failed" |

Where `myToolCall` is a `ToolCallInfo` object:
## Default-collapsed behavior

```typescript
const myToolCall: ToolCallInfo = {
id: 'call_abc123',
name: 'search_documents',
args: { query: 'Angular signals tutorial' },
result: { documents: ['doc1', 'doc2'] },
};
```
| Status | Default state |
|---|---|
| `running` | Expanded |
| `error` | Expanded |
| `complete` | Collapsed (when `[defaultCollapsed]="true"`, the default) |

## API
A user click on the header toggles open/closed. Once toggled, the user choice persists across status changes for the lifetime of the card.

### Inputs
## Inputs

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `toolCall` | `ToolCallInfo` | **Required** | The tool call data to display |
|---|---|---|---|
| `[toolCall]` | `ToolCallInfo` | — (required) | `{id, name, args, status?, result?}` |
| `[defaultCollapsed]` | `boolean` | `true` | Collapse on `complete`; pass `false` to keep cards always-expanded |

## ToolCallInfo Type
## ToolCallInfo

```typescript
interface ToolCallInfo {
id: string;
name: string;
args: unknown;
status?: 'pending' | 'running' | 'complete' | 'error';
result?: unknown;
}
```

| Property | Type | Description |
|----------|------|-------------|
| `id` | `string` | Unique identifier for the tool call |
| `name` | `string` | The tool function name (displayed in the header) |
| `args` | `unknown` | The arguments passed to the tool (displayed as formatted JSON) |
| `result` | `unknown \| undefined` | The tool's return value. When present, a green checkmark badge appears. |

## Card Behavior

### Collapsed State (Default)

The card header shows:
- A gear icon on the left
- The tool name in monospace font
- A green checkmark with "done" text when `result` is defined
- A chevron toggle on the right

### Expanded State

Clicking the header toggles expansion. The expanded area shows:
- **Inputs** section: The `args` value formatted as indented JSON
- **Output** section (when `result` is defined): The `result` value formatted as indented JSON

The component uses a `formatJson()` method that:
- Returns strings directly
- Serializes objects with `JSON.stringify(value, null, 2)`
- Falls back to `String(value)` if serialization fails

## Using with ChatToolCallsComponent

The `ChatToolCallsComponent` primitive iterates over tool calls from a `LangGraphAgent`. Combine it with `ChatToolCallCardComponent` to render a list of tool call cards:
## Basic usage

```html
<chat-tool-calls [ref]="chatRef">
<ng-template let-toolCall>
<chat-tool-call-card [toolCall]="asToolCallInfo(toolCall)" />
</ng-template>
</chat-tool-calls>
```
<chat-tool-call-card [toolCall]="tc" />

```typescript
import type { ToolCallWithResult } from '@langchain/langgraph-sdk';
import type { ToolCallInfo } from '@ngaf/chat';

asToolCallInfo(tc: ToolCallWithResult): ToolCallInfo {
return {
id: tc.id ?? '',
name: tc.name,
args: tc.args,
result: tc.result,
};
}
```

## Using in Message Templates

Display tool calls inline with AI messages:

```html
<chat-messages [ref]="chatRef">
<ng-template chatMessageTemplate="ai" let-message>
<div class="ai-message">{{ message.content }}</div>

<!-- Show tool calls attached to this AI message -->
<chat-tool-calls [ref]="chatRef" [message]="message">
<ng-template let-toolCall>
<chat-tool-call-card [toolCall]="asToolCallInfo(toolCall)" />
</ng-template>
</chat-tool-calls>
</ng-template>
</chat-messages>
<!-- Always-expanded -->
<chat-tool-call-card [toolCall]="tc" [defaultCollapsed]="false" />
```

## Styling

The card uses the following CSS custom properties:

| Variable | Applied To |
|----------|-----------|
| `--ngaf-chat-surface-alt` | Card background |
| `--ngaf-chat-separator` | Card border, section dividers |
| `--ngaf-chat-radius-card` | Card border radius |
| `--ngaf-chat-text` | Tool name and JSON content |
| `--ngaf-chat-text-muted` | Gear icon, chevron, section labels |
| `--ngaf-chat-success` | Checkmark and "done" badge |

## ARIA

- The header button has `aria-expanded` reflecting the current state
- The button has `aria-label="Toggle tool call details"`
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# ChatToolCallTemplateDirective

`ChatToolCallTemplateDirective` registers a per-tool-name template inside `<chat-tool-calls>`. The primitive collects all directive instances and dispatches each tool call to the template matching its `name`. A literal `"*"` registers a wildcard catch-all for any unmapped name.

**Selector:** `[chatToolCallTemplate]`

**Import:**

```typescript
import { ChatToolCallTemplateDirective, type ChatToolCallTemplateContext } from '@ngaf/chat';
```

## Template context

Each registered template receives:

| Variable | Type | Description |
|---|---|---|
| `let-call` (`$implicit`) | `ToolCall` | The full tool call: `{id, name, args, status, result?, error?}` |
| `let-status="status"` | `ToolCallStatus` | `'pending' \| 'running' \| 'complete' \| 'error'` |

## Examples

### Custom search-result card

```html
<chat-tool-calls [agent]="agent" [message]="msg">
<ng-template chatToolCallTemplate="search_web" let-call let-status="status">
<my-search-result-card
[query]="call.args.query"
[results]="call.result"
[status]="status"
/>
</ng-template>
</chat-tool-calls>
```

### Wildcard catch-all

```html
<chat-tool-calls [agent]="agent" [message]="msg">
<ng-template chatToolCallTemplate="search_web" let-call let-status="status">
<my-search-result-card [query]="call.args.query" [results]="call.result" />
</ng-template>

<!-- Anything not search_web falls through to here -->
<ng-template chatToolCallTemplate="*" let-call>
<chat-tool-call-card [toolCall]="call" />
</ng-template>
</chat-tool-calls>
```

### Project through `<chat>` directly

`<chat>` re-projects any `chatToolCallTemplate` directive inside it down to the inner `<chat-tool-calls>`:

```html
<chat [agent]="agent">
<ng-template chatToolCallTemplate="generate_image" let-call let-status="status">
<my-image-card
[prompt]="call.args.prompt"
[imageUrl]="call.result"
[status]="status"
/>
</ng-template>
</chat>
```

## Dispatch order

1. Per-tool template whose `name` exactly matches `tc.name`.
2. Wildcard template with `name === "*"`.
3. Default `<chat-tool-call-card>` (no template registered for either).
68 changes: 68 additions & 0 deletions apps/website/content/docs/chat/components/chat-tool-calls.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ChatToolCallsComponent

`ChatToolCallsComponent` renders all tool calls associated with an assistant message. By default sequential same-name calls auto-group into a labeled strip; consumers can register per-tool-name templates via the `chatToolCallTemplate` directive to fully replace the default card UX.

**Selector:** `chat-tool-calls`

**Import:**

```typescript
import { ChatToolCallsComponent } from '@ngaf/chat';
```

## Inputs

| Input | Type | Default | Description |
|---|---|---|---|
| `[agent]` | `Agent` | — (required) | Source of `agent.toolCalls()` |
| `[message]` | `Message \| undefined` | `undefined` | Filter to calls referenced by this message's `tool_use` content blocks |
| `[grouping]` | `'auto' \| 'none'` | `'auto'` | Auto-collapse adjacent same-name calls into a strip |
| `[groupSummary]` | `(name: string, count: number) => string` | built-in registry | Override the default strip label |

## Default group summaries

| Tool name shape | Default label |
|---|---|
| `search_*` | "Searched N sites" |
| `generate_*` | "Generated N items" |
| `read_*` | "Read N files" |
| `write_*` | "Wrote N files" |
| `list_*` | "Listed N items" |
| Anything else | "Called {name} N times" |

## Per-tool templates

Register a template per tool name (or `"*"` as a wildcard) — see [chat-tool-call-template](./chat-tool-call-template).

```html
<chat-tool-calls [agent]="agent" [message]="msg">
<ng-template chatToolCallTemplate="search_web" let-call let-status="status">
<my-search-result-card [query]="call.args.query" [results]="call.result" />
</ng-template>
</chat-tool-calls>
```

When a per-tool template is registered for a name, calls of that name skip grouping and are rendered each through the template (the consumer takes responsibility for visual density).

## Custom group summary

```html
<chat-tool-calls
[agent]="agent"
[message]="msg"
[groupSummary]="myGroupSummary"
/>
```

```typescript
myGroupSummary = (name: string, count: number) =>
name === 'fetch_user' ? `Fetched ${count} profiles` : `${name} × ${count}`;
```

## Disabling grouping

```html
<chat-tool-calls [agent]="agent" [message]="msg" [grouping]="'none'" />
```

Each call renders independently regardless of name adjacency.
24 changes: 24 additions & 0 deletions apps/website/content/docs/chat/components/chat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,27 @@ Under the hood, `ChatComponent` composes these primitives:
- `ChatErrorComponent` for error display
- `ChatInterruptComponent` for the interrupt banner
- `ChatThreadListComponent` for the sidebar

## Reasoning

When a model emits reasoning content (gpt-5 / o-series with `reasoning` blocks, Anthropic with `thinking` blocks, or any AG-UI agent emitting `REASONING_MESSAGE_*` events), the adapter populates `Message.reasoning` and `Message.reasoningDurationMs`. The `<chat>` composition automatically renders [`<chat-reasoning>`](./chat-reasoning) above the assistant response. No configuration required.

While reasoning is streaming, the pill shows "Thinking…" with a pulse dot and the body auto-expands so the user sees content arrive in real time. Once response text begins, the pill collapses to "Thought for Ns" (e.g. "Thought for 4s").

## Tool-call templates

Project a `<ng-template chatToolCallTemplate="…">` directly into `<chat>` to replace the default card UX for a specific tool name. The composition forwards the template into the inner [`<chat-tool-calls>`](./chat-tool-calls).

```html
<chat [agent]="agent">
<ng-template chatToolCallTemplate="generate_image" let-call let-status="status">
<my-image-card
[prompt]="call.args.prompt"
[imageUrl]="call.result"
[status]="status"
/>
</ng-template>
</chat>
```

A `chatToolCallTemplate="*"` wildcard catches any unmapped tool name. See [chatToolCallTemplate](./chat-tool-call-template) for the directive reference.
Loading
Loading