D Diagent docs

Embed the widget

Voice, leads & persistence

The widget is more than a chat box. It captures leads inline, transcribes voice, persists across reloads, and shows a live "human is here" state when an operator takes over. This page covers each feature and how to configure it.

Conversation persistence

Every visitor gets an anon_id (a random string written to localStorage on first visit). On reload, the widget calls /v1/widget/init with the same anon_id and the server resumes the most recent conversation if it's less than 24 hours old.

The init response includes the last 30 messages so the chat log rehydrates in the order the visitor left it. The "Clear conversation" button (in the widget header) writes a cleared_at timestamp on the conversation row β€” past messages stay in the database for analytics + lead linkage but stop being shown to the visitor.

Voice mic

A microphone button in the input area lets visitors dictate instead of type. The widget uses the browser's built-in SpeechRecognition API β€” no server-side speech model. While recording the mic shows a sonar-ring animation; clicking again stops and inserts the transcript into the input box (appended if there's already text there, so you can dictate, edit, then dictate more).

Browsers without SpeechRecognition (Firefox, older Safari) don't show the mic button. There's no fallback β€” the feature is opportunistic.

Lead capture

The widget can collect contact info inline without forcing the visitor away from the conversation. Lead capture fires when:

  • A behavior rule of kind=lead_capture matches.
  • The visitor explicitly asks to be contacted ("can someone call me?", "email me a quote").
  • You wire a CTA button to the lead_capture action.

The form fields are configured per agent. The default is name + email; you can add phone, company, and custom fields. Submitted leads are POSTed to /v1/widget/leads (rate-limited per JWT) and appear immediately in /app/inbox (the per-workspace lead list).

Smart capture from intent

The latest update (commit 9190aa5) adds intent-based capture: the widget watches the conversation for phrases that suggest a real sales intent β€” pricing questions, "is this right for…", "can I demo…" β€” and offers the lead form proactively after a few turns. Threshold and phrase list are tunable per agent.

Human takeover

When a workspace operator claims a conversation in /app/inbox, the widget receives a Reverb event (conversation.takeover) and updates the chat header to show a "Human is here" badge. From that point, the AI stays paused β€” every visitor message goes to the operator, every operator reply streams to the visitor. The visitor sees one continuous thread; under the hood the message role flips from assistant to human-agent and back.

See Inbox & human takeover for the operator side.

Citations

Whenever the agent answers from retrieved sources, citation chips appear below the message. Click one to open the source URL in a new tab. The chips are numbered ([1], [2]) matching inline references in the response text β€” visitors who care can verify the answer; visitors who don't see a clean reply.

Curated answers can include an optional citation URL too β€” useful when the canned answer is sourced from a specific page.

Streaming

Messages stream token-by-token over Server-Sent Events. The widget reads the stream and appends tokens to the DOM in real time. If the stream errors mid-flight (network blip, LLM timeout), the widget auto-retries up to 3 times before showing an error state β€” and only one user bubble appears even on retries (commit a576e1c).

Branding

The widget footer shows a "Powered by Pitchbar" link by default. It's hidden for workspaces on a plan with the remove_branding feature flag enabled β€” see Billing & plans.

The brand label, URL, and logo all come from platform-admin configuration (config('branding.*') + the optional app_settings singleton overrides), so a self-hosted deployment can rebrand the footer entirely.

Storage

The widget uses localStorage for:

  • anon_id β€” persistent visitor identifier.
  • Conversation cleared-state (which message IDs the visitor has hidden via "Clear").

No personally identifiable data is stored client-side. The JWT itself lives in memory β€” it's re-issued on every init.