ObjectStackObjectStack
Custom Front-endUI plugins

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 chatbot schema and ChatbotEnhanced / FloatingChatbot props are unchanged. A new optional suggestions: 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-chatbot

Example 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.

PropertyTypeDefaultDescription
messagesarray[]Initial chat messages
placeholderstring'Type your message...'Input field placeholder text
showTimestampbooleanfalseDisplay timestamps for messages
disabledbooleanfalseDisable chat input
userAvatarUrlstring-URL for user avatar image
userAvatarFallbackstring'You'Fallback text for user avatar
assistantAvatarUrlstring-URL for assistant avatar image
assistantAvatarFallbackstring'AI'Fallback text for assistant avatar
maxHeightstring'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
autoResponsebooleanfalseEnable auto-response (demo mode, ignored when api is set)
autoResponseTextstring-Text for auto-response
autoResponseDelayMsnumber1000Delay before auto-response, in milliseconds (renamed from autoResponseDelay, which is now refused with a message naming this key)
onSendfunction-Callback when message is sent
classNamestring''Additional Tailwind CSS classes
apistring-Backend SSE endpoint (e.g., /api/v1/ai/chat). Enables AI streaming mode
conversationIdstring-Multi-turn conversation identifier
systemPromptstring-System prompt to configure assistant behavior
modelstring-AI model identifier (e.g., gpt-4o)
streamingEnabledbooleantrueEnable SSE streaming for AI responses
headersobject-Additional headers for API requests
requestBodyobject-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
onErrorfunction-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:

retiredwrite instead
userAvataruserAvatarUrl (with userAvatarFallback)
assistantAvatarassistantAvatarUrl (with assistantAvatarFallback)
heightmaxHeight on chatbot / chatbot-enhanced; floatingConfig.panelHeight on chatbot-floating
markdownenableMarkdown, on a chatbot-enhanced or chatbot-floating node
loadingnothing - chat progress is runtime state the chat runtime owns, never authored
showAvatarsnothing - 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.

PropertyTypeDefaultDescription
floatingConfig.position'bottom-right' | 'bottom-left''bottom-right'Corner the trigger sits in; the panel is anchored to the same side
floatingConfig.defaultOpenbooleanfalseWhether the panel is already open when the node mounts
floatingConfig.panelWidthnumber400Panel width in pixels, applied from the sm breakpoint up - below it the panel is full-bleed. Snapped to a step, see below
floatingConfig.panelHeightnumber520Panel 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.titlestring'Chat'Text in the panel header, and the panel's aria-label
floatingConfig.triggerSizenumber56Diameter 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, 800
  • panelHeight - 360, 400, 420, 440, 480, 500, 520, 560, 600, 640, 720, 800
  • triggerSize - 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).

On this page