# XerticaAssistant

---

## Overview

Full-featured AI chat assistant with support for:

- Chat conversations with history and favorites
- Rich message rendering (Markdown, code, tables, charts, documents)
- Audio/podcast generation from text
- Document editing workspace
- Three layout modes: `collapsed`, `expanded`, `fullPage`
- Demo mode (mock AI responses without API key)

The component ships in **two usage patterns**:

| Pattern                | When to use                                                                  |
| ---------------------- | ---------------------------------------------------------------------------- |
| `<XerticaAssistant />` | Drop-in assistant panel — zero configuration needed                          |
| `useAssistant()`       | Headless hook — bring your own UI while reusing all state and business logic |

> **CLI scaffold:** When running `npx xertica-ui@latest init`, the assistant is included by default. To opt out during init, answer `no` at the "Include AI Assistant?" prompt. To add or remove it later from an existing project, run `npx xertica-ui update` → **Assistant**. The CLI will copy or delete `src/features/assistant/` and `src/pages/AssistantPage.tsx`, and regenerate `AuthGuard.tsx`, `HomePage.tsx`, and `TemplatePage.tsx` accordingly.

---

## Props

| Prop                      | Type                                                                      | Default      | Required                                    | Description                                                  |
| ------------------------- | ------------------------------------------------------------------------- | ------------ | ------------------------------------------- | ------------------------------------------------------------ |
| `mode`                    | `AssistantMode`                                                           | `'expanded'` | No                                          | Layout mode: `'collapsed'`, `'expanded'`, `'fullPage'`       |
| `isExpanded`              | `boolean`                                                                 | `true`       | No                                          | Whether panel is expanded (mode='expanded' only)             |
| `onToggle`                | `() => void`                                                              | —            | No                                          | Callback when panel is toggled                               |
| `defaultTab`              | `'chat' \| 'historico' \| 'favoritos'`                                    | `'chat'`     | No                                          | Initially selected tab                                       |
| `demoMode`                | `boolean`                                                                 | `true`       | No                                          | Enable demo mode (mock AI responses)                         |
| `customResponses`         | `MockResponse[]`                                                          | —            | No                                          | Custom mock responses for demo mode                          |
| `onNavigateSettings`      | `() => void`                                                              | —            | No                                          | Navigate to settings page                                    |
| `onNavigateFullPage`      | `() => void`                                                              | —            | No                                          | Navigate to full-page view                                   |
| `userName`                | `string`                                                                  | `'Usuário'`  | No                                          | Display name of logged-in user                               |
| `initialMessages`         | `Message[]`                                                               | —            | Pre-loaded messages to hydrate conversation |
| `savedConversations`      | `Conversation[]`                                                          | —            | Previously saved conversations              |
| `suggestions`             | `Suggestion[]`                                                            | —            | Suggested prompts shown in empty state      |
| `onSendMessage`           | `(message: string) => void`                                               | —            | Callback when user sends message            |
| `onFileAttach`            | `(file: File) => void`                                                    | —            | Callback when user attaches file            |
| `isProcessing`            | `boolean`                                                                 | `false`      | No                                          | Shows typing indicator                                       |
| `width`                   | `string`                                                                  | —            | No                                          | Custom panel width (expanded mode)                           |
| `height`                  | `string`                                                                  | —            | No                                          | Custom panel height (fullPage mode)                          |
| `className`               | `string`                                                                  | —            | No                                          | Additional CSS classes                                       |
| `mobileFloating`          | `boolean`                                                                 | —            | No                                          | Show as floating button on mobile                            |
| `responseGenerator`       | `(message: string) => Promise<string \| Partial<Message>>`                | —            | No                                          | Custom response generator function                           |
| `richSuggestions`         | `Suggestion[]`                                                            | —            | No                                          | Extended suggestions (shown after clicking "Mais sugestões") |
| `onRichAction`            | `(actionId: string, actionText: string) => void`                          | —            | No                                          | Callback when user clicks rich suggestion                    |
| `onEvaluation`            | `(messageId: string, type: 'like' \| 'dislike', reason?: string) => void` | —            | No                                          | Callback when user rates a message                           |
| `feedbackOptions`         | `string[]`                                                                | —            | No                                          | Negative feedback categories                                 |
| `showHistory`             | `boolean`                                                                 | `true`       | No                                          | Show conversation history tab and collapsed icon             |
| `showFavorites`           | `boolean`                                                                 | `true`       | No                                          | Show favorites tab and collapsed icon                        |
| `enableAudioInput`        | `boolean`                                                                 | `true`       | No                                          | Enable voice recording / audio upload                        |
| `enableFileAttachment`    | `boolean`                                                                 | `true`       | No                                          | Enable file attachment button                                |
| `enableDocumentCreation`  | `boolean`                                                                 | `true`       | No                                          | Enable "Create document" action in chat input                |
| `enablePodcastGeneration` | `boolean`                                                                 | `true`       | No                                          | Enable podcast generation (action chip + per-message button) |
| `enableSearch`            | `boolean`                                                                 | `true`       | No                                          | Enable "Search" action in chat input                         |

---

## Types

```typescript
type AssistantMode = 'collapsed' | 'expanded' | 'fullPage';
type AssistantTab = 'chat' | 'historico' | 'favoritos';
type MessageType = 'user' | 'assistant' | 'system';
type AttachmentType = 'file' | 'audio' | 'image' | 'video' | 'document' | 'podcast' | 'search';
type SearchResultType = 'document' | 'project' | 'conversation' | 'file' | 'contact';

interface Message {
  id: string;
  type: MessageType;
  content: string;
  timestamp: Date;
  isFavorite?: boolean;
  attachmentType?: AttachmentType;
  attachmentName?: string;
  documentContent?: string;
  documentTitle?: string;
  audioUrl?: string;
  searchResults?: SearchResult[];
  chartData?: any[];
  chartConfig?: any;
  tableData?: { caption?: string; headers: string[]; rows: (string | React.ReactNode)[][] };
  evaluation?: 'like' | 'dislike';
  evaluationReason?: string;
}

interface Conversation {
  id: string;
  title: string;
  lastMessage?: string;
  timestamp: string;
  isFavorite: boolean;
  messages: Message[];
}

interface Suggestion {
  id: string;
  text: string;
  icon?: React.ReactNode;
}

interface SearchResult {
  id: string;
  type: SearchResultType;
  title: string;
  description: string;
  path: string;
  relevance: number;
  lastModified?: string;
}
```

---

## Layout Modes

### Expanded (default)

Resizable side panel (420px default) that sits flush against the right side of the screen.

### FullPage

Dedicated full-page view with centered content container (`max-w-6xl`).

### Collapsed

Icon-only sidebar strip with tooltip tooltips.

---

## Mobile Behavior

- Desktop: Sits flush against right side, mutually exclusive with `<Sidebar>`
- Mobile: Transitions to floating Action Button (bottom-right), expands to full-screen overlay when clicked

---

## Document Editor Mode

When the assistant generates a full document, a `RichTextEditor` slides precisely from the right border overlaying the page.

---

## Examples

### Basic Integration (Demo Mode)

```tsx
import { XerticaAssistant } from 'xertica-ui/assistant';

<XerticaAssistant mode="expanded" userName="João" demoMode={true} />;
```

### With Real API

```tsx
import { XerticaAssistant } from 'xertica-ui/assistant';

<XerticaAssistant
  mode="expanded"
  demoMode={false}
  onSendMessage={msg => console.log(msg)}
  responseGenerator={async msg => {
    const response = await callGeminiAPI(apiKey, msg);
    return response;
  }}
/>;
```

### FullPage Mode

```tsx
<XerticaAssistant mode="fullPage" height="100vh" />
```

---

## Demo Mode Responses

The component includes built-in mock responses for common requests:

- Document creation: Generates markdown documents
- Search: Returns mock search results
- Summary: Generates summary tables
- Podcast: Generates audio attachments
- Charts: Renders data charts

Use `customResponses` to override defaults.

---

## Feature Flags

Each assistant capability can be individually toggled via boolean props. All flags default to `true`.

### Chat Only (no extras)

```tsx
<XerticaAssistant
  mode="expanded"
  userName="João"
  demoMode={true}
  showHistory={false}
  showFavorites={false}
  enableAudioInput={false}
  enableFileAttachment={false}
  enableDocumentCreation={false}
  enablePodcastGeneration={false}
  enableSearch={false}
/>
```

### Custom Configuration

```tsx
// Only document creation and file attachment
<XerticaAssistant
  mode="expanded"
  showHistory={false}
  showFavorites={false}
  enableAudioInput={false}
  enableFileAttachment={true}
  enableDocumentCreation={true}
  enablePodcastGeneration={false}
  enableSearch={false}
/>
```

---

## Headless Hook — `useAssistant()`

Use the headless hook when you need full control over the assistant's UI — custom layout, custom styling, or embedding the assistant logic inside a larger component.

### Import

```tsx
import { useAssistant } from 'xertica-ui/assistant';
```

### Hook Props

The hook accepts the same props as `XerticaAssistantProps` (minus purely visual props like `width`, `height`, `className`, `mobileFloating`, `onNavigateSettings`, `onNavigateFullPage`, `userName`, and feature flags).

| Prop                 | Type                                                                      | Default      | Description                                                                                                                |
| -------------------- | ------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `mode`               | `AssistantMode`                                                           | `'expanded'` | Layout mode                                                                                                                |
| `isExpanded`         | `boolean`                                                                 | `true`       | Controlled expansion state                                                                                                 |
| `onToggle`           | `() => void`                                                              | —            | Toggle callback                                                                                                            |
| `defaultTab`         | `AssistantTab`                                                            | `'chat'`     | Initially selected tab                                                                                                     |
| `demoMode`           | `boolean`                                                                 | `true`       | Enable demo mode                                                                                                           |
| `customResponses`    | `MockResponse[]`                                                          | `[]`         | Custom mock responses                                                                                                      |
| `initialMessages`    | `Message[]`                                                               | `[]`         | Pre-loaded messages — hydrated **once** on mount (one-shot). Subsequent changes to this prop do not reset the conversation |
| `savedConversations` | `Conversation[]`                                                          | `[]`         | Previously saved conversations                                                                                             |
| `suggestions`        | `Suggestion[]`                                                            | —            | Suggested prompts                                                                                                          |
| `onSendMessage`      | `(message: string) => void`                                               | —            | Send message callback                                                                                                      |
| `isProcessing`       | `boolean`                                                                 | `false`      | Shows typing indicator                                                                                                     |
| `responseGenerator`  | `(message: string) => Promise<string \| Partial<Message>>`                | —            | Custom response generator                                                                                                  |
| `richSuggestions`    | `Suggestion[]`                                                            | `[]`         | Extended suggestions                                                                                                       |
| `onRichAction`       | `(actionId: string, actionText: string) => void`                          | —            | Rich suggestion callback                                                                                                   |
| `onEvaluation`       | `(messageId: string, type: 'like' \| 'dislike', reason?: string) => void` | —            | Evaluation callback                                                                                                        |

### Hook Return Value

| Property                       | Type                                                           | Description                                                   |
| ------------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------- |
| `isFullPage`                   | `boolean`                                                      | Whether mode is `'fullPage'`                                  |
| `isExpanded`                   | `boolean`                                                      | Current expansion state                                       |
| `isMobile`                     | `boolean`                                                      | Whether viewport is mobile                                    |
| `abaSelecionada`               | `AssistantTab`                                                 | Currently selected tab                                        |
| `setAbaSelecionada`            | `(tab: AssistantTab) => void`                                  | Change selected tab                                           |
| `mensagens`                    | `Message[]`                                                    | Current conversation messages                                 |
| `setMensagens`                 | `Dispatch<SetStateAction<Message[]>>`                          | Update messages                                               |
| `mensagem`                     | `string`                                                       | Current input value                                           |
| `setMensagem`                  | `(value: string) => void`                                      | Update input value                                            |
| `conversas`                    | `Conversation[]`                                               | All saved conversations                                       |
| `conversaAtual`                | `string \| null`                                               | ID of the active conversation                                 |
| `conversasFiltradas`           | `Conversation[]`                                               | Conversations filtered by active tab                          |
| `copiedId`                     | `string \| null`                                               | ID of the message currently showing "copied" state            |
| `generatingPodcastId`          | `string \| null`                                               | ID of the message generating a podcast                        |
| `executingCommand`             | `string \| null`                                               | ID of the search command being executed                       |
| `savedSearches`                | `string[]`                                                     | IDs of saved search messages                                  |
| `editingDocument`              | `{ content: string; title: string } \| null`                   | Document being edited                                         |
| `setEditingDocument`           | `(doc \| null) => void`                                        | Open/close document editor                                    |
| `showMoreSuggestions`          | `boolean`                                                      | Whether extended suggestions are visible                      |
| `setShowMoreSuggestions`       | `(show: boolean) => void`                                      | Toggle extended suggestions                                   |
| `evaluationState`              | `EvaluationState`                                              | State of the feedback dialog                                  |
| `setEvaluationState`           | `Dispatch<...>`                                                | Update evaluation state                                       |
| `sugestoes`                    | `Suggestion[]`                                                 | Resolved suggestions (prop or defaults)                       |
| `messagesEndRef`               | `RefObject<HTMLDivElement>`                                    | Attach to the scroll anchor at the bottom of the message list |
| `fileInputRef`                 | `RefObject<HTMLInputElement>`                                  | Attach to the hidden file input                               |
| `audioInputRef`                | `RefObject<HTMLInputElement>`                                  | Attach to the hidden audio input                              |
| `handleToggle`                 | `() => void`                                                   | Toggle expansion                                              |
| `handleExpandWithTab`          | `(tab: AssistantTab) => void`                                  | Expand and switch to a specific tab                           |
| `handleEnviarMensagem`         | `(arg?: string \| ActionType) => Promise<void>`                | Send a message                                                |
| `handleToggleFavorite`         | `(messageId: string) => void`                                  | Toggle message favorite                                       |
| `handleCopyMessage`            | `(content: string, messageId: string) => Promise<void>`        | Copy message to clipboard                                     |
| `handleGeneratePodcast`        | `(messageId: string, content: string) => Promise<void>`        | Generate podcast from message                                 |
| `handleDownloadDocument`       | `(content: string, fileName: string) => void`                  | Download document as `.md`                                    |
| `handleDownloadPodcast`        | `(audioUrl: string, fileName: string) => void`                 | Download podcast as `.mp3`                                    |
| `handleEditDocument`           | `(content: string, title: string) => void`                     | Open document editor                                          |
| `handleNovaConversa`           | `() => void`                                                   | Start a new conversation                                      |
| `handleSelecionarConversa`     | `(conversaId: string) => void`                                 | Load a saved conversation                                     |
| `handleToggleFavoritaConversa` | `(conversaId: string) => void`                                 | Toggle conversation favorite                                  |
| `handleOpenSearchResult`       | `(result: SearchResult) => void`                               | Open a search result                                          |
| `handleExecuteSearchCommand`   | `(commandId, searchTerm, results, messageId) => Promise<void>` | Execute a search command                                      |
| `handleRichSuggestionClick`    | `(suggestion: Suggestion) => void`                             | Handle rich suggestion click                                  |
| `handleEvaluationClick`        | `(messageId: string, type: 'like' \| 'dislike') => void`       | Handle message evaluation                                     |
| `openFeedbackDialog`           | `(messageId: string, category: string \| null) => void`        | Open dislike feedback dialog                                  |
| `handleSubmitDislike`          | `() => void`                                                   | Submit dislike feedback                                       |

### Headless Hook Example

```tsx
import { useAssistant } from 'xertica-ui/assistant';

function MyMinimalAssistant() {
  const {
    mensagens,
    mensagem,
    setMensagem,
    handleEnviarMensagem,
    isExpanded,
    handleToggle,
    messagesEndRef,
  } = useAssistant({ demoMode: true });

  return (
    <div className="flex flex-col h-full border rounded-lg overflow-hidden">
      {/* Header */}
      <div className="flex items-center justify-between p-3 border-b">
        <span className="font-semibold">AI Assistant</span>
        <button onClick={handleToggle}>{isExpanded ? 'Collapse' : 'Expand'}</button>
      </div>

      {/* Messages */}
      <div className="flex-1 overflow-y-auto p-4 space-y-3">
        {mensagens.map(msg => (
          <div key={msg.id} className={msg.type === 'user' ? 'text-right' : 'text-left'}>
            <span
              className={`inline-block px-3 py-2 rounded-lg text-sm ${
                msg.type === 'user' ? 'bg-primary text-primary-foreground' : 'bg-muted'
              }`}
            >
              {msg.content}
            </span>
          </div>
        ))}
        <div ref={messagesEndRef} />
      </div>

      {/* Input */}
      <div className="flex gap-2 p-3 border-t">
        <input
          value={mensagem}
          onChange={e => setMensagem(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && handleEnviarMensagem()}
          placeholder="Type a message..."
          className="flex-1 border rounded px-3 py-2 text-sm"
        />
        <button
          onClick={() => handleEnviarMensagem()}
          className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm"
        >
          Send
        </button>
      </div>
    </div>
  );
}
```

---

## AI Rules

- Use `demoMode={true}` for development without API key
- Use `responseGenerator` for custom AI integration
- In fullPage mode, welcome message shows "Olá, {userName}" (not "Assistente Xertica")
- Tab switching (chat/historico/favoritos) hides navigation tabs in fullPage mode
- Feature flags are independent and can be combined freely; all default to `true`
- When `showHistory` or `showFavorites` is `false`, both the collapsed icon and expanded tab are removed
- When both `showHistory` and `showFavorites` are `false`, the entire tab navigation bar is hidden (including the Chat tab) — there is no point showing a single tab with no alternatives
- ALWAYS use `useAssistant()` when building a custom assistant UI — never re-implement message state or response generation manually
- The `messagesEndRef` from the hook MUST be attached to a div at the bottom of the message list for auto-scroll to work
- `handleEnviarMensagem()` handles both demo mode responses and `responseGenerator` — do not call `responseGenerator` directly
- `handleEnviarMensagem` accepts `arg?: string | ActionType` — pass a string to send a specific message, pass an `ActionType` (`'document'`, `'podcast'`, `'search'`) to trigger an action, or pass nothing to send the current `mensagem` input value
- `initialMessages` is hydrated **once** (one-shot) — passing a new array after mount does not reset the conversation. To reset, call `setMensagens([])` directly
- Auto-scroll is conditional: `messagesEndRef.scrollIntoView` is only called when the user is within 120px of the bottom of the chat container — preserves scroll position during history review
- **`suggestions`, `richSuggestions`, and `feedbackOptions` should come from `useAssistantConfig()` (React Query)** — never hardcode them in the page component. Use the fallback factory functions from `features/assistant/data/mock.ts` while the query loads

## Dynamic Configuration with React Query

In projects using the `features/` architecture, assistant configuration (suggestions, feedback options) is fetched via `useAssistantConfig()`:

```tsx
import { useAssistantConfig, getMockRichSuggestions, getMockFeedbackOptions } from '../features/assistant';

function MyPage() {
  const { data: assistantConfig } = useAssistantConfig();

  return (
    <XerticaAssistant
      demoMode={true}
      suggestions={assistantConfig?.suggestions}
      richSuggestions={assistantConfig?.richSuggestions ?? getMockRichSuggestions()}
      feedbackOptions={assistantConfig?.feedbackOptions ?? getMockFeedbackOptions()}
      onEvaluation={...}
    />
  );
}
```

To connect to a real configuration API, replace `fetchAssistantConfig` in `features/assistant/data/mock.ts`:

```ts
// features/assistant/data/mock.ts
export async function fetchAssistantConfig(): Promise<AssistantConfig> {
  // Replace with real endpoint:
  return fetch('/api/assistant/config').then(r => r.json());
}
```
