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

@olotalk/assistant

v0.27.0

Published

The [Olotalk](https://olotalk.com) AI chat assistant, a Svelte 5 component that mounts a RAG-powered, multilingual chat assistant into any web page via Shadow DOM.

Readme

@olotalk/assistant

The Olotalk AI chat assistant, a Svelte 5 component that mounts a RAG-powered, multilingual chat assistant into any web page via Shadow DOM.

Most users should use @olotalk/assistant-loader instead. It handles config fetching, browser compatibility, and dynamic loading with a single <script> tag. Use this package directly only if you need low-level control over assistant instantiation.


Install

npm install @olotalk/assistant

Usage

import { createAssistant } from '@olotalk/assistant'

const assistant = createAssistant(document.body, {
  assistantId: 'YOUR_ASSISTANT_ID',
  config: assistantConfig,   // fetched from /public/assistants/:id/config
  theme: 'light',
  placement: 'floating',
  cssUrl: 'https://cdn.jsdelivr.net/npm/@olotalk/assistant/dist/olotalk-assistant.css',
  // origin: 'https://your-bff.example.com',  // self-hosters only —
  // cloud builds bake in https://api.olotalk.com; dev falls back to
  // window.location.origin
})

CSS

The assistant loads its styles via a <link> injected inside the Shadow DOM. The CSS URL will be resolved automatically from the assistant config in a future release. Until then, pass it explicitly via cssUrl or import the stylesheet manually:

import '@olotalk/assistant/style.css'

When self-hosting the bundle for use with @olotalk/assistant-loader, publish dist/version.json alongside the JS and CSS files so the loader can cache-bust asset URLs automatically.


createAssistant(mount, options)

Mounts the assistant into the given element and returns a AssistantInstance.

Options

| Option | Type | Required | Description | |---|---|---|---| | assistantId | string | Yes | Your assistant ID. | | origin | string | No | Base URL of your Olotalk API. Cloud builds bake in https://api.olotalk.com at build time; dev/self-host omits and the assistant falls back to window.location.origin. Set this only when self-hosting the BFF on a different origin. | | config | AssistantConfig | Yes | Assistant configuration object from the API. | | theme | "light" | "dark" | object | No | Color scheme or custom theme overrides. | | mode | AssistantMode | No | Which shape to mount (default: "bubble"). See Shapes. | | reach | string | No | Additive triggers over the mounted shape (ADR 0226): a comma-separated list of trigger-capable shape ids, "selection", "commandk", whose triggers open this mount instead of conjuring their own panel. mode: "bubble", reach: "selection,commandk" gives a corner bubble that also opens from a text highlight and from ⌘K. Unknown ids are dropped with a console warning. See Combining shapes. | | selectionAnswers | string | No | Where a selection opens the panel: "beside" (default) flies it to the highlighted words, "in-place" opens it where the assistant lives. Only affects the selection trigger, a host calling open({ anchor, anchorPresentation }) is untouched. Unrecognised values warn and fall back to "beside". | | placement | "floating" | "embedded" | No | Deprecated alias for mode, floating → bubble, embedded → inline. Prefer mode. | | cssUrl | string | No | URL of the assistant stylesheet. Will be resolved automatically from the assistant config in a future release. | | locale | string | No | Force a display language. Accepts any well-formed BCP-47 tag (e.g. "en", "fr", "en-CA", "vi", "ja"). When omitted, the assistant resolves from a stored visitor preference, the page's <html lang>, the visitor's browser, then the operator's defaultLocale, bounded by the assistant's supportedLocales. |

AssistantConfig shape

This is returned by the Olotalk API at /public/assistants/:id/config.

type AssistantConfig = {
  tenantId: string
  assistantId: string
  displayName?: string             // shown in the assistant header; when logoUrl is set the visible label is capped at 32 chars, while the full name remains available to assistive tech/tooltips
  welcomeMessage?: string          // greeting shown on the welcome screen
  theme?: Record<string, any>
  launcher?: {
    text?: string                  // label on the closed launcher; omit or "" for the localized default ("Ask AI")
    logoUrl?: string               // when set, the header shows the logo image + displayName side by side
    position?: 'bottom-right' | 'bottom-left'
    // Starter bubbles: the stack of starter questions that rises off the
    // launcher on hover. Named `teaser` for history — it used to drive a canned
    // tooltip. ONLY `enabled` is read.
    teaser?: {
      enabled?: boolean
      // The four below are accepted and IGNORED, kept so stored configs keep
      // parsing. They budgeted an uninvited interruption; a hover needs no
      // delay, cooldown or per-session cap, and the bubbles carry the
      // operator's `starters` questions rather than canned copy.
      messages?: string[]           // @deprecated — read by nothing
      delayMs?: number              // @deprecated — read by nothing
      durationMs?: number           // @deprecated — read by nothing
      cooldownMs?: number           // @deprecated — read by nothing
      maxShowsPerSession?: number   // @deprecated — read by nothing
    }
  }
  // Suggested starter questions shown before the first message.
  // Auto-generated from ingested content when not manually configured.
  starters?: {
    topics?: Array<{ label: string; questions?: string[] }>
    questions?: string[]
  }
  allowedDomains?: string[]
  features?: {
    domRagEnabled?: boolean
    siteRagEnabled?: boolean
    citationsEnabled?: boolean
    streamingEnabled?: boolean
  }
  security?: {
    rateLimitPer5Min?: number
    captchaMode?: boolean
  }
  privacy?: {
    piiRedaction?: boolean
    retentionDays?: number
  }
}

AssistantInstance API

assistant.open()                          // Open the chat panel
assistant.close()                         // Close the chat panel
assistant.isOpen()                        // true while the panel is showing
assistant.setTheme(theme)                 // Change theme at runtime
assistant.setLocale('fr')                 // Change language, keeping the conversation
assistant.destroy(opts?: { outro?: boolean }) // Unmount and remove from DOM

// Subscribe to events
assistant.on('ready', ({ assistantId }) => { ... })
assistant.on('open',  () => { ... })
assistant.on('close', () => { ... })
assistant.on('send',  ({ text }) => { ... })
assistant.on('cta_click', ({ id, action, intent }) => { ... })
assistant.on('lead_submit', ({ leadId, status, score }) => { ... })

Shapes

The same bundle mounts fourteen ways. One mode apart, same conversation, same grounding, same citations; only the mount differs.

shape-spec.ts is the source of truth for this list, and a test fails if a live shape is missing from this table, so it cannot quietly fall behind the code.

| mode | Where it lands | How it opens | |---|---|---| | bubble | Corner launcher (default) | Its own launcher | | starters | Corner launcher, questions on hover | Its own launcher | | corner | The same corner panel, with nothing at rest | A trigger you place, or reach | | inline | Inside a container in your markup | Always open | | drawer | Full height, flush to an edge, modal | A button you place | | commandk | Centred over a dimmed page, modal | ⌘K / CtrlK | | center | The same centred panel, without the ⌘K claim | A trigger you place, or reach | | expand | A line in your copy that unfolds | A trigger you place | | anchored | Beside one element, never covering it | A trigger you place | | fullpage | The assistant is the page | Always open | | sidebar | A fixed rail down one edge | Always open | | navfield | Your own nav field | Your markup submits to it | | askbar | Your own bar at the foot of the page | Your markup submits to it | | selection | Nothing at rest; appears where text is highlighted | The visitor selecting text |

mobile is not a shape you pick. It is the presentation bubble, drawer, commandk and anchored collapse into at phone width, so one embed covers both. Passing it as a mode is not supported.

Four of these attach to something only you can point at (drawer, anchored, navfield and askbar) so they need open() wired to your own trigger. The rest mount from config alone. See the docs for the full contract.

Combining shapes

A page may mount several shapes, and they share one conversation (ADR 0245). The first one listed is the residence, the panel a bare data-olotalk-open and getAssistant(id) resolve:

<script src="…/loader.iife.js"
  data-olotalk-assistant-id="ast_…"
  data-olotalk-mode="bubble,drawer" async></script>

<button data-olotalk-open="drawer">Ask AI</button>

A corner launcher AND a header button that opens a drawer, on one transcript. Only one panel is open at a time: opening one closes the others, because two panels showing the same conversation is a duplicated transcript competing for the same screen. A shape whose sibling is open also hides its own resting affordance for as long as that lasts, while the drawer is out, the corner launcher goes away rather than floating on top of it inviting you to open what is already open (ADR 0246).

Adding a shape is how you add another panel. Adding a way in to the panel you already have is cheaper, and is what reach and doors are for (ADR 0226):

  • data-olotalk-open, your own affordances, no JavaScript. Put the attribute on any element you already have and it becomes a door:

    <button data-olotalk-open>Ask AI</button>
    <button data-olotalk-open="drawer">Ask AI</button>
    <button data-olotalk-open data-olotalk-prefill="How does billing work?">…</button>

    One delegated listener handles the page, so a button rendered later by a framework works with no re-scan. The value names which mounted shape to open; empty means the one panel on the page. data-olotalk-prefill opens it with a question, which the shape then sends or leaves editable according to its own sendOnPrefill (a corner bubble sends; select-to-ask holds it for amending). A door that names a shape nothing has mounted warns and does nothing rather than failing silently.

    A plain door toggles, press it again while the panel is open and it closes, which is what a header control that says "Ask AI" is read as. A door carrying a prefill always opens instead: those mean "ask this", so a second press re-asks rather than dismissing. getAssistant(id).isOpen() is the same state, if you want to drive your own affordance from it.

    data-olotalk-door="branded" opts a door into the assistant's own look, a quiet chip at rest, the launcher's gradient rim turning around it on hover. Opt-in on purpose: without it a door keeps your styles and we inject nothing, which is the point of putting the attribute on an element you already have.

    This is what makes the drawer installable by pasting: it has no launcher of its own by design, and its snippet now ships the button with it, branded, with the robot mark inline so it can blink.

  • getAssistant(id).open({ prefill }), the same door, in JavaScript. For a trigger that is not a click: a nav field's submit, a router event, your own component. This has always been the contract: the instance registry exists so a trigger resolves the panel that already exists rather than conjuring a second one.

  • reach, our triggers, any residence. reach: "selection,commandk" raises select-to-ask and ⌘K over whatever is mounted. ⌘K toggles the residence; it is claimed only when you ask for it, and a reach naming the residence's own trigger is simply redundant, not an error.

mode: "selection" and reach: "selection" are not the same thing. The word does double duty, and this is the easiest place to misread it:

| | at rest | ways in | |---|---|---| | mode: "selection" | nothing on the page, no button anywhere | highlighting text, and only that | | mode: "bubble", reach: "selection" | the corner launcher | the launcher and highlighting |

Select-to-ask with no button on the page is the first one, mounted on its own: it is a shape, not a modifier.

The open panel is a window

Visitors can resize almost any panel that floats over the page, and drag the anchored one. Neither is configurable: there is nothing to switch on, and nothing an embed can turn off.

drawer opts out via resizable: false on its shape record: its width is the amount of page it takes away, and it is the one shape whose opening already moves the host layout (ADR 0244).

| | resize | drag | |---|---|---| | bubble · starters · corner · commandk · center | ✅ | — | | drawer | — |, | | anchored (select-to-ask, mode: "anchored") | ✅ | ✅ | | inline · expand · fullpage · sidebar | — |, | | any shape at phone width | — |, |

Eight handles: 10px edge bands with 20px corners. Floor 320×360, ceiling the viewport less a margin.

Drag is narrower than resize on purpose. A corner bubble keeps its launcher on screen while open (it morphs into the close control) so moving the panel away would orphan that button. The anchored panel has no such tether and deliberately covers the passage it is about, so moving it is the only way to read the original words.

The chosen size survives a close; the position never does. Press the launcher and the assistant comes back to its corner, at the size the visitor picked. Both gestures are pointer-only.

The in-flow shapes are your layout, so they are left alone entirely.

Select-to-ask

Highlighting a passage raises a pill above it. Taking that offer opens the assistant beside the words (even when it normally lives in the corner) and the passage arrives as a removable quote above the composer, which stays empty for the question the visitor actually wants to ask. Send combines the two. Nothing is ever asked on their behalf.

If you would rather it stayed put, set selectionAnswers: "in-place" (data-olotalk-selection-answers="in-place") and the panel opens where the assistant lives instead of travelling to the highlight. Worth it when the assistant is a fixture your visitors have already located and the flight across the page costs more than the distance saves; the default is "beside", because usually the answer belongs where the question was asked.

A second highlight while the panel is open seeds the conversation already running: new quote, composer focused, panel not moved, anything typed kept. The passage replaces the previous quote rather than stacking.

On touch devices the pill is deliberately absent: the OS owns that gesture, and the only way to suppress its own Copy / Look Up menu is to disable text selection entirely.

Reach adds entry points, never panels. Nothing starts at page load, and opening via reach counts exactly like opening via the shape's own launcher, a session when the panel opens, a metered message when one is sent. One pairing is refused with a warning: commandk reach over the anchored shape, because a keystroke names no element for an anchored panel to position from. Try an ensemble locally: demo/shapes.html?mode=bubble&reach=selection,commandk.


Distribution files

| File | Format | Use case | |---|---|---| | dist/olotalk-assistant.js | ESM | Dynamic import() from CDN or bundler | | dist/olotalk-assistant.iife.js | IIFE | Legacy browsers (<script> tag, no module support) | | dist/olotalk-assistant.css | CSS | Styles loaded inside the Shadow DOM | | dist/version.json | JSON | Build manifest used by the loader for cache-busting | | dist/index.d.ts | TypeScript | Type definitions |


Features

  • RAG-powered answers: responses grounded in your website's knowledge base, not generic LLM output
  • Suggested starters: topic chips and question buttons auto-generated from ingested content; shown before the first message to guide visitors
  • Human escalation: amber banner notifies visitors when the conversation is handed off to your team
  • Multilingual: any BCP-47 locale supported. en, fr, de ship hand-translated dictionaries; other languages (e.g. vi, ja, ar, sw) get LLM-translated UI strings cached on the BFF. Operators declare their site's supportedLocales once; the assistant auto-detects from the embedding page or browser. Visitors who type in a different supported language see an inline "Continue in {language}?" toast that swaps the entire chrome on accept (ADR 0050)
  • Streaming (real-time text generation for a conversational feel
  • Citations) optionally surfaces source URLs alongside answers
  • Twelve shapes: one bundle, one mode apart: corner bubble, inline, drawer, ⌘K, expand-in-place, anchored, full page, docked sidebar, nav field, ask bar, select-to-ask (see Shapes)
  • Starter bubbles: the launcher raises the operator's own starter questions on hover; no canned copy, and nothing uninvited (it replaced the delay-based teaser, ADR 0219)
  • UTM + referrer attribution: first-touch UTM params and document.referrer forwarded at session creation
  • Shadow DOM isolation: assistant styles never bleed into the host page
  • Anonymous by default (no visitor login required
  • Domain whitelisting) control which sites can embed the assistant
  • Privacy controls: optional PII redaction and configurable data retention

License

MIT © Olotalk