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.