@lofcz/orb-ui 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/dist/adapters/audio-level.d.ts +28 -0
- package/dist/adapters/elevenlabs/index.d.ts +85 -0
- package/dist/adapters/gemini-live/index.d.ts +70 -0
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/livekit/browser.d.ts +49 -0
- package/dist/adapters/livekit/index.d.ts +136 -0
- package/dist/adapters/openai-realtime/index.d.ts +37 -0
- package/dist/adapters/pipecat/index.d.ts +62 -0
- package/dist/adapters/types.d.ts +7 -0
- package/dist/adapters/vapi/index.d.ts +42 -0
- package/dist/adapters.cjs +1 -0
- package/dist/adapters.js +829 -0
- package/dist/components/Orb/Orb.d.ts +2 -0
- package/dist/components/Orb/Orb.types.d.ts +87 -0
- package/dist/components/Orb/index.d.ts +2 -0
- package/dist/components/Orb/signals.d.ts +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/livekit-D-s4sYba.cjs +1 -0
- package/dist/livekit-Z3IMmWaG.js +316 -0
- package/dist/livekit-adapter.cjs +1 -0
- package/dist/livekit-adapter.js +30 -0
- package/dist/orb-ui.cjs +203 -0
- package/dist/orb-ui.js +984 -0
- package/dist/themes/bars/BarsTheme.d.ts +14 -0
- package/dist/themes/bars/index.d.ts +1 -0
- package/dist/themes/circle/CircleTheme.d.ts +14 -0
- package/dist/themes/circle/index.d.ts +1 -0
- package/dist/themes/cloud/CloudTheme.d.ts +14 -0
- package/dist/themes/cloud/index.d.ts +1 -0
- package/dist/themes/debug/DebugTheme.d.ts +15 -0
- package/dist/themes/debug/index.d.ts +1 -0
- package/dist/themes/index.d.ts +5 -0
- package/dist/themes/radial/RadialTheme.d.ts +13 -0
- package/dist/themes/radial/index.d.ts +1 -0
- package/package.json +122 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Chen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# orb-ui
|
|
2
|
+
|
|
3
|
+
**Voice agent UI that feels alive.**
|
|
4
|
+
|
|
5
|
+
Expressive, accessible React components for realtime voice agents. Connect Vapi, ElevenLabs, LiveKit, Pipecat, OpenAI Realtime, Gemini Live, or your own voice stack through one consistent UI layer.
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://orb-ui.com">
|
|
9
|
+
<img src="demo/public/og-image-v2.jpg" alt="orb-ui — Voice agent UI that feels alive" width="900" />
|
|
10
|
+
</a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://orb-ui.com">Live demo</a> · <a href="https://orb-ui.com/docs">Documentation</a> · <a href="https://orb-ui.com/playground">Playground</a> · <a href="https://www.npmjs.com/package/@lofcz/orb-ui">npm</a> · <a href="https://github.com/lofcz/orb-ui">Star on GitHub</a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
import Vapi from '@vapi-ai/web'
|
|
19
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
20
|
+
import { createVapiAdapter } from '@lofcz/orb-ui/adapters'
|
|
21
|
+
|
|
22
|
+
const vapi = new Vapi('your-public-key')
|
|
23
|
+
const adapter = createVapiAdapter(vapi, { assistantId: 'your-assistant-id' })
|
|
24
|
+
|
|
25
|
+
export function VoiceOrb() {
|
|
26
|
+
return <Orb adapter={adapter} theme="circle" aria-label="Start voice assistant" />
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Install the component package:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bun add @lofcz/orb-ui
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Provider adapters are lightweight wrappers around provider SDKs. Install the SDK for the provider you use:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Vapi
|
|
42
|
+
bun add @lofcz/orb-ui @vapi-ai/web
|
|
43
|
+
|
|
44
|
+
# ElevenLabs Conversational AI
|
|
45
|
+
bun add @lofcz/orb-ui @elevenlabs/client
|
|
46
|
+
|
|
47
|
+
# LiveKit Agents
|
|
48
|
+
bun add @lofcz/orb-ui livekit-client
|
|
49
|
+
|
|
50
|
+
# Pipecat (choose the transport used by your agent)
|
|
51
|
+
bun add @lofcz/orb-ui @pipecat-ai/client-js @pipecat-ai/small-webrtc-transport
|
|
52
|
+
|
|
53
|
+
# OpenAI Realtime uses browser WebRTC and has no additional client SDK
|
|
54
|
+
bun add @lofcz/orb-ui
|
|
55
|
+
|
|
56
|
+
# Gemini Live
|
|
57
|
+
bun add @lofcz/orb-ui @google/genai
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> **Note:** Orb uses React hooks internally — in Next.js App Router, use it in a `'use client'` component.
|
|
61
|
+
|
|
62
|
+
## How provider adapters are created
|
|
63
|
+
|
|
64
|
+
Every provider ends at the same React API:
|
|
65
|
+
|
|
66
|
+
```jsx
|
|
67
|
+
<Orb adapter={adapter} theme="circle" aria-label="Start voice assistant" />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The only difference is how the adapter obtains a provider session:
|
|
71
|
+
|
|
72
|
+
| Provider | Required browser setup |
|
|
73
|
+
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
74
|
+
| [Vapi guide](https://orb-ui.com/docs/adapters/vapi) | Pass a configured Vapi client plus `assistantId` |
|
|
75
|
+
| [ElevenLabs guide](https://orb-ui.com/docs/adapters/elevenlabs) | Pass `Conversation` plus an `agentId`, signed URL, or conversation token |
|
|
76
|
+
| [LiveKit guide](https://orb-ui.com/docs/adapters/livekit) | Provide a token endpoint and optional agent name |
|
|
77
|
+
| [Pipecat guide](https://orb-ui.com/docs/adapters/pipecat) | Pass a configured `PipecatClient` plus its connect callback |
|
|
78
|
+
| [OpenAI Realtime guide](https://orb-ui.com/docs/adapters/openai-realtime) | Return a fresh short-lived client secret from `getClientSecret` |
|
|
79
|
+
| [Gemini Live guide](https://orb-ui.com/docs/adapters/gemini-live) | Open the official Google Live session in `connect` |
|
|
80
|
+
|
|
81
|
+
The adapter owns provider event mapping and emits one consistent `OrbSignal`. OpenAI and Gemini
|
|
82
|
+
standard API keys, and LiveKit participant-token signing, stay on your server. See the
|
|
83
|
+
[adapter overview](https://orb-ui.com/docs/adapters/overview) for the responsibility boundary and
|
|
84
|
+
advanced setup shapes.
|
|
85
|
+
|
|
86
|
+
For an app-owned WebRTC, WebSocket, telephony, or speech runtime, follow the
|
|
87
|
+
[custom integration guide](https://orb-ui.com/docs/adapters/custom).
|
|
88
|
+
|
|
89
|
+
## Quick Start
|
|
90
|
+
|
|
91
|
+
Use orb-ui as a React voice AI component when you need a first-party provider voice UI or a custom animated voice orb for another realtime voice agent stack.
|
|
92
|
+
|
|
93
|
+
### With Vapi
|
|
94
|
+
|
|
95
|
+
```jsx
|
|
96
|
+
import Vapi from '@vapi-ai/web'
|
|
97
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
98
|
+
import { createVapiAdapter } from '@lofcz/orb-ui/adapters'
|
|
99
|
+
|
|
100
|
+
const vapi = new Vapi('your-public-key')
|
|
101
|
+
const adapter = createVapiAdapter(vapi, { assistantId: 'your-assistant-id' })
|
|
102
|
+
|
|
103
|
+
function App() {
|
|
104
|
+
return <Orb adapter={adapter} theme="circle" aria-label="Start Vapi assistant" />
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### With ElevenLabs
|
|
109
|
+
|
|
110
|
+
```jsx
|
|
111
|
+
import { Conversation } from '@elevenlabs/client'
|
|
112
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
113
|
+
import { createElevenLabsAdapter } from '@lofcz/orb-ui/adapters'
|
|
114
|
+
|
|
115
|
+
const adapter = createElevenLabsAdapter(Conversation, { agentId: 'your-agent-id' })
|
|
116
|
+
|
|
117
|
+
function App() {
|
|
118
|
+
return <Orb adapter={adapter} theme="circle" aria-label="Start ElevenLabs assistant" />
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### With LiveKit
|
|
123
|
+
|
|
124
|
+
```jsx
|
|
125
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
126
|
+
import { createLiveKitAdapter } from '@lofcz/orb-ui/adapters/livekit'
|
|
127
|
+
|
|
128
|
+
const adapter = createLiveKitAdapter({
|
|
129
|
+
tokenEndpoint: '/api/livekit-token',
|
|
130
|
+
agentName: 'your-agent-name',
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
function App() {
|
|
134
|
+
return <Orb adapter={adapter} theme="circle" aria-label="Start LiveKit assistant" />
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The LiveKit entrypoint creates the room and token source, assigns a fresh room name, and meters both
|
|
139
|
+
sides of the conversation with speech-oriented analyser and smoothing defaults. Existing-room and
|
|
140
|
+
custom-runtime modes remain available from the advanced `@lofcz/orb-ui/adapters` entrypoint.
|
|
141
|
+
|
|
142
|
+
### With Pipecat
|
|
143
|
+
|
|
144
|
+
```jsx
|
|
145
|
+
import { PipecatClient } from '@pipecat-ai/client-js'
|
|
146
|
+
import { SmallWebRTCTransport } from '@pipecat-ai/small-webrtc-transport'
|
|
147
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
148
|
+
import { createPipecatAdapter } from '@lofcz/orb-ui/adapters'
|
|
149
|
+
|
|
150
|
+
const client = new PipecatClient({ transport: new SmallWebRTCTransport(), enableMic: true })
|
|
151
|
+
const adapter = createPipecatAdapter(client, {
|
|
152
|
+
connect: () => client.connect({ webrtcUrl: 'https://agent.example.com/api/offer' }),
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
function App() {
|
|
156
|
+
return <Orb adapter={adapter} theme="circle" aria-label="Start Pipecat assistant" />
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The Pipecat adapter consumes the standard RTVI event surface and meters the client media tracks as
|
|
161
|
+
a browser fallback, so it works with Pipecat Cloud, Daily, SmallWebRTC, and transports that emit
|
|
162
|
+
sparse audio-level events. See the [Pipecat guide](https://orb-ui.com/docs/adapters/pipecat).
|
|
163
|
+
|
|
164
|
+
### With OpenAI Realtime
|
|
165
|
+
|
|
166
|
+
```jsx
|
|
167
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
168
|
+
import { createOpenAIRealtimeAdapter } from '@lofcz/orb-ui/adapters'
|
|
169
|
+
|
|
170
|
+
const adapter = createOpenAIRealtimeAdapter({
|
|
171
|
+
getClientSecret: async () => {
|
|
172
|
+
const response = await fetch('/api/openai-realtime-token', { method: 'POST' })
|
|
173
|
+
return (await response.json()).value
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Create client secrets with a standard OpenAI API key on your server. See the
|
|
179
|
+
[OpenAI Realtime guide](https://orb-ui.com/docs/adapters/openai-realtime).
|
|
180
|
+
|
|
181
|
+
### With Gemini Live
|
|
182
|
+
|
|
183
|
+
```jsx
|
|
184
|
+
import { GoogleGenAI } from '@google/genai'
|
|
185
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
186
|
+
import { createGeminiLiveAdapter } from '@lofcz/orb-ui/adapters'
|
|
187
|
+
|
|
188
|
+
const adapter = createGeminiLiveAdapter({
|
|
189
|
+
connect: async (callbacks) => {
|
|
190
|
+
const token = await fetch('/api/gemini-live-token', { method: 'POST' }).then((res) =>
|
|
191
|
+
res.json(),
|
|
192
|
+
)
|
|
193
|
+
const client = new GoogleGenAI({
|
|
194
|
+
apiKey: token.value,
|
|
195
|
+
httpOptions: { apiVersion: 'v1alpha' },
|
|
196
|
+
})
|
|
197
|
+
return client.live.connect({ model: token.model, config: token.config, callbacks })
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Mint one-use Gemini Live tokens on your server. See the
|
|
203
|
+
[Gemini Live guide](https://orb-ui.com/docs/adapters/gemini-live) for the matching server config that
|
|
204
|
+
disables automatic activity detection. The adapter handles client-side turn detection by default.
|
|
205
|
+
|
|
206
|
+
The examples above show the intended happy paths. Transport overrides, custom browser runtimes,
|
|
207
|
+
existing-session modes, and audio calibration hooks are optional and documented in the individual
|
|
208
|
+
adapter guides.
|
|
209
|
+
|
|
210
|
+
### Controlled mode (custom integration)
|
|
211
|
+
|
|
212
|
+
```jsx
|
|
213
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
214
|
+
import { useState } from 'react'
|
|
215
|
+
|
|
216
|
+
function App() {
|
|
217
|
+
const [state, setState] = useState('idle')
|
|
218
|
+
const [volume, setVolume] = useState(0)
|
|
219
|
+
|
|
220
|
+
return <Orb state={state} volume={volume} theme="circle" />
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Use `signal` when your integration has separate input and output levels:
|
|
225
|
+
|
|
226
|
+
```jsx
|
|
227
|
+
import { Orb } from '@lofcz/orb-ui'
|
|
228
|
+
|
|
229
|
+
function App() {
|
|
230
|
+
return <Orb signal={{ state: 'speaking', outputVolume: 0.7 }} theme="circle" />
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### External session controls
|
|
235
|
+
|
|
236
|
+
Keep the orb as a passive visual when start and stop controls belong elsewhere in your layout.
|
|
237
|
+
Provider adapters already expose the matching lifecycle methods.
|
|
238
|
+
|
|
239
|
+
```jsx
|
|
240
|
+
function VoiceExperience({ adapter }) {
|
|
241
|
+
return (
|
|
242
|
+
<>
|
|
243
|
+
<Orb adapter={adapter} theme="cloud" interactive={false} />
|
|
244
|
+
<button onClick={() => void adapter.start?.()}>Start conversation</button>
|
|
245
|
+
<button onClick={() => void adapter.stop?.()}>End conversation</button>
|
|
246
|
+
</>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Themes
|
|
252
|
+
|
|
253
|
+
| Theme | Description |
|
|
254
|
+
| -------- | ----------------------------------------------------------------------------- |
|
|
255
|
+
| `radial` | Four-lobe radial field with input-reactive rim and output-reactive twisting. |
|
|
256
|
+
| `cloud` | Atmospheric sphere with inverse listening scale and faster speaking motion. |
|
|
257
|
+
| `debug` | State + volume display with start/stop. Use to verify your integration works. |
|
|
258
|
+
| `circle` | Pulsing circle that reacts to volume. |
|
|
259
|
+
| `bars` | Five bars that animate with voice. |
|
|
260
|
+
|
|
261
|
+
When an adapter or `onStart`/`onStop` handler is provided, visual themes include keyboard-accessible `<button type="button">` controls. `radial` places its phone control below the artwork while the other clickable themes use the visual itself. Pass `interactive={false}` to keep the theme passive and call `adapter.start()` or `adapter.stop()` from external controls instead.
|
|
262
|
+
|
|
263
|
+
The radial phone control uses a small cutout that defaults to white. Match it to the surface behind
|
|
264
|
+
the orb with the typed style variable:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<Orb adapter={adapter} theme="radial" style={{ '--orb-ui-radial-control-surround': '#101010' }} />
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Props
|
|
271
|
+
|
|
272
|
+
| Prop | Type | Default | Description |
|
|
273
|
+
| ------------- | ------------------------------------------------------ | --------- | ------------------------------------------------------- |
|
|
274
|
+
| `theme` | `'debug' \| 'circle' \| 'bars' \| 'cloud' \| 'radial'` | `'debug'` | Visual theme |
|
|
275
|
+
| `signal` | `OrbSignal` | — | Rich controlled signal with state/input/output volume |
|
|
276
|
+
| `state` | `OrbState` | `'idle'` | Conversation state (controlled mode) |
|
|
277
|
+
| `volume` | `number` | `0` | Audio volume, 0–1. Overrides signal/adapter volume. |
|
|
278
|
+
| `adapter` | `OrbAdapter` | — | Provider adapter (manages signal updates automatically) |
|
|
279
|
+
| `size` | `number` | `200` | Size in pixels |
|
|
280
|
+
| `className` | `string` | — | Optional class name for the rendered theme |
|
|
281
|
+
| `style` | `OrbStyle` | — | Inline styles, including radial control surround color |
|
|
282
|
+
| `disabled` | `boolean` | `false` | Disables clickable themes and debug start/stop controls |
|
|
283
|
+
| `interactive` | `boolean` | `true` | Allows the theme control to start and stop a session |
|
|
284
|
+
| `aria-label` | `string` | generated | Accessible label for clickable visual themes |
|
|
285
|
+
| `onStart` | `() => void` | — | Custom start handler (overrides adapter.start()) |
|
|
286
|
+
| `onStop` | `() => void` | — | Custom stop handler (overrides adapter.stop()) |
|
|
287
|
+
|
|
288
|
+
## States
|
|
289
|
+
|
|
290
|
+
`idle` · `connecting` · `listening` · `thinking` · `speaking` · `error`
|
|
291
|
+
|
|
292
|
+
## Supported Providers
|
|
293
|
+
|
|
294
|
+
| Provider | Adapter |
|
|
295
|
+
| ------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
296
|
+
| [Vapi](https://vapi.ai) | `createVapiAdapter` from `@lofcz/orb-ui/adapters` |
|
|
297
|
+
| [ElevenLabs](https://elevenlabs.io/conversational-ai) | `createElevenLabsAdapter` from `@lofcz/orb-ui/adapters` |
|
|
298
|
+
| [LiveKit](https://livekit.io) | `createLiveKitAdapter` from `@lofcz/orb-ui/adapters` |
|
|
299
|
+
| [Pipecat](https://pipecat.ai) | `createPipecatAdapter` from `@lofcz/orb-ui/adapters` |
|
|
300
|
+
| [OpenAI Realtime](https://developers.openai.com/api/docs/guides/realtime) | `createOpenAIRealtimeAdapter` from `@lofcz/orb-ui/adapters` |
|
|
301
|
+
| [Gemini Live](https://ai.google.dev/gemini-api/docs/live-api) | `createGeminiLiveAdapter` from `@lofcz/orb-ui/adapters` |
|
|
302
|
+
| Custom | Use controlled mode — pass `signal`, or `state` and `volume` directly |
|
|
303
|
+
|
|
304
|
+
## Development
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
git clone https://github.com/lofcz/orb-ui.git
|
|
308
|
+
cd orb-ui
|
|
309
|
+
bun install
|
|
310
|
+
|
|
311
|
+
# Build the library
|
|
312
|
+
bun run build
|
|
313
|
+
|
|
314
|
+
# Run demo locally
|
|
315
|
+
bun run dev:demo
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Useful maintenance commands:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
bun run check # format check, lint, typechecks, tests, library build, demo build
|
|
322
|
+
bun run format # format repo files
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Releases are published as `@lofcz/orb-ui` from GitHub Actions (`release.yml`)
|
|
326
|
+
using npm trusted publishing (OIDC). Run **Release and Publish to npm** on `main`.
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
MIT © [Alexander Chen](https://github.com/alexanderqchen)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface OutputVolumeCalibration {
|
|
2
|
+
/** Raw RMS values at or below this level are treated as silence. */
|
|
3
|
+
noiseFloor: number;
|
|
4
|
+
/** Multiplier applied after the noise floor. */
|
|
5
|
+
gain: number;
|
|
6
|
+
/** Power curve applied after gain. Values below 1 lift quieter speech. */
|
|
7
|
+
exponent: number;
|
|
8
|
+
/** EMA rate used while the volume is rising. */
|
|
9
|
+
attack: number;
|
|
10
|
+
/** EMA rate used while the volume is falling. */
|
|
11
|
+
release: number;
|
|
12
|
+
}
|
|
13
|
+
export type OutputVolumeCalibrationSource = Partial<OutputVolumeCalibration> | (() => Partial<OutputVolumeCalibration>);
|
|
14
|
+
export interface OutputVolumeSample {
|
|
15
|
+
/** Unmodified RMS value measured from the provider audio stream. */
|
|
16
|
+
raw: number;
|
|
17
|
+
/** Value after noise floor, gain, and power-curve shaping. */
|
|
18
|
+
shaped: number;
|
|
19
|
+
/** Final value after attack/release smoothing. */
|
|
20
|
+
normalized: number;
|
|
21
|
+
}
|
|
22
|
+
export interface MediaStreamTrackVolumeMeter {
|
|
23
|
+
stop(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export type AudioContextSource = () => AudioContext | undefined;
|
|
26
|
+
export declare const DEFAULT_OUTPUT_VOLUME_CALIBRATION: OutputVolumeCalibration;
|
|
27
|
+
export declare function calibrateOutputVolume(rawValue: number, previous: number, source?: OutputVolumeCalibrationSource): OutputVolumeSample;
|
|
28
|
+
export declare function createMediaStreamTrackVolumeMeter(track: MediaStreamTrack, createAudioContext: AudioContextSource, onVolume: (volume: number) => void): MediaStreamTrackVolumeMeter | undefined;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { OrbAdapter } from '../types.js';
|
|
2
|
+
export type ElevenLabsMode = 'speaking' | 'listening';
|
|
3
|
+
export type ElevenLabsStatus = 'disconnected' | 'connecting' | 'connected' | 'disconnecting';
|
|
4
|
+
export type ElevenLabsConnectionType = 'websocket' | 'webrtc';
|
|
5
|
+
export interface ElevenLabsCallbacks {
|
|
6
|
+
onConnect?: (props: {
|
|
7
|
+
conversationId: string;
|
|
8
|
+
}) => void;
|
|
9
|
+
onDisconnect?: (details: unknown) => void;
|
|
10
|
+
onError?: (message: string, context?: unknown) => void;
|
|
11
|
+
onModeChange?: (prop: {
|
|
12
|
+
mode: ElevenLabsMode;
|
|
13
|
+
}) => void;
|
|
14
|
+
onStatusChange?: (prop: {
|
|
15
|
+
status: ElevenLabsStatus;
|
|
16
|
+
}) => void;
|
|
17
|
+
onVadScore?: (props: {
|
|
18
|
+
vadScore: number;
|
|
19
|
+
}) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface ElevenLabsConversation {
|
|
22
|
+
endSession(): Promise<void>;
|
|
23
|
+
getInputVolume(): number;
|
|
24
|
+
getOutputVolume(): number;
|
|
25
|
+
getInputByteFrequencyData(): Uint8Array;
|
|
26
|
+
getOutputByteFrequencyData(): Uint8Array;
|
|
27
|
+
}
|
|
28
|
+
type ElevenLabsSessionAuth = {
|
|
29
|
+
agentId: string;
|
|
30
|
+
signedUrl?: never;
|
|
31
|
+
conversationToken?: never;
|
|
32
|
+
connectionType?: ElevenLabsConnectionType;
|
|
33
|
+
} | {
|
|
34
|
+
signedUrl: string;
|
|
35
|
+
agentId?: never;
|
|
36
|
+
conversationToken?: never;
|
|
37
|
+
connectionType?: 'websocket';
|
|
38
|
+
} | {
|
|
39
|
+
conversationToken: string;
|
|
40
|
+
agentId?: never;
|
|
41
|
+
signedUrl?: never;
|
|
42
|
+
connectionType?: 'webrtc';
|
|
43
|
+
};
|
|
44
|
+
export type ElevenLabsConfig = ElevenLabsSessionAuth & {
|
|
45
|
+
/** orb-ui expects a voice conversation, so text-only sessions are intentionally unsupported. */
|
|
46
|
+
textOnly?: false;
|
|
47
|
+
/** Allow @elevenlabs/client startSession options that orb-ui does not inspect. */
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
};
|
|
50
|
+
export type ElevenLabsStartSessionOptions = ElevenLabsConfig & ElevenLabsCallbacks;
|
|
51
|
+
export interface ElevenLabsConversationClass {
|
|
52
|
+
startSession(options: ElevenLabsStartSessionOptions): Promise<ElevenLabsConversation>;
|
|
53
|
+
}
|
|
54
|
+
export interface ElevenLabsOrbAdapter extends OrbAdapter {
|
|
55
|
+
/** Start an ElevenLabs conversation. Called automatically by Orb on click. */
|
|
56
|
+
start(): Promise<void>;
|
|
57
|
+
/** Stop the current ElevenLabs conversation. Called automatically by Orb on click. */
|
|
58
|
+
stop(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Creates an OrbAdapter for ElevenLabs Conversational AI.
|
|
62
|
+
*
|
|
63
|
+
* Unlike createVapiAdapter, this adapter owns the session lifecycle because
|
|
64
|
+
* ElevenLabs requires callbacks to be injected at Conversation.startSession()
|
|
65
|
+
* time. Pass the returned adapter to Orb and Orb will call start()/stop()
|
|
66
|
+
* when the clickable theme is activated.
|
|
67
|
+
*
|
|
68
|
+
* @param ConversationClass - The Conversation class from @elevenlabs/client
|
|
69
|
+
* @param config - agentId / signedUrl + any other startSession options
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* import { Conversation } from '@elevenlabs/client'
|
|
73
|
+
* import { Orb } from '@lofcz/orb-ui'
|
|
74
|
+
* import { createElevenLabsAdapter } from '@lofcz/orb-ui/adapters'
|
|
75
|
+
*
|
|
76
|
+
* const adapter = createElevenLabsAdapter(Conversation, {
|
|
77
|
+
* agentId: 'your-agent-id',
|
|
78
|
+
* })
|
|
79
|
+
*
|
|
80
|
+
* function App() {
|
|
81
|
+
* return <Orb adapter={adapter} theme="circle" aria-label="Start ElevenLabs assistant" />
|
|
82
|
+
* }
|
|
83
|
+
*/
|
|
84
|
+
export declare function createElevenLabsAdapter(ConversationClass: ElevenLabsConversationClass, config: ElevenLabsConfig): ElevenLabsOrbAdapter;
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { OrbAdapter } from '../types.js';
|
|
2
|
+
import { OutputVolumeCalibrationSource, OutputVolumeSample } from '../audio-level.js';
|
|
3
|
+
export interface GeminiLiveInlineData {
|
|
4
|
+
data?: string;
|
|
5
|
+
mimeType?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface GeminiLiveServerMessage {
|
|
8
|
+
setupComplete?: unknown;
|
|
9
|
+
serverContent?: {
|
|
10
|
+
modelTurn?: {
|
|
11
|
+
parts?: Array<{
|
|
12
|
+
inlineData?: GeminiLiveInlineData;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
turnComplete?: boolean;
|
|
16
|
+
generationComplete?: boolean;
|
|
17
|
+
interrupted?: boolean;
|
|
18
|
+
waitingForInput?: boolean;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface GeminiLiveCallbacks {
|
|
22
|
+
onopen?: () => void;
|
|
23
|
+
onmessage: (message: GeminiLiveServerMessage) => void;
|
|
24
|
+
onerror?: (error: unknown) => void;
|
|
25
|
+
onclose?: (event: unknown) => void;
|
|
26
|
+
}
|
|
27
|
+
export interface GeminiLiveSession {
|
|
28
|
+
sendRealtimeInput(input: {
|
|
29
|
+
audio?: {
|
|
30
|
+
data: string;
|
|
31
|
+
mimeType: string;
|
|
32
|
+
};
|
|
33
|
+
audioStreamEnd?: boolean;
|
|
34
|
+
activityStart?: Record<string, never>;
|
|
35
|
+
activityEnd?: Record<string, never>;
|
|
36
|
+
}): void;
|
|
37
|
+
close(): void;
|
|
38
|
+
}
|
|
39
|
+
export interface GeminiLiveAdapterConfig {
|
|
40
|
+
/** Connects an official Google GenAI Live session using the supplied callbacks. */
|
|
41
|
+
connect(callbacks: GeminiLiveCallbacks): Promise<GeminiLiveSession>;
|
|
42
|
+
/** Defaults to `{ audio: true }`. */
|
|
43
|
+
mediaStreamConstraints?: MediaStreamConstraints;
|
|
44
|
+
/** Defaults to 16 kHz PCM input, as recommended by Gemini Live. */
|
|
45
|
+
inputSampleRate?: number;
|
|
46
|
+
/** Defaults to 0.04 normalized input volume. */
|
|
47
|
+
speechThreshold?: number;
|
|
48
|
+
/** Defaults to 500 ms before listening transitions to thinking. */
|
|
49
|
+
speechEndDelayMs?: number;
|
|
50
|
+
/** Defaults to client-side detection. Use `server` with Gemini automatic activity detection. */
|
|
51
|
+
activityDetection?: 'client' | 'server';
|
|
52
|
+
/** Runtime override for tests or custom browser wrappers. */
|
|
53
|
+
getUserMedia?: (constraints: MediaStreamConstraints) => Promise<MediaStream>;
|
|
54
|
+
/** Runtime override for tests or custom browser wrappers. */
|
|
55
|
+
createAudioContext?: () => AudioContext;
|
|
56
|
+
/** Optional live-tunable output shaping. A getter is read for every meter sample. */
|
|
57
|
+
outputVolumeCalibration?: OutputVolumeCalibrationSource;
|
|
58
|
+
/** Receives raw, shaped, and smoothed output levels for diagnostics. */
|
|
59
|
+
onOutputVolumeSample?: (sample: OutputVolumeSample) => void;
|
|
60
|
+
}
|
|
61
|
+
export interface GeminiLiveOrbAdapter extends OrbAdapter {
|
|
62
|
+
start(): Promise<void>;
|
|
63
|
+
stop(): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates a managed browser-audio adapter for Gemini Live. The app owns token
|
|
67
|
+
* creation and the official GenAI client; orb-ui owns microphone streaming,
|
|
68
|
+
* PCM playback, volume metering, and normalized voice state.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createGeminiLiveAdapter(config: GeminiLiveAdapterConfig): GeminiLiveOrbAdapter;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createVapiAdapter } from './vapi/index.js';
|
|
2
|
+
export { createElevenLabsAdapter, type ElevenLabsCallbacks, type ElevenLabsConfig, type ElevenLabsConnectionType, type ElevenLabsConversation, type ElevenLabsConversationClass, type ElevenLabsMode, type ElevenLabsOrbAdapter, type ElevenLabsStartSessionOptions, type ElevenLabsStatus, } from './elevenlabs/index.js';
|
|
3
|
+
export { createLiveKitAdapter, type LiveKitAdapterConfig, type LiveKitConnectionDetails, type LiveKitOrbAdapter, type LiveKitResolvedTokenOptions, type LiveKitTokenOptions, type LiveKitTokenSource, } from './livekit/index.js';
|
|
4
|
+
export { createPipecatAdapter, type PipecatAdapterOptions, type PipecatClientLike, type PipecatOrbAdapter, type PipecatParticipantLike, type PipecatTracksLike, } from './pipecat/index.js';
|
|
5
|
+
export { createOpenAIRealtimeAdapter, type OpenAIRealtimeAdapterConfig, type OpenAIRealtimeClientSecret, type OpenAIRealtimeOrbAdapter, } from './openai-realtime/index.js';
|
|
6
|
+
export { createGeminiLiveAdapter, type GeminiLiveAdapterConfig, type GeminiLiveCallbacks, type GeminiLiveInlineData, type GeminiLiveOrbAdapter, type GeminiLiveServerMessage, type GeminiLiveSession, } from './gemini-live/index.js';
|
|
7
|
+
export { DEFAULT_OUTPUT_VOLUME_CALIBRATION, type OutputVolumeCalibration, type OutputVolumeCalibrationSource, type OutputVolumeSample, } from './audio-level.js';
|
|
8
|
+
export type { OrbAdapter, OrbSignal, OrbSignalListener, OrbState } from './types.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { LiveKitOrbAdapter, LiveKitTokenOptions } from './index.js';
|
|
2
|
+
import { OutputVolumeCalibrationSource, OutputVolumeSample } from '../audio-level.js';
|
|
3
|
+
interface LiveKitBrowserAdapterBase extends Omit<LiveKitTokenOptions, 'agentName' | 'roomName'> {
|
|
4
|
+
/** Agent dispatched into the room. Required for LiveKit Cloud sandbox sessions. */
|
|
5
|
+
agentName?: LiveKitTokenOptions['agentName'];
|
|
6
|
+
/** Defaults to a fresh `orb-<uuid>` room name for every start. */
|
|
7
|
+
roomName?: LiveKitTokenOptions['roomName'];
|
|
8
|
+
/** LiveKit room.connect options passed through unchanged. */
|
|
9
|
+
connectOptions?: Record<string, unknown>;
|
|
10
|
+
/** Enable the local microphone after connecting. Defaults to true. */
|
|
11
|
+
enableMicrophone?: boolean;
|
|
12
|
+
/** Optional live-tunable output shaping. A getter is read for every meter sample. */
|
|
13
|
+
outputVolumeCalibration?: OutputVolumeCalibrationSource;
|
|
14
|
+
/** Receives raw, shaped, and smoothed output levels for diagnostics. */
|
|
15
|
+
onOutputVolumeSample?: (sample: OutputVolumeSample) => void;
|
|
16
|
+
}
|
|
17
|
+
export type LiveKitTokenEndpointOptions = Omit<RequestInit, 'body'>;
|
|
18
|
+
export interface LiveKitSandboxOptions {
|
|
19
|
+
/** Override LiveKit's default Cloud API base URL. */
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface LiveKitEndpointAdapterConfig extends LiveKitBrowserAdapterBase {
|
|
23
|
+
/** LiveKit TokenSource endpoint returning `{ server_url, participant_token }`. */
|
|
24
|
+
tokenEndpoint: string;
|
|
25
|
+
/** Optional fetch settings such as authenticated request headers. */
|
|
26
|
+
tokenEndpointOptions?: LiveKitTokenEndpointOptions;
|
|
27
|
+
sandboxId?: never;
|
|
28
|
+
sandboxOptions?: never;
|
|
29
|
+
}
|
|
30
|
+
export interface LiveKitSandboxAdapterConfig extends LiveKitBrowserAdapterBase {
|
|
31
|
+
/** LiveKit Cloud sandbox token server ID. Sandbox mode is intended for testing only. */
|
|
32
|
+
sandboxId: string;
|
|
33
|
+
/** The agent to dispatch into the sandbox room. */
|
|
34
|
+
agentName: NonNullable<LiveKitTokenOptions['agentName']>;
|
|
35
|
+
/** Optional LiveKit sandbox host overrides. */
|
|
36
|
+
sandboxOptions?: LiveKitSandboxOptions;
|
|
37
|
+
tokenEndpoint?: never;
|
|
38
|
+
tokenEndpointOptions?: never;
|
|
39
|
+
}
|
|
40
|
+
export type LiveKitBrowserAdapterConfig = LiveKitEndpointAdapterConfig | LiveKitSandboxAdapterConfig;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a managed LiveKit Agents adapter using the official browser SDK.
|
|
43
|
+
*
|
|
44
|
+
* This entrypoint owns the LiveKit Room, token source, microphone, playback,
|
|
45
|
+
* and audio analysers. Import the advanced factory from `@lofcz/orb-ui/adapters`
|
|
46
|
+
* only when your application already owns those pieces.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createLiveKitAdapter(config: LiveKitBrowserAdapterConfig): LiveKitOrbAdapter;
|
|
49
|
+
export type { LiveKitOrbAdapter } from './index.js';
|