Persona

A themeable, zero-dependency VanillaJS widget for embedding streaming AI chat experiences on any website.

Library
npm
v4.17.1
221stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
77/100Good
Development Activity100
Maintenance100
Community40
Maturity28
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
86/100Excellent
Architecture85
Code Quality85
Innovation90
Learning Curve90

Persona is a themeable, pluggable chat widget for websites, built in TypeScript with zero framework dependencies and rendered in plain JavaScript. It drops into any site or product — React, Vue, or none of the above — via a single initAgentWidget() call or a no-build script tag, and talks to any SSE-capable backend over its documented wire protocol.

Beyond a basic chat box, it ships streaming responses with pluggable parsers, multi-modal attachments, voice input/output, reasoning bubbles, tool-call and approval-gate visualization, an artifacts side panel, WebMCP page-tool integration, and a 14-hook plugin system for replacing any part of the UI. The monorepo includes reference backend adapters for the Vercel AI SDK, OpenAI Agents SDK, LangGraph.js, and Vercel’s Eve, plus a Hono/Express/SvelteKit host matrix showing the same adapter running everywhere.

What You Get

  • Drop-in widget via initAgentWidget()/createAgentExperience() or a single <script> tag, with floating, docked, and fullscreen layouts out of the box
  • SSE-based streaming chat with pluggable stream parsers (plain text, JSON, XML, regex) and configurable reveal animations
  • Multi-modal attachments (images, PDFs, DOCX, CSV, Excel), voice input/output via the Web Speech API or a hosted engine, and collapsible reasoning/chain-of-thought bubbles
  • Tool-call and human-in-the-loop approval bubbles, an artifacts side panel for markdown/component rendering, and WebMCP integration for exposing page actions to the model
  • A 14-hook plugin system plus a full design-token theming system for rebuilding any piece of the UI without forking
  • Reference backend adapters (Vercel AI SDK, OpenAI Agents SDK, LangGraph.js, Vercel Eve) and a host matrix (Hono, Express, SvelteKit, bare Node) showing the same agent running on every runtime

Common Use Cases

  • Adding a support/docs/sales chat assistant to a marketing site or SaaS product without adopting a frontend framework
  • Building a docked AI copilot panel alongside an existing app’s workspace UI
  • Building a fullscreen, artifact-capable AI app surface (a canvas-style assistant) with tool calls and approvals
  • Exposing page content and actions to an LLM via WebMCP so the assistant can read and act on the current page
  • Prototyping different LLM/agent backends (LangGraph, OpenAI Agents, custom SSE) behind the same chat UI

Under The Hood

Architecture — Persona layers cleanly from config down to DOM: index.ts is a thin barrel over index-core.ts (the shared public API), which drives session.ts (3,606 lines — the state machine for messages, voice, WebMCP, reconnect, and context mentions) sitting on top of client.ts (3,477 lines — the SSE transport/dispatch layer). runtime/init.ts and runtime/host-layout.ts handle DOM mounting and the floating/docked/fullscreen layout contract, while components/* (panel.ts, launcher.ts, message-bubble.ts, composer-builder.ts, artifact-pane.ts, event-stream-view.ts, and more) render the UI. plugins/registry.ts and plugins/types.ts implement the 14-hook extensibility layer over those components. Durable reconnect logic (session-reconnect.ts) is reached only via a dynamic import() from session.ts, so bundles that never opt into it never pay for it — a deliberate code-splitting discipline that recurs across the package (context-mention chips, smart-dom-reader, voice, and theme-editor are all separate subpath entry points for the same reason).

Tech Stack — Strict-mode TypeScript ("strict": true) targeting ES2020, built with tsup into ESM, CJS, and an IIFE global (dist/index.global.js) for script-tag installs, plus a dozen independent subpath builds (theme-reference, codegen, theme-editor, testing, smart-dom-reader, voice-worklet-player, plugin-kit, context-mentions, per-animation chunks) so consumers only ship the code paths they use. Runtime dependencies are few and purposeful: dompurify (sanitization), idiomorph (DOM morphing for streamed updates), marked (markdown), partial-json (incremental JSON parsing for in-flight tool-call arguments), and @mcp-b/webmcp-polyfill (WebMCP support). A custom post-build script (check:dist-vite8 / check-dist-no-in-for-init.mjs) actively guards against a known Vite 8 compatibility regression — a sign of real-world compatibility testing beyond unit tests.

Code Quality — 129 test files live alongside their source under src/ (e.g. session.test.ts, session.voice.test.ts, session.webmcp.test.ts, session.mentions.test.ts, reconnect.test.ts, reconnect-wake.test.ts), splitting the large session.ts state machine’s behavior into focused suites rather than one monolithic test file. Tests run on Vitest 4 with a Node environment, global test APIs, and a shared setup file; fake-indexeddb is pulled in specifically to exercise the storage adapter. ESLint with typescript-eslint enforces linting across the codebase, and size-limit enforces a bundle-size budget as part of CI-grade checks (pnpm size). Error handling is deliberate rather than incidental: the transport layer (client.ts) alone contains roughly two dozen distinct catch blocks around SSE/network paths.

API Design — A single initAgentWidget() (or createAgentExperience() for inline embeds) call mounts the widget, and one config key (launcher.mountMode) switches between floating, docked, and fullscreen layouts. The update() method’s merge policy is explicitly documented as a recursive patch rather than a shallow replace, with a named, deliberate list of “replace-leaf” fields (headers, agent, storageAdapter, components, etc.) that intentionally don’t get spliced — a level of API contract precision that heads off a common class of config bugs. The docked layout ships a documented “height contract” that warns in the console when an ancestor doesn’t provide a definite height, rather than silently rendering broken. generateCodeSnippet() can scaffold install code straight from a live config object for docs/onboarding tooling. Documentation is unusually deep for a UI widget: a 237-line package README plus 11 dedicated docs (CONFIGURATION-REFERENCE, PLUGINS, EXTENDING, CONTEXT-MENTIONS, STREAM-PARSERS, DYNAMIC-FORMS, and more), backed by a live interactive demo site with 35+ example pages.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search