> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Atlas

> Components and utilities for building the Atlas AI chat experience.

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

<LiveCode example="ext-atlas-chat-full-demo" fullWidth screenshot>
  ```tsx lines theme={null}
  import {
    ChatWindow,
    Header,
    Footer,
    Content,
    MarkdownMessage,
    UserMessage,
    SuggestionList,
    NotificationCard,
    UserFeedback,
  } from "@servicetitan/anvil2-ext-atlas";
  import { Text } from "@servicetitan/anvil2";
  import { useState } from "react";

  function App() {
    const [message1, setMessage1] = useState("");
    const [message2, setMessage2] = useState("");

    return (
      <div style={{ height: "600px", width: "1100px" }}>
        <ChatWindow open={true} position={{ x: 225, y: 50 }}>
          <Header title="Atlas" onClose={() => console.log("close")} />
          <Content itemsLength={3}>
            <MarkdownMessage
              message="Hello! How can I assist you today?"
              assistant="Atlas"
              toolboxProps={{
                onLike: async () => console.log("like"),
                onDislike: async () => console.log("dislike"),
                currentFeedback: UserFeedback.None,
              }}
            />
            <Text
              size="small"
              style={{
                padding: "var(--size-3)",
                color: "var(--foreground-color-subtle)",
              }}
            >
              Thank you for your feedback!
            </Text>
            <UserMessage message="I am looking for information on your services." />
            <SuggestionList
              suggestions={[
                "What is the weather like today?",
                "Tell me a joke.",
                "How do I reset my password?",
              ]}
              onSelect={(s) => console.log("selected:", s)}
            />
          </Content>
          <Footer
            message={message1}
            onMessageChange={setMessage1}
            onSubmit={() => console.log("submit")}
            placeholder="Ask Atlas"
          />
        </ChatWindow>

        <ChatWindow open={true} position={{ x: 700, y: 50 }}>
          <Header title="Atlas" onClose={() => console.log("close")} />
          <Content itemsLength={2}>
            <NotificationCard
              title="Projected increase in demand"
              message="HVAC replacement demand is set to surge in 2 weeks creating a $450K opportunity."
              timestamp="Today"
              unread={false}
              onClick={() => console.log("notification clicked")}
            />
            <NotificationCard
              title="Prioritize emergency jobs"
              message="New maintenance requests should be scheduled to meet expected demand."
              timestamp="Yesterday"
              unread={true}
              onClick={() => console.log("notification clicked")}
            />
          </Content>
          <Footer
            message={message2}
            onMessageChange={setMessage2}
            onSubmit={() => console.log("submit")}
            placeholder="Ask Atlas"
          />
        </ChatWindow>
      </div>
    );
  }

  export default App;
  ```
</LiveCode>

Atlas is an extended component library designed for building AI-powered chat interfaces. It provides a comprehensive set of components for creating conversational UI experiences, including chat windows, message displays, input composers, and recommendation cards.

## Installation

Atlas components are available from the `@servicetitan/anvil2-ext-atlas` package:

```bash theme={null}
npm install @servicetitan/anvil2-ext-atlas
```

## Import

```tsx theme={null}
import {
  ChatWindow,
  Header,
  Footer,
  Content,
  AssistantMessage,
  UserMessage,
  MarkdownMessage,
} from "@servicetitan/anvil2-ext-atlas";
```

## Key Features

### Chat Window System

The core chat experience is built around composable components that work together:

* **ChatWindow** - The main container with open/close animations and positioning
* **Header** - Configurable header with drag, fullscreen, and navigation controls
* **Footer** - Message input area with the rich text composer
* **Content** - Scrollable message container with auto-scroll behavior

### Message Components

Display different types of messages in the conversation:

* **UserMessage** - Messages sent by the user
* **AssistantMessage** - Simple text responses from Atlas
* **MarkdownMessage** - Rich markdown-formatted responses with toolbox actions
* **SystemMessage** - Interactive system prompts with radio options
* **ErrorMessage** - Error states with retry functionality

### Recommendation Cards

Present actionable recommendations to users:

* **SingleRecommendationCard** - Single-select options with radio buttons
* **MultipleRecommendationCard** - Multi-select options with checkboxes
* **ConfirmationCard** - Simple confirmation prompts with action buttons

### Utility Components

Supporting components for common patterns:

* **Suggestion** / **SuggestionList** - Clickable suggestion chips
* **Toolbox** - Copy, like, dislike, and retry actions for messages
* **Loader** - Animated loading indicator for pending responses
* **Spinner** - Full-screen loading spinner
* **Welcome** - Onboarding welcome screen
* **SystemError** - Error state display

### Hooks

React hooks for managing chat window behavior:

* **useDraggable** - Make the chat window draggable with boundary constraints
* **useInfiniteScroll** - Load more content when scrolling
* **useScrollCallback** - Respond to scroll position changes

## Component Architecture

The Atlas components are designed to be composed together to create a complete chat experience:

```tsx theme={null}
import {
  ChatWindow,
  Header,
  Content,
  Footer,
  UserMessage,
  MarkdownMessage,
} from "@servicetitan/anvil2-ext-atlas";

function AtlasChat() {
  const [open, setOpen] = useState(false);
  const [messages, setMessages] = useState([]);
  const [inputMessage, setInputMessage] = useState("");

  return (
    <ChatWindow open={open}>
      <Header
        title="Atlas"
        onClose={() => setOpen(false)}
        onCreateNewChat={handleNewChat}
      />
      <Content itemsLength={messages.length}>
        {messages.map((msg) =>
          msg.type === "user" ? (
            <UserMessage key={msg.id} message={msg.content} />
          ) : (
            <MarkdownMessage key={msg.id} message={msg.content} />
          )
        )}
      </Content>
      <Footer
        message={inputMessage}
        onMessageChange={setInputMessage}
        onSubmit={handleSend}
      />
    </ChatWindow>
  );
}
```

## Dependencies

Atlas components are built on top of the core Anvil2 component library and include:

* **Anvil2** - Core UI components (Button, Card, Flex, Text, etc.)
* **Framer Motion** - Animations and transitions
* **React Markdown** - Markdown rendering for message content
* **MobX React** - Observable state management (optional)
