npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aivy-chat-react

v0.4.2

Published

Aivy customizable chat widget for web channels

Readme

aivy-chat-react

A customizable React chat widget for Aivy web channels. Drop it into any React/Next.js app, connect your Aivy channel, and start chatting with visitors.

Install

npm install aivy-chat-react
# or
yarn add aivy-chat-react
# or
pnpm add aivy-chat-react

Peer dependencies:

npm install react react-dom lucide-react

Quick start

import { ChatWidget } from 'aivy-chat-react';
import 'aivy-chat-react/styles.css';

export default function App() {
  return (
    <ChatWidget
      channelId="your-channel-id"
      url="https://api.aivygrow.ai/api/v1"
      title="Support"
      subtitle="We reply fast"
    />
  );
}

Note: url is the API base URL including the /api/v1 prefix (e.g. https://api.aivygrow.ai/api/v1 or http://localhost:3000/api/v1 in local dev). Every request (send, upload, history) goes to ${url}/channels/website/....

The pre-chat form is enabled by default. It asks for a required name, email, and phone before the first message. It is skipped only when all three values are already available through user, visitor props, or persisted visitor data; otherwise known values are prefilled and the missing fields remain visible. A random visitorId is generated automatically as web-{uuid} and stored locally as a technical identifier; it is not used as the captured lead phone.

<ChatWidget
  channelId="your-channel-id"
  url="https://api.aivygrow.ai/api/v1"
  enablePreChatForm
  preChatFormVariant="bordered"
/>

enablePreChatForm defaults to true and preChatFormVariant defaults to 'bordered'. To force the form to appear during testing, omit at least one visitor contact field or clear the visitor storage selected by persistConversation.

Importing styles

Import the CSS once in your app entry:

import 'aivy-chat-react/styles.css';

Layout modes

Floating (default)

<ChatWidget
  channelId="..."
  url="https://api.aivygrow.ai/api/v1"
  position="bottom-right"
  width={380}
  height={560}
/>

Inline / embedded

<ChatWidget
  channelId="..."
  url="https://api.aivygrow.ai/api/v1"
  position="inline"
  defaultOpen
  width="100%"
  height={480}
/>

Side panel

<ChatWidget
  channelId="..."
  url="https://api.aivygrow.ai/api/v1"
  position="bottom-right"
  sidePanel
  sidePanelWidth={380}
  defaultOpen
/>

Themes and presets

Start from a ready-made visual system with themePreset:

<ChatWidget
  channelId="..."
  url="https://api.aivygrow.ai/api/v1"
  themePreset="apple" // "apple" | "typebot" | "whatsapp"
/>

Each preset includes light and dark palettes:

<ChatWidget themePreset="whatsapp" themeMode="dark" {...connectionProps} />

Every preset styles the complete experience: panel, header, conversation, bubbles, composer, launcher and pre-chat form. Pass a theme object to override only the tokens you want while retaining the rest of the preset.

<ChatWidget
  channelId="..."
  url="https://api.aivygrow.ai/api/v1"
  themePreset="apple"
  theme={{
    primary: '#0d9488',
    headerBackground: '#0f766e',
    launcherBackground: '#0d9488',
    panelBackground: 'rgba(255, 255, 255, 0.82)',
    conversationBackground: '#f0fdfa',
    user: '#0d9488',
    agent: '#f1f5f9',
    radius: '1.5rem',
    bubbleRadius: '1rem',
    inputRadius: '999px',
    panelBlur: '24px',
  }}
/>

The built-in definitions are exported if you need to derive a reusable theme:

import { THEME_PRESETS, type WidgetTheme } from 'aivy-chat-react';

const brandTheme: WidgetTheme = {
  ...THEME_PRESETS.typebot,
  primary: '#ea580c',
  user: '#ea580c',
  launcherBackground: '#ea580c',
};

Available visual tokens include the existing palette values plus panelBackground, conversationBackground, panelBorder, headerBorder, inputBorder, inputPlaceholder, bubbleRadius, inputRadius, launcherRadius, panelBlur, headerBlur, messageMaxWidth, and launcherShadow, launcherBorder, launcherBorderWidth, and launcherHoverScale. Typography can be controlled with fontFamily, baseFontSize, messageFontSize, headerTitleSize, headerSubtitleSize, and inputFontSize. Internal quick replies and card actions use actionBackground, actionForeground, actionBorder, and actionRadius. The outer border is disabled by default and can be enabled with panelBorderWidth and panelBorder. Use the existing launcherSize, launcherIcon, launcherGreeting, showLauncherGreeting, position, and offset props for the button's content and placement. Component render props and *ClassName escape hatches remain available for fully custom structures.

Common props

| Prop | Description | | --- | --- | | channelId | Your Aivy web channel ID | | url | Your Aivy API base URL, e.g. https://api.aivygrow.ai/api/v1 | | title | Header title | | subtitle | Header subtitle | | position | bottom-right, bottom-left, top-right, top-left, inline | | sidePanel | Render as full-height side panel | | width / height | Widget dimensions | | defaultOpen | Open the widget by default | | onOpenChange | Called when the widget opens/closes | | onClose | Called when the widget is closed | | resetVisitorOnClose | Regenerate visitorId and clear the conversation when the widget closes | | resetConversationOnClose | Clear the conversation when the widget closes (without changing visitorId) | | persistConversation | true (localStorage, shared across windows), 'session' (per-tab), false (no persistence) | | onVisitorIdChange | Called when the visitorId changes | | quickActions | Array of quick reply buttons | | features | { attachments, multipleAttachments, voice, quickActions, typingIndicator, unreadBadge, ... } | | acceptedFileTypes | HTML accept value for file input, e.g. image/*,.pdf | | maxFileSizeMB | Max size for each uploaded file | | maxFiles | Max number of files per message | | fileRestrictions | Object with maxFileSizeMB, maxFiles, acceptedFileTypes | | onFileRejected | Called when a file is rejected (too large, too many, invalid type) | | onError | Called on send/upload errors | | onMessageSent | Called after a message is confirmed by the server | | onMessageReceived | Called for every new message received from the agent | | onQuickReply | Called when a quick reply button is tapped | | onCardButton | Called when a card or carousel button is tapped |

Persistence (conversation & visitor name)

The conversation id and the visitor name are stored per channel (aivy_conversation_id_{channelId}), so multiple widgets or sites never share a conversation. Control the behavior with persistConversation:

| Value | Storage | Behavior | | --- | --- | --- | | true (default) | localStorage | Shared across windows/tabs of the same browser | | 'session' | sessionStorage | Only this tab — a new window starts a fresh chat | | false | none | New conversation on every reload |

<ChatWidget channelId="..." url="..." persistConversation="session" />

The visitor's name (from the pre-chat form or visitorName) is remembered with the same mode, so returning visitors are not asked again and messages always arrive with their name.

Polling

New messages are fetched with lightweight polling that only runs while the widget is open and the browser tab is visible. A closed widget makes zero requests. Tune the interval with pollInterval (default 2000 ms) or disable it completely:

<ChatWidget channelId="..." url="..." pollInterval={8000} enablePolling={false} />

Close callbacks

Run your own logic when the visitor closes the widget, and optionally reset the visitor:

<ChatWidget
  channelId="..."
  url="..."
  onClose={() => console.log('widget closed')}
  onVisitorIdChange={(id) => console.log('new visitorId:', id)}
  resetVisitorOnClose
/>

When resetVisitorOnClose is enabled, the widget will generate a new visitorId and clear the local conversation every time it closes. Use resetConversationOnClose if you only want to clear the conversation without changing the visitor.

Attachments

Images, documents, audio and video are supported. Select one or multiple files depending on the features.multipleAttachments flag (default true):

<ChatWidget
  channelId="..."
  url="..."
  features={{ attachments: true, multipleAttachments: true }}
  acceptedFileTypes="image/*,.pdf,.doc,.docx"
  maxFileSizeMB={10}
  maxFiles={5}
  onFileRejected={(rejection) => console.warn(rejection.message, rejection.file)}
/>

Rich messages

The widget renders rich messages received from the agent: product/service catalogs, interactive lists, order receipts, carousels, cards, quick replies, locations, templates and events. It also exposes callbacks for interactive elements:

<ChatWidget
  channelId="..."
  url="..."
  onQuickReply={(reply) => console.log('quick reply:', reply)}
  onCardButton={(button, card) => console.log('card button:', button, card)}
/>

By default, tapping a quick reply, a catalog item, a list row or a postback card button will send a new message with the item title as content.

Catalog & list

Catalog messages render as horizontally scrollable product/service cards (image, price, description). List messages render WhatsApp-style sections with tappable rows.

Order receipts

When the agent registers an order, the widget shows a receipt card with the order reference, status, itemized products (quantities, notes, prices), total, delivery info and payment methods. Updates to an existing order render the same card with newly added items highlighted.

Media bubbles

Images and videos render full-bleed inside the bubble (WhatsApp style), with multi-image grids and linkified URLs in text. Voice notes use a waveform player with real audio peaks, drag-to-seek and 1x/1.5x/2x playback speed.

New conversation

The header includes a "new conversation" button that closes the current conversation server-side and starts a fresh one on the next message. Customize its label with locale.newConversation.

Sending multiple messages

The widget uses an internal queue to send messages one by one. If the visitor sends several messages quickly, they are enqueued and sent sequentially, so the agent can process them in order.

<ChatWidget
  channelId="..."
  url="..."
/>

Custom upload handler

If you need to upload files to your own storage or add extra metadata, provide a custom uploadFile function:

<ChatWidget
  channelId="..."
  url="..."
  uploadFile={async (file) => {
    const formData = new FormData();
    formData.append('file', file);
    const res = await fetch('/api/upload', { method: 'POST', body: formData });
    const data = await res.json();
    return { url: data.url, mediaType: 'image', name: file.name };
  }}
/>

Pre-chat form

By default, the widget does not ask for the visitor's name. Enable it or prefill the data:

<ChatWidget
  channelId="..."
  url="..."
  visitorName="John Doe"
  enablePreChatForm
/>

Locale

Override any label:

<ChatWidget
  locale={{
    title: 'Habla con nosotros',
    placeholder: 'Escribe un mensaje...',
  }}
/>

License

MIT