React Hooks Reference
Headless React hooks for building custom Atlas chat UIs.
The @useatlas/react package provides headless React hooks and a pre-built chat component for embedding Atlas into React applications. Use the hooks to build a fully custom UI, or drop in the AtlasChat component for a ready-made experience.
Installation
bun add @useatlas/reactPeer Dependencies
The hooks require these peer dependencies:
bun add react react-dom ai @ai-sdk/reactThe pre-built AtlasChat component additionally uses lucide-react, react-syntax-highlighter, recharts, tailwindcss, and xlsx (all optional peer deps).
Imports
The package has two entry points:
// Pre-built AtlasChat component + UI provider
import { AtlasChat, AtlasUIProvider } from "@useatlas/react";
// Headless hooks only (smaller bundle, no UI components)
import { AtlasProvider, useAtlasChat, useAtlasAuth } from "@useatlas/react/hooks";The main entry point (@useatlas/react) exports AtlasUIProvider for use with the pre-built AtlasChat component. The hooks entry point (@useatlas/react/hooks) exports AtlasProvider, a lightweight provider for headless hooks.
AtlasChat
A pre-built, full-featured chat component. Drop it into your app for a ready-made Atlas experience with markdown rendering, code highlighting, charts, conversation history, and schema exploration.
import { AtlasChat, AtlasUIProvider } from "@useatlas/react";
function App() {
return (
<AtlasUIProvider>
<AtlasChat
apiUrl="https://your-atlas-api.example.com"
apiKey="sk-..."
theme="system"
sidebar
schemaExplorer
/>
</AtlasUIProvider>
);
}AtlasChat Props
| Prop | Type | Default | Description |
|---|---|---|---|
apiUrl | string | — | Required. Atlas API server URL. Use "" for same-origin deployments. |
apiKey | string | — | API key for simple-key auth mode. Sent as Bearer token. Also loaded from sessionStorage (atlas-api-key) on mount. |
theme | "light" | "dark" | "system" | "system" | Color theme. Applied on mount via CSS variables and persisted to localStorage. |
sidebar | boolean | false | Show the conversation history sidebar. Lazy-loads conversations after auth resolves. |
schemaExplorer | boolean | false | Show the schema explorer button and modal for browsing tables and columns. |
authClient | AtlasAuthClient | — | Custom auth client for managed auth mode. Provides session management (login, signup, logout, useSession). |
toolRenderers | ToolRenderers | — | Custom renderers for tool results. Map tool names to React components (see Tool Result Types below). |
chatEndpoint | string | "/api/chat" | Custom chat API endpoint path. Combined with apiUrl for the full URL. |
conversationsEndpoint | string | "/api/v1/conversations" | Custom conversations API endpoint path. Used for listing and loading conversations. |
Auth Mode Detection
AtlasChat auto-detects the server's auth mode by calling /api/health on mount:
| Auth Mode | Behavior |
|---|---|
managed | Uses authClient for session-based auth. Shows password change dialog if required. |
simple-key | Sends apiKey as Bearer token. Falls back to sessionStorage. |
none | No auth headers sent. |
| Health check failure | Defaults to none. Shows a persistent warning banner. |
Brand Color
If the health endpoint returns a brandColor value (OKLCH format), it's applied as the --atlas-brand CSS custom property. See Custom Styling for override options.
AtlasProvider
Wraps your app and supplies API URL, auth credentials, and an optional auth client to all Atlas hooks. Must be an ancestor of any component that uses Atlas hooks.
import { AtlasProvider } from "@useatlas/react/hooks";
function App() {
return (
// Wrap your app — all Atlas hooks read config from this provider
<AtlasProvider apiUrl="https://your-atlas-api.example.com" apiKey="sk-...">
<YourChatUI />
</AtlasProvider>
);
}Props
| Prop | Type | Required | Description |
|---|---|---|---|
apiUrl | string | Yes | Atlas API server URL. Use "" for same-origin deployments. |
apiKey | string | No | API key for simple-key auth mode. Sent as Bearer token. |
authClient | AtlasAuthClient | No | Custom auth client for managed auth mode (better-auth compatible). |
The provider automatically detects cross-origin requests and configures credential handling accordingly.
useAtlasChat
Manages chat state, message streaming, and conversation tracking. Wraps the AI SDK's useChat with Atlas-specific transport configuration.
import { useAtlasChat } from "@useatlas/react/hooks";
function ChatUI() {
const {
messages, // All messages in the conversation (AI SDK UIMessage format)
sendMessage, // Send a text message to the agent
input, // Controlled input value
setInput, // Update the input field
status, // "ready" | "submitted" | "streaming" | "error"
isLoading, // True while the agent is working
error, // Last error, if any
conversationId, // Server-assigned conversation ID
} = useAtlasChat();
return (
<form onSubmit={(e) => { e.preventDefault(); sendMessage(input); }}>
{/* Render each message's text parts */}
<div>
{messages.map((msg) => (
<div key={msg.id}>{msg.parts?.map((p, i) =>
p.type === "text" ? <p key={i}>{p.text}</p> : null
)}</div>
))}
</div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit" disabled={isLoading}>Send</button>
</form>
);
}useAtlasChat options
useAtlasChat({
initialConversationId: "existing-id", // Resume an existing conversation
onConversationIdChange: (id) => { ... }, // Called when server assigns a conversation ID
})useAtlasChat return value
| Field | Type | Description |
|---|---|---|
messages | UIMessage[] | Array of chat messages (AI SDK format) |
setMessages | (msgs | updater) => void | Replace all messages, or update via callback |
sendMessage | (text: string) => Promise<void> | Send a text message. Rejects on failure. |
input | string | Current input value (managed by the hook) |
setInput | (input: string) => void | Update the input value |
status | AtlasChatStatus | "ready", "submitted", "streaming", or "error" |
isLoading | boolean | true when status is "submitted" or "streaming" |
error | Error | null | Last error, if any |
conversationId | string | null | Current conversation ID (set by server via x-conversation-id header) |
setConversationId | (id: string | null) => void | Manually set the conversation ID |
useAtlasAuth
Detects the server's auth mode, manages authentication state, and provides login/signup/logout for managed auth.
import { useAtlasAuth } from "@useatlas/react/hooks";
// Gate component — shows login form or children based on auth state
function AuthGate({ children }: { children: React.ReactNode }) {
const { authMode, isAuthenticated, isLoading, login } = useAtlasAuth();
if (isLoading) return <p>Loading...</p>;
// Only show login for managed auth — other modes handle auth externally
if (!isAuthenticated && authMode === "managed") {
return <LoginForm onSubmit={login} />;
}
return <>{children}</>;
}useAtlasAuth return value
| Field | Type | Description |
|---|---|---|
authMode | AuthMode | null | Detected auth mode: "none", "simple-key", "byot", or "managed". null while loading. |
isAuthenticated | boolean | Whether the user is authenticated based on auth mode and credentials |
session | { user?: { email?: string } } | null | Session data for managed auth |
isLoading | boolean | true while the health check or session loading is in progress |
error | Error | null | Error from health check or auth operations |
login | (email, password) => Promise<{ error?: string }> | Sign in with email/password (managed auth) |
signup | (email, password, name) => Promise<{ error?: string }> | Sign up (managed auth) |
logout | () => Promise<{ error?: string }> | Sign out (managed auth) |
Auth mode detection works by calling the /api/health endpoint on mount. The hook retries once on failure (2 total attempts) before falling back to "none".
useAtlasConversations
Lists, loads, deletes, and stars conversations. Auth credentials are automatically wired from AtlasProvider.
import { useAtlasConversations } from "@useatlas/react/hooks";
function ConversationList() {
const {
conversations, // Array of conversation metadata (no messages)
total, // Total count for pagination
isLoading,
selectedId, // Currently highlighted conversation
setSelectedId, // Select a conversation by ID
loadConversation, // Fetch full message history for a conversation
deleteConversation,
starConversation, // Set star status (true to pin, false to unpin)
} = useAtlasConversations();
if (isLoading) return <p>Loading...</p>;
return (
<ul>
{conversations.map((c) => (
<li key={c.id} onClick={() => setSelectedId(c.id)}>
{c.title ?? "Untitled"}
{/* Toggle star — second arg is the desired starred state */}
<button onClick={() => starConversation(c.id, !c.starred)}>
{c.starred ? "Unstar" : "Star"}
</button>
</li>
))}
</ul>
);
}useAtlasConversations options
useAtlasConversations({
enabled: true, // When false, refresh() becomes a no-op (default: true)
})useAtlasConversations return value
| Field | Type | Description |
|---|---|---|
conversations | Conversation[] | Array of conversation metadata (no messages) |
total | number | Total count of conversations |
isLoading | boolean | Whether the conversation list is loading |
available | boolean | Whether the conversation API is available (requires internal DB) |
selectedId | string | null | Currently selected conversation ID |
setSelectedId | (id: string | null) => void | Set the selected conversation |
refresh | () => Promise<void> | Manually refresh the conversation list |
loadConversation | (id: string) => Promise<UIMessage[] | null> | Load a conversation's messages (returns AI SDK format) |
deleteConversation | (id: string) => Promise<boolean> | Delete a conversation |
starConversation | (id: string, starred: boolean) => Promise<boolean> | Star or unstar a conversation |
useAtlasContext
Provides raw access to the AtlasProvider context value. Throws if called outside <AtlasProvider>. Useful for building custom hooks or components that need direct access to the API URL, credentials, and cross-origin status.
import { useAtlasContext } from "@useatlas/react/hooks";
function CustomFetcher() {
const { apiUrl, apiKey, isCrossOrigin } = useAtlasContext();
// Build a custom fetch call using provider-supplied credentials
async function fetchCustomEndpoint() {
const headers: Record<string, string> = {};
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
const res = await fetch(`${apiUrl}/api/v1/custom`, {
headers,
credentials: isCrossOrigin ? "include" : "same-origin",
});
return res.json();
}
// ...
}useAtlasContext return value
| Field | Type | Description |
|---|---|---|
apiUrl | string | Atlas API server URL |
apiKey | string | undefined | API key for simple-key auth mode |
authClient | AtlasAuthClient | Auth client instance (noop if not provided to AtlasProvider) |
isCrossOrigin | boolean | Whether the API URL is cross-origin (derived from apiUrl at render time) |
useConversations
Lower-level conversation management hook that accepts explicit auth configuration instead of reading from AtlasProvider. Use this when you need full control over request headers and credentials, or when integrating outside the AtlasProvider context. useAtlasConversations wraps this hook with context-derived credentials.
import { useConversations } from "@useatlas/react";
function ConversationManager() {
const {
conversations,
total,
loading,
available,
fetchError,
selectedId,
setSelectedId,
fetchList,
loadConversation,
deleteConversation,
starConversation,
refresh,
} = useConversations({
apiUrl: "https://your-atlas-api.example.com",
enabled: true,
getHeaders: () => ({ Authorization: "Bearer sk-..." }),
getCredentials: () => "same-origin",
});
// ...
}useConversations options
interface UseConversationsOptions {
apiUrl: string;
enabled: boolean;
getHeaders: () => Record<string, string>;
getCredentials: () => RequestCredentials;
/** Custom conversations API endpoint path. Defaults to "/api/v1/conversations". */
conversationsEndpoint?: string;
}useConversations return value
| Field | Type | Description |
|---|---|---|
conversations | Conversation[] | Array of conversation metadata |
total | number | Total count of conversations |
loading | boolean | Whether the conversation list is loading |
available | boolean | Whether the conversation API is available |
fetchError | string | null | Error message from the last fetch attempt |
selectedId | string | null | Currently selected conversation ID |
setSelectedId | (id: string | null) => void | Set the selected conversation |
fetchList | () => Promise<void> | Fetch the conversation list |
loadConversation | (id: string) => Promise<UIMessage[]> | Load a conversation's messages (returns AI SDK format) |
deleteConversation | (id: string) => Promise<void> | Delete a conversation |
starConversation | (id: string, starred: boolean) => Promise<void> | Star or unstar a conversation |
refresh | () => Promise<void> | Alias for fetchList |
useAtlasConfig
Provides access to the AtlasUIProvider context value. This is the provider-level equivalent for the pre-built AtlasChat component tree (as opposed to useAtlasContext which reads from AtlasProvider). Throws if called outside <AtlasUIProvider>.
import { useAtlasConfig } from "@useatlas/react";
function CustomWidget() {
const { apiUrl, authClient, isCrossOrigin } = useAtlasConfig();
// ...
}useAtlasConfig return value
| Field | Type | Description |
|---|---|---|
apiUrl | string | Atlas API server URL |
authClient | AtlasAuthClient | Auth client instance |
isCrossOrigin | boolean | Whether the API URL is cross-origin |
Utilities
setTheme
Sets the theme mode globally without needing a hook. Useful for server-side rendering or setting the theme before React hydrates.
import { setTheme } from "@useatlas/react";
// Set theme before React renders (e.g. in a layout component)
setTheme("dark");Signature
function setTheme(mode: ThemeMode): voidbuildThemeInitScript / THEME_STORAGE_KEY
Server-side utilities for flicker-free theme initialization. buildThemeInitScript returns a <script> string that reads the user's stored preference from localStorage and applies it before the first paint. THEME_STORAGE_KEY is the localStorage key used for persistence.
import { buildThemeInitScript, THEME_STORAGE_KEY } from "@useatlas/react";
// In a server-rendered HTML template or Next.js layout:
const script = buildThemeInitScript();
// Inject `script` into <head> to prevent theme flash on page load
// Read the stored theme preference directly:
const stored = localStorage.getItem(THEME_STORAGE_KEY); // "light" | "dark" | "system" | nullAtlasUIConfig
The configuration object type accepted by AtlasUIProvider. Exported for type-safe wrapper components.
import type { AtlasUIConfig } from "@useatlas/react";
const config: AtlasUIConfig = {
apiUrl: "https://api.example.com",
authClient: myAuthClient,
};parseChatError
Parses an AI SDK chat error into a user-friendly ChatErrorInfo object. Expects error.message to contain a JSON string with { error, message, retryAfterSeconds? }. Falls back to a generic title when the body is not valid JSON (network failures, HTML error pages, etc.), preserving the original message as detail (truncated to 200 characters). Also classifies client-side errors (network failures, offline, HTTP status) via the clientCode field.
import { parseChatError } from "@useatlas/react/hooks";
const errorInfo = parseChatError(error, "managed");
console.log(errorInfo.title); // "Authentication required"
console.log(errorInfo.code); // "auth_error"
console.log(errorInfo.retryable); // false
console.log(errorInfo.clientCode); // undefined (server error, not client-side)
console.log(errorInfo.requestId); // "abc-123" (for log correlation)Signature
function parseChatError(error: Error, authMode: AuthMode): ChatErrorInfoReturn value (ChatErrorInfo)
| Field | Type | Description |
|---|---|---|
title | string | Primary user-facing message |
detail | string | undefined | Optional secondary context |
retryAfterSeconds | number | undefined | Seconds until retry (rate_limited only, clamped 0--300) |
code | ChatErrorCode | undefined | Server error code, if parseable |
clientCode | ClientErrorCode | undefined | Client-side classification (network/offline/HTTP status) |
retryable | boolean | undefined | true = transient, false = permanent, undefined = unknown |
requestId | string | undefined | Server-assigned request ID (UUID) for log correlation |
AUTH_MODES
A constant tuple of all valid AuthMode values. Useful for validation and iteration.
import { AUTH_MODES } from "@useatlas/react/hooks";
console.log(AUTH_MODES); // ["none", "simple-key", "managed", "byot"]
// Type-safe iteration
for (const mode of AUTH_MODES) {
console.log(mode); // "none" | "simple-key" | "managed" | "byot"
}Definition
const AUTH_MODES: readonly ["none", "simple-key", "managed", "byot"];useAtlasTheme
Manages light/dark/system theme mode with localStorage persistence.
import { useAtlasTheme } from "@useatlas/react/hooks";
function ThemeToggle() {
const { theme, isDark, setTheme } = useAtlasTheme();
return (
<button onClick={() => setTheme(isDark ? "light" : "dark")}>
{isDark ? "Switch to light" : "Switch to dark"}
</button>
);
}useAtlasTheme return value
| Field | Type | Description |
|---|---|---|
theme | ThemeMode | Current setting: "light", "dark", or "system" |
isDark | boolean | Whether the resolved (effective) theme is dark |
setTheme | (mode: ThemeMode) => void | Set the theme mode. Persists to localStorage. |
applyBrandColor | (color: string) => void | Apply a brand color via CSS custom property --atlas-brand (oklch format) |
Patterns
Conversation History
Use useAtlasConversations with useAtlasChat to load past conversations and manage message state. The conversation list requires an internal database (DATABASE_URL) — the available flag tells you whether the API supports it.
import { useState } from "react";
import { AtlasProvider, useAtlasChat, useAtlasConversations } from "@useatlas/react/hooks";
function App() {
return (
<AtlasProvider apiUrl="https://your-atlas-api.example.com" apiKey="sk-...">
<ChatWithHistory />
</AtlasProvider>
);
}
function ChatWithHistory() {
// Destructure conversations first — refresh() is used in onConversationIdChange below
const {
conversations,
total,
isLoading: listLoading,
available,
selectedId,
setSelectedId,
loadConversation,
deleteConversation,
starConversation,
refresh,
} = useAtlasConversations();
const {
messages,
sendMessage,
input,
setInput,
isLoading,
setMessages,
setConversationId,
conversationId,
} = useAtlasChat({
// Called when the server assigns a conversation ID to a new chat
onConversationIdChange: (id) => {
// Refresh the sidebar so the new conversation appears
refresh();
},
});
const [loadError, setLoadError] = useState<string | null>(null);
// Load a past conversation into the chat view
async function selectConversation(id: string) {
setLoadError(null);
const msgs = await loadConversation(id);
if (msgs) {
setMessages(msgs); // Replace current messages with loaded history
setConversationId(id); // Link future messages to this conversation
setSelectedId(id); // Highlight in the sidebar
} else {
setLoadError("Could not load conversation. It may have been deleted.");
}
}
// Start a fresh conversation
function handleNewChat() {
setMessages([]); // Clear message history
setConversationId(null); // Detach from any previous conversation
setSelectedId(null); // Deselect in the sidebar
}
// Conversations require an internal DB — show a message if unavailable
if (!available) {
return <p>Conversation history requires DATABASE_URL to be configured.</p>;
}
return (
<div style={{ display: "flex", gap: "1rem" }}>
{/* Sidebar: conversation list with star/delete actions */}
<aside style={{ width: 250 }}>
<button onClick={handleNewChat}>New Chat</button>
{listLoading && <p>Loading conversations...</p>}
<ul>
{conversations.map((c) => (
<li key={c.id}>
<button
onClick={() => selectConversation(c.id)}
// Highlight the active conversation
style={{ fontWeight: selectedId === c.id ? "bold" : "normal" }}
>
{c.title ?? "Untitled"}
</button>
{/* Toggle starred state — pinned conversations sort first */}
<button onClick={() => starConversation(c.id, !c.starred)}>
{c.starred ? "Unstar" : "Star"}
</button>
<button onClick={() => deleteConversation(c.id)}>Delete</button>
</li>
))}
</ul>
{/* Show total for pagination awareness */}
<p>{conversations.length} of {total} conversations</p>
</aside>
{/* Main chat area */}
<main style={{ flex: 1 }}>
{loadError && <p style={{ color: "red" }}>{loadError}</p>}
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong>
{msg.parts?.map((p, i) =>
p.type === "text" ? <span key={i}>{p.text}</span> : null
)}
</div>
))}
</div>
<form onSubmit={(e) => { e.preventDefault(); sendMessage(input); }}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask a question..."
/>
<button type="submit" disabled={isLoading}>Send</button>
</form>
</main>
</div>
);
}Custom Styling
The AtlasChat component uses CSS custom properties scoped to .atlas-root, data attributes on key elements, and Tailwind utility classes. You can customize the look without forking the component.
CSS Custom Properties
Override design tokens by targeting .atlas-root. These map to shadcn/ui's token system:
/* Override Atlas design tokens in your app's CSS */
.atlas-root {
--background: oklch(0.98 0.01 240); /* Light blue-tinted background */
--foreground: oklch(0.15 0.02 260); /* Slightly blue-shifted text */
--primary: oklch(0.45 0.2 260); /* Custom primary (indigo) */
--primary-foreground: oklch(0.98 0 0); /* White text on primary */
--radius: 0.75rem; /* Rounder corners */
--atlas-brand: oklch(0.6 0.2 260); /* Brand accent color (indigo) */
}
/* Dark mode overrides — applied when <html> has the "dark" class */
.dark .atlas-root {
--background: oklch(0.15 0.02 260);
--foreground: oklch(0.95 0.01 240);
--primary: oklch(0.7 0.15 260);
}Data Attribute Selectors
Key elements expose data-* attributes for targeted styling:
/* Style the message input field */
[data-atlas-input] {
font-family: "Inter", sans-serif;
font-size: 16px; /* Prevent iOS zoom on focus */
}
/* Style the input form container */
[data-atlas-form] {
padding-top: 1rem;
border-top: 2px solid var(--border);
}
/* Style the message list container */
[data-atlas-messages] {
gap: 1.5rem; /* More space between messages */
}
/* Style the Atlas logo */
[data-atlas-logo] {
color: var(--atlas-brand); /* Tint logo with brand color */
}Programmatic Brand Color
Use applyBrandColor from useAtlasTheme to set the brand color at runtime — for example, to match your product's theme:
import { useAtlasTheme } from "@useatlas/react/hooks";
function BrandPicker() {
const { applyBrandColor } = useAtlasTheme();
// applyBrandColor sets the --atlas-brand CSS custom property on :root.
// Use oklch format for consistency with the built-in theme tokens.
return (
<div>
<button onClick={() => applyBrandColor("oklch(0.6 0.2 260)")}>
Indigo
</button>
<button onClick={() => applyBrandColor("oklch(0.65 0.2 150)")}>
Emerald
</button>
<button onClick={() => applyBrandColor("oklch(0.6 0.25 30)")}>
Coral
</button>
</div>
);
}Error Boundaries
Atlas hooks throw when used outside their provider. Wrap Atlas components in an error boundary to catch initialization errors gracefully. For runtime errors during streaming, check the error state from useAtlasChat and render inline feedback:
import { Component, type ReactNode } from "react";
import { AtlasProvider, useAtlasChat } from "@useatlas/react/hooks";
// Error boundary that catches render errors from Atlas hooks
class AtlasErrorBoundary extends Component<
{ children: ReactNode; fallback?: ReactNode },
{ error: Error | null }
> {
state: { error: Error | null } = { error: null };
static getDerivedStateFromError(error: Error) {
return { error };
}
render() {
if (this.state.error) {
// Show fallback UI when an Atlas component throws
return this.props.fallback ?? (
<div role="alert" style={{ padding: "1rem", border: "1px solid red" }}>
<h2>Atlas encountered an error</h2>
<p>{this.state.error.message}</p>
<button onClick={() => this.setState({ error: null })}>
Retry
</button>
</div>
);
}
return this.props.children;
}
}
// Usage — wrap the chat UI so provider or network errors don't crash the page
function App() {
return (
<AtlasErrorBoundary
fallback={
<div style={{ padding: "2rem", textAlign: "center" }}>
<p>Atlas is temporarily unavailable.</p>
<p>Please refresh the page to try again.</p>
</div>
}
>
<AtlasProvider apiUrl="https://your-atlas-api.example.com" apiKey="sk-...">
<ChatUI />
</AtlasProvider>
</AtlasErrorBoundary>
);
}
function ChatUI() {
// useAtlasChat throws if used outside AtlasProvider — the error
// boundary above catches this and shows the fallback UI
const { messages, sendMessage, error, status } = useAtlasChat();
return (
<div>
{/* Show runtime errors (network, streaming) inline instead of crashing */}
{error && (
<div role="alert" style={{ color: "red", padding: "0.5rem" }}>
Error: {error.message}
</div>
)}
{messages.map((msg) => (
<div key={msg.id}>
{msg.parts?.map((p, i) =>
p.type === "text" ? <p key={i}>{p.text}</p> : null
)}
</div>
))}
</div>
);
}TypeScript Types
The @useatlas/react/hooks entry point re-exports all types you need. These are canonical from @useatlas/types — no local type duplication needed.
Message Types
Atlas uses the AI SDK's UIMessage format for chat messages. When loading conversation history, server-side Message objects are transformed into UIMessage automatically.
import type { UIMessage } from "@ai-sdk/react";
// UIMessage — the format returned by useAtlasChat().messages
// Each message contains an array of typed parts (text, tool calls, etc.)
interface UIMessage {
id: string;
role: "system" | "user" | "assistant";
parts: Array<
| { type: "text"; text: string } // Plain text content
| { type: "tool-invocation"; /* ... */ } // Tool call (executeSQL, explore, etc.)
>;
}
// Message — server-side format from the conversations API
// Transformed into UIMessage by loadConversation()
interface Message {
id: string;
conversationId: string;
role: "user" | "assistant" | "system" | "tool";
content: unknown; // String for user/assistant, structured object for tool results
createdAt: string; // ISO 8601 timestamp
}Conversation Types
// Conversation — metadata returned by useAtlasConversations().conversations
interface Conversation {
id: string;
userId: string | null;
title: string | null; // Auto-generated from first message, or null
surface: "web" | "api" | "mcp" | "slack";
connectionId: string | null; // Datasource used for this conversation
starred: boolean; // Pinned by user
createdAt: string; // ISO 8601 timestamp
updatedAt: string; // ISO 8601 timestamp
notebookState?: NotebookStateWire | null; // Server-persisted notebook layout
}
interface NotebookStateWire {
version: number;
cellOrder?: string[];
cellProps?: Record<string, { collapsed?: boolean }>;
branches?: ForkBranchWire[];
forkRootId?: string;
forkPointCellId?: string;
textCells?: Record<string, { content: string }>;
}
interface ForkBranchWire {
conversationId: string;
forkPointCellId: string;
label: string;
createdAt: string;
}
// ConversationWithMessages — returned by the GET /api/v1/conversations/:id endpoint
interface ConversationWithMessages extends Conversation {
messages: Message[];
}Tool Result Types
Custom tool renderers receive typed results via ToolRendererProps. The package exports specific result shapes for built-in tools:
import type {
ToolRendererProps,
ToolRenderers,
SQLToolResult,
ExploreToolResult,
PythonToolResult,
} from "@useatlas/react/hooks";
// ToolRendererProps<T> — props passed to custom tool renderer components
interface ToolRendererProps<T = unknown> {
toolName: string; // Name of the tool (e.g. "executeSQL")
args: Record<string, unknown>; // Input arguments passed to the tool
result: T; // Tool output (null while still running)
isLoading: boolean; // Whether the tool invocation is in progress
}
// SQLToolResult — result from the executeSQL tool
type SQLToolResult =
| { success: true; columns: string[]; rows: Record<string, unknown>[]; truncated?: boolean; explanation?: string; row_count?: number }
| { success: false; error: string };
// ExploreToolResult — result from the explore tool (plain text output)
type ExploreToolResult = string;
// PythonToolResult — result from the executePython tool
type PythonToolResult =
| { success: true; output?: string; explanation?: string; table?: { columns: string[]; rows: unknown[][] }; charts?: { base64: string; mimeType: "image/png" }[]; rechartsCharts?: { type: "line" | "bar" | "pie"; data: Record<string, unknown>[]; categoryKey: string; valueKeys: string[] }[] }
| { success: false; error: string; output?: string };
// ToolRenderers — map of tool names to custom renderer components
// Known tools get typed result generics; arbitrary tool names use `unknown`
type ToolRenderers = {
executeSQL?: ComponentType<ToolRendererProps<SQLToolResult | null>>;
explore?: ComponentType<ToolRendererProps<ExploreToolResult | null>>;
executePython?: ComponentType<ToolRendererProps<PythonToolResult | null>>;
} & {
[toolName: string]: ComponentType<ToolRendererProps> | undefined;
};Auth & Error Types
// AuthMode — detected from the server's /api/health endpoint
type AuthMode = "none" | "simple-key" | "managed" | "byot";
// AtlasChatStatus — chat state from useAtlasChat()
type AtlasChatStatus = "ready" | "submitted" | "streaming" | "error";
// ThemeMode — theme setting from useAtlasTheme()
type ThemeMode = "light" | "dark" | "system";
// ChatErrorCode — structured error codes from the chat API
type ChatErrorCode =
| "auth_error"
| "rate_limited"
| "configuration_error"
| "no_datasource"
| "invalid_request"
| "provider_model_not_found"
| "provider_auth_error"
| "provider_rate_limit"
| "provider_timeout"
| "provider_unreachable"
| "provider_error"
| "internal_error"
| "validation_error"
| "not_found"
| "forbidden"
| "session_expired"
| "forbidden_role"
| "org_not_found"
| "plan_limit_exceeded"
| "trial_expired"
| "billing_check_failed"
| "workspace_check_failed"
| "workspace_throttled"
| "workspace_suspended"
| "workspace_deleted";
// ChatErrorInfo — parsed error details (use parseChatError() to extract)
interface ChatErrorInfo {
title: string; // Primary user-facing message
detail?: string; // Optional secondary context
retryAfterSeconds?: number; // Seconds until retry (rate_limited only, clamped 0–300)
code?: ChatErrorCode; // Server error code, if parseable
clientCode?: ClientErrorCode; // Client-side classification (network/offline/HTTP status)
retryable?: boolean; // true = transient, false = permanent, undefined = unknown
requestId?: string; // Server-assigned request ID (UUID) for log correlation
}
// ClientErrorCode — client-side error classification
type ClientErrorCode =
| "api_unreachable" // fetch failed, ECONNREFUSED, ENOTFOUND
| "auth_failure" // HTTP 401
| "rate_limited_http" // HTTP 429
| "server_error" // HTTP 5xx
| "offline"; // navigator.onLine === falseHook Option & Return Types
All hooks export their option and return types for use in wrapper components:
import type {
// Provider
AtlasProviderProps,
AtlasContextValue,
AtlasAuthClient,
// useAtlasChat
UseAtlasChatOptions,
UseAtlasChatReturn,
AtlasChatStatus,
// useAtlasAuth
UseAtlasAuthReturn,
// useAtlasContext
AtlasContextValue,
// useAtlasConversations
UseAtlasConversationsOptions,
UseAtlasConversationsReturn,
// useAtlasTheme
UseAtlasThemeReturn,
ThemeMode,
} from "@useatlas/react/hooks";
// Root entry point exports (not available from /hooks subpath)
import type {
UseConversationsOptions,
UseConversationsReturn,
} from "@useatlas/react";Full Example: Custom Chat UI
A minimal working example combining multiple hooks:
import { AtlasProvider, useAtlasChat, useAtlasAuth, useAtlasConversations } from "@useatlas/react/hooks";
function App() {
return (
// Provider supplies API URL and credentials to all hooks below
<AtlasProvider apiUrl="https://your-atlas-api.example.com" apiKey="sk-...">
<Chat />
</AtlasProvider>
);
}
function Chat() {
// Combine auth, chat, and conversation hooks for a complete UI
const { isAuthenticated, isLoading: authLoading } = useAtlasAuth();
const { messages, sendMessage, input, setInput, isLoading, setMessages, setConversationId } = useAtlasChat();
const { conversations, loadConversation } = useAtlasConversations();
if (authLoading) return <p>Connecting...</p>;
if (!isAuthenticated) return <p>Not authenticated</p>;
// Load a past conversation's messages into the chat view
async function selectConversation(id: string) {
const msgs = await loadConversation(id);
if (msgs) {
setMessages(msgs); // Replace current messages with loaded history
setConversationId(id); // Link future messages to this conversation
}
}
return (
<div style={{ display: "flex", gap: "1rem" }}>
{/* Sidebar: conversation history */}
<aside>
<h3>History</h3>
{conversations.map((c) => (
<button key={c.id} onClick={() => selectConversation(c.id)}>
{c.title ?? "Untitled"}
</button>
))}
</aside>
{/* Main area: messages + input */}
<main>
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong>
{msg.parts?.map((p, i) => p.type === "text" ? <span key={i}>{p.text}</span> : null)}
</div>
))}
</div>
<form onSubmit={(e) => { e.preventDefault(); sendMessage(input); }}>
<input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Ask a question..." />
<button type="submit" disabled={isLoading}>Send</button>
</form>
</main>
</div>
);
}Hooks vs. AtlasChat Component
Headless Hooks (@useatlas/react/hooks) | AtlasChat Component (@useatlas/react) | |
|---|---|---|
| Provider | AtlasProvider from @useatlas/react/hooks | AtlasUIProvider from @useatlas/react |
| Import | import { useAtlasChat } from "@useatlas/react/hooks" | import { AtlasChat } from "@useatlas/react" |
| UI | You build your own | Pre-built chat interface with markdown, code highlighting, charts |
| Bundle size | Minimal (React + AI SDK) | Larger (includes Recharts, syntax highlighter, Radix UI) |
| Styling | Your choice | Tailwind CSS 4 + shadcn/ui |
| Use case | Custom UIs, design system integration | Quick setup, internal tools, embedded widgets |
The AtlasChat component uses the same hooks internally — you can start with AtlasChat and switch to hooks when you need more control.
See Also
- Embedding Widget — Script-tag widget embedding (no React required)
- SDK Reference — Typed TypeScript client for server-side and headless usage
- Error Codes — Full catalog of
ChatErrorCodeandClientErrorCodevalues - Authentication — Auth mode setup (affects
useAtlasAuthbehavior) - Choosing an Integration — Compare widget, hooks, SDK, and REST API
Last updated on