Tool

Embed a themeable AI chat widget into any website

Themeable, zero-dependency TypeScript AI chat widget for any website - streaming, voice, tool-call approvals, artifacts, and a plugin system.

Works with openaivercellangchainlanggraphanthropic

91
Spark score
out of 100
Updated 12 days ago
Source checked Sep 10, 2026
Version @runtypelabs/persona

Add to Favorites

Why it matters

Enable developers to quickly add a production-ready AI assistant interface to their website or application without building chat UI from scratch, supporting streaming responses, voice input/output, tool calls, and multi-modal content across any backend or framework.

Outcomes

What it gets done

01

Drop a zero-dependency TypeScript chat widget into any site with script tag or npm install

02

Stream AI responses with tool call visualization, approval gates, and artifact rendering

03

Connect the widget to any SSE-capable backend using pre-built adapters for Vercel AI SDK, OpenAI, LangGraph, and more

04

Customize themes, layouts (floating, docked, fullscreen), and behavior through a declarative plugin system

Source

Get it from source

Spark does not host a copy of it.

Open source

Reports

Agent outcome reports

No reports yet

Overview

Persona

Persona is a themeable, framework-agnostic AI chat widget built in TypeScript with zero dependencies. It handles SSE streaming, multi-modal attachments, voice I/O, tool-call visualization with human-in-the-loop approvals, side-panel artifacts, and WebMCP page tools, in floating, docked, or fullscreen layouts, extensible through a 14-hook plugin system. Use it to add AI chat to an existing site or app (with or without React/Vue) using plain TS/JS, backed by any SSE-capable agent backend; for a JSX-first chat framework, the project itself points to Assistant UI, CopilotKit, or Vercel AI Elements instead.

What it does

Persona is a themeable, pluggable AI chat widget for websites, built in TypeScript with zero framework dependencies and rendered in vanilla JS - a drop-in UI that works on essentially any site or product, whether or not it already runs React, Vue, or another framework. Out of the box it handles streaming responses, multi-modal content (images and documents), voice input/output, tool-call visualization with human-in-the-loop approval gates, side-panel artifact rendering, and safe markdown/HTML rendering, with a plugin system that lets you replace any layer of the UI without forking.

When to use - and when NOT to

Use it when you want to add an AI chat experience quickly and declaratively using plain TS/JS, including layering it on top of an existing React/Vue app rather than rebuilding around it - Persona is explicitly lightweight and designed to sit alongside whatever frontend stack is already there. It works with any SSE-capable backend through its own wire protocol, with featured adapters for Vercel's eve, the OpenAI Agents SDK, LangGraph.js, and Vercel's AI SDK/OpenAI Responses, plus a documented host matrix (Hono, bare HTML with a script tag, Express, SvelteKit) showing the same canonical agent re-hosted with only the thin wrapper changing. If you'd rather build AI UI in JSX, the project itself points you elsewhere - Assistant UI, CopilotKit, or Vercel's AI Elements - rather than pretending to be a fit for that workflow.

Inputs and outputs

Input is a widget config (initAgentWidget({ target, config: { apiUrl, launcher } }) or an equivalent script-tag window.siteAgentConfig) plus user messages, attachments, voice input, and optional @-mention context pulled from host-provided sources. Output is a rendered chat surface in one of three layouts controlled by the launcher config - Floating (a corner launcher, the default), Docked (a resizable side panel that pushes, overlays, or resizes the host layout), or Fullscreen (the widget owns the page, with an optional artifact split) - streaming assistant responses, expandable tool-call and approval bubbles, collapsible reasoning traces, and optional voice playback, all through pluggable stream parsers (plain text, JSON, XML, regex) and reveal animations.

Integrations

corepack enable
pnpm install
pnpm dev

That starts a local proxy at http://localhost:43111 and the demo app at http://localhost:5173 with hot-reloading against the workspace widget package (requires Node.js 24). In production, install just the widget (npm install @runtypelabs/persona) or add the optional Hono-based proxy package (@runtypelabs/persona-proxy) for secure API-key handling and flow configuration; an even simpler path embeds the widget against a hosted Runtype backend using a browser-safe clientToken, with no backend code at all. Page-level integration includes WebMCP (document.modelContext) to expose page actions to the model, programmatic message injection with dual display/LLM content, and a context-providers API for feeding page or editor state into a turn.

Who it's for

Teams adding an AI chat or copilot experience to an existing website or product - regardless of its frontend framework - who want fine-grained control over streaming, tool-call approval, voice, and artifact rendering without adopting a JSX-first chat framework, and who are willing to bring their own SSE-emitting backend or use one of the provided adapters. It's released under the MIT license.

Source README

Persona.js

npm
Live demo
Ask DeepWiki

A themeable, pluggable AI chat widget for websites: built in TypeScript with zero framework dependencies. It renders using Vanilla JS. Its initial bundle tries hard to be small.

Persona gives you a drop-in UI for your AI assistant that works on basically any site or product on the web. It ships with support for streaming responses, direct client-token installs, WebMCP/page tools, built-in local client tools, voice I/O, multi-modal content, tool call visualization, approval gates, artifact rendering, safe markdown/HTML rendering, and a plugin system so you can customize every layer of the UI.

Persona chat widget slideshow

Persona works with any SSE-capable backend. See the "examples" section below for pre-built framework / platform / frontend combos.

Built something cool that you'd like to contribute back? Awesome! We'd love that.

Live demo

persona-chat.dev hosts the interactive showcase: streaming chat, voice, docked and fullscreen layouts, themes, tool calls, artifacts, and more. It's the hosted version of apps/web. To run the same pages on your machine with hot reload while you edit code, run pnpm dev from the repository root: the Vite dev server reloads the demo, and the app resolves @runtypelabs/persona from the workspace (packages/widget), so widget changes apply without publishing to npm.

When should you use this?

If you want to create AI experiences quickly within your site or app, configured in a declarative manner, and love building with plain TS/JS hooks... Persona will be one of your best friends!

This includes layering on top of what's already been built with React, Vue, or any other FE framework. Persona is lightweight and is built to work alongside.

That said, if you really don't like the idea of building AI without JSX... you probably want to check out Assistant UI, CopilotKit, or Vercel's AI Elements. No worries, Persona still thinks you are cool.

Packages

Package npm Description
packages/widget @runtypelabs/persona The installable chat widget
packages/proxy @runtypelabs/persona-proxy Optional Hono-based proxy server for flow configuration

Apps

App Platform Description
apps/web Vite The Persona showcase: 35+ interactive demo pages (live)

Examples

Example Platform Description
examples/ai-sdk-webmcp Next.js WebMCP page tools using Vercel AI SDK (live)
examples/ai-sdk-next Next.js Minimal SSE adapters for AI SDK and OpenAI Responses
examples/eve-next Next.js Vercel eve agent backend (beta; needs Node 24 + a running eve server)
examples/openai-agents-next Next.js OpenAI Agents SDK (@openai/agents) backend
examples/langgraph-next Next.js LangGraph.js (@langchain/langgraph) backend
examples/echo-hono Hono Host matrix: same adapter on Hono (Node/Bun/Deno/Workers)
examples/echo-script-tag Bare HTML + node:http Host matrix: zero-framework <script> install + bare Node backend
examples/echo-express Express Host matrix: the (req, res) callback-style bridge
examples/echo-sveltekit SvelteKit Host matrix: one-line +server.ts Web-standard route
examples/runtype-script-tag Static / Runtype Hosted backend: a clientToken embed with no backend at all
examples/runtype-hono-proxy Hono (Node / Vercel / Workers) Runtype API proxy: powers local dev and proxy.persona-chat.dev

Bring Your Own Backend

Persona is designed to be backend-agnostic. You can plug it into any streaming agent or model SDK using the Persona SSE protocol.

Prefer not to run a backend at all? examples/runtype-script-tag embeds the widget against a hosted Runtype backend with a browser-safe clientToken. No server code: the direct counterpart to the self-hosted echo-script-tag example.

Featured Adapters

Backend adapters in this repo, each emitting Persona's SSE wire from a different SDK:

More adapters (Anthropic Claude Agent SDK, Google Gen AI, Mastra, Cloudflare Agents) live in runtypelabs/persona-examples.

Host Matrix

The adapter is a plain Web (Request) => Response, so it runs anywhere, not just React. These four examples re-host the same canonical agent: each uses the same persona-wire.ts and adapter, and only the thin host wrapper changes. Diff them to see exactly what each framework needs (and what it gives you for free). All four run with no API key (a zero-dependency echo agent, with a documented one-line swap to a real model).

  • Hono: one app.fetch handler that runs on Node, Bun, Deno, and Cloudflare Workers.
  • Bare HTML + <script>: no framework, no bundler: the drop-in script-tag install over a bare node:http backend.
  • Express: the callback-style host. It shows the (req, res) → Web Response bridge.
  • SvelteKit: a one-line Web-standard +server.ts route.

Protocol Documentation

Quick Start

corepack enable
pnpm install
pnpm dev

This starts the proxy on http://localhost:43111 and the demo app at http://localhost:5173. Both depend on the local widget package via workspace linking, so changes hot-reload without publishing.

Note: Requires Node.js 24 (nvm use reads .nvmrc). Corepack manages pnpm for you.

Install from npm

npm install @runtypelabs/persona        # widget
npm install @runtypelabs/persona-proxy   # proxy (optional)

Three primary layouts

While Persona supports much more, the majority of the frontend AI experiences you see fit into the three buckets below. We recommend starting here.

You move between them by changing the launcher config:

  • Floating (the default): a launcher in the corner that opens a floating panel. The entry point for support, docs, sales, or onboarding, no layout config required.
  • Docked: a copilot docked beside your app. Wrap a page region and reveal a side panel that resizes, pushes, or overlays your layout.
  • Fullscreen: a full-height assistant that owns the page. Fill a container as an app surface, with an optional artifact split.

With npm (any bundler):

import { initAgentWidget } from "@runtypelabs/persona";

// 1. Floating: the launcher in the corner is the default
initAgentWidget({ target: "#chat", config: { apiUrl } });

// 2. Docked: wrap a region, reveal a side panel
initAgentWidget({
  target: "#workspace",
  config: { apiUrl, launcher: { mountMode: "docked", dock: { side: "right", width: "420px" } } },
});

// 3. Fullscreen: turn the launcher off, let the widget own the page
initAgentWidget({
  target: "#app",
  config: { apiUrl, launcher: { enabled: false, fullHeight: true } },
});

Or with a script tag (no build step): the same config via window.siteAgentConfig, and the installer loads the widget and its CSS for you. Swap in the same launcher field for docked or fullscreen.

<script>
  window.siteAgentConfig = {
    target: "#chat",
    apiUrl: "https://your-api.com/chat",
    // floating launcher is the default
  };
</script>
<script src="https://cdn.jsdelivr.net/npm/@runtypelabs/persona@4/dist/install.global.js"></script>

See the live launcher, docked panel, and fullscreen assistant demos. The widget configuration reference covers mountMode, every dock.reveal mode (resize, emerge, overlay, push), and the docked height contract.

Features

Everything below is opt-in and configurable via the widget config, feature flags, or the plugin system.

Streaming Chat

SSE-based message streaming with pluggable parsers (plain text, JSON, XML, regex). Bring your own stream parser or use the built-ins. Supports partial JSON parsing for incomplete chunks, configurable dispatch-failure copy via errorMessage, and optional stream reveal animations (typewriter, letter-rise, word-fade, wipe, glyph-cycle, pop-bubble, or custom plugins).

Multi-Modal Content

Text, images (PNG, JPEG, GIF, WebP, SVG), and documents (PDF, DOCX, TXT, CSV, JSON, Excel). Configure allowed file types, size limits, and previews through the attachments config.

Voice Input & Output

Optional speech-to-text via the Web Speech API or Runtype's WebSocket voice service with barge-in interruption and voice activity detection. Text-to-speech playback for assistant responses: auto-speak via textToSpeech, or a per-message "Read aloud" button with play/pause/resume via messageActions.showReadAloud. TTS is backed by a pluggable SpeechEngine (browser Web Speech API by default, or a hosted engine via textToSpeech.createEngine). Enable via voiceRecognition and textToSpeech.

Custom voice providers use the same microphone controls. Updating or disabling voiceRecognition.provider disconnects the old provider before installing its replacement. A provider's disconnect() must release its resources and callbacks. For concurrent turns, pass { turnId } as the fourth onTranscript callback argument, using the same ID on each turn's user and assistant transcripts. Persona rejects replies belonging to cancelled or superseded identified turns. Providers that omit IDs must discard cancelled output before emitting the next user final; untagged overlapping replies cannot be correlated by the widget.

Reasoning & Extended Thinking

Collapsible reasoning bubbles that display model chain-of-thought with duration tracking and streaming. Controlled by features.showReasoning: on by default, or override the renderer with a plugin hook.

Tool Calls, Approvals & Local Client Tools

Expandable tool call bubbles showing name, status, arguments, and results, with compact display modes, active previews, grouping, and loading animations. Optional human-in-the-loop approval bubbles include friendly summaries, hidden/collapsed technical details, agent-stated reasons, and custom approve/deny handlers. Built-in LOCAL client tools (ask_user_question and suggest_replies) can be advertised from the widget with features.askUserQuestion.expose and suggestions.followUps.expose.

Artifacts

Optional side-panel for rendering markdown and component content. Desktop split layout (resizable, with an optional expand-to-full-width toolbar toggle via layout.showExpandToggle) or mobile drawer. Enable via features.artifacts, configure toolbar presets, copy behavior, and appearance. Add product-specific buttons (icon, label, handler) to the pane toolbar and reference card with toolbarActions and cardActions.

Event Stream Inspector

Optional real-time event capture with search/filter, badge coloring, timestamps, expandable payloads, and output-throughput diagnostics. Enable via features.showEventStreamToggle. Customize rows, toolbar, and payload rendering through plugin hooks.

Composer, Scrolling & Keyboard Shortcuts

Enter sends a message (Shift+Enter for a newline) and is inert while a response is streaming: it never interrupts generation. Press Esc within the widget to stop an in-flight response (the visible Stop button does the same). Up/Down navigate previously sent messages for quick re-entry or editing: entered only when the caret is at the start of the input, so multi-line editing is preserved, and your in-progress draft is restored when you page back to the present. History navigation is on by default; disable via features.composerHistory: false. Streaming scroll behavior is configurable with features.scrollBehavior (follow, anchor-top, or none), and the shared scroll-to-bottom affordance shows a new-message count while you're scrolled away.

Themes & Styling

Light and dark themes included. Full design token system (palette, semantic, component-level) with CSS variable support. Extend with built-in plugins for accessibility, reduced motion, high contrast, and branding, or create your own.

Layout, Docking & Fast Script Installs

Start from a built-in preset (shop, minimal, fullscreen) or configure from scratch. Header layouts, message layouts, avatars, timestamps, and slot-based rendering are all customizable. Dock as a floating widget, wrap a page region with a side panel (resize, emerge, overlay, or push reveals with a dock.maxHeight viewport guard), or embed inline. Script-tag installs paint a tiny real launcher first and defer the full panel bundle until first open when the config allows it.

Plugin System

14 render hooks covering the launcher, header, composer, messages, reasoning, tool calls, ask-user-question sheets, approvals, loading/idle indicators, and the event stream. Priority-based ordering with automatic fallback to defaults. Replace any piece of the UI without forking; use the optional @runtypelabs/persona/plugin-kit helpers for Shadow-DOM-safe styles and popovers.

Extending Persona

Persona ships various ways to extend it. Plugins, an inline component registry, postprocessors, theme plugins, stream parsers and animations, voice providers, sanitization hooks, action parsers/handlers, context providers/WebMCP, layout slots, and a storage adapter are all replaceable seams. The Extending Persona guide maps every extension point to its public API and deep-dive doc; Authoring Plugins covers building your own.

Component System

Register custom components and render them inline via directives. Stream-aware parser and middleware support dynamic UI insertion during streaming, with live DOM element hydration so event listeners survive transcript re-renders.

Message Injection, Context & Page Tools

Programmatically insert messages (injectMessage, injectAssistantMessage, injectUserMessage, injectSystemMessage) with dual-content support: display one thing to the user while sending different content to the LLM. Inject page/editor context with contextProviders and requestMiddleware; use webmcp: { enabled: true } to expose page actions through document.modelContext. For richer page context, import the optional @runtypelabs/persona/smart-dom-reader provider.

Context Mentions

Let users pull external context into a single turn by typing @ or clicking a visible composer button; both open one searchable menu of host-provided sources. Selecting a mention strips the typed query and adds a removable pill chip; the resolved content reaches the model via llmAppend (lead with this, it always works with no backend changes) or an opt-in structured context.mentions channel. Set display: "inline" to insert Slack-style atomic tokens in the sentence instead of chips, with the menu anchored to the @ glyph. Disabled by default; when enabled, the heavy menu/chip runtime (~15 kB gzipped) is lazy-loaded on first use, so an install that leaves it off ships only a small affordance, not the whole feature. Full guide: CONTEXT-MENTIONS.md.

import initAgentWidget, { createStaticMentionSource } from "@runtypelabs/persona";
import { createSmartDomMentionSource } from "@runtypelabs/persona/smart-dom-reader";

initAgentWidget({
  config: {
    contextMentions: {
      enabled: true,
      sources: [
        createStaticMentionSource({
          id: "files",
          label: "Files",
          items: [{ id: "app", label: "App.tsx", iconName: "file-code" }],
          // Lead with `llmAppend` so the model sees the context with no backend changes.
          resolve: (item) => ({ llmAppend: `Contents of ${item.label}:\n${readFile(item.id)}` }),
        }),
        // Supported, page-aware source: resolves the chosen element's live text at submit.
        createSmartDomMentionSource(),
      ],
    },
  },
});

See the Context Mentions demo, the inline mode demo, and the full guide.

Extending & contributing

Built a plugin, theme, backend adapter, or product with Persona? See the
extensibility map (EXTENDING.md) and
CONTRIBUTING.md. While Persona is launching, please
contribute customizations back to this monorepo as demos, examples, docs, or
package changes so the ecosystem grows in one place.

Proxy Deployment

Both proxy examples handle secure API key management, CORS, and multiple flow configurations.

  • runtype-hono-proxy: Runtype API proxy on Hono. Node (pnpm dev), Vercel (api/), or Cloudflare Workers (pnpm dev:runtype-workers)

Publishing

This monorepo uses Changesets for version management.

pnpm changeset            # create a changeset after making changes
pnpm changeset version    # bump versions and generate changelogs
pnpm release              # maintainer-only release workflow

See packages/widget/README.md for the full configuration reference.

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.