npm package: @rodgerai/embed
Description: Embeddable chat widget for Rodger.ai agents
Latest version: 2.0.0

# @rodger/embed

Embeddable chat widget for Rodger.ai agents. Drop a fully functional AI chat interface into any website with just a few lines of code.

## Features

- Drop-in embeddable chat widget
- Customizable positioning (4 corner positions)
- Themeable (colors, fonts, sizes)
- Unread message notifications
- Session management
- Custom tool widgets
- TypeScript support
- Framework agnostic (works with any React app)

## Installation

```bash
npm install @rodger/embed
# or
pnpm add @rodger/embed
# or
yarn add @rodger/embed
```

## Quick Start

### Basic Usage

```tsx
import { RodgerEmbed } from '@rodger/embed';

function App() {
  return (
    <div>
      {/* Your app content */}

      <RodgerEmbed
        endpoint="/api/chat"
        sessionId="user-123"
      />
    </div>
  );
}
```

### With Custom Theme

```tsx
import { RodgerEmbed } from '@rodger/embed';

function App() {
  return (
    <RodgerEmbed
      endpoint="/api/chat"
      position="bottom-right"
      theme={{
        primaryColor: '#6366f1',
        fontFamily: 'Inter, sans-serif',
        borderRadius: '12px',
        width: '450px',
        height: '650px',
      }}
      welcomeMessage={
        <div>
          <h3>Welcome to Support Chat!</h3>
          <p>How can we help you today?</p>
        </div>
      }
    />
  );
}
```

### With Custom Widgets

```tsx
import { RodgerEmbed } from '@rodger/embed';

function WeatherWidget({ data }) {
  return (
    <div className="rounded-lg border p-4">
      <h4>{data.location}</h4>
      <p className="text-2xl">{data.temperature}°</p>
      <p>{data.conditions}</p>
    </div>
  );
}

function App() {
  return (
    <RodgerEmbed
      endpoint="/api/chat"
      widgets={{
        weather: WeatherWidget,
      }}
    />
  );
}
```

### Advanced Usage with Custom Trigger

```tsx
import { EmbedProvider, EmbedTrigger, EmbedDialog, useEmbed } from '@rodger/embed';

function CustomTrigger() {
  const { toggle, state } = useEmbed();

  return (
    <button onClick={toggle} className="my-custom-button">
      Chat with us {state.unreadCount > 0 && `(${state.unreadCount})`}
    </button>
  );
}

function App() {
  return (
    <EmbedProvider
      config={{
        endpoint: '/api/chat',
        position: 'bottom-right',
      }}
    >
      <div>
        {/* Your app content */}
        <CustomTrigger />
        <EmbedDialog />
      </div>
    </EmbedProvider>
  );
}
```

## Configuration

### EmbedConfig

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `endpoint` | `string` | required | API endpoint for chat (e.g., `/api/chat`) |
| `sessionId` | `string` | auto-generated | Session ID for this user. Auto-generated as `session-{timestamp}` if not provided |
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Position of the widget on the screen |
| `theme` | `EmbedTheme` | - | Theme configuration for customizing appearance |
| `widgets` | `Record<string, ComponentType>` | - | Custom widgets for rendering tool results |
| `welcomeMessage` | `ReactNode` | - | Welcome message shown when thread is empty |
| `trigger` | `TriggerConfig` | - | Trigger button customization |
| `defaultOpen` | `boolean` | `false` | Whether the chat dialog is open by default |
| `notifications` | `boolean` | `true` | Enable unread message count badge on trigger button |
| `zIndex` | `number` | `50` | Z-index for widget positioning |

### EmbedTheme

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `primaryColor` | `string` | - | Primary brand color (hex, rgb, or CSS color) |
| `fontFamily` | `string` | - | Font family for the chat interface |
| `borderRadius` | `string` | - | Border radius for the chat dialog |
| `width` | `string` | `'400px'` | Width of chat dialog |
| `height` | `string` | `'600px'` | Height of chat dialog |
| `cssVariables` | `Record<string, string>` | - | Custom CSS variables for advanced theming |

### TriggerConfig

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `icon` | `ReactNode` | `<MessageCircle />` | Custom icon for the trigger button |
| `label` | `string` | `'Open chat'` | Aria label for accessibility |
| `className` | `string` | - | Additional CSS classes for the trigger button |

## API

### Hooks

#### `useEmbed()`

Access the embed context to control the widget programmatically.

```tsx
const { config, state, open, close, toggle, incrementUnread, resetUnread } = useEmbed();
```

Returns:
- `config`: Current embed configuration
- `state`: Current embed state (isOpen, unreadCount, sessionId)
- `open()`: Open the chat dialog
- `close()`: Close the chat dialog
- `toggle()`: Toggle the chat dialog
- `incrementUnread()`: Increment unread count
- `resetUnread()`: Reset unread count to 0

## Examples

### E-commerce Site

```tsx
<RodgerEmbed
  endpoint="/api/support"
  position="bottom-right"
  theme={{
    primaryColor: '#10b981',
    width: '420px',
  }}
  welcomeMessage={
    <div>
      <h3>Need help?</h3>
      <p>Ask us about products, orders, or shipping!</p>
    </div>
  }
  widgets={{
    product: ProductCard,
    order: OrderStatus,
  }}
/>
```

### SaaS Application

```tsx
<RodgerEmbed
  endpoint="/api/assistant"
  sessionId={userId}
  position="bottom-left"
  theme={{
    primaryColor: '#3b82f6',
    height: '700px',
  }}
  trigger={{
    label: 'Ask AI Assistant',
  }}
  defaultOpen={false}
/>
```

### Documentation Site

```tsx
<RodgerEmbed
  endpoint="/api/docs-chat"
  position="top-right"
  theme={{
    width: '500px',
    height: '80vh',
  }}
  welcomeMessage="Search our docs or ask a question"
  widgets={{
    docLink: DocLinkCard,
    codeSnippet: CodeSnippet,
  }}
/>
```

## Backend Integration

The embed widget requires a chat API endpoint that follows the Vercel AI SDK streaming protocol. Here's an example Next.js API route:

```typescript
// app/api/chat/route.ts
import { createAgent } from '@rodger/core';
import { streamText } from 'ai';

const agent = createAgent({
  name: 'support-agent',
  llm: { provider: 'openai', model: 'gpt-4o' },
  systemPrompt: 'You are a helpful customer support agent.',
});

export async function POST(req: Request) {
  const { messages, sessionId } = await req.json();

  const stream = await streamText({
    model: agent.llm,
    messages,
    // Add your tools here
  });

  return stream.toDataStreamResponse();
}

// History endpoint (optional but recommended)
export async function POST(req: Request) {
  const { sessionId } = await req.json();

  // Load message history from your database
  const messages = await loadMessageHistory(sessionId);

  return Response.json({ messages });
}
```

### Session Management

The widget automatically manages sessions:

1. If `sessionId` is provided, it uses that ID
2. If not provided, generates a unique session ID (`session-{timestamp}`)
3. Session ID is passed to both the chat endpoint and history endpoint
4. Use the session ID to persist conversations in your database

### Message History

The widget attempts to load message history from `{endpoint}/history` on mount. Implement this endpoint to restore previous conversations:

```typescript
// app/api/chat/history/route.ts
export async function POST(req: Request) {
  const { sessionId } = await req.json();

  // Load from your database
  const messages = await db.messages.findMany({
    where: { sessionId },
    orderBy: { createdAt: 'asc' }
  });

  return Response.json({ messages });
}
```

## Migration from Thread Component

If you're currently using the standalone `Thread` component from `@rodger/ui`, migrating to the embed widget is straightforward:

### Before (Thread)

```tsx
import { Thread, RodgerProvider, useRodgerChat } from '@rodger/ui';

function ChatPage() {
  const { runtime } = useRodgerChat({
    endpoint: '/api/chat',
    sessionId: 'session-123',
  });

  return (
    <RodgerProvider runtime={runtime}>
      <div className="h-screen">
        <Thread widgets={myWidgets} />
      </div>
    </RodgerProvider>
  );
}
```

### After (Embed)

```tsx
import { RodgerEmbed } from '@rodger/embed';

function App() {
  return (
    <div>
      {/* Your app content */}

      <RodgerEmbed
        endpoint="/api/chat"
        sessionId="session-123"
        widgets={myWidgets}
      />
    </div>
  );
}
```

The embed widget handles all the setup (RodgerProvider, useRodgerChat, positioning) automatically.

## TypeScript

Full TypeScript support with exported types:

```typescript
import type { EmbedConfig, EmbedTheme, EmbedState } from '@rodger/embed';

const config: EmbedConfig = {
  endpoint: '/api/chat',
  position: 'bottom-right',
  theme: {
    primaryColor: '#3b82f6',
    width: '450px',
  },
};
```

## Browser Support

- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- React 18+ or React 19+

## Development

```bash
# Install dependencies
pnpm install

# Build the package
pnpm build

# Development mode with watch
pnpm dev

# Type check
pnpm type-check
```

## Troubleshooting

### Widget doesn't appear

1. Make sure you've imported the component correctly
2. Check that `endpoint` prop points to a valid API route
3. Verify Tailwind CSS is configured if using custom styles

### Messages not loading

1. Verify your chat API endpoint is returning a valid stream
2. Check browser console for errors
3. Ensure session ID is being passed correctly

### Styling issues

1. The widget uses Tailwind CSS internally - make sure it's configured
2. Use `theme.cssVariables` for advanced custom styling
3. Check z-index if widget is hidden behind other elements

## License

MIT