npm package: @kite-copilot/chat-panel
Description: AI-powered chat panel SDK with programmatic lifecycle control
Author: kite
Latest version: 0.2.57

# @kite-copilot/chat-panel

An AI-powered chat panel SDK for embedding intelligent chat assistants. This package provides a **side panel** interface that integrates with any AI backend agent. The panel slides in from the right edge and automatically adjusts page content - no layout changes required.

## Features

- **Side Panel UX**: Slides in from the right edge, pushes page content (no overlay)
- **Zero-Config**: Drop-in integration - no layout changes required from host apps
- **Automatic URL Tracking**: Captures the exact browser URL (including path and query params) for context
- **AI-Powered Chat**: Connects to your AI backend agent for intelligent responses
- **CSV Bulk Operations**: Upload CSV files for bulk data processing
- **Interactive Guides**: Built-in guided tours with animated cursor
- **Dynamic Starting Questions**: Customizable per-user questions from backend
- **Customizable Themes**: Light and dark mode support
- **Responsive Design**: Works on desktop and mobile
- **Easy Integration**: Programmatic API or React component
- **PWA-Ready**: Fully bundled by your build tool, no CDN required
- **Offline-Safe**: Works with service workers and precaching

## Installation

```bash
npm install @kite-copilot/chat-panel
# or
yarn add @kite-copilot/chat-panel
# or
pnpm add @kite-copilot/chat-panel
```

### Peer Dependencies

Make sure you have the following peer dependencies installed:

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

## Quick Start

### Programmatic API (Recommended)

The programmatic API provides explicit lifecycle control - ideal for PWAs, Vue, Angular, or any framework.

```ts
import { createKiteChat } from '@kite-copilot/chat-panel';
import '@kite-copilot/chat-panel/style.css';

// Create the chat instance
const chat = createKiteChat({
  userId: 'user-123',
  orgId: 'org-456', // Organization ID for multi-tenant data isolation
  agentUrl: 'https://your-api.example.com',
  // Optional: Pass user info for session tracking in control center
  userName: 'John Doe',
  userEmail: 'john@example.com',
  // Optional: Provide authentication headers for API requests
  getAuthHeaders: async () => {
    return {
      Authorization: `Bearer ${await getCurrentToken()}`
    };
  },
  onNavigate: (page, subtab) => {
    router.push(`/${page}${subtab ? `?tab=${subtab}` : ''}`);
  },
});

// Mount - that's it! No container needed, panel handles its own positioning
chat.mount(document.body);

// Control the panel programmatically
chat.open();   // Open the side panel
chat.close();  // Close the side panel
chat.toggle(); // Toggle open/closed

// Check state
if (chat.isOpen()) {
  console.log('Panel is open');
}

// Clean up when done
chat.unmount();

// Note: The chat panel automatically captures the browser URL (window.location.href)
// so you don't need to manually track or pass the current page.
```

### Vue.js

Create a composable for reusable chat functionality:

```ts
// composables/useKiteChat.ts
import { ref, onMounted, onUnmounted } from 'vue';
import { createKiteChat, type KiteChatInstance, type KiteChatConfig } from '@kite-copilot/chat-panel';
import '@kite-copilot/chat-panel/style.css';

export function useKiteChat(config: KiteChatConfig) {
  const chat = ref<KiteChatInstance | null>(null);
  const isOpen = ref(false);

  onMounted(() => {
    chat.value = createKiteChat({
      ...config,
      onNavigate: config.onNavigate,
    });
    chat.value.mount(document.body);
  });

  onUnmounted(() => {
    chat.value?.unmount();
  });

  const open = () => {
    chat.value?.open();
    isOpen.value = true;
  };

  const close = () => {
    chat.value?.close();
    isOpen.value = false;
  };

  const toggle = () => {
    chat.value?.toggle();
    isOpen.value = !isOpen.value;
  };

  return { isOpen, open, close, toggle };
}
```

Use in your App.vue or any component:

```vue
<!-- App.vue -->
<script setup lang="ts">
import { useKiteChat } from './composables/useKiteChat';
import { useRouter } from 'vue-router';

const router = useRouter();

const { open, close, toggle } = useKiteChat({
  userId: 'user-123',
  agentUrl: 'https://your-api.example.com',
  onNavigate: (page, subtab) => {
    router.push(`/${page}${subtab ? `?tab=${subtab}` : ''}`);
  },
});

// Note: The chat panel automatically captures window.location.href,
// so you don't need to manually track route changes.
</script>

<template>
  <div>
    <button @click="toggle">Toggle Help Panel</button>
    <!-- Your app content -->
    <router-view />
  </div>
</template>
```

Or initialize directly in main.ts for app-wide availability:

```ts
// main.ts
import { createApp } from 'vue';
import { createKiteChat } from '@kite-copilot/chat-panel';
import '@kite-copilot/chat-panel/style.css';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router);
app.mount('#app');

// Initialize chat after Vue app is mounted
const chat = createKiteChat({
  userId: 'user-123',
  agentUrl: 'https://your-api.example.com',
  onNavigate: (page) => router.push(`/${page}`),
});
chat.mount(document.body);

// Expose globally if needed
window.kiteChat = chat;
```

### React Component

For React applications, use the `ChatPanelWithToggle` component:

```tsx
import { ChatPanelWithToggle } from '@kite-copilot/chat-panel';
import '@kite-copilot/chat-panel/style.css';

function App() {
  return (
    <>
      <YourExistingApp />
      {/* Just add this - no layout changes needed */}
      <ChatPanelWithToggle
        agentUrl="https://your-api.example.com"
        userId="user-123"
        userName="John Doe"
        userEmail="john@example.com"
        onNavigate={(page) => router.push(`/${page}`)}
      />
    </>
  );
}
```

For controlled mode:

```tsx
import { useState } from 'react';
import { ChatPanelWithToggle } from '@kite-copilot/chat-panel';

function App() {
  const [isPanelOpen, setIsPanelOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsPanelOpen(true)}>Open Help</button>
      <ChatPanelWithToggle 
        isOpen={isPanelOpen}
        onOpenChange={setIsPanelOpen}
        agentUrl="https://your-api.example.com"
      />
    </>
  );
}
```

## How It Works

The side panel uses fixed positioning and automatically manages the body's `padding-right`:

```
┌─────────────────────────────────┐    ┌────────────────────────┬────────┐
│                                 │    │                        │        │
│                                 │    │                        │  Side  │
│        Your Page Content        │ →  │   Your Page Content    │  Panel │
│                                 │    │   (shifted left)       │        │
│                            [◀]  │    │                   [▶]  │        │
└─────────────────────────────────┘    └────────────────────────┴────────┘
        Panel Closed                            Panel Open
```

- **Closed**: Only the toggle arrow (◀) is visible on the right edge
- **Open**: Panel slides in, toggle becomes (▶), body gets `padding-right: 400px`
- **No overlays**: Content is pushed, not covered

## Dynamic Starting Questions

The panel shows suggested questions when empty. These can be customized per-user:

```ts
// Option 1: Pass questions directly
const chat = createKiteChat({
  userId: 'user-123',
  startingQuestions: [
    { id: '1', label: 'Changing layouts', prompt: 'How do I customize the layout?' },
    { id: '2', label: 'Bulk uploads', prompt: 'How do I upload data in bulk?' },
    { id: '3', label: 'Example setups', prompt: 'Show me example configurations' },
  ],
});

// Option 2: Fetch from backend (per-user)
const chat = createKiteChat({
  userId: 'user-123',
  startingQuestionsEndpoint: '/api/user/starting-questions',
});
```

The endpoint should return:
```json
[
  { "id": "1", "label": "Question label", "prompt": "Full prompt to send" },
  { "id": "2", "label": "Another question", "prompt": "Another prompt" }
]
```

## User Info & Session Tracking

Pass user information to enable session tracking in the control center. This allows support agents to see who they're chatting with when conversations are escalated.

```ts
const chat = createKiteChat({
  userId: 'user-123',
  // Optional user info for control center display
  userName: 'John Doe',
  userEmail: 'john@example.com',
});
```

| Field | Description |
|-------|-------------|
| `userName` | Displayed in conversations list and live chat |
| `userEmail` | Shown in user details panel |

**Note:** If user info is not provided, placeholder values ("Anonymous User", "Not provided") will be displayed in the control center.

## API Reference

### createKiteChat(config)

Creates a new chat instance with explicit lifecycle control.

#### KiteChatConfig

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `userId` | `string` | Yes | Unique identifier for the current user |
| `orgId` | `string` | No | Organization ID for multi-tenant data isolation |
| `agentUrl` | `string` | No | Backend agent API URL |
| ~~`currentPage`~~ | `string` | No | **Deprecated** - URL is now captured automatically |
| `userName` | `string` | No | User's display name (for control center) |
| `userEmail` | `string` | No | User's email address (for control center) |
| `theme` | `'light' \| 'dark' \| 'system'` | No | Theme preference |
| `startingQuestions` | `StartingQuestion[]` | No | Custom starting questions |
| `startingQuestionsEndpoint` | `string` | No | URL to fetch per-user questions |
| `getAuthHeaders` | `() => Promise<Record<string, string>>` | No | Async function to provide auth headers |
| `onNavigate` | `(page: string, subtab?: string) => void` | No | Navigation callback |
| `onActionComplete` | `(actionType: string, data: any) => void` | No | Action completion callback |

#### KiteChatInstance

| Method | Description |
|--------|-------------|
| `mount(container)` | Mount the chat widget (container is just a React mount point) |
| `unmount()` | Unmount and clean up the chat widget |
| `open()` | Open the side panel |
| `close()` | Close the side panel |
| `toggle()` | Toggle the panel open/closed |
| `isOpen()` | Check if the panel is currently open |
| ~~`setCurrentPage(page)`~~ | **Deprecated** - URL is now captured automatically |
| `updateConfig(config)` | Update configuration options |
| `isMounted()` | Check if the widget is currently mounted |

### ChatPanelWithToggle (React)

The recommended React component with integrated toggle button.

```tsx
import { ChatPanelWithToggle } from '@kite-copilot/chat-panel';
```

#### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `agentUrl` | `string` | `localhost:5002` | Backend agent URL |
| ~~`currentPage`~~ | `string` | - | **Deprecated** - URL is now captured automatically |
| `userId` | `string` | - | Unique user identifier |
| `orgId` | `string` | - | Organization ID for multi-tenant data isolation |
| `userName` | `string` | - | User's display name (for control center) |
| `userEmail` | `string` | - | User's email address (for control center) |
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
| `isOpen` | `boolean` | - | Controlled open state |
| `onOpenChange` | `(isOpen: boolean) => void` | - | Called when open state changes |
| `startingQuestions` | `StartingQuestion[]` | - | Custom starting questions |
| `startingQuestionsEndpoint` | `string` | - | URL to fetch questions |
| `getAuthHeaders` | `() => Promise<Record<string, string>>` | - | Async function to provide auth headers |
| `onNavigate` | `(page, subtab?) => void` | - | Navigation callback |
| `onActionComplete` | `(type, data) => void` | - | Action completion callback |

### Low-Level Components

For advanced customization, you can use the individual components:

```tsx
import { ChatPanel, PanelToggle } from '@kite-copilot/chat-panel';

function CustomPanel() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <PanelToggle isOpen={isOpen} onClick={() => setIsOpen(!isOpen)} />
      <ChatPanel isOpen={isOpen} onClose={() => setIsOpen(false)} />
    </>
  );
}
```

## Framework Examples

### Angular

```ts
import { Injectable, OnDestroy } from '@angular/core';
import { createKiteChat, KiteChatInstance } from '@kite-copilot/chat-panel';

@Injectable({ providedIn: 'root' })
export class ChatService implements OnDestroy {
  private chat: KiteChatInstance;

  constructor(private router: Router) {
    this.chat = createKiteChat({
      userId: 'user-123',
      orgId: 'org-456',
      onNavigate: (page) => this.router.navigate([page]),
    });
    this.chat.mount(document.body);
  }

  open() { this.chat.open(); }
  close() { this.chat.close(); }
  toggle() { this.chat.toggle(); }

  ngOnDestroy() {
    this.chat.unmount();
  }
}
```

### Next.js App Router

```tsx
// app/layout.tsx
import { ChatPanelWithToggle } from '@kite-copilot/chat-panel';
import '@kite-copilot/chat-panel/style.css';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <ChatPanelWithToggle 
          agentUrl={process.env.NEXT_PUBLIC_AGENT_URL}
        />
      </body>
    </html>
  );
}
```

## Backend Integration

The ChatPanel expects your backend agent to expose the following SSE endpoints.

### `/chat/stream` (POST)

Main chat endpoint for streaming responses.

**Request Body:**
```json
{
  "session_id": "uuid",
  "user_id": "user-123",
  "org_id": "org-456",
  "message": "user message",
  "current_page": "https://example.com/dashboard/123?tab=settings",
  "user_name": "John Doe",
  "user_email": "john@example.com"
}
```

**SSE Events:**
- `progress`: Processing status updates
- `response`: AI response with optional actions
- `error`: Error messages
- `done`: Stream completion

### `/chat/bulk/stream` (POST)

CSV file upload endpoint for bulk operations.

**Request:** FormData with `file`, `message`, `session_id`, `user_id`, `org_id`, `current_page` (full browser URL)

### `/chat/bulk/confirm` (POST)

Confirm and execute bulk operation.

### `/api/user/starting-questions` (GET) - Optional

Return per-user starting questions.

**Response:**
```json
[
  { "id": "1", "label": "Changing layouts", "prompt": "How do I change the layout?" },
  { "id": "2", "label": "Bulk uploads", "prompt": "How do I upload in bulk?" }
]
```

## Styling

### Using the Default Styles

```tsx
import '@kite-copilot/chat-panel/style.css';
```

### CSS Variables

Override theme variables in your CSS:

```css
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
}
```

## TypeScript

Full TypeScript support with exported types:

```tsx
import type {
  KiteChatConfig,
  KiteChatInstance,
  ChatPanelProps,
  ChatPanelWithToggleProps,
  PanelToggleProps,
  StartingQuestion,
  ActionType,
  ActionData,
  Page,
  SettingsTab,
} from '@kite-copilot/chat-panel';
```

## Migration from Overlay Version

If upgrading from a previous version with overlay/floating panel:

1. **Remove layout changes**: You no longer need flex containers or special positioning
2. **Update imports**: Use `ChatPanelWithToggle` instead of `ChatPanel` for the full experience
3. **Update API calls**: Use `open()`/`close()`/`toggle()` instead of managing `isCollapsed` state

```tsx
// Before (overlay)
<div style={{ display: 'flex' }}>
  <main style={{ flex: 1 }}>...</main>
  <div id="chat-container" />
</div>

// After (side panel)
<main>...</main>
<ChatPanelWithToggle />
```

## Local Development with Test Page

To test the embed script locally using the included test page:

1. **Install dependencies:**
   ```bash
   npm install
   ```

2. **Build the embed script:**
   ```bash
   npm run build
   ```

3. **Serve the test page:**
   ```bash
   npx serve .
   ```

4. **Open in browser:**
   Navigate to `http://localhost:3000/test/test-embed.html`

The test page (`test/test-embed.html`) provides:
- Interactive controls to open/close/toggle the panel
- Page context switching buttons
- Backend URL toggle (localhost vs production)
- Event logging for navigation and action callbacks

**Note:** Make sure your backend agent is running on `http://localhost:5002` (or toggle to production in the test page).

## License

MIT © Kite