Xandi
AI chatbot component for workforce and hiring applications
Overview
Xandi is a composable AI chatbot component designed for workforce and hiring applications. It provides a ready-to-use chat interface with welcome messages, prompt suggestions, message actions, and chat history management.
Import
import { Xandi, XandiProvider, XHeader, XSidebar, type XandiHandlers } from "@px-ui/ai";Usage
How can I help you today?
import { Xandi, XandiProvider, type XandiHandlers, type XandiResponse } from "@px-ui/ai";
const handlers: XandiHandlers = {
fetchResp: async (message, options) => {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message, conversation_id: options?.conversationId }),
signal: options?.signal, // Enable request cancellation
});
const data = await response.json();
return {
content: data.message,
conversationId: data.data.conversation_id,
debugTrace: data.trace,
};
},
};
function MyPage() {
return (
<XandiProvider handlers={handlers}>
<Xandi />
</XandiProvider>
);
}Architecture
Xandi uses a provider pattern to manage chat state. The XandiProvider wraps your chat components and provides context for:
- Message state - Manages conversation history
- Conversation management - Handles chat sessions with optional restoration
- API communication - Delegates to your custom handler functions
Basic Setup
import { type XandiHandlers, type XandiResponse, type FeedbackType } from "@px-ui/ai";
const handlers: XandiHandlers = {
// Required: Fetch AI response
fetchResp: async (message, options): Promise<XandiResponse> => {
const response = await fetch("https://api.example.com/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer your-token",
},
body: JSON.stringify({ message, conversation_id: options?.conversationId }),
signal: options?.signal, // Enable request cancellation
});
const data = await response.json();
return {
content: data.message,
conversationId: data.data.conversation_id,
debugTrace: data.trace,
};
},
// Optional: Handle feedback
onFeedback: (messageId: string, conversationId: string, feedback: FeedbackType) => {
console.log("Feedback:", { messageId, conversationId, feedback });
},
// Optional: Handle stop request (for server-side cleanup)
onStop: (conversationId: string) => {
console.log("Stopped:", conversationId);
},
};
<XandiProvider
handlers={handlers}
conversationId="existing-conversation-id" // optional, for restoring sessions
>
<Xandi
welcomeMessage="How can I help you today?"
suggestions={[
{ id: "1", label: "Open Jobs", prompt: "Show me all open jobs" },
{ id: "2", label: "Pending Timesheets", prompt: "Show me pending timesheets" },
]}
/>
</XandiProvider>With Header and Sidebar
import { useState } from "react";
import { Xandi, XandiProvider, XHeader, XSidebar, type XandiConfig, type XandiHandlers } from "@px-ui/ai";
// Custom configuration
const config: XandiConfig = {
avatarUrl: "https://example.com/my-assistant-avatar.png",
assistantName: "Assistant",
};
const handlers: XandiHandlers = {
fetchResp: async (message, options) => {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message, conversation_id: options?.conversationId }),
signal: options?.signal,
});
const data = await response.json();
return {
content: data.message,
conversationId: data.data.conversation_id,
};
},
onFeedback: (messageId, conversationId, feedback) => {
// Send feedback to API
},
onStop: (conversationId) => {
// Server-side cleanup if needed
},
};
function ChatWindow() {
const [sidebarOpen, setSidebarOpen] = useState(false);
return (
<div className="flex h-full">
<XandiProvider handlers={handlers} config={config}>
<XSidebar
isOpen={sidebarOpen}
onClose={() => setSidebarOpen(false)}
/>
<div className="flex flex-1 flex-col">
<XHeader
onToggleHistory={() => setSidebarOpen((prev) => !prev)}
/>
<Xandi />
</div>
</XandiProvider>
</div>
);
}API Reference
XandiProvider
The context provider that manages chat state. Requires a handlers object with API communication functions.
| Prop | Type | Required | Description |
|---|---|---|---|
handlers | XandiHandlers | Yes | Object containing all handler functions |
conversationId | string | No | Existing conversation ID to restore a chat |
config | XandiConfig | No | Configuration for assistant appearance |
Xandi
The main chat component. Must be wrapped in XandiProvider.
| Prop | Type | Default | Description |
|---|---|---|---|
welcomeMessage | string | "How can I help you today?" | Message shown when chat is empty |
suggestions | Suggestion[] | [] | Prompt suggestions shown below the input |
uiMode | XandiUIMode | "full" | UI mode: full = no close button (default); sidebar / floating = show close button in header. Overrides provider config when set. |
XHeader
Header component for floating windows and sidebars. Must be inside XandiProvider. Avatar and title come from context config; New Chat uses startNewConversation from context. Close button is hidden when config.uiMode is "full".
| Prop | Type | Default | Description |
|---|---|---|---|
onClose | () => void | - | Callback when close button is clicked |
onToggleHistory | () => void | - | Callback when menu button is clicked |
XSidebar
Sidebar component for chat history. Must be inside XandiProvider. Fetches conversation history via getConvHistory from context when opened and manages its own loading state.
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | true | Whether the sidebar is visible |
onClose | () => void | - | Callback when close button is clicked |
useXandi Hook
Access the chat context from any component within XandiProvider.
import { useXandi } from "@px-ui/ai";
function CustomComponent() {
const {
conversation,
isLoading,
sendMessage,
stopRequest,
loadConversation,
startNewConversation,
submitFeedback,
config,
} = useXandi();
// Access messages from conversation
const messages = conversation.messages;
const conversationId = conversation.id;
// ...
}| Property | Type | Description |
|---|---|---|
conversation | Conversation | Current conversation object with messages, id, title, and timestamps |
isLoading | boolean | Whether a response is being fetched |
sendMessage | (text: string) => void | Function to send a message |
stopRequest | () => void | Stop the current request |
loadConversation | (conversationId: string, options?: GetConvOptions) => Promise<void> | Load an existing conversation (page/perPage default 1/20) |
startNewConversation | () => void | Reset to a new empty conversation |
submitFeedback | (messageId: string, feedback: FeedbackType) => void | Submit feedback for a message |
getConvHistory | () => Promise<ConversationSummary[]> | Handler to fetch conversation list (from provider) |
config | XandiConfig | Configuration object with avatar URL and assistant name |
Types
// Options for fetchResp handler
interface FetchRespOptions {
conversationId?: string;
signal?: AbortSignal; // For canceling requests
}
// Options for getConv handler (defaults: page 1, perPage 20)
interface GetConvOptions {
page?: number;
perPage?: number;
}
// Handler functions for API communication
interface XandiHandlers {
fetchResp: (message: string, options?: FetchRespOptions) => Promise<XandiResponse>;
getConv?: (conversationId: string, options?: GetConvOptions) => Promise<Conversation>;
getConvHistory?: () => Promise<ConversationSummary[]>;
onFeedback?: (messageId: string, conversationId: string, feedback: FeedbackType) => void;
onStop?: (conversationId: string) => void;
}
// Configuration for assistant appearance
interface XandiConfig {
avatarUrl?: string; // URL for the assistant's avatar
assistantName?: string; // Name of the assistant (default: "Xandi")
}
// Response from fetchResp handler
interface XandiResponse {
content: string;
type?: "text" | "markdown";
debugTrace?: unknown;
conversationId?: string;
}
// API response structure from backend
interface XandiApiResponse {
success: boolean;
message: string;
data: {
intent: string;
data: unknown;
conversation_id: string;
};
trace?: {
trace_id: string;
execution_mode: string;
intent: string;
tool_id: string;
debug_trace: unknown;
};
}
// Full conversation with messages (id is null for new unsaved conversations)
interface Conversation {
id: string | null;
title: string;
messages: Message[];
createdAt: Date;
updatedAt: Date;
}
// Conversation summary for history list
interface ConversationSummary {
id: string;
title: string;
timestamp: Date;
}
interface Suggestion {
id: string;
label: string;
prompt: string;
}
interface Message {
id: string;
role: "user" | "assistant";
content: string;
type?: "text" | "markdown";
debugTrace?: unknown;
}
type FeedbackType = "up" | "down" | null;
interface ChatHistoryItem {
id: string;
title: string;
timestamp: Date;
}Message Actions
Assistant messages include action buttons for feedback, copying, and debugging.
- Thumbs Up/Down - Provide feedback on message quality
- Copy - Copy message content to clipboard
- Debug - View debug trace (when
debugTraceis present in the message)
Installation
pnpm add @px-ui/aiDon't forget to import the styles:
import "@px-ui/ai/styles.css";