Skip to Content
PatternsAI Chat

AI Chat

A composable AI chat UI component for Apollo Vertex. Built with React, TypeScript, and Tailwind CSS. Designed to work with TanStack AI 

Features

  • TanStack AI Integration — Works with useChat from @tanstack/ai-react and UIMessage types
  • ComposableAiChat 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
  • 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
  • Data Fabric Table Tool — Display entity data as filterable tables with list, search, and range filters, and multi-entity joins
  • Data Fabric Distribution Tool — Render histogram charts for numeric or datetime fields with optional aggregations, filters, and joins
  • Suggestion Buttons — Interactive choice buttons rendered from tool results

Installation

Apollo Vertex components are published to a custom shadcn registry under the @uipath namespace. Before running the add command, register the namespace once in your project’s components.json so shadcn knows where to fetch from (otherwise the CLI will prompt you for a registry URL):

{ "registries": { "@uipath": "https://apollo-vertex.vercel.app/r/{name}.json" } }

Then install the component:

npx shadcn@latest add @uipath/ai-chat

This registries setup is a one-time configuration per project — every @uipath/* component on this site uses the same alias.

Quick Start

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() { const connection = createAgentHubConnection({ baseUrl: 'https://cloud.uipath.com/{org}/{tenant}/agenthub_/llm/api', model: { vendor: 'openai' as const, name: 'gpt-4o' }, accessToken: () => getAccessToken(), systemPrompt: 'You are a helpful assistant.', }); const { messages, sendMessage, isLoading, stop, clear, error } = useChat({ connection, }); return ( <AiChat messages={messages} isLoading={isLoading} onSendMessage={(text) => sendMessage(text)} onStop={stop} onClearChat={clear} error={error} title="AI Assistant" > {messages.map((message) => ( <AiChatMessage key={message.id} message={message} /> ))} </AiChat> ); }

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.

import { z } from 'zod'; import { toolDefinition } from '@tanstack/ai'; import { clientTools } from '@tanstack/ai-client'; import { stream, 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({ entityName: z.string(), columns: z.array(z.string()), }); const showResultsDef = toolDefinition({ name: 'show_results', description: 'Display a results table', inputSchema: showResultsInput, outputSchema: showResultsInput, }); const showResults = showResultsDef.client((input) => input); const toolDefs = clientTools(showResults); // 2. Wire it up — iterate parts, render tools inline function ChatWithTools() { const { messages, sendMessage, isLoading, stop } = useChat({ connection, tools: toolDefs, }); return ( <AiChat messages={messages} isLoading={isLoading} onSendMessage={(text) => sendMessage(text)} onStop={stop} > {messages.map((message) => ( <AiChatMessage key={message.id} message={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 <ResultsTable key={part.id} entity={part.output.entityName} columns={part.output.columns} />; } return null; })} </AiChatMessage> ))} </AiChat> ); }

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.

import { createAgentHubConnection, type AgentHubAdapterConfig } from '@/components/ui/ai-chat/adapters/agenthub/adapter'; const connection = createAgentHubConnection({ baseUrl: 'https://cloud.uipath.com/{org}/{tenant}/agenthub_/llm/api', model: { vendor: 'openai', name: 'gpt-4o' }, accessToken: () => getAccessToken(), systemPrompt: 'You are a helpful assistant.', maxTokens: 2048, temperature: 0.7, tools: toolDefs, });

The model.vendor field controls wire-format differences:

  • "openai" — flat tool definitions ({ name, description, parameters })
  • "anthropic" — Anthropic tool format ({ type: "custom", input_schema }), non-empty assistant content on tool-call messages
  • The X-UiPath-LlmGateway-NormalizedApi-ModelName header is always sent for routing
  • Responses are always OpenAI-compatible SSE regardless of the underlying model

Conversational Agent Adapter

The built-in adapter for a deployed UiPath Conversational Agent. It opens a session against the agent, forwards the latest user message, and bridges the agent’s streaming response back into TanStack AI StreamChunk events.

import { useChat } from '@tanstack/ai-react'; import { UiPath } from '@uipath/uipath-typescript/core'; import { createConversationalAgentConnection, type ConversationalAgentAdapterConfig, } from '@/components/ui/ai-chat/adapters/conversational-agent/adapter'; const sdk = new UiPath({ /* baseUrl, accessToken, ... */ }); const connection = createConversationalAgentConnection({ sdk, agentId, // number — the deployed agent id folderId, // number — the folder the agent lives in }); const { messages, sendMessage, isLoading, stop, clear, error } = useChat({ connection, }); // Dispose the session when the connection is no longer needed useEffect(() => () => connection.dispose(), [connection]);

Notes:

  • The adapter manages a single session per connection — call connection.dispose() when unmounting, or key the component by agent id so a new connection is created on switch.
  • Tools are driven by the agent itself, not by the client — the tools option is not used with this adapter.
  • Only the latest user message is sent per turn; prior history is tracked on the agent server-side.

Data Fabric Table Tool

The data_fabric_table tool renders entity data as interactive tables powered by @uipath/apollo-dashboarding. It supports server-side filtering — list filters, text search, and numeric ranges — so users can ask for filtered views directly in the chat.

import { createDataFabricTableTool, dataFabricTableClient, } from '@/components/ui/ai-chat/tools/data-fabric-table'; const tableTool = createDataFabricTableTool({ entities, // Record<string, Entity> — entity metadata with field names and types accessToken, // Bearer token for Data Fabric API dataFabricBaseUrl, // Base URL for Data Fabric proxy }); // Use dataFabricTableClient in your tools array, tableTool.toolPrompt in your system prompt, // and tableTool.renderTable(part.output, part.id) in your parts loop.

Filter types

The LLM can pass filters based on the user’s request:

  • List filter — match or exclude specific values: "show invoices where Status is Pending"
  • Search filter — text pattern matching (contains, startsWith, endsWith): "find customers starting with A"
  • Range filter (numeric) — numeric min/max: "show orders over $200"
  • Range filter (datetime) — ISO 8601 min/max: "show orders from the last 30 days". The tool prompt is given today’s date so the LLM can resolve relative phrases into absolute ISO dates before calling the tool.

Filters are passed through the table configuration to @uipath/apollo-dashboarding, which translates them to Data Fabric query filters server-side.

Multi-entity joins

The tool can combine data from related entities via the joins argument. The entityName field is the primary entity, and each join supplies the entity to attach and an on clause with EntityName.FieldName references:

{ "entityName": "Invoice", "dimensions": ["Invoice.Number", "Invoice.Total", "Customer.Name"], "joins": [ { "type": "LEFT", "entity": "Customer", "on": { "left": "Invoice.CustomerId", "right": "Customer.Id" } } ] }

When joins are present, dimensions and filter fields must use qualified EntityName.FieldName names (using the exact entity names from the Entity Reference — never aliases). The join condition goes in joins[].on; don’t also add it as a filter.


Data Fabric Distribution Tool

The data_fabric_distribution tool renders a histogram from a Data Fabric entity by binning a single numeric or datetime field. It shares the filter and join system with the table tool, and adds an optional aggregation metric.

import { createDataFabricDistributionTool, dataFabricDistributionClient, } from '@/components/ui/ai-chat/tools/data-fabric-distribution'; const distributionTool = createDataFabricDistributionTool({ entities, // Record<string, Entity> — entity metadata with field names and types accessToken, // Bearer token for Data Fabric API dataFabricBaseUrl, // Base URL for Data Fabric proxy }); // Use dataFabricDistributionClient in your tools array, distributionTool.toolPrompt in your // system prompt, and distributionTool.renderDistribution(part.output, part.id) in your parts loop.

Dimension

The dimension is the field used for binning and must be numeric or datetime:

  • Datetime dimensions bin by time (e.g. orders per month).
  • Numeric dimensions bin by value range.

Metric

Omit metric entirely for the default COUNT of records per bin. To plot an aggregated numeric field, pass { aggregation, field }:

  • COUNT — records per bin (default; field optional, picks the primary key).
  • SUM, AVG, MIN, MAX — applied to a numeric field.
{ "entityName": "Order", "dimension": "OrderDate", "metric": { "aggregation": "SUM", "field": "Total" } }

Filters and joins

Filters and joins use the same schemas as the table tool (including the new datetime range filter). When joins are present, the dimension and metric.field must use qualified EntityName.FieldName names.


Suggestion Buttons

The presentChoices tool renders interactive suggestion buttons. Define the tool with a Zod schema, and render choices inline in the parts loop:

import { presentChoicesClient, renderChoices, CHOICES_TOOL_PROMPT, } from '@/components/ui/ai-chat/tools/choices'; // 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 ( <div key={part.id}> {renderChoices(part.output, { onAction: (text) => sendMessage(text) })} </div> ); } return null; })}

Try it out — type “give me some choices” in the demo above to see suggestion buttons in action.


API Reference

<AiChat>

Chat shell component. Handles layout, scroll, input, loading indicator, suggestions, and errors. Render messages as children.

PropTypeDefaultDescription
messagesUIMessage[]requiredMessages from useChat
isLoadingbooleanrequiredLoading state from useChat
onSendMessage(content: string) => voidrequiredSend handler
onStop() => voidrequiredStop/abort handler
childrenReactNodeMessage list (typically messages.map(...))
onClearChat() => voidClear handler
assistantNamestring"AI Assistant"Assistant display name
titlestringChat title in the header
emptyStateReactNodeCustom empty state
placeholderstringInput placeholder
showClearButtonbooleantrueShow the clear button
errorError | nullInline error banner

<AiChatMessage>

Renders a single message with avatar, name, markdown text, and children for custom content (tool output).

PropTypeDefaultDescription
messageUIMessagerequiredThe message to render
assistantNamestring"AI Assistant"Assistant display name
childrenReactNodeCustom content rendered below the message text (tool output, etc.)

AgentHubAdapterConfig

Configuration for the AgentHub adapter.

PropertyTypeDefaultDescription
baseUrlstringrequiredAgentHub base URL (/chat/completions is appended)
model{ vendor: 'openai' | 'anthropic'; name: string }requiredModel config
accessTokenstring | () => string | nullrequiredBearer token (refreshed per request if function)
systemPromptstring | () => stringSystem prompt prepended to messages (function form is called per request)
maxTokensnumber2048Max response tokens
temperaturenumber0.7Sampling temperature
toolsReadonlyArray<AnyClientTool>Client tools — wire-format definitions are derived automatically