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

@rahul_vendure/ai-chat-react

v0.1.10

Published

Drop-in AI shopping assistant React component for Vendure stores — chat UI with product cards, cart actions, and order tracking

Readme

@rahul_vendure/ai-chat-react

Drop-in AI shopping assistant React component for Vendure stores. Renders a floating chat panel with product cards, collection badges, cart summary, and quick actions — all powered by the @rahul_vendure/ai-chat-plugin backend.

Ships a pre-compiled CSS file — just import and go. Works with any React framework (Next.js, Remix, Vite, CRA, etc.).

Features

  • Drop-in Component<VendureAiChat> renders a floating chat button + panel, zero config
  • Headless HookuseVendureChat() for full UI control
  • Product Cards — Automatic product display with images, prices, and add-to-cart buttons
  • Collection Badges — Clickable collection/category links
  • Cart Summary — Shows active order with line items and totals
  • Zero Config Styles — Ships pre-compiled CSS, just import and go. Inherits your theme via CSS variables
  • Dark Mode — Works with Tailwind's dark mode out of the box
  • Framework Agnostic — Works with Next.js, Remix, Vite, CRA, etc.
  • Streaming — Real-time response streaming via Vercel AI SDK
  • lucide-react Icons — Clean, consistent icons throughout

Requirements

Installation

npm install @rahul_vendure/ai-chat-react ai @ai-sdk/react

Quick Start

import { VendureAiChat } from '@rahul_vendure/ai-chat-react';
import '@rahul_vendure/ai-chat-react/styles.css';

function App() {
    return (
        <div>
            <h1>My Store</h1>
            <VendureAiChat vendureUrl="https://my-vendure.com" />
        </div>
    );
}

This renders a floating chat button in the bottom-right corner. Click to open the chat panel.

Note: The component uses CSS variables like --primary, --foreground, --muted, etc., following the standard shadcn/ui convention. If you're using shadcn/ui, it works out of the box. Otherwise, define these CSS variables in your globals (see Theming below).

Usage Levels

Level 1: Drop-in (Zero Config)

<VendureAiChat vendureUrl="https://my-vendure.com" />

Level 1 with auth, callbacks, and formatting

Same component with auth token, price formatting, add-to-cart, cart refresh, and navigation — typical for a storefront navbar or layout:

'use client';

import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { VendureAiChat } from '@rahul_vendure/ai-chat-react';
import '@rahul_vendure/ai-chat-react/styles.css';
import { formatPrice } from '@/lib/format';

export function NavbarChat({ vendureUrl, authToken }: { vendureUrl: string; authToken?: string }) {
    const router = useRouter();

    return (
        <VendureAiChat
            vendureUrl={vendureUrl}
            authToken={authToken}
            formatPrice={formatPrice}
            position="bottom-right"
            onCartUpdate={() => router.refresh()}
            onAddToCart={async (variantId, quantity, productName) => {
                const { addToCart } = await import('@/app/product/[slug]/actions');
                const result = await addToCart(variantId, quantity);
                if (result.success) {
                    toast.success('Added to cart', {
                        description: `${productName} has been added to your cart`,
                    });
                    router.refresh();
                } else {
                    toast.error('Error', {
                        description: result.error || 'Failed to add item to cart',
                    });
                }
            }}
            onNavigate={(path) => router.push(path)}
        />
    );
}

Get authToken from your server (e.g. via a wrapper like NavbarChatWithAuth) — see Authentication.

formatPrice — Prices from the chat API are in cents (smallest currency unit). Provide a function (price: number) => string:

// lib/format.ts — example: USD with Intl
export function formatPrice(price: number): string {
    return new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD',
    }).format(price / 100);
}

// Or minimal: "$12.34"
export function formatPrice(cents: number): string {
    return `$${(cents / 100).toFixed(2)}`;
}

Level 2: All config options

You can configure appearance, callbacks, and custom renderers. Full list of props:

<VendureAiChat
    // Connection
    vendureUrl="https://my-vendure.com"
    authToken={sessionToken}

    // Appearance
    position="bottom-right"          // "bottom-right" | "bottom-left" | "inline"
    title="Shopping Assistant"
    placeholder="Ask about products..."
    welcomeMessage="Hi! How can I help you today?"
    quickActions={[
        'What do you sell?',
        'Show me your collections',
        'Help me find a gift',
    ]}

    // Callbacks
    onAddToCart={async (variantId, quantity, productName) => {
        await addToCart(variantId, quantity);
        toast.success(`Added ${productName} to cart`);
    }}
    onCartUpdate={() => router.refresh()}
    onNavigate={(path) => router.push(path)}

    // Custom rendering
    renderProduct={(product) => <MyProductCard {...product} />}
    renderCollection={(collection) => <MyBadge {...collection} />}
    renderCartSummary={(order) => <MyCartWidget {...order} />}

    // Formatting
    formatPrice={(price) => `$${(price / 100).toFixed(2)}`}

    // Styling
    className="my-custom-class"
/>

See Props Reference for types and defaults.

Level 3: Headless Hook (Full Control)

import { useVendureChat } from '@rahul_vendure/ai-chat-react';

function MyCustomChat() {
    const {
        messages,
        status,
        sendMessage,
        isLoading,
        products,
        collections,
        activeOrder,
        getMessageText,
        getMessageData,
    } = useVendureChat({
        vendureUrl: 'https://my-vendure.com',
        authToken: token,
    });

    return (
        <div>
            {messages.map(msg => (
                <div key={msg.id}>
                    <p>{getMessageText(msg)}</p>
                    {msg.role === 'assistant' && getMessageData(msg).products.map(p => (
                        <div key={p.id}>{p.name} — {p.price}</div>
                    ))}
                </div>
            ))}
            <input onKeyDown={e => {
                if (e.key === 'Enter') sendMessage(e.currentTarget.value);
            }} />
        </div>
    );
}

Props Reference

<VendureAiChat> Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | vendureUrl | string | required | Vendure server URL | | authToken | string | — | Bearer token for authenticated features | | position | 'bottom-right' \| 'bottom-left' \| 'inline' | 'bottom-right' | Chat position | | title | string | 'Shopping Assistant' | Header title | | placeholder | string | 'Ask about products...' | Input placeholder | | welcomeMessage | string | Default greeting | Empty state message | | quickActions | string[] | Default actions | Quick action buttons | | onAddToCart | (variantId, quantity, name) => void | — | Cart callback | | onNavigate | (path) => void | — | Navigation callback | | renderProduct | (product) => ReactNode | — | Custom product renderer | | renderCollection | (collection) => ReactNode | — | Custom collection renderer | | renderCartSummary | (order) => ReactNode | — | Custom cart renderer | | formatPrice | (price) => string | $X.XX | Price formatter | | className | string | — | Additional CSS class |

useVendureChat() Return

| Field | Type | Description | |-------|------|-------------| | messages | UIMessage[] | All chat messages | | status | string | 'ready' \| 'streaming' \| 'submitted' \| 'error' | | sendMessage | (text: string) => Promise<void> | Send a message | | isLoading | boolean | Whether chat is loading/streaming | | products | ChatProduct[] | Products from latest response | | collections | ChatCollection[] | Collections from latest response | | activeOrder | ChatActiveOrder \| undefined | Cart state from latest response | | getMessageText | (msg) => string | Extract text from a message | | getMessageData | (msg) => ExtractedData | Extract structured data from a message |

Theming

The component uses Tailwind utility classes that reference CSS variables following the shadcn/ui convention:

  • bg-primary, text-primary-foreground — primary button/accent color
  • bg-muted, text-muted-foreground — assistant message bubbles, secondary UI
  • bg-background, text-foreground — main background and text
  • border — borders using border-border

If you use shadcn/ui, everything works out of the box — the component inherits your theme.

If you don't use shadcn/ui, add these CSS variables to your globals:

@layer base {
    :root {
        --background: 0 0% 100%;
        --foreground: 222.2 84% 4.9%;
        --primary: 221.2 83.2% 53.3%;
        --primary-foreground: 210 40% 98%;
        --muted: 210 40% 96.1%;
        --muted-foreground: 215.4 16.3% 46.9%;
        --border: 214.3 31.8% 91.4%;
        --input: 214.3 31.8% 91.4%;
        --ring: 221.2 83.2% 53.3%;
    }

    .dark {
        --background: 222.2 84% 4.9%;
        --foreground: 210 40% 98%;
        --primary: 217.2 91.2% 59.8%;
        --primary-foreground: 222.2 84% 4.9%;
        --muted: 217.2 32.6% 17.5%;
        --muted-foreground: 215 20.2% 65.1%;
        --border: 217.2 32.6% 17.5%;
        --input: 217.2 32.6% 17.5%;
        --ring: 224.3 76.3% 48%;
    }
}

You can customize the component's look by changing these CSS variables in your Tailwind config.

Authentication

The authToken prop is optional. Without it, the chat works in anonymous mode — users can search products, browse collections, and filter by price.

To unlock authenticated features (cart management, order history, order tracking, checkout), pass the Vendure session token:

| Feature | Anonymous | Authenticated | |---------|-----------|---------------| | Product search | Yes | Yes | | Vector/semantic search | Yes | Yes | | Browse collections | Yes | Yes | | Filter by price | Yes | Yes | | Add to cart | No | Yes | | View cart | No | Yes | | Order history | No | Yes | | Track orders | No | Yes | | Checkout flow | No | Yes |

How it works

When a customer logs in to your Vendure storefront, Vendure returns a session token. You store this token (typically in an HTTP-only cookie), then pass it to <VendureAiChat> as the authToken prop. The component sends it as an Authorization: Bearer <token> header with every chat request, and the backend resolves the customer session to enable personalized tools.

Next.js App Router (recommended pattern)

The token must flow from a server component (which can read cookies) to the client component (which renders the chat). Here's the full pattern:

1. Auth helper — read/write the Vendure session token from cookies:

// lib/auth.ts
import { cookies } from 'next/headers';

const AUTH_TOKEN_COOKIE = 'vendure-auth-token';

export async function getAuthToken(): Promise<string | undefined> {
    const cookieStore = await cookies();
    return cookieStore.get(AUTH_TOKEN_COOKIE)?.value;
}

export async function setAuthToken(token: string) {
    const cookieStore = await cookies();
    cookieStore.set(AUTH_TOKEN_COOKIE, token);
}

Set this cookie when the customer logs in (e.g. after calling the login mutation on the Vendure Shop API, store the returned token with setAuthToken(token)).

2. Client component — wraps <VendureAiChat> (must be a client component since it uses hooks):

// components/storefront-chat.tsx
'use client';
import { VendureAiChat } from '@rahul_vendure/ai-chat-react';

interface StorefrontChatProps {
    vendureUrl: string;
    authToken?: string;
}

export function StorefrontChat({ vendureUrl, authToken }: StorefrontChatProps) {
    return (
        <VendureAiChat
            vendureUrl={vendureUrl}
            authToken={authToken}
            onNavigate={(path) => { window.location.href = path; }}
        />
    );
}

3. Server component — reads the token from cookies and passes it down:

// app/layout.tsx (or any server component like a Navbar)
import { getAuthToken } from '@/lib/auth';
import { StorefrontChat } from '@/components/storefront-chat';

const VENDURE_URL = process.env.VENDURE_SHOP_API_URL!.replace(/\/shop-api\/?$/, '');

export default async function Layout({ children }) {
    const authToken = await getAuthToken();

    return (
        <html>
            <body>
                {children}
                <StorefrontChat vendureUrl={VENDURE_URL} authToken={authToken} />
            </body>
        </html>
    );
}

This pattern ensures the token is read server-side (secure, no exposure to client JS) and passed as a prop to the chat component. If the chat lives in a child (e.g. Navbar) that is not async, use the wrapper pattern below.

Chat in Navbar (or other non-async parent)

If the component that should render the chat (e.g. your Navbar) is not an async server component, use a small async wrapper that fetches the token and renders the client chat. Wrap the wrapper in <Suspense> to avoid errors.

1. Auth helper — same as above, e.g. in lib/auth.ts:

// lib/auth.ts
import { cookies } from 'next/headers';

const AUTH_TOKEN_COOKIE = process.env.VENDURE_AUTH_TOKEN_COOKIE || 'vendure-auth-token';

export async function getAuthToken(): Promise<string | undefined> {
    const cookieStore = await cookies();
    return cookieStore.get(AUTH_TOKEN_COOKIE)?.value;
}

2. Async server wrapper — fetches the token and passes it to the client chat:

// components/layout/navbar/navbar-chat-with-auth.tsx
import { getAuthToken } from '@/lib/auth';
import { NavbarChat } from './navbar-chat';

interface NavbarChatWithAuthProps {
    vendureUrl: string;
}

export async function NavbarChatWithAuth({ vendureUrl }: NavbarChatWithAuthProps) {
    const authToken = await getAuthToken();
    return <NavbarChat vendureUrl={vendureUrl} authToken={authToken} />;
}

3. Client component — wraps <VendureAiChat> with your callbacks (e.g. add-to-cart, navigation, formatting):

// components/layout/navbar/navbar-chat.tsx
'use client';

import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { VendureAiChat } from '@rahul_vendure/ai-chat-react';
import { formatPrice } from '@/lib/format';

interface NavbarChatProps {
    vendureUrl: string;
    authToken?: string;
}

export function NavbarChat({ vendureUrl, authToken }: NavbarChatProps) {
    const router = useRouter();

    return (
        <VendureAiChat
            vendureUrl={vendureUrl}
            authToken={authToken}
            formatPrice={formatPrice}
            position="bottom-right"
            onCartUpdate={() => router.refresh()}
            onAddToCart={async (variantId, quantity, productName) => {
                const { addToCart } = await import('@/app/product/[slug]/actions');
                const result = await addToCart(variantId, quantity);
                if (result.success) {
                    toast.success('Added to cart', {
                        description: `${productName} has been added to your cart`,
                    });
                    router.refresh();
                } else {
                    toast.error('Error', {
                        description: result.error || 'Failed to add item to cart',
                    });
                }
            }}
            onNavigate={(path) => router.push(path)}
        />
    );
}

4. Use the wrapper in your Navbar — keep the Navbar as a regular (sync) component and wrap the chat in Suspense:

// components/layout/navbar.tsx
import { NavbarChatWithAuth } from '@/components/layout/navbar/navbar-chat-with-auth';
import { Suspense } from 'react';


export function Navbar() {
    return (
        <header>
            {/* ... other navbar content ... */}
            <Suspense>
                <NavbarChatWithAuth vendureUrl={VENDURE_BASE_URL} />
            </Suspense>
        </header>
    );
}

Using the async wrapper + Suspense keeps the token on the server and avoids making the whole Navbar async.

Remix

// app/root.tsx
import { VendureAiChat } from '@rahul_vendure/ai-chat-react';
import { useLoaderData, useNavigate } from '@remix-run/react';
import type { LoaderFunctionArgs } from '@remix-run/node';

export async function loader({ request }: LoaderFunctionArgs) {
    const cookie = request.headers.get('Cookie') ?? '';
    const authToken = cookie.match(/vendure-auth-token=([^;]+)/)?.[1];
    return { authToken };
}

export default function App() {
    const { authToken } = useLoaderData<typeof loader>();
    const navigate = useNavigate();

    return (
        <html>
            <body>
                <Outlet />
                <VendureAiChat
                    vendureUrl="https://my-vendure.com"
                    authToken={authToken}
                    onNavigate={(path) => navigate(path)}
                />
            </body>
        </html>
    );
}

Plain Vite / CRA (client-side only)

If you don't have a server component layer, read the token from wherever you store it (localStorage, cookie, auth context):

import { VendureAiChat } from '@rahul_vendure/ai-chat-react';

function App() {
    // Read from wherever your app stores the Vendure session token
    const authToken = localStorage.getItem('vendure-auth-token') ?? undefined;

    return (
        <div>
            <h1>My Store</h1>
            <VendureAiChat vendureUrl="https://my-vendure.com" authToken={authToken} />
        </div>
    );
}

Inline Mode

Embed the chat panel inside your layout (no floating button):

<div style={{ height: '600px', width: '400px' }}>
    <VendureAiChat vendureUrl="https://my-vendure.com" position="inline" />
</div>

Exports

// Main component
import { VendureAiChat } from '@rahul_vendure/ai-chat-react';

// Headless hook
import { useVendureChat } from '@rahul_vendure/ai-chat-react';

// Sub-components (for advanced customization)
import { ChatMessage, ProductCard, CollectionBadge, CartSummary } from '@rahul_vendure/ai-chat-react';

// Utilities
import { extractDataFromMessage, getMessageText, cn } from '@rahul_vendure/ai-chat-react';

// Types
import type { ChatProduct, ChatCollection, ChatActiveOrder, ExtractedData } from '@rahul_vendure/ai-chat-react';

CSS Setup

The package ships a pre-compiled CSS file with all the styles the component needs. Import it once in your app:

import '@rahul_vendure/ai-chat-react/styles.css';

That's it — no Tailwind @source directives or content config needed.

The CSS uses CSS variables for theming (see Theming), so it inherits your existing theme if you use shadcn/ui. It does not include a CSS reset/preflight, so it won't conflict with your existing styles.

Troubleshooting: If the chat button appears inline in your navbar instead of floating in the corner, make sure you've imported the CSS file.

CORS

The component connects directly to your Vendure server (no proxy needed). Make sure CORS is configured on the backend:

// vendure-config.ts
export const config: VendureConfig = {
    apiOptions: {
        cors: {
            origin: ['https://my-storefront.com'],
        },
    },
};

License

AGPL-3.0 — see LICENSE for details.