Skip to content

Chat Component ​

The main chat component with history sidebar.

New in 0.4.0

Approval and question cards, reattach and server-side Stop, usage limits, per-agent settings, uploads (drag and drop, paste, progress), localized errors, the opt-in portal experience, and controllerRef. Each group of props below links to its guide.

Import ​

tsx
import { Chat } from "@cognipeer/chat-ui";

Usage ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  theme="dark"
/>

Props ​

Chat works on its own and inside a ChatI18nProvider. It needs a baseUrl (or a transport); without an agentId it lists the server's agents and lets the person pick one.

Connection ​

PropTypeDefaultDescription
baseUrlstring-Base URL of the agent server API (not needed with transport)
transportChatTransport-Another backend, see Transports
agentIdstringfirst agentID of the agent to chat with
authorizationstring-Authorization header
headersRecord<string, string>-Custom headers
getHeaders() => headers | Promise<headers>-Fresh headers before every request
fetchtypeof fetch-Replacement for fetch
conversationIdstring-Conversation to open (loaded on mount and whenever it changes; autoSend never sends into it)
initialMessagesMessage[]-Messages to start with
streamingbooleantrueEnable streaming

Look ​

PropTypeDefaultDescription
theme"dark" | "light""dark"Theme mode. Read when the chat mounts: remount with a key to switch, see Theming
themeColorsChatTheme["colors"]-Any colour key, including the status colours success, danger, warning, info and their ...Bg tints
classNamestring-Container class name
showHistorybooleantrueShow history sidebar
defaultHistoryOpenbooleanfalseSidebar open at first (mobile)
showAgentNamebooleanfalseThe agent's name beside the time on each history row
showAvatars, showTimestampsbooleantrue, falseMessage chrome
inputPlaceholderstringlocalizedComposer placeholder
autoScrollbooleantrueScroll to new messages
autoFocusInputbooleanfalseFocus the composer on mount and after new chat / selection
enableMessageSearchbooleanfalseSearch box in the history
showStatusbooleantrueA badge on history rows that are working, waiting for you or stopped on their usage limit
enableCitationsbooleantrueSources under answers
readOnlybooleanfalseA transcript: the composer is closed with a notice

Language ​

PropTypeDefaultDescription
localestringenclosing ChatI18nProvider, else ?lang, else defaultLocaleLocale code (en, tr, or one you supply)
defaultLocalestring"en"Used when nothing else decides
onLocaleChange(locale) => void-Called when the locale changes
customMessagesRecord<locale, Record<key, string>>-Replace or add strings; merged over an enclosing provider's. Keys a locale leaves out fall back to English

Files ​

PropTypeDefaultDescription
enableFileUploadbooleantrueEnable file uploads (an agent's capabilities.attachments.enabled or metadata.enableFileUpload overrides it)
allowedFileTypesstring[]anyAllowed MIME types / extensions (an agent's allowedMimeTypes overrides it)
maxFileSizenumber10MBMax file size in bytes (an agent's maxFileSize overrides it)
maxFilesnumber10Max files per message, or per conversation (an agent's maxFiles counts per conversation)
maxFilesScope"message" | "conversation""message"What maxFiles counts
fileTransport"inline" | "upload" | "upload-on-select""inline"Files in the message body, uploaded when the message is sent, or uploaded as soon as they are picked
attachmentsOnlyMessagestringlocalizedText sent with files and no typed text ("" sends none)
renderImage(props: ImageRenderProps) => ReactNode-Draw images yourself, e.g. fetch them with credentials

Approvals and questions ​

PropTypeDefaultDescription
enableToolApprovalsbooleantrueAnswer approvals in this app
enableQuestionsbooleantrueAnswer questions in this app
allowArgumentEditbooleanfalseEdit a call's arguments in the approval card (an agent's capabilities.approvalArgsEdit overrides it)
expiryWarningSecondsnumber600Cards turn amber with this much time left
pendingActionComposerMode"block" | "cancel""block"What the composer does while a card waits (an agent's metadata.pendingActionComposerMode overrides it)
renderToolApproval, renderQuestionfunctionbuilt-in cardsReplace a card
onActionResolvedfunction-Called after the server accepted an answer

Usage limit and stopped answers ​

PropTypeDefaultDescription
showBudgetMeterbooleantrueThe spent / limit bar of a conversation that has a limit
allowBudgetContinuebooleanfalse"Continue" in the notice once a paused conversation's limit has room (an agent's capabilities.budget.allowContinue overrides it)
allowBudgetEditbooleanfalseRaise or remove the limit from the notice (an agent's capabilities.budget.allowEdit overrides it)
renderBudgetBanner(props: BudgetBannerProps) => ReactNodeBudgetBannerReplace the meter and the notice
renderStopNotice(props: StopNoticeRenderProps) => ReactNodebuilt-inReplace the notice under an answer that stopped early
onBudgetReached(info) => void-A turn ended on the conversation's usage limit

See Usage limits and Running turns, Stop and reattach.

Portal experience ​

Every one of these is off until you switch it on. See Portal Experience for what each does and the requests behind it.

PropTypeDefaultDescription
messageActions{ copy?, regenerate?, edit? }offButtons under messages (regenerate and edit need a transport with regenerateMessage / editMessage)
feedbackboolean | { enabled?, comments?, reasons? }offThumbs up / down with reasons and a comment (needs sendFeedback)
historyActions{ rename?, pin?, bulkDelete? }offRename, pin and select-and-delete in the history
exportMenuboolean | { enabled?, formats?, includeTools? }offDownload the conversation as Markdown, JSON or PDF
notifications{ tabBadge?, sound?, desktop? }offAlerts while the tab is in the background
disclosure{ aiLabel?, labelText?, links? }offAn AI label on answers and a row of legal links
footerTextstring-A short notice under the composer
announcementsAnnouncement[]-Dismissible notices above the chat
onAnnouncementDismiss(announcement) => void-A person closed one
starters, welcome, welcomeImageHostsConversationStarter[], string, string[]-Suggested first messages and a Markdown welcome (an agent's metadata.starters / metadata.welcome win)
initialAgentId, initialInput, autoSendstring, string, boolean-Deep links
controllerRefRef<ChatController>-prefill(text, { send }), newChat(), focus()
onClientToolCall, getMessageMetadatafunction-Tools of an embedding page and extra message metadata, see Embedding the Chat
onActionRequired, onFeedbackfunction-An approval or question started waiting; a rating was taken

Callback Props ​

PropTypeDescription
onMessageSent(message: Message) => voidCalled when user sends a message
onMessageReceived(message: Message) => voidCalled when response is received
onStreamText(chunk: string, fullText: string) => voidCalled on each text chunk
onToolCall(name: string, args: object) => voidCalled when tool is called
onToolResult(name: string, result: any) => voidCalled when tool returns
onConversationCreated(conv: Conversation) => voidCalled on new conversation
onConversationSelected(item: ConversationListItem) => voidCalled on conversation switch
onConversationTitleGenerated(id: string, title: string) => voidThe server named the conversation
onAgentChange(agentId: string) => voidThe selected agent changed
onError(error: Error) => voidCalled on error (a refused file, a failed request). Well-known server codes arrive localized, with code, status and serverMessage
onBudgetReached(info) => voidA turn ended on the conversation's usage limit
onActionRequired, onActionResolvedfunctionAn approval or question started waiting; the server accepted an answer
onFeedbackfunctionA rating was taken

onBackgroundStreamCompleted (a turn ended after the person had left that conversation) is a useChat callback. Chat handles it itself by refreshing the sidebar and does not forward it to your code.

Render Props ​

PropTypeDescription
renderMessageActions(props: MessageActionProps) => ReactNodeCustom message actions
renderAvatar(role) => ReactNodeCustom avatar
renderEmptyState() => ReactNodeCustom empty state
renderHeader(props) => ReactNodeCustom header. props has onMenuClick, agents, selectedAgentId, onAgentChange and actions: the built-in header buttons that are switched on (export menu, notifications bell), to place where you want them
renderHistoryHeader, renderHistoryFooter() => ReactNodeCustom sidebar header / footer
renderToolApproval, renderQuestionfunctionSee above
renderImagefunctionSee above
renderStopNotice, renderBudgetBannerfunctionSee above

Choosing what to switch on ​

I wantProps
A support assistant with approvalsnothing: cards are on by default
Operators who can raise a used-up limitallowBudgetEdit (or the agent's capabilities.budget.allowEdit)
Large uploads with progressfileTransport="upload-on-select"
A workplace portalmessageActions, feedback, historyActions, exportMenu, disclosure, announcements, starters, welcome (see Portal Experience)
A transcriptreadOnly
Another languagelocale, customMessages

Accessibility ​

The composer and its buttons are labelled; the message list is a live log (busy while a reply streams, so the finished answer is read once); an error is an alert. An approval or question that appears while the person is not typing takes keyboard focus and is announced, and focus returns to the composer once the last card is answered. Spinners and pulses stop under prefers-reduced-motion. A file going up has a progress bar with a value, the usage meter is a progressbar, a notice about a stopped answer is a status, and every row of the history is a button that the arrow keys, Home and End move between.

Examples ​

Basic ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
/>

With Auth ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  authorization="Bearer your-token"
/>

Light Theme ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  theme="light"
/>

Custom Colors ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  themeColors={{
    bgPrimary: "#1a1a2e",
    accentPrimary: "#e94560",
  }}
/>

With Callbacks ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  onMessageSent={(msg) => console.log("Sent:", msg)}
  onMessageReceived={(msg) => console.log("Received:", msg)}
  onError={(err) => console.error("Error:", err)}
/>

With Feedback Actions ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  renderMessageActions={({ message, isStreaming }) => {
    if (isStreaming || message.role !== "assistant") return null;
    
    return (
      <div className="flex gap-2">
        <button onClick={() => sendFeedback(message.id, "up")}>[thumbs-up]</button>
        <button onClick={() => sendFeedback(message.id, "down")}>[thumbs-down]</button>
      </div>
    );
  }}
/>

Images Only ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="vision"
  allowedFileTypes={["image/*"]}
  maxFiles={1}
/>

Your Own Strings ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  locale="tr"
  customMessages={{ tr: { "chat.input.placeholder": "Bir şey sorun..." } }}
/>

No History ​

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  showHistory={false}
/>

TypeScript ​

tsx
import { Chat, type ChatProps } from "@cognipeer/chat-ui";

const props: ChatProps = {
  baseUrl: "/api/agents",
  agentId: "assistant",
  theme: "dark",
};

<Chat {...props} />

Sizing ​

The Chat component fills its container:

tsx
// Full screen
<div className="h-screen">
  <Chat ... />
</div>

// Fixed height
<div style={{ height: 600 }}>
  <Chat ... />
</div>

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub