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

@perspective-ai/sdk-react

v1.8.0

Published

React components for Perspective AI embed SDK

Readme

@perspective-ai/sdk-react

React hooks and components for Perspective AI — AI-powered conversation agents.

Not using React? Use @perspective-ai/sdk for vanilla JavaScript.

Installation

npm install @perspective-ai/sdk-react

Peer Dependencies: React 18+ or 19+

Quick Start

import { usePopup } from "@perspective-ai/sdk-react";

function App() {
  const { open } = usePopup({
    researchId: "your-research-id",
    onSubmit: () => console.log("Conversation completed!"),
  });

  return <button onClick={open}>Get Started</button>;
}

API Overview

| API | Type | Description | | ---------------- | --------- | -------------------------------------- | | usePopup | Hook | Open popup modal programmatically | | useSlider | Hook | Open slider panel programmatically | | useAutoOpen | Hook | Auto-trigger popup (timeout/exit) | | useFloatBubble | Hook | Floating chat bubble lifecycle | | Widget | Component | Inline embed in a container | | Fullpage | Component | Full viewport takeover | | FloatBubble | Component | Convenience wrapper for useFloatBubble |

Mental Model:

  • Hooks for overlays (popup, slider, float bubble) - you control the trigger
  • Components for embeds (widget, fullpage) - render inline DOM

Hooks

usePopup

Open a popup modal with your own trigger element.

import { usePopup } from "@perspective-ai/sdk-react";

function App() {
  const { open, close, isOpen } = usePopup({
    researchId: "your-research-id",
    onSubmit: () => console.log("Done!"),
  });

  return (
    <>
      <button onClick={open}>Start Conversation</button>
      {isOpen && <span>Survey is open</span>}
    </>
  );
}

Programmatic trigger:

const { open } = usePopup({ researchId: "xxx" });

useEffect(() => {
  if (userCompletedCheckout) {
    open();
  }
}, [userCompletedCheckout, open]);

Controlled mode:

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

const popup = usePopup({
  researchId: "xxx",
  open: isOpen,
  onOpenChange: setIsOpen,
});

// External control
<button onClick={() => setIsOpen(true)}>Open from anywhere</button>;

useSlider

Open a slider panel with your own trigger element.

import { useSlider } from "@perspective-ai/sdk-react";

function App() {
  const { open, close, isOpen } = useSlider({
    researchId: "your-research-id",
  });

  return <button onClick={open}>Open Panel</button>;
}

useAutoOpen

Auto-trigger a popup based on a timeout or exit intent — no user click needed.

import { useAutoOpen } from "@perspective-ai/sdk-react";

function FeedbackTrigger() {
  const { cancel, triggered } = useAutoOpen({
    researchId: "your-research-id",
    trigger: { type: "timeout", delay: 5000 },
    showOnce: "session",
    onSubmit: () => console.log("Completed!"),
  });

  // Renders nothing — popup opens automatically after 5s
  return null;
}

Exit intent:

useAutoOpen({
  researchId: "your-research-id",
  trigger: { type: "exit-intent" },
  showOnce: "visitor",
});

Options:

| Option | Type | Default | Description | | ------------ | --------------- | ----------- | ------------------------------------------------------------------------ | | researchId | string | — | Research ID (required) | | trigger | TriggerConfig | — | { type: "timeout", delay: ms } or { type: "exit-intent" } (required) | | showOnce | ShowOnce | "session" | "session", "visitor", or false |

Plus all standard EmbedConfig options (theme, params, brand, callbacks).

Returns:

| Property | Type | Description | | ----------- | ------------ | ----------------------------- | | cancel | () => void | Cancel the pending trigger | | triggered | boolean | Whether the trigger has fired |

useFloatBubble

Manage a floating chat bubble lifecycle.

import { useFloatBubble } from "@perspective-ai/sdk-react";

function App() {
  const { open, close, isOpen } = useFloatBubble({
    researchId: "your-research-id",
  });

  // Bubble mounts on component mount
  // Use open/close for programmatic control
  return null;
}

With launcher customization:

import { HeadphonesIcon } from "lucide-react";

const { open, close } = useFloatBubble({
  researchId: "your-research-id",
  launcher: {
    icon: <HeadphonesIcon className="w-6 h-6" />, // React component as icon
    style: {
      width: "64px",
      height: "64px",
      borderRadius: "12px",
      backgroundColor: "#0ea5e9",
    },
  },
});

React components passed as icon are converted to static SVG via renderToStaticMarkup. See the core SDK docs for all launcher options.

Components

Widget

Inline embed that renders in a container.

import { Widget } from "@perspective-ai/sdk-react";

<Widget
  researchId="your-research-id"
  onSubmit={() => console.log("Done!")}
  className="my-widget"
  style={{ height: 600 }}
/>;

Fullpage

Full viewport takeover embed.

import { Fullpage } from "@perspective-ai/sdk-react";

<Fullpage researchId="your-research-id" />;

FloatBubble

Convenience wrapper around useFloatBubble hook.

import { FloatBubble } from "@perspective-ai/sdk-react";

<FloatBubble researchId="your-research-id" />;

With launcher customization:

<FloatBubble
  researchId="your-research-id"
  launcher={{
    icon: "avatar",
    style: { width: "64px", height: "64px", borderRadius: "12px" },
    className: "my-custom-launcher",
  }}
/>

Hook Options

All hooks accept options from EmbedConfig:

interface UsePopupOptions {
  researchId: string; // The ID of your Perspective agent
  host?: string;
  theme?: "light" | "dark" | "system";
  channel?: "TEXT" | "VOICE" | ["TEXT", "VOICE"]; // Interaction mode
  welcomeMessage?: string; // Teaser text (float only)
  buttonText?: string; // Trigger button text (popup/slider)
  params?: Record<string, string>;
  brand?: {
    light?: {
      primary?: string;
      secondary?: string;
      bg?: string;
      text?: string;
    };
    dark?: { primary?: string; secondary?: string; bg?: string; text?: string };
  };
  disableClose?: boolean; // Prevent closing popup/slider

  // Callbacks
  onReady?: () => void;
  onSubmit?: (data: { researchId: string }) => void;
  onClose?: () => void;
  onNavigate?: (url: string) => void;
  onError?: (error: EmbedError) => void;
  onAuth?: (data: { researchId: string; token: string }) => void;

  // Controlled mode
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
}

useFloatBubble also accepts launcher for button customization — see Launcher Customization.

Hook Return Types

interface UsePopupReturn {
  open: () => void;
  close: () => void;
  toggle: () => void;
  isOpen: boolean;
  handle: EmbedHandle | null;
}

interface UseSliderReturn {
  open: () => void;
  close: () => void;
  toggle: () => void;
  isOpen: boolean;
  handle: EmbedHandle | null;
}

interface UseFloatBubbleReturn {
  open: () => void;
  close: () => void;
  toggle: () => void;
  unmount: () => void;
  isOpen: boolean;
  handle: FloatHandle | null;
}

Other Hooks

useThemeSync

Sync theme between your app and embedded interviews:

import { useThemeSync } from "@perspective-ai/sdk-react";

function App() {
  const [theme, setTheme] = useState<"light" | "dark">("light");

  // Syncs theme changes to all active embeds
  useThemeSync(theme);

  return (
    <button onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}>
      Toggle Theme
    </button>
  );
}

TypeScript

All types are exported:

import type {
  // Hook types
  UsePopupOptions,
  UsePopupReturn,
  UseSliderOptions,
  UseSliderReturn,
  UseFloatBubbleOptions,
  UseFloatBubbleReturn,
  UseAutoOpenOptions,
  UseAutoOpenReturn,
  // Component types
  WidgetProps,
  FloatBubbleProps,
  FullpageProps,
} from "@perspective-ai/sdk-react";

// Re-exported from @perspective-ai/sdk
import type {
  EmbedConfig,
  EmbedHandle,
  FloatHandle,
  BrandColors,
  ThemeValue,
  EmbedError,
} from "@perspective-ai/sdk-react";

SSR Safety

All hooks and components are SSR-safe and include the "use client" directive. Works with Next.js, Remix, and other React frameworks.

License

MIT