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

@papermap/papermap

v1.1.2

Published

Embeddable AI chat bar and UI components from the Papermap data analytics platform

Downloads

297

Readme

Papermap

Embeddable AI-powered components from the Papermap data analytics platform. This is a standalone package — independent from the main Papermap app. Services, SSE streaming, and UI are self-contained; the repos do not depend on each other.

Backend setup

For server-side setup (HMAC auth, dashboards per tenant, embed tokens, API endpoints), see the Backend implementation guide in the Papermap docs.

Main components

  • PaperChat — Full AI chat assistant with streaming, conversation history, and chart generation. Supports four layouts: default (floating toolbar + modal assistant), side (fixed side panel for sheet-style pages), floating (chart preview overlay), and dialog (tawk.to-style floating launcher + popup chat widget).
  • PaperCard — Standalone chart card with toolbar actions. Use variant="streaming" for an embedded chart + conversation dialog (without the floating assistant).
  • PaperInsight — "Quick Insights" panel: streams up to 3 AI-generated insights (highlight / trends / recommendation) for a dashboard + date range.
  • PaperBoard — Responsive grid layout of chart cards, toolbar (screenshot, generate dashboard, theme), optional chat assistant + PaperInsight tile, and controlled or uncontrolled layouts.
import { PaperChat, PaperCard, PaperInsight, PaperBoard } from '@papermap/papermap'

// Default: floating toolbar + expandable assistant (dashboard-style)
<PaperChat
  token="your-base64-api-token"
  workspaceId="your-workspace-id"
  dashboardId="your-dashboard-id"
/>

// Side panel (controlled open state; typical for sheet / narrow layouts)
<PaperChat
  variant="side"
  token="..."
  workspaceId="..."
  dashboardId="..."
  open={sideOpen}
  onOpenChange={setSideOpen}
/>

// Floating chart overlay (preview over your content)
<PaperChat
  variant="floating"
  token="..."
  workspaceId="..."
  dashboardId="..."
  open={overlayOpen}
  onOpenChange={setOverlayOpen}
/>

// Dialog (live-chat widget: floating launcher + popup; outside-click is ignored,
// closes only via the X button or by clicking the launcher again)
<PaperChat
  variant="dialog"
  token="..."
  workspaceId="..."
  dashboardId="..."
  title="Support"
  subtitle="Ask anything about your data"
  launcherPosition="bottom-right"
/>

// Standalone chart card
<PaperCard
  token="your-base64-api-token"
  workspaceId="your-workspace-id"
  dashboardId="your-dashboard-id"
  chartId="your-chart-id"
/>

// Streaming chart card (embedded dialog + chat)
<PaperCard
  token="your-base64-api-token"
  workspaceId="your-workspace-id"
  dashboardId="your-dashboard-id"
  variant="streaming"
  chartId="your-chart-id" // optional; card shows empty state if omitted
/>

// Standalone Quick Insights panel (streams 3 AI insights for the period)
<PaperInsight
  token="your-base64-api-token"
  workspaceId="your-workspace-id"
  dashboardId="your-dashboard-id"
  startDate="2026-04-25"
  endDate="2026-05-25"
/>

// Full grid dashboard (wraps chart cards + optional PaperChat + insight tile)
<PaperBoard
  token="..."
  workspaceId="..."
  dashboardId="..."
  showChatAssistant
  showInsights
/>

Each top-level component handles API calls, authentication, and UI internally unless you compose lower-level exports yourself.


Quick start

1. Install

npm install @papermap/papermap
# or
pnpm add @papermap/papermap

Requirements: React 18+ and React DOM 18+ (peer dependencies).

2. Import styles once

import '@papermap/papermap/styles.css'

3. Choose an integration pattern

A) Recommended: provider at app root

PapermapConfigProvider is an alias for PapermapProvider — same component, useful for naming consistency in app code.

import { PapermapConfigProvider, PaperChat, PaperCard } from '@papermap/papermap'

function App() {
  return (
    <PapermapConfigProvider
      token="your-base64-api-token"
      workspaceId="your-workspace-id"
      dashboardId="your-dashboard-id"
      // optional: apiUrl="https://dataapi.papermap.ai"
    >
      <PaperChat />
      <PaperCard chartId="existing-chat-id" />
    </PapermapConfigProvider>
  )
}

B) Standalone components (no provider)

Pass token, workspaceId, and dashboardId on each component (or rely on an existing parent PapermapProvider).

import { PaperChat, PaperCard } from '@papermap/papermap'

function App() {
  return (
    <>
      <PaperChat
        token="your-base64-api-token"
        workspaceId="your-workspace-id"
        dashboardId="your-dashboard-id"
      />

      <PaperCard
        token="your-base64-api-token"
        workspaceId="your-workspace-id"
        dashboardId="your-dashboard-id"
        chartId="existing-chat-id"
      />
    </>
  )
}

4. Props

PaperChat props

| Prop | Type | Required | Default | Description | | --------------------------- | -------------------------------------------------------------- | ----------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | variant | 'default' \| 'side' \| 'floating' \| 'dialog' | No | 'default' | Layout: floating dashboard chat, fixed side panel, chart overlay, or popup widget. | | token | string | Yes* | -- | Base64-encoded API key token | | workspaceId | string | Yes* | -- | Workspace ID | | dashboardId | string | Yes* | -- | Dashboard ID | | apiUrl | string | No | https://dataapi.papermap.ai | API base URL | | placeholder | string | No | "Ask anything..." | Input placeholder (default variant) | | shortcutKey | string | No | "k" | Focus shortcut (Cmd/Ctrl + key, default variant) | | autoFade | boolean | No | false | Fade toolbar after inactivity (default variant) | | fadeDelay | number | No | 5000 | Milliseconds before auto-fade | | className | string | No | -- | Extra CSS class on toolbar container (default variant) | | showToolbar | boolean | No | true | Show the bottom chat toolbar (default variant) | | hideChartArea | boolean | No | false | Hides chart preview regions where applicable | | onToolbarHeightChange | (height: number) => void | No | -- | Fired when toolbar height changes (e.g. multiline input) | | onAssistantClose | () => void | No | -- | Fired when the floating assistant panel closes (default variant) | | initialChatScroll | 'top' \| 'bottom' | No | -- | Conversation scroll anchoring; top matches main-app onboarding | | isViewer | boolean | No | -- | Hides bookmark actions (viewer workspaces); use provider when nested | | showAvatars | boolean | No | see below | Hide message-row avatars when false. When omitted on variant="default", ChatAssistant shows avatars whenever the layout is not inline (assistantInline in the store, default false). variant="side" and variant="dialog" default to showing avatars (true). | | avatars | PaperChatAvatars | No | -- | Replace the user / assistant / “thinking” icons in each message row (see Chat assistant customization). | | assistantInline | boolean | No | -- | Inline assistant layout; for variant="side" defaults to true | | open | boolean | Yes† / Yes‡ | -- | Surface open: side rail, floating overlay, dialog modal (see onOpenChange) | | onOpenChange | (open: boolean) => void | Yes† / Yes‡ | -- | Toggle for side, floating, and dialog | | onChartVisibilityChange | (visible: boolean) => void | No | -- | Sheet: chart overlay visibility | | chartOverlayPortalRef | RefObject<HTMLDivElement \| null> | No | -- | Optional portal target for chart overlay | | onPendingSheetEditsChange | (edits: { edit_session_id?: string }[]) => void | No | -- | Sheet: pending edit sessions | | clearPendingSheetEditsRef | MutableRefObject<(() => void) \| null> | No | -- | Ref to clear pending edits | | visible | boolean | No | -- | Deprecated. Use open for variant="floating". | | onClose | () => void | No | -- | Deprecated. Use onOpenChange(false) for variant="floating"; still called after onOpenChange on close if both are passed. | | historyOpen | boolean | No | -- | Show chart history in overlay instead of chat column | | onCloseHistory | () => void | No | -- | Close history sub-view in overlay | | title | string | No | "Chat" | Dialog header title (variant="dialog") | | subtitle | string | No | ask-anything hint | Dialog header subtitle (variant="dialog") | | showLauncher | boolean | No | true | Render the floating launcher (variant="dialog") | | launcherAriaLabel | string | No | "Open chat" | Accessible label for the launcher (variant="dialog") | | launcherPosition | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | No | 'bottom-right' | Viewport corner for the launcher (variant="dialog") | | dialogTitle | string | No | -- | Deprecated. Use title. | | dialogSubtitle | string | No | -- | Deprecated. Use subtitle. | | dialogOpen | boolean | No | -- | Deprecated. Use open for controlled dialog. | | onDialogOpenChange | (open: boolean) => void | No | -- | Deprecated. Use onOpenChange. | | showDialogLauncher | boolean | No | -- | Deprecated. Use showLauncher. | | dialogLauncherAriaLabel | string | No | -- | Deprecated. Use launcherAriaLabel. | | dialogLauncherPosition | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | No | -- | Deprecated. Use launcherPosition. |

*Omit on the component when values come from PapermapConfigProvider / PapermapProvider.

†Required when variant is "side". ‡For variant="floating", pass open + onOpenChange (or the deprecated visible + onClose).

Chat assistant customization

The transcript UI is implemented by ChatAssistant (also exported for custom layouts). PaperChat forwards branding-related props on every variant except floating (that variant is chart-first and does not expose these).

Avatars (PaperChatAvatars)

Import the type and pass an avatars object on PaperChat, PaperChatSidePanel / ChatSidePanel, or PaperChatDialogInner:

import { PaperChat, type PaperChatAvatars } from '@papermap/papermap'

const avatars: PaperChatAvatars = {
  user: <span className="text-xs font-bold text-primary-foreground">ME</span>,
  assistant: <img src="/your-product-mark.svg" alt="" className="h-4 w-4" />,
  thinking: <img src="/your-spinner-or-mark.svg" alt="" className="h-4 w-4" />,
}

<PaperChat
  token="..."
  workspaceId="..."
  dashboardId="..."
  avatars={avatars}
/>

Each slot replaces only the inner icon. The outer 32px circular affordance (background colors, alignment) stays the same—size custom icons for parity with the defaults.

Showing or hiding avatars

Set showAvatars={false} for a denser transcript without avatar columns.

When showAvatars is omitted on variant="default", visibility is handled inside ChatAssistant: avatars show unless the assistant is in inline column mode (assistantInline in the store). With variant="side" or variant="dialog", omitting showAvatars defaults to true.

If you compose ChatAssistant yourself with inline (compact column layout), avatars are hidden by default unless you pass showAvatars={true}—matching the main app’s compact sidebar behavior.

Dialog variant: floating launcher “button”

For variant="dialog", the launcher is a fixed circular control portaled to document.body (MessageSquare icon). You can:

  • Move it with launcherPosition (bottom-right | bottom-left | top-right | top-left).
  • Change the accessible name with launcherAriaLabel (also used as the native title tooltip on the launcher).
  • Hide it with showLauncher={false} and drive the popup yourself via open / onOpenChange from your own button elsewhere in the host UI.

Legacy names (dialogLauncherPosition, dialogLauncherAriaLabel, showDialogLauncher, dialogOpen, onDialogOpenChange, dialogTitle, dialogSubtitle) still work; open, title, and the other canonical props take precedence when both are set.

Side variant: custom open trigger

When variant="side" and the panel is closed, pass renderSidePanelOpenTrigger to render your own control. It receives { open, show, toggle } so you can wire a menu item, FAB, or icon button while open / onOpenChange remain the source of truth.

<PaperChat
  variant="side"
  token="..."
  workspaceId="..."
  dashboardId="..."
  open={open}
  onOpenChange={setOpen}
  renderSidePanelOpenTrigger={({ toggle }) => (
    <button type="button" className="your-menu-styles" onClick={toggle}>
      Open assistant
    </button>
  )}
/>
Advanced composition

Use PapermapProvider (or PapermapConfigProvider) and render ChatAssistant with avatars, showAvatars, inline, and hideChartArea as needed—the same props PaperChat uses internally.

Prefer PaperChat with variant for a single integration path. If you already wrap subtrees in PapermapProvider, you can compose PaperChatSidePanel or PaperChatFloatingChartOverlay directly (they reuse the parent store when present, or create a provider when not).

PaperCard props

| Prop | Type | Required | Default | Description | | -------------- | -------------------------------------------- | -------- | ----------------------------- | ------------------------------------------------ | | token | string | Yes* | -- | Base64-encoded API key token | | workspaceId | string | Yes* | -- | Workspace ID | | dashboardId | string | Yes* | -- | Dashboard ID | | apiUrl | string | No | https://dataapi.papermap.ai | API base URL | | chartId | string | No | -- | Backend chat id to load the latest chart for | | chart | TChartResponse | No | -- | Pre-loaded chart data (skips API fetch) | | theme | 'light' \| 'dark' | No | -- | Force light or dark theme | | onEditClick | (chartId: string) => void | No | -- | Called when the edit button is clicked | | onDelete | (chartId: string) => void | No | -- | Called when chart deletion is confirmed | | onPinChange | (chartId: string, pinned: boolean) => void | No | -- | Called when pin state changes | | wide | boolean | No | false | Wide mode for table charts | | hideVariants | boolean | No | true | Hide the chart variation selector | | showToolbar | boolean | No | true | Show toolbar with maximize/edit/delete buttons | | className | string | No | -- | Extra CSS class on the card container | | variant | 'default' \| 'streaming' | No | 'default' | 'streaming': edit opens embedded dialog + chat |

Pre-loaded chart:

import { PaperCard } from '@papermap/papermap'
import type { TChartResponse } from '@papermap/papermap'

const chart: TChartResponse = {
  llm_data_chat_id: 'my-chart-id',
  name: 'Revenue',
  meta: { title: 'Revenue by Quarter', variant: 'default' },
  pin: false,
  chart_response: {
    chart_type: 'bar',
    data: [
      { quarter: 'Q1', revenue: 42000 },
      { quarter: 'Q2', revenue: 55000 },
    ],
    response_type: 'chart',
    schema_hints: { x_key: 'quarter', y_key: 'revenue', label_key: 'quarter' },
    visualization_config: { colors: ['#3b82f6'], width: 600, height: 400 },
  },
}

<PaperCard
  token="..."
  workspaceId="..."
  dashboardId="..."
  chart={chart}
  onEditClick={(id) => console.log('Edit', id)}
  onDelete={(id) => console.log('Delete', id)}
/>

PaperInsight props

| Prop | Type | Required | Default | Description | | ------------------------- | -------------------------------------------------- | -------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | token | string | Yes* | -- | Base64-encoded API key token | | workspaceId | string | Yes* | -- | Workspace ID | | dashboardId | string | Yes* | -- | Dashboard ID | | apiUrl | string | No | https://dataapi.papermap.ai | API base URL | | startDate | Date \| string (yyyy-MM-dd) | Yes | -- | Inclusive start of the insight window | | endDate | Date \| string (yyyy-MM-dd) | Yes | -- | Inclusive end of the insight window | | theme | 'light' \| 'dark' \| 'system' | No | -- | Scoped color scheme (independent of host page) | | dashboardTheme | DashboardTheme \| null | No | -- | Themed mark color + module surface (matches main app workspace theme) | | isEditMode | boolean | No | false | Disable interactions while a parent grid is in layout-edit mode | | periodPreset | InsightsPeriodPreset | No | -- | Show the period dropdown (last_3_months \| this_week \| this_month \| this_year \| custom). Hide by omitting. | | onPeriodPresetChange | (preset: InsightsPeriodPreset) => void | No | -- | Fired when the user picks a preset | | onRangeChange | (start: Date, end: Date) => void | No | -- | Required for the inline custom-range scrubber to function — the host owns the dates and updates them when this fires | | showCustomRangeScrubber | boolean | No | true | Render the inline TimelineRange scrubber when periodPreset === 'custom'. Set false when a parent (e.g. PaperBoard) renders its own. | | onExplore | (chatId: string) => void | No | (opens chat assistant) | Per-row "Explore in Chat" arrow. Defaults to calling openPapermapChatAssistant when a store is mounted. | | onInsight | (insight: DashboardInsight) => void | No | -- | Fired once per insight SSE event | | onComplete | (result: DashboardInsightsResult) => void | No | -- | Fired when the server emits insights_complete | | onError | (error: Error) => void | No | -- | Fired on transport / error SSE event | | className | string | No | -- | Root element class | | style | CSSProperties | No | -- | Root element inline style | | classNames | Partial<Record<PaperInsightSlot, string>> | No | -- | Per-slot class overrides (header, title, dateLabel, refreshButton, row, body, footer, …) | | styles | Partial<Record<PaperInsightSlot, CSSProperties>> | No | -- | Per-slot inline style overrides |

*Omit on the component when values come from PapermapProvider.

The SSE call is POST /api/v1/analytics/dashboards/:dashboardId/insights/stream with body { start_date, end_date }. The component renders the panel's chrome (Activity icon, "Quick Insights" title, animated TickSpinner + cycling "Thinking… / Crunching… / Cooking…", optional period dropdown, Refresh button) and up to 3 streamed insights with metric chips and an "Explore in Chat" arrow.

PaperBoard props (high level)

| Prop | Notes | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | token, workspaceId, dashboardId, apiUrl | Same as other components; can come from provider. | | charts | Optional pre-loaded charts; otherwise fetched when enableFetch is true (default). | | layouts / onLayoutsChange | Controlled grid layouts per breakpoint. | | isEditMode, editLayout, isViewer | Edit vs view behavior. | | showToolbar, showScreenshot, showGenerateDashboard, showHeader, showChatAssistant | Feature toggles. | | showInsights | Mount the PaperInsight tile at the top of the grid (mirrors main app TimelineInsights). | | insightsPeriodPreset, onInsightsPeriodPresetChange | Controlled period for the insight tile. Defaults internally to 'last_3_months'. | | insightsStartDate, insightsEndDate | Controlled range overrides (use with 'custom' to drive the dashboard TimelineRange scrubber from the host). | | onInsightExplore | Per-row "Explore in Chat" arrow. Defaults to opening the floating chat assistant when one is mounted. | | variant | 'default' \| 'streaming' for embedded chart cards (matches PaperCard). | | dashboardTheme, onDashboardThemeChange, persistWorkspaceTheme, renderThemeModal | Theming; built-in modal uses ThemeCustomizationSettings. | | Callbacks | onEditChart, onDeleteChart, onPinChange, onGenerateDashboard, onTakeScreenshot, onThemeModalOpen, etc. |

See TypeScript types PaperBoardProps and Storybook stories for full detail.

Streaming chart card variant

With variant="streaming", the edit action opens an embedded expanded view with chart + conversation, SSE streaming, save-to-dashboard and maximize actions, and audit log on assistant messages.

import { PaperCard } from '@papermap/papermap'

export function EmbeddedStreamingChart() {
  return (
    <div className="h-[420px] w-[520px]">
      <PaperCard
        token="..."
        workspaceId="..."
        dashboardId="..."
        variant="streaming"
        chartId="existing-chat-id"
      />
    </div>
  )
}

Supported chart types: bar, line, area, pie, radar, scatter, table, tile — aligned with the main Papermap app.


Examples

Every example below has a matching Storybook story — run npm run storybook (http://localhost:3001) to see it live. Replace the '...' placeholders with your own token / workspaceId / dashboardId, or supply them once via PapermapConfigProvider / PapermapProvider. Controlled examples use React useState.

PaperChat

Auto-fading toolbar

Fade the floating toolbar after a period of inactivity.

<PaperChat token="..." workspaceId="..." dashboardId="..." autoFade fadeDelay={3000} />

Custom placeholder and focus shortcut

<PaperChat
  token="..."
  workspaceId="..."
  dashboardId="..."
  placeholder="Ask about your data..."
  shortcutKey="j"
/>

Side panel (controlled)

Fixed-width rail for sheet-style pages. open / onOpenChange are required.

function SidePanelExample() {
  const [open, setOpen] = useState(true)
  return (
    <div className="flex h-screen min-h-0">
      <PaperChat
        variant="side"
        token="..."
        workspaceId="..."
        dashboardId="..."
        sidePosition="left"
        sideContentInsetMode="auto"
        open={open}
        onOpenChange={setOpen}
        className="h-full min-h-0 shrink-0"
      />
      <main className="min-h-0 min-w-0 flex-1 overflow-auto">Your page</main>
    </div>
  )
}

sideContentInsetMode="auto" places the panel in a flex row beside your main column; "none" keeps it viewport-fixed and overlays content. sidePosition docks it left or right.

Floating chart overlay

function FloatingExample() {
  const [open, setOpen] = useState(true)
  return (
    <PaperChat
      variant="floating"
      token="..."
      workspaceId="..."
      dashboardId="..."
      open={open}
      onOpenChange={setOpen}
    />
  )
}

Dialog widget (floating launcher)

<PaperChat
  variant="dialog"
  token="..."
  workspaceId="..."
  dashboardId="..."
  title="Ask Alan"
  subtitle="Continue your conversation about this report"
  launcherAriaLabel="Open chat"
  launcherPosition="bottom-right"
/>

Dialog widget (controlled, no launcher)

Hide the built-in launcher and drive the popup from your own button.

function ControlledDialog() {
  const [open, setOpen] = useState(false)
  return (
    <>
      <button type="button" onClick={() => setOpen(true)}>
        Open chat
      </button>
      <PaperChat
        variant="dialog"
        token="..."
        workspaceId="..."
        dashboardId="..."
        title="Support"
        open={open}
        onOpenChange={setOpen}
        showLauncher={false}
      />
    </>
  )
}

PaperCard

These examples pass a preloaded chart (a TChartResponse) — see Pre-loaded chart above for the shape.

Streaming card with a preloaded chart

variant="streaming" with chart data skips the API round-trip.

<PaperCard token="..." workspaceId="..." dashboardId="..." variant="streaming" chart={chart} />

Standalone streaming card (no chart yet)

Pass a stable id and no chartId — the card shows an empty state and does not call the chart API until a link exists.

<PaperCard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  id="my-card-1"
  onSave={(chart) => console.log('Saved', chart)}
/>

Wide table card

<PaperCard token="..." workspaceId="..." dashboardId="..." chart={tableChart} wide />

Showing the variant selector

The chart variation combobox is hidden by default (hideVariants is true).

<PaperCard token="..." workspaceId="..." dashboardId="..." chart={chart} hideVariants={false} />

Scoped dashboard theme

<PaperCard
  token="..."
  workspaceId="..."
  dashboardId="..."
  chart={chart}
  dashboardTheme={{
    primary: '#0f766e',
    secondary: '#64748b',
    interactive: '#14b8a6',
    container: '#f0fdfa',
    module: '#ffffff',
    accent: '#ccfbf1',
    outline: '#99f6e4',
    dialog: '#ffffff',
    fontFamily: 'Inter',
    borderRadius: 0.75,
  }}
/>

Streaming lifecycle hooks

Refetch host state after a chart is pinned or the embedded assistant closes.

<PaperCard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  chart={chart}
  onPinnedToDashboard={(chart) => console.log('Pinned', chart)}
  onAssistantClosed={() => console.log('Assistant closed')}
/>

PaperInsight

Minimal — fixed date range

<PaperInsight
  token="..."
  workspaceId="..."
  dashboardId="..."
  startDate="2026-04-25"
  endDate="2026-05-25"
/>

With period dropdown + custom-range scrubber

When periodPreset === 'custom' the inline TimelineRange scrubber renders between the header and the insight rows; dragging it fires onRangeChange.

import {
  PaperInsight,
  computeInsightsPeriodRange,
  type InsightsPeriodPreset,
  type InsightsPeriodMenuPreset,
} from '@papermap/papermap'

function MyInsights() {
  const [preset, setPreset] = useState<InsightsPeriodPreset>('last_3_months')
  const [range, setRange] = useState(() => computeInsightsPeriodRange('last_3_months'))

  return (
    <PaperInsight
      token="..."
      workspaceId="..."
      dashboardId="..."
      startDate={range.start}
      endDate={range.end}
      periodPreset={preset}
      onPeriodPresetChange={(next) => {
        setPreset(next)
        if (next !== 'custom') {
          setRange(computeInsightsPeriodRange(next as InsightsPeriodMenuPreset))
        }
      }}
      onRangeChange={(start, end) => {
        setRange({ start, end })
        setPreset('custom')
      }}
    />
  )
}

Wire "Explore in Chat" to the floating assistant

PaperInsight defaults onExplore to openPapermapChatAssistant(storeApi, { editChartId: chatId }) whenever a PapermapProvider is mounted. Override to route somewhere else (e.g. your own router).

<PaperInsight
  token="..."
  workspaceId="..."
  dashboardId="..."
  startDate="2026-04-25"
  endDate="2026-05-25"
  onExplore={(chatId) => router.push(`/dashboard?conversation_id=${chatId}`)}
/>

Custom styling via slots

<PaperInsight
  token="..."
  workspaceId="..."
  dashboardId="..."
  startDate="2026-04-25"
  endDate="2026-05-25"
  className="rounded-2xl shadow-xl"
  classNames={{
    title: 'font-serif text-amber-200',
    refreshButton: 'bg-amber-500 text-black hover:bg-amber-400',
    row: 'hover:bg-amber-500/10',
  }}
  styles={{
    body: { paddingInline: 24 },
  }}
/>

Listen to stream events (analytics)

<PaperInsight
  token="..."
  workspaceId="..."
  dashboardId="..."
  startDate="2026-04-25"
  endDate="2026-05-25"
  onInsight={(insight) => track('insight_received', { type: insight.insightType })}
  onComplete={(result) => track('insights_complete', { count: result.insights.length })}
  onError={(err) => Sentry.captureException(err)}
/>

PaperBoard

Default streaming grid

<PaperBoard token="..." workspaceId="..." dashboardId="..." variant="streaming" showToolbar />

With app chrome and chat assistant

showAppChrome adds the dashboard header + tab bar (off by default).

<PaperBoard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  showAppChrome
  showToolbar
  showChatAssistant
/>

Embed mode (no app chrome)

<PaperBoard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  showAppChrome={false}
  showToolbar
/>

Static charts without fetching

enableFetch={false} renders the supplied charts + layouts and skips the API. layouts is keyed per breakpoint (lg / md / sm / xs / xxs).

<PaperBoard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  enableFetch={false}
  charts={charts}
  layouts={layouts}
  showToolbar
  showHeader={false}
/>

Minimal toolbar

<PaperBoard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  showToolbar
  showScreenshot={false}
  showGenerateDashboard={false}
/>

Dashboard tour control

Adds an opt-in tour button to the header (does not auto-start).

<PaperBoard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  showToolbar
  showDashboardTour
  autoStartDashboardTour={false}
/>

With the Quick Insights tile

showInsights mounts the PaperInsight tile at the top of the grid and renders the dashboard-level TimelineRange scrubber between the header and the grid when the user picks Custom range. The insight tile and the scrubber share the same preset / range state internally.

<PaperBoard
  token="..."
  workspaceId="..."
  dashboardId="..."
  variant="streaming"
  showToolbar
  showChatAssistant
  showInsights
/>

Pass a controlled preset / range if the host owns the dashboard timeline:

import {
  PaperBoard,
  computeInsightsPeriodRange,
  type InsightsPeriodPreset,
  type InsightsPeriodMenuPreset,
} from '@papermap/papermap'

function MyDashboard() {
  const [preset, setPreset] = useState<InsightsPeriodPreset>('last_3_months')
  const [range, setRange] = useState(() => computeInsightsPeriodRange('last_3_months'))

  return (
    <PaperBoard
      token="..."
      workspaceId="..."
      dashboardId="..."
      showInsights
      insightsPeriodPreset={preset}
      onInsightsPeriodPresetChange={(next) => {
        setPreset(next)
        if (next !== 'custom') {
          setRange(computeInsightsPeriodRange(next as InsightsPeriodMenuPreset))
        }
      }}
      insightsStartDate={range.start}
      insightsEndDate={range.end}
      onInsightExplore={(chatId) => router.push(`?conversation_id=${chatId}`)}
    />
  )
}

All paths use the package entry: @papermap/papermap.