Atlas
Reference

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/react

Peer Dependencies

The hooks require these peer dependencies:

bun add react react-dom ai @ai-sdk/react

The 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

PropTypeDefaultDescription
apiUrlstringRequired. Atlas API server URL. Use "" for same-origin deployments.
apiKeystringAPI 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.
sidebarbooleanfalseShow the conversation history sidebar. Lazy-loads conversations after auth resolves.
schemaExplorerbooleanfalseShow the schema explorer button and modal for browsing tables and columns.
authClientAtlasAuthClientCustom auth client for managed auth mode. Provides session management (login, signup, logout, useSession).
toolRenderersToolRenderersCustom renderers for tool results. Map tool names to React components (see Tool Result Types below).
chatEndpointstring"/api/chat"Custom chat API endpoint path. Combined with apiUrl for the full URL.
conversationsEndpointstring"/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 ModeBehavior
managedUses authClient for session-based auth. Shows password change dialog if required.
simple-keySends apiKey as Bearer token. Falls back to sessionStorage.
noneNo auth headers sent.
Health check failureDefaults 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

PropTypeRequiredDescription
apiUrlstringYesAtlas API server URL. Use "" for same-origin deployments.
apiKeystringNoAPI key for simple-key auth mode. Sent as Bearer token.
authClientAtlasAuthClientNoCustom 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

FieldTypeDescription
messagesUIMessage[]Array of chat messages (AI SDK format)
setMessages(msgs | updater) => voidReplace all messages, or update via callback
sendMessage(text: string) => Promise<void>Send a text message. Rejects on failure.
inputstringCurrent input value (managed by the hook)
setInput(input: string) => voidUpdate the input value
statusAtlasChatStatus"ready", "submitted", "streaming", or "error"
isLoadingbooleantrue when status is "submitted" or "streaming"
errorError | nullLast error, if any
conversationIdstring | nullCurrent conversation ID (set by server via x-conversation-id header)
setConversationId(id: string | null) => voidManually 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

FieldTypeDescription
authModeAuthMode | nullDetected auth mode: "none", "simple-key", "byot", or "managed". null while loading.
isAuthenticatedbooleanWhether the user is authenticated based on auth mode and credentials
session{ user?: { email?: string } } | nullSession data for managed auth
isLoadingbooleantrue while the health check or session loading is in progress
errorError | nullError 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

FieldTypeDescription
conversationsConversation[]Array of conversation metadata (no messages)
totalnumberTotal count of conversations
isLoadingbooleanWhether the conversation list is loading
availablebooleanWhether the conversation API is available (requires internal DB)
selectedIdstring | nullCurrently selected conversation ID
setSelectedId(id: string | null) => voidSet 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

FieldTypeDescription
apiUrlstringAtlas API server URL
apiKeystring | undefinedAPI key for simple-key auth mode
authClientAtlasAuthClientAuth client instance (noop if not provided to AtlasProvider)
isCrossOriginbooleanWhether 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

FieldTypeDescription
conversationsConversation[]Array of conversation metadata
totalnumberTotal count of conversations
loadingbooleanWhether the conversation list is loading
availablebooleanWhether the conversation API is available
fetchErrorstring | nullError message from the last fetch attempt
selectedIdstring | nullCurrently selected conversation ID
setSelectedId(id: string | null) => voidSet 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

FieldTypeDescription
apiUrlstringAtlas API server URL
authClientAtlasAuthClientAuth client instance
isCrossOriginbooleanWhether 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): void

buildThemeInitScript / 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" | null

AtlasUIConfig

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): ChatErrorInfo

Return value (ChatErrorInfo)

FieldTypeDescription
titlestringPrimary user-facing message
detailstring | undefinedOptional secondary context
retryAfterSecondsnumber | undefinedSeconds until retry (rate_limited only, clamped 0--300)
codeChatErrorCode | undefinedServer error code, if parseable
clientCodeClientErrorCode | undefinedClient-side classification (network/offline/HTTP status)
retryableboolean | undefinedtrue = transient, false = permanent, undefined = unknown
requestIdstring | undefinedServer-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

FieldTypeDescription
themeThemeModeCurrent setting: "light", "dark", or "system"
isDarkbooleanWhether the resolved (effective) theme is dark
setTheme(mode: ThemeMode) => voidSet the theme mode. Persists to localStorage.
applyBrandColor(color: string) => voidApply 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 === false

Hook 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)
ProviderAtlasProvider from @useatlas/react/hooksAtlasUIProvider from @useatlas/react
Importimport { useAtlasChat } from "@useatlas/react/hooks"import { AtlasChat } from "@useatlas/react"
UIYou build your ownPre-built chat interface with markdown, code highlighting, charts
Bundle sizeMinimal (React + AI SDK)Larger (includes Recharts, syntax highlighter, Radix UI)
StylingYour choiceTailwind CSS 4 + shadcn/ui
Use caseCustom UIs, design system integrationQuick 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

Edit on GitHub

Last updated on

On this page