Plugin Chatbot
Chat interface component with message history, typing indicators, and customizable avatars.
v5.4+ — the chat surface is now composed from vendored Vercel AI Elements (MIT). The public
chatbotschema andChatbotEnhanced/FloatingChatbotprops are unchanged. A new optionalsuggestions: string[]field renders chip prompts in the empty state.
For advanced compositions, the underlying elements are re-exported:
import { AIElements } from '@object-ui/plugin-chatbot';
// AIElements.Conversation, AIElements.Message, AIElements.PromptInput, ...Installation
npm install @object-ui/plugin-chatbotExample schemas
Basic Chatbot
Basic Chatbot
{
"type": "chatbot",
"messages": [
{
"id": "1",
"role": "assistant",
"content": "Hello! How can I help you today?"
}
],
"placeholder": "Type your message...",
"showTimestamp": false,
"autoResponse": true,
"autoResponseText": "Thank you for your message! This is an automated response.",
"autoResponseDelayMs": 1000,
"maxHeight": "400px"
}Chatbot with Timestamps
Chatbot With Timestamps
{
"type": "chatbot",
"messages": [
{
"id": "1",
"role": "assistant",
"content": "Welcome to our support chat!",
"timestamp": "9:00 AM"
},
{
"id": "2",
"role": "user",
"content": "I need help with my account",
"timestamp": "9:01 AM"
},
{
"id": "3",
"role": "assistant",
"content": "I'd be happy to help you with your account. What specific issue are you experiencing?",
"timestamp": "9:01 AM"
}
],
"showTimestamp": true,
"placeholder": "Ask a question...",
"autoResponse": true,
"autoResponseText": "I'll look into that for you right away.",
"maxHeight": "450px"
}Customer Support Chat
Customer Support Chat
{
"type": "chatbot",
"messages": [
{
"id": "system-1",
"role": "system",
"content": "Support chat session started"
},
{
"id": "1",
"role": "assistant",
"content": "Hi! I'm Sarah from customer support. How may I assist you today?",
"avatarFallback": "SA"
},
{
"id": "2",
"role": "user",
"content": "I'm having trouble accessing my dashboard",
"avatarFallback": "JD"
},
{
"id": "3",
"role": "assistant",
"content": "I understand. Let me help you with that. Could you tell me if you're seeing any error messages?",
"avatarFallback": "SA"
}
],
"placeholder": "Type your message here...",
"userAvatarFallback": "JD",
"assistantAvatarFallback": "SA",
"showTimestamp": true,
"autoResponse": true,
"autoResponseDelayMs": 1500,
"autoResponseText": "Let me check that for you...",
"maxHeight": "500px"
}Usage
Basic Usage
// Import once in your app entry point
import '@object-ui/plugin-chatbot'
import type { ChatbotSchema } from '@object-ui/types'
// Use in schemas
const schema: ChatbotSchema = {
type: 'chatbot',
messages: [
{
id: '1',
role: 'assistant',
content: 'Hello! How can I help you?'
}
],
placeholder: 'Type your message...',
autoResponse: true
}Features
- Message History: Display user and assistant chat messages
- System Messages: Show system notifications in the chat
- Timestamps: Optional timestamp display for each message
- Custom Avatars: Configurable avatar images and fallback text
- Auto-scroll: Automatically scroll to newest messages
- Typing Indicator: Built-in typing indicator component
- Auto-response: Demo mode with automatic responses
- Responsive Floating Panel: Console assistants keep the panel inside safe browser gutters and hide the FAB while the chat is open
- Conversation States: Empty streaming messages render as an assistant responding indicator, the submit control becomes Stop while streaming, and backend errors collapse into a retryable notice with optional details
- Lightweight: Pure React components with minimal dependencies
Schema API
{
type: 'chatbot',
messages?: ChatMessage[],
placeholder?: string,
showTimestamp?: boolean,
disabled?: boolean,
userAvatarUrl?: string,
userAvatarFallback?: string,
assistantAvatarUrl?: string,
assistantAvatarFallback?: string,
maxHeight?: string,
autoResponse?: boolean,
autoResponseText?: string,
autoResponseDelayMs?: number,
onSend?: (content: string, messages: ObjectChatMessage[]) => void,
className?: string,
// AI / service-ai integration fields
api?: string,
conversationId?: string,
systemPrompt?: string,
model?: string,
streamingEnabled?: boolean,
headers?: Record<string, string>,
requestBody?: Record<string, unknown>,
onError?: (error: Error) => void,
}ChatMessage
{
id: string,
role: 'user' | 'assistant' | 'system' | 'tool',
content: string,
timestamp?: string | Date,
metadata?: any,
streaming?: boolean,
toolInvocations?: ChatToolInvocation[],
}Properties
showTimestamp through onSend below are declared on ChatbotSchema
(@object-ui/types) — previously they existed only in an anonymous type local
to the renderer, referenceable, validatable and documentable by nothing
outside that one file (objectui#6169).
This page documents three registrations - chatbot, chatbot-enhanced and
chatbot-floating - and they do not all read the same keys. A row whose
description carries no bolded scope note is read by all three. The rows only
some of them read say so in bold at the start of the description, and name what
to author instead on the registrations that ignore the key.
Each registration has its own importable authoring-face type in
@object-ui/types (objectui#7655): ChatbotSchema for chatbot,
ChatbotEnhancedSchema for chatbot-enhanced and ChatbotFloatingSchema for
chatbot-floating. Each declares exactly the keys its registration reads - the
nineteen shared rows below are one declaration the two newer faces pick off
ChatbotSchema by name, and a scoped row is declared only on the face(s) whose
registration reads it. A key a registration ignores is therefore not a declared
member of its type. In a typed literal it is a compile error (objectui#8347
removed the BaseSchema index signature that typed an unlisted key any), but
it still parses (the Zod twins are .passthrough()). It is then dropped silently at render
time, on all three registrations alike. chatbot-floating used to be the
exception: its registration ended its panel element with an unfiltered props
spread, so three keys its type does not declare (processVisibility, surface,
showAvatars) did reach the panel. That channel was accidental, not a contract
to author against, and objectui#7708 closed it - the spread is filtered through
the DOM-safe whitelist and moved ahead of every named prop, the shape the two
sibling registrations already used. A chatbot-floating node's declared set is
now also its delivered set. That is why the scope is spelled out here as well as
in the types.
The table below is that shared chat surface. chatbot-floating declares
six more keys of its own - the six floatingConfig entries - which no row
below carries and which the other two registrations have no trigger or panel
to apply. They are documented in their own table after this one, under
chatbot-floating panel and trigger keys.
| Property | Type | Default | Description |
|---|---|---|---|
messages | array | [] | Initial chat messages |
placeholder | string | 'Type your message...' | Input field placeholder text |
showTimestamp | boolean | false | Display timestamps for messages |
disabled | boolean | false | Disable chat input |
userAvatarUrl | string | - | URL for user avatar image |
userAvatarFallback | string | 'You' | Fallback text for user avatar |
assistantAvatarUrl | string | - | URL for assistant avatar image |
assistantAvatarFallback | string | 'AI' | Fallback text for assistant avatar |
maxHeight | string | '500px' | chatbot and chatbot-enhanced only (declared on ChatbotSchema and ChatbotEnhancedSchema; ChatbotFloatingSchema does not declare it). Maximum height of the chat message container, as a CSS length. chatbot-floating does not read it: its panel is sized by floatingConfig.panelHeight (a number of pixels, default 520), and the panel pins its inner chat to maxHeight: '100%' so it fills that panel - a maxHeight authored on a floating node would be overridden even if it were forwarded. Size a floating chatbot with floatingConfig.panelHeight instead |
autoResponse | boolean | false | Enable auto-response (demo mode, ignored when api is set) |
autoResponseText | string | - | Text for auto-response |
autoResponseDelayMs | number | 1000 | Delay before auto-response, in milliseconds (renamed from autoResponseDelay, which is now refused with a message naming this key) |
onSend | function | - | Callback when message is sent |
className | string | '' | Additional Tailwind CSS classes |
api | string | - | Backend SSE endpoint (e.g., /api/v1/ai/chat). Enables AI streaming mode |
conversationId | string | - | Multi-turn conversation identifier |
systemPrompt | string | - | System prompt to configure assistant behavior |
model | string | - | AI model identifier (e.g., gpt-4o) |
streamingEnabled | boolean | true | Enable SSE streaming for AI responses |
headers | object | - | Additional headers for API requests |
requestBody | object | - | Additional body parameters sent with each API request. Authored on the node as requestBody; the renderer forwards it to the chat runtime under its own body option. Writing body on a chatbot node instead is refused by name - that key is retired there (objectui#8572) and the refusal names requestBody as the replacement. The refusal is delivered when the document is parsed (objectui validate) or type-checked, not while it renders. objectui check does not deliver it: a file whose root carries a structural key (body is one, children another) is recognised by that key and never parsed against the schema, so check counts it as recognised but not validated and exits 0; objectui validate delivers the refusal |
surface | 'card' | 'plain' | 'card' | chatbot-enhanced only (declared on ChatbotEnhancedSchema). Controls whether the chat renders as a bordered panel ('card') or a frameless full-page workspace ('plain'). chatbot renders the plain chat component, which has no such chrome to switch, and does not read this key. chatbot-floating has no named read for it and ChatbotFloatingSchema does not declare it; its panel is a ChatbotEnhanced, and an authored value used to reach that panel through the registration's unfiltered props spread, an accidental channel objectui#7708 fenced - it is dark there now, so switch the node to chatbot-enhanced if you need the frameless chrome |
processVisibility | 'hidden' | 'summary' | 'debug' | 'summary' | chatbot-enhanced only (declared on ChatbotEnhancedSchema; ChatbotSchema still declares it too, though the chatbot registration has no read for it). Controls how much agent reasoning and tool detail is shown. chatbot renders the plain chat component, which has no agent-process display to configure at all - switch the node to chatbot-enhanced if you need one. chatbot-floating has no named read for it and ChatbotFloatingSchema does not declare it; an authored value used to reach its panel through the registration's unfiltered props spread, an accidental channel objectui#7708 fenced - it is dark there now, and there is no floating-side substitute to author |
onError | function | - | Error callback for streaming/API errors |
There are no loading, showAvatars, userAvatar, assistantAvatar,
markdown or height keys. All six were declared on ChatbotSchema, mirrored
on its Zod twin, and read by no registration - a showAvatars: true or a
height: 400 on a chatbot node type-checked, parsed green, and changed
nothing. objectui#7703 retired them (ADR-0049 enforce-or-remove, one decision per
key): each is a never tombstone on the declaration and a named refusal on the
twin, so writing one is now a compile error and a parse error that says what
to write instead. Four have a live replacement, and it is the key the
registrations already read:
| retired | write instead |
|---|---|
userAvatar | userAvatarUrl (with userAvatarFallback) |
assistantAvatar | assistantAvatarUrl (with assistantAvatarFallback) |
height | maxHeight on chatbot / chatbot-enhanced; floatingConfig.panelHeight on chatbot-floating |
markdown | enableMarkdown, on a chatbot-enhanced or chatbot-floating node |
loading | nothing - chat progress is runtime state the chat runtime owns, never authored |
showAvatars | nothing - a chatbot node already renders an avatar beside every message; the images are the *AvatarUrl keys above |
showAvatars is the one of the six that was not always inert: ChatbotEnhanced
has such a prop, and until objectui#7708 fenced it the chatbot-floating
registration's unfiltered props spread delivered an authored value to it. It is
dark on all three registrations now. The other five were live on no path at any
time.
There is no maxToolRoundtrips key: tool-loop caps live on the agent, as
planning.maxIterations. A chat node cannot honour a cap because the tool loop
runs on the server agent, so objectui#5605 retired the key on all three chat
nodes: writing it is a compile error and a parse error that names
planning.maxIterations.
chatbot-floating panel and trigger keys
The six keys below are declared in the chatbot-floating registration's own
inputs (packages/plugin-chatbot/src/renderer.tsx). They configure the
floating action button and the panel it opens; the chatbot and
chatbot-enhanced registrations render neither and ignore them. floatingConfig
is declared on ChatbotFloatingSchema only. It used to be declared on
ChatbotSchema as well, so authoring it on a chatbot node type-checked and
parsed, and was then dropped at render time, because the chatbot node never
read it. objectui#6152 retired it there: writing floatingConfig on a chatbot
node is now a compile error and a parse error that names chatbot-floating,
the node type that reads it. ChatbotEnhancedSchema never declared it.
There is no displayMode key. The presentation is selected by the node's
own type: author a chatbot-floating node for the trigger-and-panel
presentation, and a chatbot or chatbot-enhanced node for an inline one.
displayMode ('inline' | 'floating') used to be declared on both faces,
offered as a Display Mode control in the designer and seeded as
'floating' into every node the designer created - and read by nothing: it was
a second spelling of the choice type already makes, so 'inline' on a
chatbot-floating node changed nothing and 'floating' on a chatbot node
produced no trigger. objectui#7654 retired it (maintainer ruling, 2026-09-05):
the declaration is a never tombstone on ChatbotSchema and
ChatbotFloatingSchema, so writing the key against either face is now a
compile error, and the designer control and default are gone. Stored documents
that still carry the key parse exactly as they did - it never had a Zod arm and
the twins are passthrough - and the value is ignored at render time, as it
always was.
| Property | Type | Default | Description |
|---|---|---|---|
floatingConfig.position | 'bottom-right' | 'bottom-left' | 'bottom-right' | Corner the trigger sits in; the panel is anchored to the same side |
floatingConfig.defaultOpen | boolean | false | Whether the panel is already open when the node mounts |
floatingConfig.panelWidth | number | 400 | Panel width in pixels, applied from the sm breakpoint up - below it the panel is full-bleed. Snapped to a step, see below |
floatingConfig.panelHeight | number | 520 | Panel height in pixels, as a number - not a CSS length string. This is the key that sizes a floating chatbot: maxHeight is a string, belongs to the two inline registrations, and is not read here. Snapped to a step, see below |
floatingConfig.title | string | 'Chat' | Text in the panel header, and the panel's aria-label |
floatingConfig.triggerSize | number | 56 | Diameter of the floating action button in pixels. Snapped to a step, see below |
The three size keys snap to the nearest declared step. FloatingChatbotPanel
and FloatingChatbotTrigger resolve each number through a fixed table of
Tailwind classes and fall back to the closest entry, so a number outside the
table does not render at that size - a panelHeight of 530 renders at 520:
panelWidth- 300, 320, 340, 360, 380, 400, 420, 440, 450, 460, 480, 500, 520, 560, 600, 640, 720, 800panelHeight- 360, 400, 420, 440, 480, 500, 520, 560, 600, 640, 720, 800triggerSize- 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80
On small screens panelHeight is additionally capped to the viewport
(min(step, 100svh - 6rem - safe-area-inset-bottom)), and while the panel is
fullscreen it ignores both size keys and fills the screen.
Authored on the node, with the node's own type - ChatbotFloatingSchema pins
type to 'chatbot-floating' and declares floatingConfig, so this fence
compiles against the published types. (Until objectui#7655 no type could
annotate a floating node - ChatbotSchema pins type to 'chatbot' - and
this example had to be untyped JSON.)
import type { ChatbotFloatingSchema } from '@object-ui/types';
const supportChat: ChatbotFloatingSchema = {
type: 'chatbot-floating',
messages: [], // seed with your own ChatMessage values
floatingConfig: {
position: 'bottom-left',
defaultOpen: false,
panelWidth: 400,
panelHeight: 520,
title: 'Support',
triggerSize: 56,
},
placeholder: 'Ask us anything...',
};The config object also has its own exported type:
import type { FloatingChatbotConfig } from '@object-ui/types';
const floatingConfig: FloatingChatbotConfig = {
position: 'bottom-left',
panelWidth: 400,
panelHeight: 520, // pixels, as a number - '520px' does not type-check
title: 'Support',
triggerSize: 56,
};Operating Modes
The chatbot supports two modes, automatically selected based on the api field:
Local/Demo Mode (default)
When api is not set, the chatbot operates in local mode with optional auto-response:
import type { ChatbotSchema } from '@object-ui/types';
const schema: ChatbotSchema = {
type: 'chatbot',
messages: [], // seed with your own ChatMessage values
autoResponse: true,
autoResponseText: 'Thanks!',
autoResponseDelayMs: 1000,
};AI Streaming Mode (service-ai)
When api is set, the chatbot uses @ai-sdk/react for real SSE streaming:
import type { ChatbotSchema } from '@object-ui/types';
const schema: ChatbotSchema = {
type: 'chatbot',
api: '/api/v1/ai/chat',
model: 'gpt-4o',
systemPrompt: 'You are a helpful assistant.',
streamingEnabled: true,
messages: [],
};Message Kinds
User Messages
Messages from the user appear on the right side with primary styling:
import type { ChatMessage } from '@object-ui/types';
const userMessage: ChatMessage = {
id: '1',
role: 'user',
content: 'Hello!',
timestamp: '10:30 AM',
};Assistant Messages
Messages from the assistant appear on the left side:
import type { ChatMessage } from '@object-ui/types';
const assistantMessage: ChatMessage = {
id: '2',
role: 'assistant',
content: 'Hi! How can I help?',
timestamp: '10:30 AM',
};System Messages
System messages appear centered with muted styling:
import type { ChatMessage } from '@object-ui/types';
const systemMessage: ChatMessage = {
id: '3',
role: 'system',
content: 'Chat session started',
};Tool Messages (AI Mode)
Tool messages represent results from tool invocations during AI streaming. They are generated automatically by the vercel/ai SDK when the backend performs tool calls (e.g., fetching weather, querying a database). The SDK may emit tool messages as well as populate the assistant message's toolInvocations array.
By default, ChatbotEnhanced renders tool invocations as a compact agent activity summary. Repeated calls are grouped, raw tool names are hidden, and reasoning text is not shown. Use processVisibility="debug" for developer/admin views that need the full reasoning panel, raw tool names, parameters, and results. Use processVisibility="hidden" to suppress non-interactive activity entirely; approval and draft-review actions remain visible.
Each step is named by the locale pack's chatbot.tool.<tool_name> entry, a
short sentence in the user's language ("List the existing objects"). A tool
the pack has no entry for falls back to its name, title-cased.
The AI build conversation's two cards draw only what the build agent's tool
results declare. The plan card (propose_blueprint) reads the result's
planCard: a chip per field with the format its sample data is held to, a
dashed chip for a field the agent added on its own, the record's name field,
one line per view, the assumptions, the sample data the build will stage, and
how the build will land (posture: live at once, or saved as a draft).
A plan whose result says decision: 'iterate_or_new' overlaps an app the user
already has (existingAppOverlap, by name and label). Its card draws no
"Build it" button: in its place it asks whether to keep improving that app or
build a separate one, naming the app, and each choice sends its answer as a
chat message (planExtendExistingMessage(app) and planBuildNewAppMessage,
which the console picks by the conversation's language). The question survives
a reload from server history and from the console's message cache.
The result card (apply_blueprint, then a later verify_build in the same
turn) reads checklist: a passed/total chip, the verified lines in two
columns, and the app and package that "Open app" and "View in Studio" open.
A result without those keys keeps the simpler rows the cards drew before.
The change card (a changes_proposed result) draws what the copilot's tools
say about how a change will land. When the result carries a posture, the
card is headed "Change plan · N changes" and draws one row per change: a
symbol for add, change or remove, the kind (field, view, object), the
object · field it touches, and its type and parameters in small type. A
change to an app that is live and holds records (posture.mode: 'draft') is
saved as a Studio draft: the card's note says so in amber, and the primary
button reads "Save as draft" when primaryAction is save_draft (the message
it sends is still changesConfirmMessage). Once applied, the card shows the
result's appliedCard title and lines, "Review changes and publish" through
onReviewDrafts(packageId), and "Undo" through onDiscardDrafts(packageId).
Undo is meant for POST /api/v1/packages/:id/discard-drafts, which discards
every pending draft of the package, so the card asks first whenever other
drafts are pending: it reads draftState.others from the result, and the live
count from fetchPendingDraftCount when the host provides one. A host without
onReviewDrafts keeps the inline Publish button; a result without these keys
draws the card as before.
Use surface="plain" for full-page chat workspaces where the surrounding app
already provides navigation chrome. The default surface="card" remains a
better fit for embedded dashboards, side panels, and floating chat windows.
Both keys are authorable as metadata on a chatbot-enhanced node, typed with
that node's own face (objectui#7655):
import type { ChatbotEnhancedSchema } from '@object-ui/types';
const workspace: ChatbotEnhancedSchema = {
type: 'chatbot-enhanced',
messages: [], // seed with your own ChatMessage values
api: '/api/v1/ai/chat',
surface: 'plain',
processVisibility: 'debug',
enableFileUpload: true,
};Console chat surfaces also keep a sanitized browser-side display cache for the current conversation. When a conversation can be reopened but the server returns no message rows, the UI restores user/assistant text and grouped tool names plus states. Reasoning, tool parameters, and raw tool results are not stored in this cache.
import type { ChatMessage } from '@object-ui/types';
const toolMessage: ChatMessage = {
id: '4',
role: 'assistant',
content: 'The weather in SF is 68°F.',
toolInvocations: [
{
toolCallId: 'tc-1',
toolName: 'getWeather',
args: { city: 'San Francisco' },
result: { temp: 68, condition: 'Sunny' },
state: 'result',
},
],
};Examples
Simple AI Chat
import type { ChatbotSchema } from '@object-ui/types';
const aiChat: ChatbotSchema = {
type: 'chatbot',
messages: [
{
id: 'welcome',
role: 'assistant',
content: 'Hello! I\'m your AI assistant. Ask me anything!'
}
],
placeholder: 'Ask me a question...',
assistantAvatarFallback: 'AI',
userAvatarFallback: 'You',
autoResponse: true,
autoResponseText: 'That\'s a great question! Let me think about that...',
maxHeight: '600px'
}Support Ticket Chat
import type { ChatbotSchema } from '@object-ui/types';
const supportChat: ChatbotSchema = {
type: 'chatbot',
messages: [
{
id: 'sys-1',
role: 'system',
content: 'Ticket #12345 - Account Access Issue'
},
{
id: '1',
role: 'assistant',
content: 'Hi! I\'m here to help with your account access issue.',
avatarFallback: 'SP',
timestamp: '2:15 PM'
}
],
placeholder: 'Describe your issue...',
showTimestamp: true,
userAvatarFallback: 'JD',
assistantAvatarFallback: 'SP',
className: 'w-full max-w-3xl mx-auto'
};Sales Bot
import type { ChatbotSchema } from '@object-ui/types';
const salesBot: ChatbotSchema = {
type: 'chatbot',
messages: [
{
id: '1',
role: 'assistant',
content: 'Welcome! I\'m here to help you find the perfect product. What are you looking for today?',
avatarFallback: 'SB'
}
],
placeholder: 'Tell us what you need...',
assistantAvatarFallback: 'SB',
userAvatarFallback: 'You',
autoResponse: true,
autoResponseText: 'Great choice! Let me show you some options...',
autoResponseDelayMs: 1200,
maxHeight: '500px',
className: 'border-2 border-primary rounded-xl'
};Multi-agent Chat
import type { ChatbotSchema } from '@object-ui/types';
const multiAgentChat: ChatbotSchema = {
type: 'chatbot',
messages: [
{
id: '1',
role: 'assistant',
content: 'Hello! Sarah from Sales here.',
avatarFallback: 'SA'
},
{
id: '2',
role: 'user',
content: 'I need help with pricing',
avatarFallback: 'CU'
},
{
id: '3',
role: 'system',
content: 'Transferring to Finance team...'
},
{
id: '4',
role: 'assistant',
content: 'Hi! Mike from Finance. I can help with that.',
avatarFallback: 'MI'
}
],
showTimestamp: true,
userAvatarFallback: 'CU'
};Custom Avatars
Avatar Images
import type { ChatbotSchema } from '@object-ui/types';
const schema: ChatbotSchema = {
type: 'chatbot',
userAvatarUrl: 'https://example.com/user-avatar.jpg',
assistantAvatarUrl: 'https://example.com/bot-avatar.jpg',
messages: [], // seed with your own ChatMessage values
};Avatar Fallbacks
When images aren't available, fallback text is displayed:
import type { ChatbotSchema } from '@object-ui/types';
const schema: ChatbotSchema = {
type: 'chatbot',
userAvatarFallback: 'JD', // User initials
assistantAvatarFallback: 'AI', // Bot identifier
messages: [], // seed with your own ChatMessage values
};Per-message Avatars
Override avatars for individual messages:
import type { ChatbotEnhancedMessage } from '@object-ui/plugin-chatbot';
const message: ChatbotEnhancedMessage = {
id: '1',
role: 'assistant',
content: 'Message content',
avatar: 'https://example.com/special-avatar.jpg',
avatarFallback: 'SP',
};Event Handling
onSend Callback
Handle message sending in your application:
import type { ChatbotSchema } from '@object-ui/types';
const schema: ChatbotSchema = {
type: 'chatbot',
messages: [], // seed with your own ChatMessage values
onSend: (content, allMessages) => {
console.log('User sent:', content);
console.log('All messages:', allMessages);
// Send to your backend
fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message: content }),
});
},
};Customization
Container Styling
import type { ChatbotSchema } from '@object-ui/types';
const schema: ChatbotSchema = {
type: 'chatbot',
className: 'w-full max-w-2xl mx-auto border-2 rounded-xl shadow-lg',
maxHeight: '600px',
messages: [], // seed with your own ChatMessage values
};Responsive Heights
import type { ChatbotSchema } from '@object-ui/types';
const schema: ChatbotSchema = {
type: 'chatbot',
maxHeight: '400px', // or use Tailwind: 'h-96'
className: 'sm:max-h-[500px] lg:max-h-[600px]',
messages: [], // seed with your own ChatMessage values
};TypeScript Support
Each of the three registrations has its own authoring-face type:
ChatbotSchema (chatbot), ChatbotEnhancedSchema (chatbot-enhanced) and
ChatbotFloatingSchema (chatbot-floating) - see the typed examples under
Tool Messages and chatbot-floating panel and trigger keys above.
import type { ChatbotSchema, ChatbotEnhancedSchema, ChatbotFloatingSchema, ChatMessage, ChatToolInvocation } from '@object-ui/types'
import { useObjectChat } from '@object-ui/plugin-chatbot'
// Basic messages
const messages: ChatMessage[] = [
{
id: '1',
role: 'assistant',
content: 'Hello!'
}
]
// Local/demo mode
const demoSchema: ChatbotSchema = {
type: 'chatbot',
messages,
placeholder: 'Type here...',
showTimestamp: true
}
// AI streaming mode (service-ai)
const aiSchema: ChatbotSchema = {
type: 'chatbot',
messages: [],
api: '/api/v1/ai/chat',
model: 'gpt-4o',
systemPrompt: 'You are a helpful assistant.',
streamingEnabled: true,
conversationId: 'conv-123',
}What comes back out: ObjectChatMessage
You AUTHOR with @object-ui/types' ChatMessage. What useObjectChat HANDS
BACK — from messages and from onSend(content, messages) — is
ObjectChatMessage, exported from @object-ui/plugin-chatbot:
import type { ObjectChatMessage } from '@object-ui/plugin-chatbot'It is the authoring shape plus the render-only keys API mode really carries
(buildProgress, blueprintProgress, charts, and pendingActionId /
draftReview / proposedPlan / proposedChanges / builderHandoff on each
tool invocation — the approval card, the "Review N changes" affordance, the plan
card, the build panel, the inline charts), with timestamp narrowed to string
because both modes absorb an authored Date before emitting.
In API mode its tool invocations can also carry the AI SDK's three approval
states — approval-requested, approval-responded and output-denied. Those
are runtime-only: the authoring ChatToolInvocation refuses them, with or
without an approval envelope (objectui#10018). So ObjectChatMessage is
not a subtype of the authoring ChatMessage, and an onSend callback that
declares its parameter as ChatMessage[] does not type-check; declare
ObjectChatMessage[], which is also what lets it read those keys. Rebuilding a
message field-by-field from the authoring type drops every one of them —
silently, and with the compiler's agreement (objectui#4424).