diff --git a/apps/apollo-vertex/app/patterns/ai-chat/page.mdx b/apps/apollo-vertex/app/patterns/ai-chat/page.mdx
index f963d74f7..5b3dbd2ee 100644
--- a/apps/apollo-vertex/app/patterns/ai-chat/page.mdx
+++ b/apps/apollo-vertex/app/patterns/ai-chat/page.mdx
@@ -11,8 +11,8 @@ A composable AI chat UI component for Apollo Vertex. Built with React, TypeScrip
## Features
- **TanStack AI Integration** — Works with `useChat` from `@tanstack/ai-react` and `UIMessage` types
-- **Composable** — `AiChat` is the shell, `AiChatMessage` renders messages, you iterate parts and render tools inline
-- **Type-Safe Tool Rendering** — Check `part.name` in the parts loop and TypeScript narrows `part.output` automatically
+- **One-prop wiring** — Pass `messages` and `status` from `useChat`; the component owns the message loop, scroll, and per-message action wiring
+- **Type-Safe Tool Rendering** — Pass a `renderToolPart` callback; TypeScript narrows `part.output` automatically when you check `part.name`
- **AgentHub Adapter** — Built-in adapter for the UiPath AgentHub normalized LLM endpoint (OpenAI + Anthropic models)
- **Conversational Agent Adapter** — Built-in adapter for a deployed UiPath Conversational Agent, with session management
- **Markdown Rendering** — Renders assistant responses with GitHub Flavored Markdown
@@ -49,7 +49,6 @@ npx shadcn@latest add @uipath/ai-chat
```tsx
import { useChat } from '@tanstack/ai-react';
import { AiChat } from '@/components/ui/ai-chat/components/ai-chat';
-import { AiChatMessage } from '@/components/ui/ai-chat/components/ai-chat-message';
import { createAgentHubConnection } from '@/components/ui/ai-chat/adapters/agenthub/adapter';
function BasicChat() {
@@ -60,39 +59,34 @@ function BasicChat() {
systemPrompt: 'You are a helpful assistant.',
});
- const { messages, sendMessage, isLoading, stop, clear, error } = useChat({
+ const { messages, sendMessage, status, stop, clear, error } = useChat({
connection,
});
return (
sendMessage(text)}
onStop={stop}
onClearChat={clear}
error={error}
title="AI Assistant"
- >
- {messages.map((message) => (
-
- ))}
-
+ />
);
}
```
## Tool Rendering
-Render tool output inline in the chat — just like TanStack AI's own examples. Define tools with `toolDefinition`, pass the input through as output in your client tool, then check `part.name` in the parts loop. TypeScript narrows `part.output` automatically.
+Define tools with `toolDefinition`, pass the input through as output in your client tool, then provide a `renderToolPart` callback to ``. The part is already narrowed to tool-call parts; check `part.name` and TypeScript narrows `part.output` to the right tool's output type automatically.
```tsx
import { z } from 'zod';
import { toolDefinition } from '@tanstack/ai';
import { clientTools } from '@tanstack/ai-client';
-import { stream, useChat } from '@tanstack/ai-react';
+import { useChat } from '@tanstack/ai-react';
import { AiChat } from '@/components/ui/ai-chat/components/ai-chat';
-import { AiChatMessage } from '@/components/ui/ai-chat/components/ai-chat-message';
// 1. Define tools — output passes input through for rendering
const showResultsInput = z.object({
@@ -110,9 +104,9 @@ const showResultsDef = toolDefinition({
const showResults = showResultsDef.client((input) => input);
const toolDefs = clientTools(showResults);
-// 2. Wire it up — iterate parts, render tools inline
+// 2. Wire it up — return tool output from renderToolPart
function ChatWithTools() {
- const { messages, sendMessage, isLoading, stop } = useChat({
+ const { messages, sendMessage, status, stop } = useChat({
connection,
tools: toolDefs,
});
@@ -120,26 +114,50 @@ function ChatWithTools() {
return (
sendMessage(text)}
onStop={stop}
- >
- {messages.map((message) => (
-
- {message.parts.map((part) => {
- // TypeScript narrows part.output when you check part.name
- if (part.type === 'tool-call' && part.name === 'show_results' && part.output) {
- return ;
- }
- return null;
- })}
-
- ))}
-
+ renderToolPart={(part) => {
+ // TypeScript narrows part.output when you check part.name
+ if (part.name === 'show_results' && part.output) {
+ return ;
+ }
+ return null;
+ }}
+ />
);
}
```
+`` keys each rendered part by `part.id`, so you don't need to add `key` yourself.
+
+## Message Actions
+
+Each message can show inline actions — copy, thumbs-up/down feedback, regenerate, and edit. Pass the optional callbacks directly to ``; it wires them to the right messages (assistant for feedback/regenerate, user for edit) and the action row appears automatically.
+
+```tsx
+const { messages, sendMessage, reload, status, stop } = useChat({ connection });
+
+ sendMessage(text)}
+ onStop={stop}
+ onFeedback={(messageId, type) => {
+ // type: 'positive' | 'negative' — send to your analytics / feedback endpoint
+ void recordFeedback({ messageId, type });
+ }}
+ getFeedback={(messageId) => feedbackById[messageId] ?? null}
+ onRegenerate={() => void reload()}
+ onEditMessage={(_messageId, content) => {
+ // Re-runs the conversation with the edited user message
+ void sendMessage(content);
+ }}
+/>
+```
+
+Copy is always available and needs no wiring. Feedback and edit only render when their callbacks are supplied.
+
## AgentHub Adapter
The built-in adapter for the **UiPath AgentHub** normalized LLM endpoint. It converts TanStack AI `UIMessage` arrays to the AgentHub wire format, calls the endpoint, and parses the SSE response back into AG-UI `StreamChunk` events.
@@ -186,7 +204,7 @@ const connection = createConversationalAgentConnection({
folderId, // number — the folder the agent lives in
});
-const { messages, sendMessage, isLoading, stop, clear, error } = useChat({
+const { messages, sendMessage, status, stop, clear, error } = useChat({
connection,
});
@@ -218,7 +236,7 @@ const tableTool = createDataFabricTableTool({
});
// Use dataFabricTableClient in your tools array, tableTool.toolPrompt in your system prompt,
-// and tableTool.renderTable(part.output, part.id) in your parts loop.
+// and tableTool.renderTable(part.output, part.id) in your renderToolPart callback.
```
### Filter types
@@ -271,7 +289,7 @@ const distributionTool = createDataFabricDistributionTool({
});
// Use dataFabricDistributionClient in your tools array, distributionTool.toolPrompt in your
-// system prompt, and distributionTool.renderDistribution(part.output, part.id) in your parts loop.
+// system prompt, and distributionTool.renderDistribution(part.output, part.id) in your renderToolPart callback.
```
### Dimension
@@ -319,7 +337,7 @@ const barTool = createDataFabricBarTool({
});
// Use dataFabricBarClient in your tools array, barTool.toolPrompt in your
-// system prompt, and barTool.renderBar(part.output, part.id) in your parts loop.
+// system prompt, and barTool.renderBar(part.output, part.id) in your renderToolPart callback.
```
### When to use bar vs distribution vs line
@@ -375,7 +393,7 @@ const lineTool = createDataFabricLineTool({
});
// Use dataFabricLineClient in your tools array, lineTool.toolPrompt in your
-// system prompt, and lineTool.renderLine(part.output, part.id) in your parts loop.
+// system prompt, and lineTool.renderLine(part.output, part.id) in your renderToolPart callback.
```
### When to use line vs distribution
@@ -408,7 +426,7 @@ const multiLineTool = createDataFabricMultiLineTool({
});
// Use dataFabricMultiLineClient in your tools array, multiLineTool.toolPrompt in your
-// system prompt, and multiLineTool.renderMultiLine(part.output, part.id) in your parts loop.
+// system prompt, and multiLineTool.renderMultiLine(part.output, part.id) in your renderToolPart callback.
```
### Metrics
@@ -455,7 +473,7 @@ const kpiTool = createDataFabricKpiTool({
});
// Use dataFabricKpiClient in your tools array, kpiTool.toolPrompt in your
-// system prompt, and kpiTool.renderKpi(part.output, part.id) in your parts loop.
+// system prompt, and kpiTool.renderKpi(part.output, part.id) in your renderToolPart callback.
```
### When to use KPI vs other chart tools
@@ -486,7 +504,7 @@ Filter and join schemas are shared with the other Data Fabric tools (including t
## Suggestion Buttons
-The `presentChoices` tool renders interactive suggestion buttons. Define the tool with a Zod schema, and render choices inline in the parts loop:
+The `presentChoices` tool renders interactive suggestion buttons. Define the tool with a Zod schema, and render choices from `renderToolPart`:
```tsx
import {
@@ -497,17 +515,20 @@ import {
// Add presentChoicesClient to your tools array and CHOICES_TOOL_PROMPT to your system prompt.
-// In your parts loop:
-{message.parts.map((part) => {
- if (part.type === 'tool-call' && part.name === 'presentChoices' && part.output) {
- return (
-