GitHub 0

Conversation Bar

Previous Next

A compact conversation input bar with voice and text modes. Provider-agnostic — pass a ConversationAdapter wired to ElevenLabs, OpenAI Realtime, or any voice/chat backend.

Customer Support

ConversationBar is provider-agnostic — pass a ConversationAdapter wired to ElevenLabs, OpenAI Realtime, or any other voice/chat backend. This demo has no backend hooked up.

Installation

npx shadcn-svelte@latest add https://sv11.ui.twango.dev/r/conversation-bar.json

Usage

<script lang="ts">
	import { ConversationBar } from "$lib/registry/ui/conversation-bar";
</script>
 
<ConversationBar />

<ConversationBar> requires an adapter prop — see Providers for the interface and Adapters for provider recipes.

Examples

Basic Usage

Pass any object that matches ConversationAdapter and wire up the lifecycle callbacks you care about.

<script lang="ts">
	import { ConversationBar } from "$lib/registry/ui/conversation-bar";
	import type { ConversationAdapter } from "$lib/registry/ui/conversation-bar";
 
	const adapter: ConversationAdapter = createMyAdapter(/* ... */);
</script>
 
<ConversationBar
	{adapter}
	onConnect={() => console.log("Connected")}
	onDisconnect={() => console.log("Disconnected")}
	onMessage={(message) => console.log("Message:", message)}
	onError={(error) => console.error("Error:", error)}
/>

Custom Styling

Merge extra classes onto the container with class, and onto the inner waveform container with waveformClassName.

<ConversationBar
	{adapter}
	class="max-w-2xl"
	waveformClassName="bg-gradient-to-r from-blue-500 to-purple-500"
/>

Custom Disconnected Label

Replace the placeholder text shown in the waveform slot while the session is idle.

<ConversationBar {adapter} disconnectedText="Tap to call support" />

Collecting Messages

Accumulate the incoming ConversationMessage stream into reactive state — handy for pairing ConversationBar with a transcript or chat view.

<script lang="ts">
	import { ConversationBar } from "$lib/registry/ui/conversation-bar";
	import type { ConversationAdapter, ConversationMessage } from "$lib/registry/ui/conversation-bar";
 
	const adapter: ConversationAdapter = createMyAdapter(/* ... */);
	let messages: ConversationMessage[] = $state([]);
</script>
 
<ConversationBar {adapter} onMessage={(m) => (messages = [...messages, m])} />

API Reference

Prop Type Default Description
adapter ConversationAdapter — Backend bridge that owns the session lifecycle — connecting, streaming messages, and muting. Conforms to ConversationAdapter.
disconnectedText? string "Customer Support" Label shown in the waveform slot while the session is disconnected.
waveformClassName? string — Extra classes merged onto the inner waveform container.
onConnect? () => void — Fired after the adapter reports a successful connection.
onDisconnect? () => void — Fired after the adapter reports disconnection.
onError? (error: Error) => void — Fired when the adapter surfaces an error or connect() rejects.
onMessage? (message: ConversationMessage) => void — Fired for each message emitted by the adapter (both user and AI turns).
onSendMessage? (message: string) => void — Fired after a text-input message has been handed off to the adapter.
ref? HTMLDivElement | null $bindable(null) Bindable ref to the root &lt;div&gt; element.

Notes

  • Every method on ConversationAdapter is driven from user interaction: the mic, keyboard, phone, and send buttons call setMuted, sendContextualUpdate, disconnect/connect, and sendMessage respectively.
  • The component tracks an internal sessionId so late callbacks from a prior connect() are ignored — swapping adapters or rapid connect/disconnect cycles cannot leak stale state into the UI.
  • Text input fires sendContextualUpdate(text) on every keystroke while the keyboard is open, and sendMessage(text) on Enter (shift-enter inserts a newline). Adapters that do not implement contextual updates can make sendContextualUpdate a no-op.
  • The waveform visualizes the active microphone stream while connected && !muted. The mic, keyboard, and end-call buttons are disabled outside the connected state.
  • onDestroy calls adapter.disconnect() if a session is still active, so unmounting the component always releases the underlying resources.
  • adapter is a required prop on ConversationBarProps, so TypeScript will flag its omission at compile time. At runtime, pressing the phone button without an adapter would throw from connect() — the demo on this page uses a stub adapter that always rejects there to illustrate the onError path.