npm package: @navsi.ai/sdk
Description: React SDK for Navsi AI chatbot - Ask and Navigate modes
Author: Kushagra Tiwari
Latest version: 2.3.2

# @navsi.ai/sdk - Navsi AI Chatbot SDK

AI-powered chatbot SDK for websites with Ask & Navigate modes.

> ⚠️ **Note**: This package is part of the `navsi-integrated` monorepo.

## Installation (for external users)

```bash
npm install @navsi.ai/sdk
# or
yarn add @navsi.ai/sdk
# or
pnpm add @navsi.ai/sdk
```

For **Next.js** projects, ensure `next` is installed (optional peer dependency):  
`pnpm add next` (or use your existing Next.js app).

## Quick Start

> **Zero-config styling** — All widget styles are auto-injected at runtime.
> You do **not** need to import any CSS file.

```tsx
import { ChatbotProvider, ChatbotWidget } from '@navsi.ai/sdk';

function App() {
  return (
    <ChatbotProvider
      apiKey="YOUR_API_KEY"
      serverUrl="wss://your-server.com/ws"
    >
      <YourApp />
      <ChatbotWidget />
    </ChatbotProvider>
  );
}
```

### Overriding Default Styles

The SDK auto-injects its styles in a `<style>` tag at the top of `<head>`. Any CSS you add in your own stylesheet will automatically take precedence. All widget classes are prefixed `navsi-chatbot-*`.

```css
/* Example: custom toggle button */
.navsi-chatbot-toggle {
  background: #e11d48;
  box-shadow: 0 4px 16px rgba(225, 29, 72, 0.4);
}

/* Example: custom chat window size */
.navsi-chatbot-window {
  width: 420px;
  height: 600px;
}
```

#### Available CSS Classes

| Class Name                           | Element                              |
| ------------------------------------ | ------------------------------------ |
| `.navsi-chatbot-container`           | Root fixed-position container        |
| `.navsi-chatbot-toggle`              | Floating open/close button           |
| `.navsi-chatbot-window`              | Chat window panel                    |
| `.navsi-chatbot-header`              | Window header bar                    |
| `.navsi-chatbot-header-left`         | Header left section (title + status) |
| `.navsi-chatbot-title`               | Header title text                    |
| `.navsi-chatbot-status`              | Connection status text               |
| `.navsi-chatbot-clear`               | "Clear" chat button                  |
| `.navsi-chatbot-mode-toggle`         | Ask/Action mode toggle container     |
| `.navsi-chatbot-mode-button`         | Individual mode button               |
| `.navsi-chatbot-mode-active`         | Active mode button                   |
| `.navsi-chatbot-lang-toggle`         | Language toggle container            |
| `.navsi-chatbot-lang-button`         | Individual language button           |
| `.navsi-chatbot-lang-active`         | Active language button               |
| `.navsi-chatbot-messages`            | Messages scroll area                 |
| `.navsi-chatbot-message`             | Individual message wrapper           |
| `.navsi-chatbot-message-user`        | User message bubble                  |
| `.navsi-chatbot-message-assistant`   | Assistant message bubble             |
| `.navsi-chatbot-message-content`     | Message content (markdown)           |
| `.navsi-chatbot-message-paragraph`   | Paragraph inside assistant message   |
| `.navsi-chatbot-message-list`        | Bullet list inside assistant message |
| `.navsi-chatbot-message-list-item`   | List item inside assistant message   |
| `.navsi-chatbot-welcome`             | Welcome screen container             |
| `.navsi-chatbot-welcome-text`        | Welcome message text                 |
| `.navsi-chatbot-welcome-hint`        | Welcome hint/subtitle                |
| `.navsi-chatbot-input-area`          | Input area container                 |
| `.navsi-chatbot-input`               | Text input field                     |
| `.navsi-chatbot-send-button`         | Send button                          |
| `.navsi-chatbot-voice-button`        | Voice input button                   |
| `.navsi-chatbot-voice-button-active` | Voice button while recording         |
| `.navsi-chatbot-stop-button`         | Stop execution button                |
| `.navsi-chatbot-error`               | Error banner                         |
| `.navsi-chatbot-error-close`         | Error dismiss button                 |
| `.navsi-chatbot-voice-error`         | Voice error message                  |
| `.navsi-chatbot-voice-transcript`    | Live voice transcript                |
| `.navsi-chatbot-resize-handle`       | Drag handle for manual resize        |
| `.navsi-chatbot-voice-indicator`     | Header voice indicator               |
| `.navsi-chatbot-banner`              | Execution status banner              |
| `.navsi-chatbot-pill`                | Progress step pill                   |
| `.navsi-chatbot-progress-container`  | Progress bar wrapper                 |
| `.navsi-chatbot-progress-bar`        | Progress bar track                   |
| `.navsi-chatbot-progress-fill`       | Progress bar fill                    |
| `.navsi-chatbot-typing`              | Typing dots indicator                |

#### CSS Custom Properties

You can also set these CSS variables on `.navsi-chatbot-container` (or let the `theme` config set them automatically):

| Variable                | Default           | Description                           |
| ----------------------- | ----------------- | ------------------------------------- |
| `--navsi-primary`       | `#2563eb`         | Primary brand colour                  |
| `--navsi-primary-hover` | `#1d4ed8`         | Primary hover colour                  |
| `--navsi-bg`            | `#ffffff`         | Window background                     |
| `--navsi-text`          | `#1f2937`         | Text colour                           |
| `--navsi-font`          | System font stack | Font family                           |
| `--navsi-radius`        | `12px`            | Border radius (window, bubbles)       |
| `--navsi-radius-sm`     | `6px`             | Small border radius (inputs, buttons) |
| `--navsi-window-width`  | `380px`           | Chat window width                     |
| `--navsi-window-height` | `540px`           | Chat window height                    |
| `--navsi-button-size`   | `48px`            | Toggle button size                    |

### With Configuration Fetching

For a complete setup with widget configuration from your server:

```tsx
import React, { useState, useEffect } from 'react';
import { ChatbotProvider, ChatbotWidget } from '@navsi.ai/sdk';
import type { FlatWidgetConfig } from '@navsi.ai/shared';

function App() {
  const [widgetConfig, setWidgetConfig] = useState<FlatWidgetConfig | null>(null);
  const [configError, setConfigError] = useState<string | null>(null);

  const API_KEY = import.meta.env.VITE_NAVSI_API_KEY || process.env.REACT_APP_NAVSI_API_KEY;
  const API_BASE = import.meta.env.VITE_NAVSI_API_URL || process.env.REACT_APP_NAVSI_API_URL || 'https://api.example.com';
  
  // Convert HTTP URL to WebSocket URL
  const getWebSocketUrl = (httpUrl: string): string => {
    const url = new URL(httpUrl);
    const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
    return `${wsProtocol}//${url.host}${url.pathname.replace(/\/$/, '')}/ws`;
  };
  const WS_URL = getWebSocketUrl(API_BASE);

  useEffect(() => {
    fetch(`${API_BASE}/api/sdk/config`, {
      headers: { 'x-api-key': API_KEY },
    })
      .then((r) => {
        if (!r.ok) throw new Error(`Failed to fetch config: ${r.status}`);
        return r.json();
      })
      .then((data) => {
        if (data?.config && typeof data.config === 'object') {
          setWidgetConfig(data.config as FlatWidgetConfig);
        }
      })
      .catch((err) => {
        console.error('Failed to load widget config:', err);
        setConfigError(err.message);
        // Widget will still work with default config
      });
  }, []);

  return (
    <ChatbotProvider
      apiKey={API_KEY}
      serverUrl={WS_URL}
      config={widgetConfig}
    >
      <YourApp />
      <ChatbotWidget />
    </ChatbotProvider>
  );
}
```

### Next.js (App Router)

Use a **Client Component** to wrap your app with the chatbot and provide a Next.js navigation adapter so the AI can navigate between pages:

1. Create a client component (e.g. `app/ClientProviders.tsx`) with `'use client'` at the top.
2. Use `useRouter()` and `usePathname()` from `next/navigation`, and `useNextRouterAdapter(router, pathname)` from `@navsi.ai/sdk`.
3. Wrap your app in `ChatbotProvider` (with `navigationAdapter`) and render `ChatbotWidget`.
4. Set `NEXT_PUBLIC_NAVSI_API_KEY` and `NEXT_PUBLIC_NAVSI_API_URL` in `.env.local` (WebSocket URL is derived from the API URL).

```tsx
// app/ClientProviders.tsx
'use client';

import { useRouter, usePathname } from 'next/navigation';
import { ChatbotProvider, ChatbotWidget, useNextRouterAdapter } from '@navsi.ai/sdk';

function getWebSocketUrl(httpUrl: string): string {
  const url = new URL(httpUrl);
  const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
  return `${wsProtocol}//${url.host}${url.pathname.replace(/\/$/, '')}/ws`;
}

export function ClientProviders({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  const pathname = usePathname();
  const adapter = useNextRouterAdapter(router, pathname);

  const apiKey = process.env.NEXT_PUBLIC_NAVSI_API_KEY ?? '';
  const apiUrl = process.env.NEXT_PUBLIC_NAVSI_API_URL ?? 'http://localhost:3001';
  const serverUrl = getWebSocketUrl(apiUrl);

  return (
    <ChatbotProvider apiKey={apiKey} serverUrl={serverUrl} navigationAdapter={adapter} routes={['/', '/products', '/cart']}>
      {children}
      <ChatbotWidget />
    </ChatbotProvider>
  );
}
```

In your root `app/layout.tsx`, wrap your app with `<ClientProviders>{children}</ClientProviders>` (or nest it inside your layout so the widget is available on all pages).

### Next.js (Pages Router)

Use the Pages Router adapter inside `_app.tsx` so Navigate mode can follow route changes driven by `next/router`:

1. Use `useRouter()` from `next/router` and `useNextPagesRouterAdapter(router)` from `@navsi.ai/sdk`.
2. Wrap your app in `ChatbotProvider` with `navigationAdapter`.
3. Set `NEXT_PUBLIC_NAVSI_API_KEY` and `NEXT_PUBLIC_NAVSI_API_URL` in `.env.local`.

```tsx
// pages/_app.tsx
import type { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { ChatbotProvider, ChatbotWidget, useNextPagesRouterAdapter } from '@navsi.ai/sdk';

function getWebSocketUrl(httpUrl: string): string {
  const url = new URL(httpUrl);
  const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
  return `${wsProtocol}//${url.host}${url.pathname.replace(/\/$/, '')}/ws`;
}

export default function MyApp({ Component, pageProps }: AppProps) {
  const router = useRouter();
  const adapter = useNextPagesRouterAdapter(router);

  const apiKey = process.env.NEXT_PUBLIC_NAVSI_API_KEY ?? '';
  const apiUrl = process.env.NEXT_PUBLIC_NAVSI_API_URL ?? 'http://localhost:3001';
  const serverUrl = getWebSocketUrl(apiUrl);

  return (
    <ChatbotProvider apiKey={apiKey} serverUrl={serverUrl} navigationAdapter={adapter} routes={['/', '/products', '/cart']}>
      <Component {...pageProps} />
      <ChatbotWidget />
    </ChatbotProvider>
  );
}
```

## Features

### Ask Mode
- Natural language Q&A about your website
- Context-aware responses based on current page
- Powered by Gemini AI

### Navigate Mode
- AI-guided navigation through your website
- Automatic form filling
- Multi-step task completion

## Configuration Options

```typescript
interface ChatbotProviderProps {
  // Required
  apiKey: string;              // Your API key from the admin dashboard
  serverUrl: string;            // WebSocket server URL (e.g., 'wss://api.example.com/ws')
  
  // Optional
  config?: FlatWidgetConfig | null;  // Widget config fetched from /api/sdk/config
  options?: Partial<WidgetConfig>;   // Additional widget configuration (e.g. voiceLanguage: 'hi-IN')
  navigationAdapter?: NavigationAdapter;  // Custom navigation adapter
  serverActions?: ServerAction[];    // Server-side actions the AI can execute
  routes?: string[];                // List of available routes (for AI context)
  debug?: boolean;                  // Enable debug logging
  autoConnect?: boolean;            // Auto-connect WebSocket on mount (default: true)
  children: React.ReactNode;       // Your app components
}
```

### Widget Configuration

The `config` prop accepts a flat widget configuration object fetched from your server:

```typescript
interface FlatWidgetConfig {
  headerTitle?: string;
  welcomeMessage?: string;
  inputPlaceholder?: string;
  showModeToggle?: boolean;
  defaultMode?: 'ask' | 'navigate';
  defaultOpen?: boolean;
  /** BCP-47 language for voice input, output, and AI responses (e.g. 'hi-IN' for Hindi) */
  voiceLanguage?: string;
  primaryColor?: string;
  secondaryColor?: string;
  backgroundColor?: string;
  textColor?: string;
  fontFamily?: string;
  borderRadius?: number;
  darkMode?: boolean;
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
  windowWidth?: number;
  windowHeight?: number;
  buttonSize?: number; // defaults to 48px
  draggable?: boolean;
  resizable?: boolean; // live resize handle, desktop only
  appearanceVariant?: 'default' | 'light-demo';
}
```

When `resizable` is enabled, the open widget shows a resize grip in the bottom-left corner on viewports wider than `640px`. Resized dimensions are stored in `localStorage` per API key/widget namespace, so the same browser keeps the chosen size across navigation and reloads.

### Voice language (e.g. Hindi)

Set `voiceLanguage` to a BCP-47 locale (e.g. `'hi-IN'` for Hindi) to:

- Use that language for **speech recognition** (mic input)
- Use that language for **speech synthesis** (AI reply read aloud)
- Have the AI **respond in that language** (e.g. Hindi in Devanagari)

Example:

```tsx
<ChatbotProvider
  apiKey={API_KEY}
  serverUrl={WS_URL}
  options={{ voiceLanguage: 'hi-IN' }}
>
  <ChatbotWidget />
</ChatbotProvider>
```

You can also pass `voiceLanguage` in the `config` object from your API. Browser support for Hindi (and other locales) depends on the client; Chrome supports `hi-IN` for both recognition and synthesis.

## Server Actions

Register server-side actions that the AI can execute:

```tsx
const serverActions = [
  {
    id: 'add-to-cart',
    name: 'Add to Cart',
    description: 'Add a product to the shopping cart',
    parameters: [
      { name: 'productId', type: 'string', required: true },
      { name: 'quantity', type: 'number', required: false, defaultValue: 1 },
    ],
    handler: async (params, context) => {
      // Your logic here
      return { success: true, data: { message: 'Added to cart!' } };
    },
  },
];

<ChatbotProvider 
  apiKey="YOUR_API_KEY"
  serverUrl="wss://your-server.com/ws"
  serverActions={serverActions}
>
```

## Hooks

### useChatbot

Access chatbot state and controls:

```tsx
import { useChatbot } from '@navsi.ai/sdk';

function MyComponent() {
  const { 
    isConnected,
    mode,
    setMode,
    sendMessage,
    messages 
  } = useChatbot();
}
```

### useWebSocket

Access WebSocket connection state:

```tsx
import { useWebSocket } from '@navsi.ai/sdk';

function ConnectionStatus() {
  const { isConnected, isReconnecting } = useWebSocket();
  
  return <div>Status: {isConnected ? 'Connected' : isReconnecting ? 'Reconnecting' : 'Disconnected'}</div>;
}
```

### useActionExecution

Track action execution progress:

```tsx
import { useActionExecution } from '@navsi.ai/sdk';

function ProgressBar() {
  const { progress } = useActionExecution();
  
  return progress ? (
    <div>Step {progress.current + 1} of {progress.total}</div>
  ) : null;
}
```

## Environment Variables

For production deployments, use environment variables:

```bash
# Vite (recommended)
VITE_NAVSI_API_KEY=your_api_key_here
VITE_NAVSI_API_URL=https://api.example.com

# Create React App
REACT_APP_NAVSI_API_KEY=your_api_key_here
REACT_APP_NAVSI_API_URL=https://api.example.com

# Next.js
NEXT_PUBLIC_NAVSI_API_KEY=your_api_key_here
NEXT_PUBLIC_NAVSI_API_URL=https://api.example.com
```

**Important**: Never commit API keys to version control. Use environment variables or secure configuration services.

## Troubleshooting

### Widget doesn't connect

1. Check that `serverUrl` is correct (use `wss://` for HTTPS, `ws://` for HTTP)
2. Verify your API key is valid
3. Check browser console for WebSocket connection errors
4. Ensure CORS is configured on your server

### Configuration not loading

1. Verify the `/api/sdk/config` endpoint returns valid JSON
2. Check that the `x-api-key` header is included in the request
3. Widget will fall back to default config if fetch fails

### Actions not working

1. Ensure `navigationAdapter` is provided if using React Router or Next.js router adapters
2. Check that DOM elements have proper `data-chatbot-action` attributes
3. Enable `debug={true}` to see detailed logs

## Development

```bash
# From monorepo root
pnpm --filter @navsi.ai/sdk dev     # Watch mode
pnpm --filter @navsi.ai/sdk build   # Build
pnpm --filter @navsi.ai/sdk lint    # Lint
```

## License

MIT