# @classic-homes/chat-widget

High-performance embeddable chat widget built with Svelte. Can be integrated via multiple methods: Vanilla JS, React, Docusaurus plugin, or Custom Element.

## Installation

```bash
npm install @classic-homes/chat-widget
```

For React projects, ensure you have React 17+ installed:

```bash
npm install react react-dom
```

## Usage

### Vanilla JavaScript

The simplest way to add the widget to any website:

```javascript
import { init, destroy } from '@classic-homes/chat-widget/vanilla';

// Initialize the widget
init({
  apiBase: 'https://api.example.com',
  site: 'docs',
  siteName: 'Documentation',
  title: 'Help Assistant',
  welcomeMessage: 'How can I help you today?',
  welcomeTopics: ['Getting started', 'API reference', 'Troubleshooting'],
});

// Later, to destroy the widget
destroy();
```

### React Component

Use the React adapter for seamless integration with React applications:

```tsx
import { ChatWidget } from '@classic-homes/chat-widget/react';

function App() {
  return (
    <ChatWidget
      apiBase="https://api.example.com"
      site="docs"
      siteName="Documentation"
      title="Help Assistant"
      welcomeMessage="How can I help you today?"
      welcomeTopics={['Getting started', 'API reference', 'Troubleshooting']}
    />
  );
}
```

The React adapter also provides `ChatWidgetElement` for using the custom element directly:

```tsx
import { ChatWidgetElement } from '@classic-homes/chat-widget/react';

function App() {
  return (
    <ChatWidgetElement
      apiBase="https://api.example.com"
      site="docs"
      siteName="Documentation"
    />
  );
}
```

### Docusaurus Plugin

For Docusaurus 3.x sites:

```javascript
// docusaurus.config.js
module.exports = {
  plugins: [
    [
      '@classic-homes/chat-widget/docusaurus',
      {
        apiBase: 'https://api.example.com',
        site: 'docs',
        siteName: 'Documentation',
        title: 'Help Assistant',
      },
    ],
  ],
};
```

### Custom Element (Web Component)

Use the `<chat-widget>` custom element directly in HTML:

```html
<script type="module">
  import '@classic-homes/chat-widget';
</script>

<chat-widget
  api-base="https://api.example.com"
  site="docs"
  site-name="Documentation"
  title="Help Assistant"
  welcome-message="How can I help you today?"
  welcome-topics='["Getting started", "API reference"]'
></chat-widget>
```

Note: Attribute names use kebab-case (e.g., `api-base` instead of `apiBase`).

## Configuration Options

| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `apiBase` | `string` | Yes | - | Base URL for the chat API |
| `site` | `string` | Yes | - | Site identifier (e.g., "docs", "marketing") |
| `siteName` | `string` | Yes | - | Display name for current site |
| `title` | `string` | No | "Chat Assistant" | Widget header title |
| `logoUrl` | `string` | No | - | Logo URL for header |
| `primaryColor` | `string` | No | - | Primary brand color (CSS color value) |
| `position` | `"bottom-right"` \| `"bottom-left"` | No | `"bottom-right"` | Widget position |
| `fullscreen` | `boolean` | No | `false` | Enable fullscreen mode |
| `welcomeMessage` | `string` | No | - | Welcome message text |
| `welcomeTopics` | `string[]` | No | - | Suggested topics shown in welcome |
| `disclaimerHtml` | `string` | No | - | Disclaimer HTML shown below welcome |
| `storagePrefix` | `string` | No | `"chat_"` | Prefix for localStorage keys |
| `sentryDsn` | `string` | No | - | Sentry DSN for error tracking |
| `environment` | `"development"` \| `"production"` | No | `"production"` | Environment name |
| `sites` | `Record<string, SiteConfig>` | No | - | Cross-site configuration |
| `onNavigate` | `(url: string) => void` | No | - | Custom navigation handler |
| `onOpen` | `() => void` | No | - | Called when widget opens |
| `onClose` | `() => void` | No | - | Called when widget closes |
| `getAuthToken` | `() => string \| null \| Promise<string \| null>` | No | - | CHAPI account bearer token provider (see [Account Login](#account-login)) |

## Account Login

The widget is **anonymous by default** — history is scoped to a per-browser anon
token. To scope history to a logged-in CHAPI account instead, the **host page owns
login** and hands the widget a `getAuthToken` callback that returns the current
account bearer token (or `null` when logged out). The widget attaches it as
`Authorization: Bearer <token>` and, on first authenticated init, claims the
visitor's anon history into the account.

The widget bundles **no** login UI — the embedding page is responsible for
authenticating against CHAPI (it already must, so its origin is whitelisted for SSO).

**Programmatic / Vanilla / Docusaurus** — pass `getAuthToken` in the config:

```js
const widget = initChatWidget(element, {
  apiBase: 'https://api.example.com',
  site: 'docs',
  siteName: 'Documentation',
  getAuthToken: () => myAuthClient.getAccessToken(), // string | null | Promise<...>
});
```

**React** — pass `getAuthToken` as a prop. Use a **stable reference**
(`useCallback`); the token provider is intentionally excluded from the remount deps,
so its identity changing will not remount the widget:

```tsx
const getAuthToken = useCallback(() => myAuthClient.getAccessToken(), []);
<ChatWidget apiBase={...} site="docs" siteName="Documentation" getAuthToken={getAuthToken} />
```

**Custom element `<chat-widget>`** — a function can't be an HTML attribute, so set it
as a **property** before the element connects:

```html
<chat-widget id="chat" api-base="https://api.example.com" site="docs" site-name="Documentation"></chat-widget>
<script type="module">
  import '@classic-homes/chat-widget';
  const el = document.getElementById('chat');
  el.getAuthToken = () => myAuthClient.getAccessToken();
</script>
```

Set the property before the element is added to the DOM (or set it, then re-append)
so it's present when the widget mounts.

**After login/logout** — the account is bound at mount. If the host's auth state
changes while the widget is already mounted, call `widget.refresh()` to remount and
re-run the account claim with the new token.

## Widget API

The `initChatWidget` function returns a `ChatWidgetAPI` object with the following methods:

### Conversation Management

```typescript
const widget = initChatWidget(element, config);

// List all conversations
const conversations = await widget.listConversations();

// Get a specific conversation with messages
const conversation = await widget.getConversation(id);

// Switch to a conversation
await widget.selectConversation(id);

// Start a new conversation
await widget.startNewConversation();

// Delete a conversation
await widget.deleteConversation(id);

// Rename a conversation
await widget.renameConversation(id, 'New Title');
```

### State Getters

```typescript
// Get current conversation ID (null if new chat)
const currentId = await widget.getCurrentConversationId();

// Get current conversation title
const title = await widget.getCurrentTitle();
```

### Lifecycle Methods

```typescript
// Clear messages and reset chat
widget.clearMessages();

// Refresh widget (reload from storage)
widget.refresh();

// Destroy the widget
widget.destroy();
```

## Events

Subscribe to widget events using the `on` method:

```typescript
const widget = initChatWidget(element, config);

// Subscribe to events
const unsubscribe = widget.on('message:received', ({ message }) => {
  console.log('New message:', message.content);
});

// One-time subscription
widget.once('ready', () => {
  console.log('Widget is ready');
});

// Unsubscribe
unsubscribe();
```

### Available Events

| Event | Payload | Description |
|-------|---------|-------------|
| `ready` | `undefined` | Widget has finished initializing |
| `conversation:created` | `{ id: string, title: string \| null }` | New conversation created |
| `conversation:selected` | `{ id: string \| null, title: string \| null }` | Conversation selected/switched |
| `conversation:deleted` | `{ id: string }` | Conversation deleted |
| `conversation:titleChanged` | `{ id: string, title: string }` | Conversation renamed |
| `conversations:updated` | `{ conversations: ConversationPreview[] }` | Conversation list changed |
| `message:sent` | `{ message: Message }` | User sent a message |
| `message:received` | `{ message: Message }` | Assistant response received |
| `error` | `{ message: string, error?: Error }` | Error occurred |

## TypeScript Support

Full TypeScript definitions are included. Import types as needed:

```typescript
import type {
  ChatWidgetConfig,
  ChatWidgetAPI,
  PageContext,
  Message,
  ConversationPreview,
} from '@classic-homes/chat-widget';
```

## Styling

The widget includes all necessary styles. For custom styling, you can target the widget's CSS custom properties:

```css
chat-widget {
  --chat-primary-color: #0066cc;
  --chat-border-radius: 12px;
}
```

## Browser Support

- Chrome/Edge 88+
- Firefox 78+
- Safari 14+

The widget requires support for:
- Custom Elements v1
- ES Modules
- CSS Custom Properties

## License

MIT
