Chatbot interface plugin for Object UI with full AI SDUI support, built on Vercel AI Elements (MIT) and shadcn/ui (MIT).
Why AI Elements? They give us a production-grade, shadcn-style chat surface — conversation, message bubbles, streaming reasoning, tool calls, sources, code blocks, suggestions — that already matches our Tailwind 4 design tokens. We vendor them into
src/elements/so we can ship without a network dep, and we wrap them insideChatbotEnhancedto keep the public ObjectUI prop API stable. See Architecture below.
npm install @object-ui/plugin-chatbotimport { Chatbot } from '@object-ui/plugin-chatbot';
function App() {
const [messages, setMessages] = useState([
{
id: '1',
role: 'assistant',
content: 'Hello! How can I help you today?'
}
]);
const handleSend = (content: string) => {
const newMessage = {
id: Date.now().toString(),
role: 'user',
content
};
setMessages([...messages, newMessage]);
};
return (
<Chatbot
messages={messages}
onSendMessage={handleSend}
placeholder="Type your message..."
/>
);
}When api is set in the schema, the chatbot connects to a backend SSE endpoint
using @ai-sdk/react v3 (Vercel UI Message Stream protocol) for streaming,
tool-calling, and production-grade chat:
import '@object-ui/plugin-chatbot';
const schema = {
type: 'chatbot',
api: '/api/v1/ai/chat',
model: 'gpt-4o',
systemPrompt: 'You are a helpful assistant.',
streamingEnabled: true,
conversationId: 'conv-123',
messages: [
{ id: '1', role: 'assistant', content: 'Hello! Ask me anything.' }
],
placeholder: 'Type your message...',
};For custom integrations, you can use the useObjectChat hook directly:
import { useObjectChat } from '@object-ui/plugin-chatbot';
function MyChat() {
const {
messages,
isLoading,
error,
sendMessage,
stop,
reload,
clear,
isApiMode,
} = useObjectChat({
api: '/api/v1/ai/chat',
model: 'gpt-4o',
systemPrompt: 'You are helpful.',
});
return (
<div>
{messages.map(msg => (
<div key={msg.id}>{msg.content}</div>
))}
{isLoading && <button onClick={stop}>Stop</button>}
{error && <button onClick={reload}>Retry</button>}
</div>
);
}Use useAgents to fetch the list of agents exposed by @objectstack/service-ai
at GET {apiBase}/agents. This is what the global console FAB uses to populate
its in-header agent picker:
import { useAgents } from '@object-ui/plugin-chatbot';
function AgentPicker() {
const { agents, isLoading, error } = useAgents({
apiBase: 'http://localhost:3000/api/v1/ai',
// Optional fallback list shown when the backend is unreachable
fallback: [{ name: 'data_chat', label: 'Data Chat' }],
});
if (isLoading) return <span>Loading agents…</span>;
if (error) return <span>Backend unreachable</span>;
return (
<select>
{agents.map(a => (
<option key={a.name} value={a.name}>{a.label}</option>
))}
</select>
);
}Each agent's chat endpoint is POST {apiBase}/agents/{name}/chat — pass that
URL as the api option to useObjectChat to talk to it.
The console (@object-ui/app-shell) auto-mounts a global floating chatbot
when useDiscovery().isAiEnabled is true. Configure the backend in your
console .env:
# AI service endpoint (defaults to ${VITE_SERVER_URL}/api/v1/ai when unset)
VITE_AI_BASE_URL=http://localhost:3000/api/v1/ai
# Default agent to select on first open (must match an agent name returned
# by GET ${VITE_AI_BASE_URL}/agents)
VITE_AI_DEFAULT_AGENT=sales_copilotThe picker lets the user switch agents at runtime; switching transparently
remounts the chat hook against the new agent's /chat endpoint.
This plugin automatically registers with ObjectUI's component registry when imported:
import '@object-ui/plugin-chatbot';
// Local/demo mode
const demoSchema = {
type: 'chatbot',
messages: [
{ id: '1', role: 'assistant', content: 'Hello!' }
],
placeholder: 'Type your message...',
autoResponse: true,
};
// AI streaming mode
const aiSchema = {
type: 'chatbot',
api: '/api/v1/ai/chat',
model: 'gpt-4o',
systemPrompt: 'You are a helpful assistant.',
streamingEnabled: true,
messages: [],
placeholder: 'Ask the AI...',
};| Feature | Local/Demo Mode | AI Streaming Mode |
|---|---|---|
api |
Not set | Set to SSE endpoint |
| Responses | Auto-response (configurable) | Real AI streaming via SSE |
| Streaming | Simulated | Full SSE streaming |
| Tool calling | N/A | Supported via vercel/ai |
| Stop/Reload | Stop cancels timer | Stop interrupts stream |
| Backend | None required | service-ai (IAIService) |
- React: 18.x or 19.x
- Node.js: ≥ 18
- TypeScript: ≥ 5.0 (strict mode)
@objectstack/spec: ^3.3.0@objectstack/client: ^3.3.0- Tailwind CSS: ≥ 3.4 (for packages with UI)
Internally the chatbot is composed from three layers:
┌─────────────────────────────────────────────────────┐
│ ChatbotEnhanced (public surface, stable props) │
│ ├─ Conversation / Message (AI Elements) │
│ ├─ PromptInput (AI Elements) │
│ └─ Suggestion / Reasoning / Tool (AI Elements) │
├─────────────────────────────────────────────────────┤
│ src/elements/ │
│ Vendored Vercel AI Elements + missing shadcn │
│ primitives (button-group, input-group). Rewritten │
│ to import from `@object-ui/components`. Do NOT │
│ edit — re-sync from registry.ai-sdk.dev. │
├─────────────────────────────────────────────────────┤
│ @object-ui/components (shadcn + Tailwind 4) │
└─────────────────────────────────────────────────────┘
The vendored components are also re-exported under a namespace for advanced users who want to compose their own chat surface:
import { AIElements } from '@object-ui/plugin-chatbot';
<AIElements.Conversation>
<AIElements.ConversationContent>
<AIElements.Message from="assistant">
<AIElements.MessageContent>Hello.</AIElements.MessageContent>
</AIElements.Message>
</AIElements.ConversationContent>
</AIElements.Conversation>;If you wire @ai-sdk/react's useChat() directly and want to render its
UIMessage[] with <ChatbotEnhanced>, use the exported mappers instead
of writing your own — they handle parts: [{ type: 'text' | 'reasoning' | 'tool-*' | 'source-*' }], the streaming-cursor flag, and the legacy
msg.toolInvocations fallback:
import { useChat } from '@ai-sdk/react';
import {
ChatbotEnhanced,
uiMessagesToChatMessages,
} from '@object-ui/plugin-chatbot';
function MyChat() {
const { messages, status } = useChat({ api: '/api/chat' });
const isStreaming = status === 'streaming' || status === 'submitted';
return (
<ChatbotEnhanced
messages={uiMessagesToChatMessages(messages, { isStreaming })}
onSend={/* … */}
/>
);
}uiMessageToChatMessage(msg, { streaming }) is the single-message
variant. Both are also used internally by useObjectChat, so the
mapping logic stays consistent across direct and managed usage.
This package depends on streamdown, shiki, mermaid, and katex
for code/markdown rendering. Together they weigh ~20 MB minified.
In host apps that don't always show the chat panel (e.g. Studio's
opt-in AI sidebar), import this package via React.lazy so the heavy
chunks only download when the panel actually opens. See
apps/studio/src/routes/__root.tsx for the reference pattern, and
apps/studio/vite.config.ts for the manualChunks rules that keep
the chat-only deps in dedicated, lazy-loadable chunks.
MIT © ObjectStack Inc.
Third-party code shipped under src/elements/:
- Vercel AI Elements — MIT © Vercel, Inc.
- shadcn/ui primitives (
button-group,input-group) — MIT © shadcn.