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

@basegent/react

v0.2.9

Published

React hooks and components for embedding Basegent support chat

Readme

@basegent/react

React hooks and components for embedding Basegent support chat in any React or Next.js app.

Two integration styles, same package:

  • Headless — use hooks (useChat, useLiveSession, useWidget) with your own UI
  • Drop-in — use pre-built Tailwind v4 + shadcn-pattern components (BasegentChat, BasegentMessages, BasegentInput, BasegentFAB)

Installation

npm install @basegent/react react react-dom
# or
pnpm add @basegent/react react react-dom

Quick Start (Drop-in)

With Tailwind v4 (recommended)

Add the package source to your Tailwind input so its utility classes are included:

/* app/globals.css */
@import "tailwindcss";
@source "../../node_modules/@basegent/react/dist";

Then wrap your layout:

// app/layout.tsx
import { BasegentProvider, BasegentChat } from "@basegent/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <BasegentProvider tenantId="t_your_tenant_id">
          {children}
          <BasegentChat title="Support" placeholder="Ask anything…" />
        </BasegentProvider>
      </body>
    </html>
  );
}

Without Tailwind (pre-built CSS)

// app/layout.tsx
import { BasegentProvider, BasegentChat } from "@basegent/react";
import "@basegent/react/styles.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <BasegentProvider tenantId="t_your_tenant_id">
          {children}
          <BasegentChat />
        </BasegentProvider>
      </body>
    </html>
  );
}

BasegentProvider Props

| Prop | Type | Default | Description | | ---------------------- | ------------------------------------------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | tenantId | string | required | Your Basegent tenant ID | | apiBase | string | "" | Base URL of your Basegent API (omit for same-origin) | | customerToken | string | — | Pre-issued signed identity JWT (optional) | | tokenUrl | string | — | URL of an endpoint returning { token: string }. The provider fetches it on mount and uses the result as customerToken. Ideal for SPAs — point it at any serverless function that signs the JWT. | | escalationKeywords | string[] | — | Words that trigger the “speak to a human?” offer | | showEscalationButton | boolean | — | Show a persistent “Talk to a person” button in the widget header | | persona | object | — | { name, avatar, greeting, tone } overrides | | collectVisitorInfo | "post" \| "pre" \| false \| CollectVisitorInfoConfig | — | Ask anonymous visitors for their name/email. "post" prompts after the first reply; "pre" prompts before the first message. Use the config object for field and email verification options. |


Client-only / SPA integration

For frameworks without server-side rendering (Vite, CRA, Expo Web, etc.) you cannot use your secret API key in the browser. There are two options:

Option A — tokenUrl prop (recommended)

Deploy a lightweight serverless function that signs the JWT server-side, then point tokenUrl at it. The provider fetches it on mount automatically:

// main.tsx / App.tsx
import { BasegentProvider, BasegentChat } from "@basegent/react";

export default function App() {
  return (
    <BasegentProvider
      tenantId={import.meta.env.VITE_BASEGENT_TENANT_ID}
      apiBase={import.meta.env.VITE_BASEGENT_API_BASE}
      tokenUrl="https://your-worker.example.com/basegent-token" // ← serverless fn
    >
      <YourApp />
      <BasegentChat title="Support" />
    </BasegentProvider>
  );
}

Example Cloudflare Worker (signs the token using your secret API key):

// worker.ts
export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    // CORS — allow your SPA origin
    if (req.method === "OPTIONS") {
      return new Response(null, { headers: corsHeaders(req) });
    }

    // Authenticate the user however your app does it
    // (session cookie, Authorization header, etc.)
    const userId = await getUserId(req, env); // your auth logic
    if (!userId) return new Response("Unauthorized", { status: 401 });

    const res = await fetch(`${env.BASEGENT_API_BASE}/api/v1/auth/customer-token`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${env.BASEGENT_API_KEY}`, // secret — stays in the worker
      },
      body: JSON.stringify({ userId }),
    });
    const { data } = await res.json<{ data: { token: string } }>();
    return Response.json({ token: data.token }, { headers: corsHeaders(req) });
  },
};

Option B — useCustomerToken hook

For headless scenarios where you need more control (loading/error states, refetch on login, etc.):

import { BasegentProvider, BasegentChat, useCustomerToken } from "@basegent/react";

function Root() {
  const { token, loading, error, refetch } = useCustomerToken(
    isLoggedIn ? "/api/basegent-token" : null, // pass null to skip (anonymous)
  );

  return (
    <BasegentProvider tenantId={import.meta.env.VITE_BASEGENT_TENANT_ID} customerToken={token}>
      <App />
      <BasegentChat />
    </BasegentProvider>
  );
}

Returns: { token, loading, error, refetch }

Option C — Anonymous sessions

If you don’t need identity-linked sessions (no conversation history, no customer profile in the inbox), just omit customerToken and tokenUrl entirely. The widget works fully anonymously:

<BasegentProvider tenantId="t_xxx" apiBase="https://...">
  <BasegentChat />
</BasegentProvider>

Headless Hooks

useChat()

Core AI chat + escalation state.

import { useChat } from "@basegent/react";

function MyChat() {
  const { turns, loading, streaming, isEscalated, send, escalate, react, reset } = useChat();

  return (
    <div>
      {turns.map((t, i) => (
        <div key={i} style={{ textAlign: t.role === "user" ? "right" : "left" }}>
          {t.content}
        </div>
      ))}
      {loading && <p>Thinking…</p>}
      <button onClick={() => send("Hello!")}>Send</button>
      {!isEscalated && <button onClick={() => escalate()}>Talk to a human</button>}
    </div>
  );
}

Returns:

| Value | Type | Description | | ------------- | ------------------------------------------------------------------------ | -------------------------------------------------- | | turns | Turn[] | All conversation turns | | loading | boolean | True while AI response is in flight | | streaming | string | Partial token stream during SSE | | sessionId | string \| null | Current session ID (null until first message) | | isEscalated | boolean | True once handed over to a human agent | | send | (message: string) => Promise<void> | Send a message — auto-routes to live agent if live | | escalate | (reason?: string) => Promise<void> | Manually trigger human handover | | react | (turnIndex: number, reaction: "helpful"\|"unhelpful") => Promise<void> | Submit a thumbs reaction | | reset | () => void | Clear all turns and start fresh |


useLiveSession()

Live agent message sync (polls every 3 s when escalated).

import { useLiveSession } from "@basegent/react";

function LiveIndicator() {
  const { isLive, agentMessages, sendMessage } = useLiveSession();

  if (!isLive) return null;

  return (
    <div>
      <p>Connected to a live agent</p>
      {agentMessages.map((m) => (
        <p key={m.id}>{m.content}</p>
      ))}
      <button onClick={() => sendMessage("Can you help?")}>Reply</button>
    </div>
  );
}

Returns:

| Value | Type | Description | | --------------- | ------------------------------------ | --------------------------------------------- | | isLive | boolean | True when session is under live agent control | | escalationId | string \| null | Active escalation ID | | liveMessages | LiveMessage[] | All live messages (both directions) | | agentMessages | LiveMessage[] | Filtered to agent-sent messages only | | sendMessage | (content: string) => Promise<void> | Send a customer message to the live agent |


useWidget()

Open/close state for the chat panel.

import { useWidget } from "@basegent/react";

function MyFAB() {
  const { isOpen, toggle } = useWidget();
  return <button onClick={toggle}>{isOpen ? "Close" : "Chat"}</button>;
}

Returns: { isOpen, open, close, toggle }


useVisitorIdentity()

Headless hook for visitor self-identification. Reads/writes visitor profile from localStorage and the Basegent API. Only meaningful when collectVisitorInfo is set on BasegentProvider.

import { useVisitorIdentity } from "@basegent/react";

function IdentityForm() {
  const {
    visitorProfile,
    showIdentityForm,
    identityFields,
    identityDismissed,
    verifyPending,
    verifyEmailEnabled,
    identify,
    dismiss,
    reopen,
    clear,
    resendVerification,
  } = useVisitorIdentity();

  if (!showIdentityForm) {
    if (visitorProfile) {
      // Already identified — show "Chatting as…" chip
      return (
        <div>
          Chatting as {visitorProfile.name ?? visitorProfile.email}{" "}
          <button onClick={clear}>Not you?</button>
        </div>
      );
    }
    if (identityDismissed) {
      // Show a minimal re-open link
      return <button onClick={reopen}>Add contact info</button>;
    }
    return null;
  }

  if (verifyPending) {
    return (
      <div>
        <p>Check your inbox to verify your email.</p>
        <button onClick={resendVerification}>Resend</button>
      </div>
    );
  }

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        const fd = new FormData(e.currentTarget);
        identify({
          name: fd.get("name") as string,
          email: fd.get("email") as string,
        });
      }}
    >
      {identityFields.includes("name") && <input name="name" placeholder="Your name" />}
      {identityFields.includes("email") && (
        <input name="email" type="email" placeholder="Your email" />
      )}
      <button type="submit">Save</button>
      <button type="button" onClick={dismiss}>
        Not now
      </button>
    </form>
  );
}

Returns:

| Value | Type | Description | | -------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------- | | visitorProfile | { profileId: string; name?: string; email?: string } \| null | Persisted profile (from localStorage + API), null if unknown | | identityMode | "post" \| "pre" \| null | Active mode derived from collectVisitorInfo config | | identityFields | ("name" \| "email" \| "phone" \| "company")[] | Fields to show in the identity form (default: ["name","email"]) | | showIdentityForm | boolean | Whether the form should currently be visible | | identityDismissed | boolean | Whether the visitor clicked "Not now" | | verifyPending | boolean | Verification email has been sent, awaiting click | | verifyEmailEnabled | boolean | Whether email verification is enabled on the config | | identify | (fields: Record<string, string>) => Promise<void> | Submit visitor info to the API and persist locally | | dismiss | () => void | Hide the form (can be re-opened with reopen()) | | reopen | () => void | Show the form again after dismissal | | clear | () => void | Clear persisted profile ("Not you?" action) | | resendVerification | () => Promise<void> | Re-send the verification email |


Pre-built Components

All pre-built components accept a className prop for Tailwind overrides.

<BasegentChat />

Full panel — FAB + collapsible chat panel. Renders BasegentFAB, BasegentMessages, and BasegentInput together.

<BasegentChat
  title="Support" // panel header text (default: "Support")
  placeholder="Ask…" // input placeholder
  className="" // extra classes on the panel wrapper
  fabClassName="" // extra classes on the FAB button
/>

<BasegentMessages />

Scrollable message list. Merges AI turns with live agent turns, shows a typing indicator while streaming.

<BasegentMessages className="h-96" />

<BasegentInput />

Rounded-pill text input + Send button. Shows "Reply to agent…" placeholder when session is escalated.

<BasegentInput placeholder="Type a message…" className="" />

<BasegentFAB />

Fixed floating action button, bottom-right.

<BasegentFAB className="bottom-8 right-8" />

Custom UI with Headless Hooks

import { BasegentProvider, useChat, useLiveSession, useWidget } from "@basegent/react";

function MyCustomChat() {
  const { turns, send, isEscalated, loading } = useChat();
  const { isLive, agentMessages } = useLiveSession();
  const { isOpen, toggle } = useWidget();

  return (
    <div>
      <button onClick={toggle}>💬</button>
      {isOpen && (
        <div className="my-chat-panel">
          {turns.map((t, i) => (
            <div key={i} className={t.role === "user" ? "user-bubble" : "ai-bubble"}>
              {t.content}
            </div>
          ))}
          {isLive &&
            agentMessages.map((m) => (
              <div key={m.id} className="agent-bubble">
                {m.content}
              </div>
            ))}
          {loading && <span>…</span>}
          <input
            onKeyDown={(e) => e.key === "Enter" && send(e.currentTarget.value)}
            placeholder={isEscalated ? "Reply to agent…" : "Ask anything…"}
          />
        </div>
      )}
    </div>
  );
}

export default function App() {
  return (
    <BasegentProvider tenantId="t_xxx" apiBase="https://your-basegent.example.com">
      <MyCustomChat />
    </BasegentProvider>
  );
}

Styling

The package ships with design tokens as CSS custom properties so you can theme the pre-built components without forking them:

/* Override in your global CSS */
:root {
  --basegent-primary: #6366f1; /* FAB + user bubble + send button */
  --basegent-primary-foreground: #fff;
  --basegent-background: #fff;
  --basegent-foreground: #0f172a;
  --basegent-border: #e2e8f0;
  --basegent-muted: #f1f5f9;
  --basegent-muted-foreground: #64748b;
  --basegent-radius: 0.75rem;
}

Exports

// Provider + context
export { BasegentProvider } from "./provider";

// Hooks
export { useChat } from "./hooks/useChat";
export { useLiveSession } from "./hooks/useLiveSession";
export { useWidget } from "./hooks/useWidget";
export { useCustomerToken } from "./hooks/useCustomerToken";
export { useVisitorIdentity } from "./hooks/useVisitorIdentity";

// Pre-built components
export { BasegentChat, BasegentMessages, BasegentInput, BasegentFAB } from "./components";

// Types
export type {
  BasegentConfig,
  Turn,
  LiveMessage,
  Source,
  BasegentContextValue,
  UseCustomerTokenResult,
  UseVisitorIdentityReturn,
  CollectVisitorInfoConfig,
  VisitorProfileResult,
} from "./types";

CSS: import "@basegent/react/styles.css" (only needed without Tailwind v4 in the host app)


Build

# JS + types
pnpm build:js

# Pre-built CSS
pnpm build:css

# Both
pnpm build

Output in dist/: index.js (ESM), index.cjs (CJS), index.d.ts, styles.css.