devic-ui
npx skills add https://github.com/devicai/skills --skill devic-ui
Agent 安装分布
Skill 文档
Devic UI Integration Guide
This guide explains how to integrate the @devicai/ui library into your React application to add AI assistant chat capabilities.
Prerequisites
- Node.js 20+
- React 17+ application
- Devic API key (obtain from Devic dashboard)
Installation
npm install @devicai/ui
# or
yarn add @devicai/ui
# or
pnpm add @devicai/ui
Basic Integration
Step 1: Import Styles
Add the CSS import to your application entry point:
// App.tsx or index.tsx
import '@devicai/ui/styles.css';
Step 2: Wrap Your App with DevicProvider
import { DevicProvider } from '@devicai/ui';
function App() {
return (
<DevicProvider
apiKey="your-devic-api-key"
baseUrl="https://api.devic.ai" // Optional, defaults to this
>
<YourApp />
</DevicProvider>
);
}
Step 3: Add ChatDrawer Component
import { ChatDrawer } from '@devicai/ui';
function YourApp() {
return (
<div>
{/* Your app content */}
<ChatDrawer
assistantId="your-assistant-identifier"
options={{
position: 'right',
welcomeMessage: 'Hello! How can I help you today?',
suggestedMessages: [
'Help me get started',
'What can you do?',
],
}}
/>
</div>
);
}
Multi-Tenant Integration
For SaaS applications with multiple tenants:
<DevicProvider
apiKey="your-api-key"
tenantId="global-tenant-id"
tenantMetadata={{ organizationId: 'org-123' }}
>
<ChatDrawer
assistantId="support-assistant"
tenantId="specific-tenant-override" // Overrides provider
tenantMetadata={{
userId: 'user-456',
plan: 'enterprise'
}}
/>
</DevicProvider>
Client-Side Tools (Model Interface Protocol)
Enable the assistant to call functions in your application:
import { ChatDrawer, ModelInterfaceTool } from '@devicai/ui';
// Define client-side tools
const tools: ModelInterfaceTool[] = [
{
toolName: 'get_user_location',
schema: {
type: 'function',
function: {
name: 'get_user_location',
description: 'Get the current user geographic location',
parameters: {
type: 'object',
properties: {},
},
},
},
callback: async () => {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
(pos) => resolve({
latitude: pos.coords.latitude,
longitude: pos.coords.longitude,
}),
(err) => reject(new Error(err.message))
);
});
},
},
{
toolName: 'get_current_page',
schema: {
type: 'function',
function: {
name: 'get_current_page',
description: 'Get the current page URL and title',
parameters: {
type: 'object',
properties: {},
},
},
},
callback: async () => ({
url: window.location.href,
title: document.title,
pathname: window.location.pathname,
}),
},
{
toolName: 'navigate_to_page',
schema: {
type: 'function',
function: {
name: 'navigate_to_page',
description: 'Navigate the user to a specific page',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'The path to navigate to',
},
},
required: ['path'],
},
},
},
callback: async ({ path }) => {
window.location.href = path;
return { success: true, navigatedTo: path };
},
},
];
function App() {
return (
<ChatDrawer
assistantId="my-assistant"
modelInterfaceTools={tools}
onToolCall={(toolName, params) => {
console.log(`Tool called: ${toolName}`, params);
}}
/>
);
}
Custom Chat UI with Hooks
Build a completely custom chat interface:
import { useDevicChat } from '@devicai/ui';
function CustomChat() {
const {
messages,
isLoading,
status,
error,
sendMessage,
clearChat,
} = useDevicChat({
assistantId: 'my-assistant',
onMessageReceived: (message) => {
console.log('New message:', message);
},
onError: (error) => {
console.error('Chat error:', error);
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const message = formData.get('message') as string;
if (message.trim()) {
sendMessage(message);
e.currentTarget.reset();
}
};
return (
<div className="custom-chat">
<div className="messages">
{messages.map((msg) => (
<div key={msg.uid} className={`message ${msg.role}`}>
<strong>{msg.role}:</strong>
<p>{msg.content.message}</p>
</div>
))}
{isLoading && <div className="loading">Thinking...</div>}
{error && <div className="error">{error.message}</div>}
</div>
<form onSubmit={handleSubmit}>
<input
name="message"
placeholder="Type a message..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
<button onClick={clearChat}>Clear Chat</button>
</div>
);
}
File Uploads
Enable file attachments in chat:
<ChatDrawer
assistantId="document-assistant"
options={{
enableFileUploads: true,
allowedFileTypes: {
images: true,
documents: true,
audio: false,
video: false,
},
maxFileSize: 10 * 1024 * 1024, // 10MB
}}
/>
Display Modes
ChatDrawer supports two display modes via the mode prop:
Drawer Mode (default)
Renders as an overlay panel with a floating trigger button. Can be toggled open/closed.
<ChatDrawer
mode="drawer"
assistantId="my-assistant"
options={{
position: 'right',
defaultOpen: false,
zIndex: 1000,
}}
/>
Inline Mode
Renders embedded in the page layout, always visible, no trigger button or toggle behavior.
<ChatDrawer
mode="inline"
assistantId="my-assistant"
options={{
width: 400,
borderRadius: 12,
}}
/>
Resizable Drawer
Enable drag-to-resize with width constraints:
<ChatDrawer
assistantId="my-assistant"
options={{
resizable: true,
width: 400,
minWidth: 300,
maxWidth: 800,
position: 'right', // resize handle appears on the opposite edge
}}
/>
Custom Rendering
Custom Loading Indicator
<ChatDrawer
assistantId="my-assistant"
options={{
loadingIndicator: <MySpinner />,
}}
/>
Custom Send Button
The click handler is managed by an overlay, so the node doesn’t need to handle click events.
<ChatDrawer
assistantId="my-assistant"
options={{
sendButtonContent: <MyCustomIcon />,
}}
/>
Custom Tool Renderers
Replace the default tool call summary with custom UI per tool name:
<ChatDrawer
assistantId="my-assistant"
options={{
toolRenderers: {
search_products: (input, output) => (
<ProductGrid products={output.results} query={input.query} />
),
},
toolIcons: {
search_products: <SearchIcon />,
},
}}
/>
Theming
Using Props
All color and typography properties can be set via options:
<ChatDrawer
assistantId="my-assistant"
options={{
color: '#6366f1', // Primary color
backgroundColor: '#ffffff', // Drawer background
textColor: '#1e293b', // Text color
secondaryBackgroundColor: '#f8fafc', // Input/selector background
borderColor: '#e2e8f0', // Border color
userBubbleColor: '#6366f1', // User message bubble
userBubbleTextColor: '#ffffff', // User message text
assistantBubbleColor: '#f1f5f9', // Assistant message bubble
assistantBubbleTextColor: '#1e293b',// Assistant message text
sendButtonColor: '#6366f1', // Send button background
fontFamily: '"Inter", sans-serif', // Font override
}}
/>
Using CSS Variables
Override the default theme by setting CSS variables:
/* your-styles.css */
:root {
--devic-primary: #6366f1; /* Primary color */
--devic-primary-hover: #4f46e5; /* Primary hover */
--devic-primary-light: #eef2ff; /* Light primary background */
--devic-bg: #ffffff; /* Background */
--devic-bg-secondary: #f8fafc; /* Secondary background */
--devic-text: #1e293b; /* Text color */
--devic-text-secondary: #64748b; /* Secondary text */
--devic-text-muted: #94a3b8; /* Muted text */
--devic-border: #e2e8f0; /* Border color */
--devic-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
--devic-radius: 12px; /* Border radius */
--devic-radius-sm: 6px;
--devic-radius-lg: 20px;
}
Using the color Option
Quick color customization:
<ChatDrawer
assistantId="my-assistant"
options={{
color: '#6366f1', // Sets primary color
}}
/>
Controlled Mode
Control the drawer state externally:
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>
Open Chat
</button>
<ChatDrawer
assistantId="my-assistant"
isOpen={isOpen}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
/>
</>
);
}
Continuing Existing Conversations
Load and continue a previous chat:
function App() {
// Get chatUid from URL, localStorage, or your backend
const existingChatUid = 'previous-chat-uid';
return (
<ChatDrawer
assistantId="my-assistant"
chatUid={existingChatUid}
onChatCreated={(newChatUid) => {
// Save the new chat UID for future reference
localStorage.setItem('lastChatUid', newChatUid);
}}
/>
);
}
Event Callbacks
Handle various chat events:
<ChatDrawer
assistantId="my-assistant"
onMessageSent={(message) => {
// Track user messages
analytics.track('chat_message_sent', {
messageLength: message.content.message?.length,
});
}}
onMessageReceived={(message) => {
// Track assistant responses
analytics.track('chat_message_received', {
hasToolCalls: !!message.tool_calls?.length,
});
}}
onToolCall={(toolName, params) => {
// Track tool usage
analytics.track('chat_tool_called', { toolName });
}}
onError={(error) => {
// Report errors
errorReporting.capture(error);
}}
onChatCreated={(chatUid) => {
// Store chat reference
saveChatReference(chatUid);
}}
onOpen={() => {
// Track drawer open
analytics.track('chat_opened');
}}
onClose={() => {
// Track drawer close
analytics.track('chat_closed');
}}
/>
API Client Direct Usage
For advanced use cases, use the API client directly:
import { DevicApiClient } from '@devicai/ui';
const client = new DevicApiClient({
apiKey: 'your-api-key',
baseUrl: 'https://api.devic.ai',
});
// List available assistants
const assistants = await client.getAssistants();
// Send a message (async mode)
const { chatUid } = await client.sendMessageAsync('assistant-id', {
message: 'Hello!',
tenantId: 'tenant-123',
metadata: { source: 'web-app' },
});
// Poll for response
const checkResponse = async () => {
const result = await client.getRealtimeHistory('assistant-id', chatUid);
if (result.status === 'completed') {
return result.chatHistory;
} else if (result.status === 'error') {
throw new Error('Processing failed');
}
// Continue polling
await new Promise(r => setTimeout(r, 1000));
return checkResponse();
};
const messages = await checkResponse();
Server-Side Rendering (SSR)
The library is SSR-compatible. Ensure you only render the ChatDrawer on the client:
// Next.js example
import dynamic from 'next/dynamic';
const ChatDrawer = dynamic(
() => import('@devicai/ui').then(mod => mod.ChatDrawer),
{ ssr: false }
);
function Page() {
return (
<div>
<h1>My Page</h1>
<ChatDrawer assistantId="my-assistant" />
</div>
);
}
TypeScript Support
All types are exported for TypeScript users:
import type {
// Chat types
ChatMessage,
ChatDrawerProps,
ChatDrawerOptions,
ChatDrawerHandle,
// AICommandBar types
AICommandBarProps,
AICommandBarOptions,
AICommandBarHandle,
AICommandBarCommand,
CommandBarResult,
ToolCallSummary,
// AIGenerationButton types
AIGenerationButtonProps,
AIGenerationButtonOptions,
AIGenerationButtonHandle,
AIGenerationButtonMode,
GenerationResult,
// Tool types
ModelInterfaceTool,
ModelInterfaceToolSchema,
// Hook types
UseDevicChatOptions,
UseDevicChatResult,
// API types
RealtimeChatHistory,
AssistantSpecialization,
// Feedback types
FeedbackSubmission,
FeedbackEntry,
FeedbackTheme,
} from '@devicai/ui';
// Use types in your code
const chatOptions: ChatDrawerOptions = {
position: 'right',
width: 400,
welcomeMessage: 'Hello!',
};
const commandBarOptions: AICommandBarOptions = {
shortcut: 'cmd+k',
placeholder: 'Ask AI...',
};
const generationOptions: AIGenerationButtonOptions = {
mode: 'modal',
modalTitle: 'Generate with AI',
};
const handleMessage = (message: ChatMessage) => {
console.log(message.content.message);
};
const handleCommandResult = (result: CommandBarResult) => {
console.log('Chat UID:', result.chatUid);
console.log('Tool calls:', result.toolCalls.length);
console.log('Response:', result.message.content);
};
const handleGenerationResult = (result: GenerationResult) => {
console.log('Generated content:', result.message.content.message);
console.log('Tool calls executed:', result.toolCalls);
};
ChatDrawer Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
mode |
'drawer' | 'inline' |
'drawer' |
Display mode: overlay drawer or embedded inline |
assistantId |
string |
required | Assistant identifier |
chatUid |
string |
â | Existing chat UID to continue conversation |
options |
ChatDrawerOptions |
â | Display and behavior options (see below) |
enabledTools |
string[] |
â | Tools enabled from assistant’s configured tool groups |
modelInterfaceTools |
ModelInterfaceTool[] |
â | Client-side tools for model interface protocol |
tenantId |
string |
â | Tenant ID (overrides provider) |
tenantMetadata |
Record<string, any> |
â | Tenant metadata (overrides provider) |
apiKey |
string |
â | API key (overrides provider) |
baseUrl |
string |
â | Base URL (overrides provider) |
isOpen |
boolean |
â | Controlled open state (drawer mode only) |
className |
string |
â | Additional CSS class |
onMessageSent |
(message) => void |
â | Fires when user sends a message |
onMessageReceived |
(message) => void |
â | Fires when assistant responds |
onToolCall |
(toolName, params) => void |
â | Fires when a tool is called |
onError |
(error) => void |
â | Fires on error |
onChatCreated |
(chatUid) => void |
â | Fires when a new chat is created |
onOpen |
() => void |
â | Fires when drawer opens |
onClose |
() => void |
â | Fires when drawer closes |
onConversationChange |
(chatUid) => void |
â | Fires when active conversation changes |
ChatDrawerOptions Reference
| Option | Type | Default | Description |
|---|---|---|---|
position |
'left' | 'right' |
'right' |
Drawer position |
width |
number | string |
'100%' |
Drawer width (px number or CSS string) |
defaultOpen |
boolean |
false |
Whether drawer starts open |
resizable |
boolean |
false |
Enable drag-to-resize handle |
minWidth |
number |
300 |
Minimum width when resizable (px) |
maxWidth |
number |
800 |
Maximum width when resizable (px) |
zIndex |
number |
1000 |
Z-index for the drawer |
borderRadius |
number | string |
0 |
Border radius for the container |
style |
CSSProperties |
â | Additional inline styles |
title |
string | ReactNode |
'Chat' |
Header title |
showAvatar |
boolean |
false |
Show assistant image next to title |
welcomeMessage |
string |
â | Welcome message shown at start |
suggestedMessages |
string[] |
â | Quick action suggestions |
inputPlaceholder |
string |
'Type a message...' |
Input placeholder text |
showToolTimeline |
boolean |
true |
Show tool execution timeline |
enableFileUploads |
boolean |
false |
Enable file attachments |
allowedFileTypes |
AllowedFileTypes |
â | Filter by file type (images, documents, audio, video) |
maxFileSize |
number |
10485760 |
Max file size in bytes (10MB) |
color |
string |
'#1890ff' |
Primary theme color |
backgroundColor |
string |
â | Drawer background color |
textColor |
string |
â | Text color |
secondaryBackgroundColor |
string |
â | Input/selector background color |
borderColor |
string |
â | Border color |
userBubbleColor |
string |
â | User message bubble background |
userBubbleTextColor |
string |
â | User message bubble text |
assistantBubbleColor |
string |
â | Assistant message bubble background |
assistantBubbleTextColor |
string |
â | Assistant message bubble text |
sendButtonColor |
string |
â | Send button background color |
fontFamily |
string |
â | Font family override |
loadingIndicator |
ReactNode |
â | Custom loading spinner |
sendButtonContent |
ReactNode |
â | Custom send button content |
toolRenderers |
Record<string, (input, output) => ReactNode> |
â | Custom tool call renderers by tool name |
toolIcons |
Record<string, ReactNode> |
â | Custom tool call icons by tool name |
showFeedback |
boolean |
true |
Show thumbs up/down feedback buttons on assistant messages |
Message Feedback
Both ChatDrawer and AICommandBar support message feedback (thumbs up/down with optional comments). Feedback is submitted to the Devic API and associated with the chat.
ChatDrawer Feedback
Feedback buttons appear on assistant messages by default. Users can click thumbs up/down and optionally add a comment via a modal.
<ChatDrawer
assistantId="my-assistant"
options={{
showFeedback: true, // default: true
}}
/>
AICommandBar Feedback
When showResultCard is enabled, feedback buttons appear below the response. The feedback UI automatically adapts to the command bar’s theme.
<AICommandBar
assistantId="my-assistant"
options={{
showResultCard: true,
// Feedback inherits theme from these options:
backgroundColor: '#1f2937',
textColor: '#f9fafb',
borderColor: '#374151',
}}
/>
Feedback Theming
The feedback modal and action buttons automatically inherit theme colors from the parent component. For custom implementations, you can pass a FeedbackTheme object:
interface FeedbackTheme {
backgroundColor?: string; // Modal background
textColor?: string; // Primary text
textMutedColor?: string; // Muted/secondary text
secondaryBackgroundColor?: string; // Button backgrounds, hover states
borderColor?: string; // Modal borders
primaryColor?: string; // Primary action color
primaryHoverColor?: string; // Primary button hover
}
Feedback API
Feedback is automatically submitted to the Devic API using these endpoints:
POST /api/v1/assistants/:identifier/chats/:chatUid/feedback– Submit feedbackGET /api/v1/assistants/:identifier/chats/:chatUid/feedback– Get feedback entries
You can also use the API client directly:
import { DevicApiClient, FeedbackSubmission } from '@devicai/ui';
const client = new DevicApiClient({ apiKey: 'your-api-key' });
// Submit feedback
const feedback: FeedbackSubmission = {
messageId: 'message-uid',
feedback: true, // true = positive, false = negative
feedbackComment: 'Very helpful response!',
feedbackData: { category: 'accuracy' },
};
await client.submitChatFeedback('assistant-id', 'chat-uid', feedback);
// Get all feedback for a chat
const entries = await client.getChatFeedback('assistant-id', 'chat-uid');
Feedback Types
import type {
FeedbackSubmission,
FeedbackEntry,
FeedbackTheme,
} from '@devicai/ui';
// Submission payload
interface FeedbackSubmission {
messageId: string;
feedback?: boolean; // true = positive, false = negative
feedbackComment?: string; // Optional comment
feedbackData?: Record<string, any>; // Custom metadata
}
// Response from API
interface FeedbackEntry {
_id: string;
requestId: string;
chatUID?: string;
feedback?: boolean;
feedbackComment?: string;
feedbackData?: Record<string, any>;
creationTimestamp: string;
lastEditTimestamp?: string;
}
AICommandBar Component
A floating command bar (similar to Spotlight/Command Palette) for quick AI interactions. It provides a minimal input interface that processes messages, shows tool execution progress, and displays results in a compact card.
Basic Usage
import { AICommandBar } from '@devicai/ui';
function App() {
return (
<AICommandBar
assistantId="my-assistant"
options={{
placeholder: 'Ask AI...',
shortcut: 'cmd+k',
}}
onResponse={({ message, toolCalls }) => {
console.log('Response:', message.content);
}}
/>
);
}
Fixed Position with Keyboard Shortcut
<AICommandBar
assistantId="support-assistant"
options={{
position: 'fixed',
fixedPlacement: { bottom: 20, right: 20 },
shortcut: 'cmd+j',
placeholder: 'Ask AI about your data...',
showShortcutHint: true,
}}
/>
Integration with ChatDrawer
Hand off conversations to the full ChatDrawer after getting a quick answer:
import { useRef } from 'react';
import { AICommandBar, ChatDrawer, ChatDrawerHandle } from '@devicai/ui';
function App() {
const drawerRef = useRef<ChatDrawerHandle>(null);
return (
<>
<AICommandBar
assistantId="my-assistant"
onExecute="openDrawer"
chatDrawerRef={drawerRef}
options={{
shortcut: 'cmd+k',
showResultCard: false, // Don't show result since drawer opens
}}
/>
<ChatDrawer
ref={drawerRef}
assistantId="my-assistant"
/>
</>
);
}
Command History
Command history is enabled by default. Users can:
- Press Arrow Up/Down to navigate through previous prompts
- Use the
/historycommand to see the history list - Click on a history item to reuse it
<AICommandBar
assistantId="my-assistant"
options={{
enableHistory: true, // default: true
maxHistoryItems: 50, // default: 50
historyStorageKey: 'my-app-command-history', // localStorage key
showHistoryCommand: true, // adds /history command
}}
/>
Commands System
Define slash commands that trigger predefined messages:
<AICommandBar
assistantId="my-assistant"
options={{
commands: [
{
keyword: 'summarize',
description: 'Summarize the current page',
message: 'Please summarize the content of this page.',
icon: <SummarizeIcon />,
},
{
keyword: 'translate',
description: 'Translate selected text',
message: 'Translate the following text to Spanish: ',
},
{
keyword: 'explain',
description: 'Explain like I\'m five',
message: 'Explain this concept in simple terms: ',
},
],
}}
/>
When the user types /, a dropdown shows available commands. Arrow keys navigate, Enter selects, Tab autocompletes.
Custom Tool Rendering
Display tool calls with custom icons and renderers:
<AICommandBar
assistantId="my-assistant"
options={{
toolIcons: {
search_database: <DatabaseIcon />,
fetch_weather: <WeatherIcon />,
},
toolRenderers: {
search_database: (input, output) => (
<div className="custom-result">
Found {output.count} results for "{input.query}"
</div>
),
},
}}
/>
Theming
<AICommandBar
assistantId="my-assistant"
options={{
color: '#6366f1', // Primary color (spinner, badges)
backgroundColor: '#ffffff', // Bar background
textColor: '#1f2937', // Text color
borderColor: '#e5e7eb', // Border color
borderRadius: 12, // Border radius (px or string)
fontFamily: 'Inter, sans-serif',
fontSize: 14,
padding: '12px 16px',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.1)',
animationDuration: 200, // ms
}}
/>
Controlled Visibility
function App() {
const [isVisible, setIsVisible] = useState(false);
const commandBarRef = useRef<AICommandBarHandle>(null);
return (
<>
<button onClick={() => commandBarRef.current?.toggle()}>
Toggle Command Bar
</button>
<AICommandBar
ref={commandBarRef}
assistantId="my-assistant"
isVisible={isVisible}
onVisibilityChange={setIsVisible}
onOpen={() => console.log('Opened')}
onClose={() => console.log('Closed')}
/>
</>
);
}
Using the Hook Directly
For custom UI implementations:
import { useAICommandBar, formatShortcut } from '@devicai/ui';
function CustomCommandBar() {
const {
isVisible,
open,
close,
toggle,
inputValue,
setInputValue,
inputRef,
focus,
isProcessing,
currentToolSummary,
toolCalls,
result,
error,
history,
showingHistory,
showingCommands,
filteredCommands,
submit,
reset,
handleKeyDown,
} = useAICommandBar({
assistantId: 'my-assistant',
options: { shortcut: 'cmd+k' },
onResponse: (result) => console.log(result),
});
if (!isVisible) return null;
return (
<div className="my-command-bar">
{isProcessing ? (
<span>{currentToolSummary || 'Processing...'}</span>
) : (
<input
ref={inputRef}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Ask AI..."
/>
)}
</div>
);
}
AICommandBar Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
assistantId |
string |
required | Assistant identifier |
apiKey |
string |
â | API key (overrides provider) |
baseUrl |
string |
â | Base URL (overrides provider) |
tenantId |
string |
â | Tenant ID (overrides provider) |
tenantMetadata |
Record<string, any> |
â | Tenant metadata |
options |
AICommandBarOptions |
â | Display and behavior options |
isVisible |
boolean |
â | Controlled visibility state |
onVisibilityChange |
(visible: boolean) => void |
â | Fires when visibility changes |
onExecute |
'openDrawer' | 'callback' |
'callback' |
What to do on completion |
chatDrawerRef |
RefObject<ChatDrawerHandle> |
â | Ref to ChatDrawer (for openDrawer mode) |
onResponse |
(result: CommandBarResult) => void |
â | Fires on completion (callback mode) |
modelInterfaceTools |
ModelInterfaceTool[] |
â | Client-side tools |
onSubmit |
(message: string) => void |
â | Fires when user submits |
onToolCall |
(toolName, params) => void |
â | Fires when a tool is called |
onError |
(error: Error) => void |
â | Fires on error |
onOpen |
() => void |
â | Fires when bar opens |
onClose |
() => void |
â | Fires when bar closes |
className |
string |
â | Additional CSS class |
AICommandBarOptions Reference
| Option | Type | Default | Description |
|---|---|---|---|
position |
'inline' | 'fixed' |
'inline' |
Positioning mode |
fixedPlacement |
{ top?, right?, bottom?, left? } |
â | Position offsets for fixed mode |
shortcut |
string |
â | Keyboard shortcut (e.g., 'cmd+k', 'ctrl+j') |
showShortcutHint |
boolean |
true |
Show shortcut badge in bar |
placeholder |
string |
'Ask AI...' |
Input placeholder |
icon |
ReactNode |
Sparkles icon | Custom icon for idle state |
width |
number | string |
400 |
Bar width |
maxWidth |
number | string |
'100%' |
Maximum width |
zIndex |
number |
9999 |
Z-index |
showResultCard |
boolean |
true |
Show result card on completion |
resultCardMaxHeight |
number | string |
300 |
Max height for result card |
processingMessage |
string |
'Processing...' |
Fallback message during processing |
color |
string |
'#3b82f6' |
Primary color |
backgroundColor |
string |
'#ffffff' |
Background color |
textColor |
string |
'#1f2937' |
Text color |
borderColor |
string |
'#e5e7eb' |
Border color |
borderRadius |
number | string |
12 |
Border radius |
fontFamily |
string |
System fonts | Font family |
fontSize |
number | string |
14 |
Font size |
padding |
number | string |
'12px 16px' |
Bar padding |
boxShadow |
string |
Light shadow | Box shadow |
animationDuration |
number |
200 |
Animation duration (ms) |
toolRenderers |
Record<string, (input, output) => ReactNode> |
â | Custom tool renderers |
toolIcons |
Record<string, ReactNode> |
â | Custom tool icons |
enableHistory |
boolean |
true |
Enable command history |
maxHistoryItems |
number |
50 |
Max history items to store |
historyStorageKey |
string |
'devic-command-bar-history' |
localStorage key |
commands |
AICommandBarCommand[] |
â | Slash commands |
showHistoryCommand |
boolean |
true |
Add built-in /history command |
AICommandBarHandle Reference
Methods exposed via ref:
| Method | Description |
|---|---|
open() |
Open the command bar |
close() |
Close the command bar |
toggle() |
Toggle visibility |
focus() |
Focus the input |
submit(message?: string) |
Submit a message |
reset() |
Reset state (clear input, result, errors) |
AIGenerationButton Component
A button component for triggering AI generation with three configurable interaction modes. Useful for “Generate with AI” buttons in forms, editors, and other UI contexts.
Basic Usage
import { AIGenerationButton } from '@devicai/ui';
function App() {
return (
<AIGenerationButton
assistantId="my-assistant"
options={{
mode: 'modal',
modalTitle: 'Generate Content',
placeholder: 'Describe what you want to generate...',
}}
onResponse={({ message }) => {
console.log('Generated:', message.content.message);
}}
/>
);
}
Interaction Modes
Direct Mode
Sends a predefined prompt immediately when clicked. Best for specific, predetermined actions.
<AIGenerationButton
assistantId="my-assistant"
options={{
mode: 'direct',
prompt: 'Generate a product description based on the form data',
label: 'Auto-Generate Description',
loadingLabel: 'Generating...',
}}
onBeforeSend={(prompt) => {
// Optionally modify the prompt before sending
return `${prompt}\n\nProduct: ${productName}`;
}}
onResponse={({ message }) => setDescription(message.content.message)}
/>
Modal Mode (Default)
Opens a modal dialog for the user to enter a custom prompt.
<AIGenerationButton
assistantId="my-assistant"
options={{
mode: 'modal',
modalTitle: 'Generate with AI',
modalDescription: 'Describe what you want and the AI will generate it for you.',
placeholder: 'E.g., Create a function that validates email addresses...',
confirmText: 'Generate',
cancelText: 'Cancel',
}}
onResponse={({ message }) => {
// Handle the generated content
setCode(message.content.message);
}}
/>
Tooltip Mode
Shows a compact inline input next to the button. Good for quick prompts without modal interruption.
<AIGenerationButton
assistantId="my-assistant"
options={{
mode: 'tooltip',
tooltipPlacement: 'bottom', // 'top' | 'bottom' | 'left' | 'right'
tooltipWidth: 350,
placeholder: 'What should I generate?',
}}
onResponse={handleGeneration}
/>
Button Styling
<AIGenerationButton
assistantId="my-assistant"
options={{
// Button variant
variant: 'primary', // 'primary' | 'secondary' | 'outline' | 'ghost'
// Button size
size: 'medium', // 'small' | 'medium' | 'large'
// Label and icon
label: 'Generate with AI',
hideLabel: false, // Set true for icon-only button
icon: <CustomSparkleIcon />, // Custom icon
hideIcon: false,
// Loading state
loadingLabel: 'Generating...',
}}
/>
Theming
<AIGenerationButton
assistantId="my-assistant"
options={{
color: '#6366f1', // Primary color
backgroundColor: '#ffffff', // Button background (for secondary/outline variants)
textColor: '#1f2937', // Text color
borderColor: '#e5e7eb', // Border color
borderRadius: 8, // Border radius
fontFamily: 'Inter, sans-serif',
fontSize: 14,
zIndex: 10000, // Z-index for modal/tooltip
animationDuration: 200, // Animation duration in ms
}}
/>
Custom Button Content
Use children to completely customize the button appearance:
<AIGenerationButton
assistantId="my-assistant"
options={{ mode: 'modal' }}
onResponse={handleResponse}
>
<span className="my-custom-button">
<SparkleIcon /> Generate Code
</span>
</AIGenerationButton>
Programmatic Control
Use ref to control the component programmatically:
import { useRef } from 'react';
import { AIGenerationButton, AIGenerationButtonHandle } from '@devicai/ui';
function Editor() {
const buttonRef = useRef<AIGenerationButtonHandle>(null);
const handleKeyboardShortcut = (e: KeyboardEvent) => {
if (e.metaKey && e.key === 'g') {
buttonRef.current?.open(); // Open modal/tooltip
}
};
const generateDirectly = async () => {
const result = await buttonRef.current?.generate('Generate a summary');
if (result) {
console.log('Generated:', result.message.content.message);
}
};
return (
<AIGenerationButton
ref={buttonRef}
assistantId="my-assistant"
options={{ mode: 'modal' }}
onResponse={handleResponse}
/>
);
}
Using the Hook Directly
For completely custom UI implementations:
import { useAIGenerationButton } from '@devicai/ui';
function CustomGenerateButton() {
const {
isOpen,
isProcessing,
inputValue,
setInputValue,
error,
result,
inputRef,
open,
close,
generate,
reset,
handleKeyDown,
} = useAIGenerationButton({
assistantId: 'my-assistant',
onResponse: (result) => console.log('Generated:', result),
onError: (error) => console.error('Error:', error),
});
return (
<div>
<button onClick={() => open()}>
{isProcessing ? 'Generating...' : 'Generate'}
</button>
{isOpen && (
<div className="custom-modal">
<textarea
ref={inputRef}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Describe what to generate..."
/>
<button onClick={() => generate()} disabled={isProcessing}>
{isProcessing ? 'Working...' : 'Generate'}
</button>
<button onClick={close}>Cancel</button>
{error && <p className="error">{error.message}</p>}
</div>
)}
</div>
);
}
AIGenerationButton Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
assistantId |
string |
required | Assistant identifier |
apiKey |
string |
â | API key (overrides provider) |
baseUrl |
string |
â | Base URL (overrides provider) |
tenantId |
string |
â | Tenant ID (overrides provider) |
tenantMetadata |
Record<string, any> |
â | Tenant metadata |
options |
AIGenerationButtonOptions |
â | Display and behavior options |
modelInterfaceTools |
ModelInterfaceTool[] |
â | Client-side tools |
onResponse |
(result: GenerationResult) => void |
â | Fires on successful generation |
onBeforeSend |
(prompt: string) => string | undefined |
â | Modify prompt before sending |
onError |
(error: Error) => void |
â | Fires on error |
onStart |
() => void |
â | Fires when processing starts |
onOpen |
() => void |
â | Fires when modal/tooltip opens |
onClose |
() => void |
â | Fires when modal/tooltip closes |
disabled |
boolean |
false |
Disable the button |
className |
string |
â | Additional CSS class for button |
containerClassName |
string |
â | CSS class for container |
children |
ReactNode |
â | Custom button content |
theme |
FeedbackTheme |
â | Theme for modal/tooltip |
AIGenerationButtonOptions Reference
| Option | Type | Default | Description |
|---|---|---|---|
mode |
'direct' | 'modal' | 'tooltip' |
'modal' |
Interaction mode |
prompt |
string |
â | Predefined prompt (required for direct mode) |
placeholder |
string |
'Describe what you want to generate...' |
Input placeholder |
modalTitle |
string |
'Generate with AI' |
Modal title |
modalDescription |
string |
â | Modal description text |
confirmText |
string |
'Generate' |
Confirm button text |
cancelText |
string |
'Cancel' |
Cancel button text |
tooltipPlacement |
'top' | 'bottom' | 'left' | 'right' |
'top' |
Tooltip position |
tooltipWidth |
number | string |
300 |
Tooltip width |
variant |
'primary' | 'secondary' | 'ghost' | 'outline' |
'primary' |
Button variant |
size |
'small' | 'medium' | 'large' |
'medium' |
Button size |
icon |
ReactNode |
Sparkles icon | Custom button icon |
hideIcon |
boolean |
false |
Hide button icon |
label |
string |
'Generate with AI' |
Button label |
hideLabel |
boolean |
false |
Hide button label (icon-only) |
loadingLabel |
string |
'Generating...' |
Label during processing |
color |
string |
'#3b82f6' |
Primary color |
backgroundColor |
string |
â | Background color |
textColor |
string |
â | Text color |
borderColor |
string |
â | Border color |
borderRadius |
number | string |
8 |
Border radius |
fontFamily |
string |
System fonts | Font family |
fontSize |
number | string |
14 |
Font size |
zIndex |
number |
10000 |
Z-index for overlays |
animationDuration |
number |
200 |
Animation duration (ms) |
toolRenderers |
Record<string, (input, output) => ReactNode> |
â | Custom tool call renderers by tool name |
toolIcons |
Record<string, ReactNode> |
â | Custom tool icons by tool name |
processingMessage |
string |
'Processing...' |
Message shown during processing |
AIGenerationButtonHandle Reference
Methods exposed via ref:
| Method | Description |
|---|---|
generate(prompt?: string) |
Trigger generation (returns Promise with result) |
open() |
Open modal/tooltip (for modal and tooltip modes) |
close() |
Close modal/tooltip |
reset() |
Reset component state |
isProcessing |
Boolean indicating if processing |
Troubleshooting
Chat not loading
- Verify your API key is correct
- Check the browser console for errors
- Ensure the assistant identifier exists
Styles not applied
- Make sure you imported the CSS:
import '@devicai/ui/styles.css' - Check for CSS conflicts with your application styles
- Try increasing specificity or using CSS variables
Tools not being called
- Verify the tool schema matches OpenAI function calling format
- Check that
toolNamematchesfunction.namein schema - Ensure the assistant has been configured to use client-side tools
File uploads not working
- Enable file uploads in options:
enableFileUploads: true - Check allowed file types configuration
- Verify file size is within limits
Support
For issues and feature requests, visit the GitHub repository.