

import { XandiBasicDemo } from "../../../src/components/xandi-basic-demo";

## 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

```tsx
import { Xandi, XandiProvider, XHeader, XSidebar, type XandiHandlers } from "@px-ui/ai";
```

## Usage

<Preview>
  <XandiBasicDemo />
</Preview>

<CodeBlock>
  ```tsx
  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>
    );
  }
  ```
</CodeBlock>

## 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

```tsx
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

```tsx
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`.

```tsx
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

```tsx
// 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 `debugTrace` is present in the message)

## Installation

```bash
pnpm add @px-ui/ai
```

Don't forget to import the styles:

```tsx
import "@px-ui/ai/styles.css";
```
