@pinecall/skills 0.1.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/README.md +65 -0
- package/build.mjs +204 -0
- package/package.json +29 -0
- package/skills/pinecall-concepts/SKILL.md +41 -0
- package/skills/pinecall-concepts/references/concepts/agents-and-channels.md +155 -0
- package/skills/pinecall-concepts/references/concepts/deployment-topologies.md +120 -0
- package/skills/pinecall-concepts/references/concepts/hot-reload.md +119 -0
- package/skills/pinecall-concepts/references/concepts/philosophy.md +100 -0
- package/skills/pinecall-concepts/references/concepts/server-vs-client-llm.md +119 -0
- package/skills/pinecall-examples/SKILL.md +59 -0
- package/skills/pinecall-examples/references/examples/browser-widget.md +206 -0
- package/skills/pinecall-examples/references/examples/chat-bot.md +184 -0
- package/skills/pinecall-examples/references/examples/headless-agent.md +121 -0
- package/skills/pinecall-examples/references/examples/index.md +183 -0
- package/skills/pinecall-examples/references/examples/multi-channel-bot.md +173 -0
- package/skills/pinecall-examples/references/examples/outbound-dispatch.md +109 -0
- package/skills/pinecall-examples/references/examples/turn-detection.md +150 -0
- package/skills/pinecall-guides/SKILL.md +68 -0
- package/skills/pinecall-guides/references/guides/call-ringing.md +149 -0
- package/skills/pinecall-guides/references/guides/conversation-history.md +377 -0
- package/skills/pinecall-guides/references/guides/dev-mode.md +130 -0
- package/skills/pinecall-guides/references/guides/events.md +677 -0
- package/skills/pinecall-guides/references/guides/human-takeover.md +184 -0
- package/skills/pinecall-guides/references/guides/inbound-voice.md +201 -0
- package/skills/pinecall-guides/references/guides/knowledge-bases.md +166 -0
- package/skills/pinecall-guides/references/guides/live-listening.md +199 -0
- package/skills/pinecall-guides/references/guides/multi-tenant.md +158 -0
- package/skills/pinecall-guides/references/guides/outbound-calls.md +279 -0
- package/skills/pinecall-guides/references/guides/sse-streaming.md +207 -0
- package/skills/pinecall-guides/references/guides/testing-agents.md +272 -0
- package/skills/pinecall-guides/references/guides/tools-and-functions.md +254 -0
- package/skills/pinecall-guides/references/guides/webrtc-browser.md +200 -0
- package/skills/pinecall-guides/references/guides/whatsapp.md +370 -0
- package/skills/pinecall-guides/references/guides/ws-streaming.md +235 -0
- package/skills/pinecall-quickstart/SKILL.md +54 -0
- package/skills/pinecall-quickstart/references/index.md +123 -0
- package/skills/pinecall-quickstart/references/quickstart.md +185 -0
- package/skills/pinecall-reference/SKILL.md +43 -0
- package/skills/pinecall-reference/references/reference/cli.md +578 -0
- package/skills/pinecall-reference/references/reference/events.md +366 -0
- package/skills/pinecall-reference/references/reference/llm-providers.md +263 -0
- package/skills/pinecall-reference/references/reference/rest-api.md +122 -0
- package/skills/pinecall-reference/references/reference/session-limits.md +119 -0
- package/skills/pinecall-reference/references/reference/stt-providers.md +174 -0
- package/skills/pinecall-reference/references/reference/tts-providers.md +149 -0
- package/skills/pinecall-sdk-api/SKILL.md +56 -0
- package/skills/pinecall-sdk-api/references/api/agent.md +328 -0
- package/skills/pinecall-sdk-api/references/api/call.md +324 -0
- package/skills/pinecall-sdk-api/references/api/pinecall.md +186 -0
- package/skills/pinecall-sdk-api/references/api/reply-stream.md +148 -0
- package/skills/pinecall-security/SKILL.md +37 -0
- package/skills/pinecall-security/references/security.md +138 -0
- package/skills/pinecall-web-chat/SKILL.md +38 -0
- package/skills/pinecall-web-chat/references/web/chat/chat-session.md +178 -0
- package/skills/pinecall-web-chat/references/web/chat/overview.md +98 -0
- package/skills/pinecall-web-components/SKILL.md +37 -0
- package/skills/pinecall-web-components/references/web/components/overview.md +128 -0
- package/skills/pinecall-web-voice/SKILL.md +40 -0
- package/skills/pinecall-web-voice/references/web/core/datachannel-protocol.md +149 -0
- package/skills/pinecall-web-voice/references/web/core/overview.md +70 -0
- package/skills/pinecall-web-voice/references/web/core/state-and-phases.md +153 -0
- package/skills/pinecall-web-voice/references/web/core/voice-session.md +279 -0
- package/skills/pinecall-web-widget/SKILL.md +41 -0
- package/skills/pinecall-web-widget/references/web/widget/overview.md +67 -0
- package/skills/pinecall-web-widget/references/web/widget/props.md +291 -0
- package/skills/pinecall-web-widget/references/web/widget/theming.md +131 -0
- package/skills/pinecall-web-widget/references/web/widget/tools-api.md +381 -0
- package/skills/pinecall-web-widget/references/web/widget/use-voice-session-hook.md +130 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Props"
|
|
3
|
+
description: "Every prop the VoiceWidget accepts — including token security, tools, theming, and multi-language."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Props
|
|
7
|
+
|
|
8
|
+
Full reference for `<VoiceWidget />`.
|
|
9
|
+
|
|
10
|
+
## All props
|
|
11
|
+
|
|
12
|
+
| Prop | Type | Default | Description |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| `agent` | `string` | **required** | Agent ID to connect to |
|
|
15
|
+
| `server` | `string` | `"https://voice.pinecall.io"` | Pinecall API base URL (override for self-hosted) |
|
|
16
|
+
| `name` | `string` | `"Agent"` | Display name shown in status label |
|
|
17
|
+
| `label` | `string` | `"Talk to {name}"` | Tooltip shown on hover when idle |
|
|
18
|
+
| `preset` | `VoiceWidgetPreset` | `"dark"` | Theme preset (`dark`, `midnight`, `aurora`, `sunset`, `light`) |
|
|
19
|
+
| `theme` | `Partial<VoiceWidgetTheme>` | — | Custom theme overrides, merged on top of `preset` |
|
|
20
|
+
| `config` | `Record<string, unknown>` | — | Session config overrides (voice, STT, language) |
|
|
21
|
+
| `metadata` | `Record<string, unknown>` | — | Metadata passed to the agent (available as `call.metadata`) |
|
|
22
|
+
| `languages` | `Record<string, LanguagePreset>` | — | Multi-language presets (see below) |
|
|
23
|
+
| `defaultLanguage` | `string` | first key | Initial language selection |
|
|
24
|
+
| `onLanguageChange` | `(lang, preset) => void` | — | Called when the user picks a language |
|
|
25
|
+
| `tokenProvider` | `() => Promise<{token, server}>` | — | Custom token provider for WebRTC (keeps API keys server-side) |
|
|
26
|
+
| `trackedTools` | `string[]` | — | Tool names to track in widget state for UI rendering |
|
|
27
|
+
| `tools` | `Record<string, ToolRenderer>` | — | Map of tool names → render functions for inline tool UI |
|
|
28
|
+
| `onStatusChange` | `(status) => void` | — | Called when connection status changes |
|
|
29
|
+
| `className` | `string` | — | Extra CSS class on the root wrapper |
|
|
30
|
+
|
|
31
|
+
## `tokenProvider` — token security
|
|
32
|
+
|
|
33
|
+
Browser connections require **short-lived tokens**. Your backend generates them using `@pinecall/sdk`, and the widget fetches them via the `tokenProvider` callback. This keeps your API key server-side.
|
|
34
|
+
|
|
35
|
+
### Backend setup
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// server.js
|
|
39
|
+
import express from "express";
|
|
40
|
+
import { Pinecall } from "@pinecall/sdk";
|
|
41
|
+
|
|
42
|
+
const app = express();
|
|
43
|
+
const pc = new Pinecall();
|
|
44
|
+
|
|
45
|
+
const florencia = pc.agent("florencia", {
|
|
46
|
+
voice: "elevenlabs/sarah",
|
|
47
|
+
language: "es",
|
|
48
|
+
stt: "deepgram/flux-en",
|
|
49
|
+
llm: "openai/gpt-5-chat-latest",
|
|
50
|
+
prompt: "...",
|
|
51
|
+
greeting: "¡Hola!",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Token endpoint — add your own auth in production
|
|
55
|
+
app.get("/api/token", authMiddleware, async (req, res) => {
|
|
56
|
+
const token = await florencia.createToken("webrtc");
|
|
57
|
+
res.json(token);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
app.listen(3000);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Two ways to generate tokens
|
|
64
|
+
|
|
65
|
+
| Method | When to use |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `agent.createToken(channel)` | You have the `Agent` instance in the same process |
|
|
68
|
+
| `pc.createToken(channel, agentId)` | The agent runs in a separate process; you only have the `Pinecall` client |
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Option A: from the agent instance
|
|
72
|
+
const token = await florencia.createToken("webrtc");
|
|
73
|
+
|
|
74
|
+
// Option B: from the Pinecall client (agent in another process)
|
|
75
|
+
const token = await pc.createToken("webrtc", "florencia");
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Both return the same shape:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{ "token": "tok_...", "server": "wss://voice.pinecall.io", "expires_in": 60 }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Frontend
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<VoiceWidget
|
|
88
|
+
agent="florencia"
|
|
89
|
+
tokenProvider={async () => {
|
|
90
|
+
const res = await fetch("/api/token?channel=webrtc", {
|
|
91
|
+
credentials: "include", // send your session cookie
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) throw new Error(`Token failed: ${res.status}`);
|
|
94
|
+
return res.json();
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `allowedOrigins` + `tokenProvider` — recommended combo
|
|
100
|
+
|
|
101
|
+
Use **both** for the best experience:
|
|
102
|
+
|
|
103
|
+
- `allowedOrigins` lets the widget auto-fetch tokens during **local development** (where your backend might not be running)
|
|
104
|
+
- `tokenProvider` provides **production security** — tokens go through your backend with your auth
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Backend — agent config
|
|
108
|
+
const florencia = pc.agent("florencia", {
|
|
109
|
+
// Dev fallback — widget can auto-fetch tokens from matching origins
|
|
110
|
+
allowedOrigins: ["https://mysite.com", "http://localhost:*"],
|
|
111
|
+
// ...
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
// Frontend — tokenProvider for production
|
|
117
|
+
<VoiceWidget
|
|
118
|
+
agent="florencia"
|
|
119
|
+
tokenProvider={async () => {
|
|
120
|
+
const res = await fetch("/api/token");
|
|
121
|
+
if (!res.ok) throw new Error(`Token failed: ${res.status}`);
|
|
122
|
+
return res.json();
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
When `tokenProvider` is set, the widget uses it. When it's not set (or fails), `@pinecall/web/core` falls back to fetching directly from the server using `allowedOrigins`.
|
|
128
|
+
|
|
129
|
+
> **Security note:** `allowedOrigins` alone is origin-header based — real browsers can't spoof it, but scripts/curl can. Always pair it with `tokenProvider` in production.
|
|
130
|
+
|
|
131
|
+
### How the token is used
|
|
132
|
+
|
|
133
|
+
The token is consumed **once** during the WebRTC handshake. Here's the full flow:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
1. Widget calls tokenProvider()
|
|
137
|
+
→ returns { token: "tok_...", server: "wss://voice.pinecall.io" }
|
|
138
|
+
|
|
139
|
+
2. Widget fetches ICE config
|
|
140
|
+
→ GET {server}/webrtc/ice-servers → STUN/TURN servers
|
|
141
|
+
|
|
142
|
+
3. Browser requests mic access
|
|
143
|
+
→ navigator.mediaDevices.getUserMedia()
|
|
144
|
+
|
|
145
|
+
4. Widget creates a WebRTC offer
|
|
146
|
+
→ new RTCPeerConnection → addTrack(mic) → createOffer()
|
|
147
|
+
|
|
148
|
+
5. Widget sends the offer + token to the server
|
|
149
|
+
→ POST {server}/webrtc/offer
|
|
150
|
+
{ sdp: "...", type: "offer", token: "tok_...", config, metadata }
|
|
151
|
+
▲
|
|
152
|
+
└── token goes here, consumed on use
|
|
153
|
+
|
|
154
|
+
6. Server validates the token, creates a session, returns the SDP answer
|
|
155
|
+
→ { sdp: "...", type: "answer" }
|
|
156
|
+
|
|
157
|
+
7. WebRTC connection established — audio flows peer-to-peer
|
|
158
|
+
→ token is discarded, all communication is via PeerConnection
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
After step 5, the token is gone. It can't be reused, replayed, or shared. The WebRTC connection is secured by the PeerConnection itself.
|
|
162
|
+
|
|
163
|
+
### Token properties
|
|
164
|
+
|
|
165
|
+
| Property | Value | Effect |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| Single-use | Consumed on first connection | Can't be reused |
|
|
168
|
+
| Short-lived | 60 second TTL | Expires quickly |
|
|
169
|
+
| Scoped | Locked to agent + org | Can't be used elsewhere |
|
|
170
|
+
|
|
171
|
+
> **Never** store API keys in frontend code. See [Security](/security) for the full token model.
|
|
172
|
+
|
|
173
|
+
## `config` — session overrides
|
|
174
|
+
|
|
175
|
+
Pass session-level overrides to the agent:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<VoiceWidget
|
|
179
|
+
agent="mara"
|
|
180
|
+
config={{
|
|
181
|
+
voice: "elevenlabs/sarah",
|
|
182
|
+
stt: "deepgram/flux-en",
|
|
183
|
+
language: "es",
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## `metadata` — server-side context
|
|
189
|
+
|
|
190
|
+
Whatever you pass shows up as `call.metadata` in your agent:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
<VoiceWidget
|
|
194
|
+
agent="mara"
|
|
195
|
+
metadata={{
|
|
196
|
+
userId: currentUser.id,
|
|
197
|
+
plan: currentUser.plan,
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
On the server:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
agent.on("call.started", (call) => {
|
|
206
|
+
console.log("Call from user", call.metadata.userId);
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## `languages` — multi-language selector
|
|
211
|
+
|
|
212
|
+
Enables a language pill bar that appears on hover and stays visible during calls.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<VoiceWidget
|
|
216
|
+
agent="mara"
|
|
217
|
+
languages={{
|
|
218
|
+
en: {
|
|
219
|
+
label: "English",
|
|
220
|
+
flag: "🇬🇧",
|
|
221
|
+
voice: "elevenlabs/sarah",
|
|
222
|
+
stt: "deepgram/flux-en",
|
|
223
|
+
language: "en",
|
|
224
|
+
},
|
|
225
|
+
es: {
|
|
226
|
+
label: "Español",
|
|
227
|
+
flag: "🇪🇸",
|
|
228
|
+
voice: "elevenlabs/george",
|
|
229
|
+
stt: "deepgram/flux-en",
|
|
230
|
+
language: "es",
|
|
231
|
+
},
|
|
232
|
+
}}
|
|
233
|
+
defaultLanguage="en"
|
|
234
|
+
/>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `LanguagePreset` shape
|
|
238
|
+
|
|
239
|
+
| Field | Type | Description |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| `label` | `string` | Display name (e.g. `"Español"`) |
|
|
242
|
+
| `flag` | `string` | Flag emoji (e.g. `"🇪🇸"`) |
|
|
243
|
+
| `voice` | `string` | Voice ID in `provider:id` format |
|
|
244
|
+
| `stt` | `string \| object` | STT shortcut (`"deepgram/flux-en"`) or full config |
|
|
245
|
+
| `language` | `string` | Language code for STT (`"es"`, `"en"`, etc.) |
|
|
246
|
+
|
|
247
|
+
### Behavior
|
|
248
|
+
|
|
249
|
+
- **Pre-call**: Pill bar appears on hover. Selecting a language updates the session config.
|
|
250
|
+
- **Mid-call**: Pills stay visible. Selecting a language sends a `configure` message via DataChannel — voice, STT, and language hot-swap without disconnecting.
|
|
251
|
+
|
|
252
|
+
## `tools` — inline tool renderers
|
|
253
|
+
|
|
254
|
+
Map tool names to React render functions. When a server-side tool completes, the result renders inline:
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<VoiceWidget
|
|
258
|
+
agent="booking-demo"
|
|
259
|
+
tools={{
|
|
260
|
+
getAvailableSlots: (result, { respond, dismiss }) => (
|
|
261
|
+
<div className="slots">
|
|
262
|
+
{result.slots.map((slot: string) => (
|
|
263
|
+
<button key={slot} onClick={() => { respond(`I'd like ${slot}`); dismiss(); }}>
|
|
264
|
+
{slot}
|
|
265
|
+
</button>
|
|
266
|
+
))}
|
|
267
|
+
</div>
|
|
268
|
+
),
|
|
269
|
+
}}
|
|
270
|
+
/>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
See [Tools API](/web/widget/tools-api) for the full pattern.
|
|
274
|
+
|
|
275
|
+
## `onStatusChange` — observability
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
<VoiceWidget
|
|
279
|
+
agent="mara"
|
|
280
|
+
onStatusChange={(status) => {
|
|
281
|
+
if (status === "connected") analytics.track("call_started");
|
|
282
|
+
if (status === "idle") analytics.track("call_ended");
|
|
283
|
+
}}
|
|
284
|
+
/>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## What's next
|
|
288
|
+
|
|
289
|
+
- [Theming](/web/widget/theming) — all CSS variables and preset values
|
|
290
|
+
- [Tools API](/web/widget/tools-api) — interactive UI from tool calls
|
|
291
|
+
- [`useVoiceSession` hook](/web/widget/use-voice-session-hook) — bypass the orb, build custom UI
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Theming"
|
|
3
|
+
description: "Theme presets, CSS variables, and full customization of the orb and transcript UI."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Theming
|
|
7
|
+
|
|
8
|
+
Every visual aspect of the widget is controlled by CSS custom properties. You can pick a built-in preset, override individual values, or skip the props entirely and override variables with CSS.
|
|
9
|
+
|
|
10
|
+
## Five built-in presets
|
|
11
|
+
|
|
12
|
+
| Preset | Orb | Rings | Panels | Best for |
|
|
13
|
+
|---|---|---|---|---|
|
|
14
|
+
| `"dark"` | Pearl white | Warm red | Dark purple glass | Dark-themed sites (default) |
|
|
15
|
+
| `"midnight"` | Deep sapphire | Ice blue | Navy glass | Corporate / professional |
|
|
16
|
+
| `"aurora"` | Emerald / teal | Green | Forest dark | Nature / wellness brands |
|
|
17
|
+
| `"sunset"` | Warm coral | Golden amber | Warm dark | Hospitality / warm brands |
|
|
18
|
+
| `"light"` | Clean white | Soft blue | White glass | Light-themed sites |
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<VoiceWidget preset="midnight" agent="mara" />
|
|
22
|
+
<VoiceWidget preset="aurora" agent="mara" />
|
|
23
|
+
<VoiceWidget preset="sunset" agent="mara" />
|
|
24
|
+
<VoiceWidget preset="light" agent="mara" />
|
|
25
|
+
<VoiceWidget preset="dark" agent="mara" /> // default
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Access preset values programmatically:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { PRESETS } from "@pinecall/web";
|
|
32
|
+
console.log(PRESETS.midnight); // full theme object
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Custom themes via the `theme` prop
|
|
36
|
+
|
|
37
|
+
Pass overrides — they merge on top of the chosen `preset`:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<VoiceWidget
|
|
41
|
+
agent="mara"
|
|
42
|
+
preset="midnight"
|
|
43
|
+
theme={{
|
|
44
|
+
orbFrom: "200, 150, 255",
|
|
45
|
+
orbMid: "140, 80, 220",
|
|
46
|
+
orbTo: "80, 30, 160",
|
|
47
|
+
colorAccent: "124, 58, 237",
|
|
48
|
+
ringColor: "216, 65, 44",
|
|
49
|
+
panelBg: "rgba(16, 14, 20, .92)",
|
|
50
|
+
bubbleUserColor: "#e0d4f7",
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## All theme variables
|
|
56
|
+
|
|
57
|
+
| Field | CSS variable | Type | Default (dark) | Controls |
|
|
58
|
+
|---|---|---|---|---|
|
|
59
|
+
| `orbFrom` | `--vw-orb-from` | RGB triplet | `255, 255, 255` | Idle orb gradient center |
|
|
60
|
+
| `orbMid` | `--vw-orb-mid` | RGB triplet | `240, 238, 231` | Idle orb gradient midtone |
|
|
61
|
+
| `orbTo` | `--vw-orb-to` | RGB triplet | `184, 181, 168` | Idle orb gradient edge |
|
|
62
|
+
| `colorConnecting` | `--vw-color-connecting` | RGB triplet | `245, 158, 11` | Connecting state orb |
|
|
63
|
+
| `colorActive` | `--vw-color-active` | RGB triplet | `76, 175, 80` | Connected / listening orb |
|
|
64
|
+
| `colorUserSpeaking` | `--vw-color-user-speaking` | RGB triplet | `52, 211, 153` | User speaking orb |
|
|
65
|
+
| `colorSpeaking` | `--vw-color-speaking` | RGB triplet | `248, 113, 113` | Agent speaking orb |
|
|
66
|
+
| `colorThinking` | `--vw-color-thinking` | RGB triplet | `139, 92, 246` | Thinking / processing orb |
|
|
67
|
+
| `colorWarning` | `--vw-color-warning` | RGB triplet | `255, 160, 0` | Idle warning blink |
|
|
68
|
+
| `colorAccent` | `--vw-color-accent` | RGB triplet | `124, 58, 237` | User bubble accent |
|
|
69
|
+
| `ringColor` | `--vw-ring-color` | RGB triplet | `216, 65, 44` | Idle ring glow |
|
|
70
|
+
| `panelBg` | `--vw-panel-bg` | CSS color | `rgba(16,14,20,.92)` | Transcript panel bg |
|
|
71
|
+
| `panelBorder` | `--vw-panel-border` | CSS color | `rgba(255,255,255,.08)` | Transcript panel border |
|
|
72
|
+
| `bubbleBotBg` | `--vw-bubble-bot-bg` | CSS color | `rgba(18,16,22,.9)` | Bot bubble background |
|
|
73
|
+
| `bubbleBotColor` | `--vw-bubble-bot-color` | CSS color | `#e8e4f0` | Bot bubble text |
|
|
74
|
+
| `bubbleUserColor` | `--vw-bubble-user-color` | CSS color | `#e0d4f7` | User bubble text |
|
|
75
|
+
| `labelBg` | `--vw-label-bg` | CSS color | `#181818` | Label tooltip background |
|
|
76
|
+
| `labelColor` | `--vw-label-color` | CSS color | `#fff` | Label tooltip text |
|
|
77
|
+
|
|
78
|
+
> **RGB triplet vs CSS color.** Variables that need alpha variants are stored as `"R, G, B"` strings (the widget uses `rgba(var(--vw-color-x), 0.3)` etc.). Plain CSS color values are used for backgrounds and text where alpha is baked into the value.
|
|
79
|
+
|
|
80
|
+
## CSS-only override (no JS)
|
|
81
|
+
|
|
82
|
+
Skip the `theme` prop entirely — override the CSS variables directly:
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
.vw-wrap {
|
|
86
|
+
--vw-orb-from: 200, 150, 255;
|
|
87
|
+
--vw-ring-color: 100, 80, 200;
|
|
88
|
+
--vw-panel-bg: rgba(20, 10, 40, .95);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This is handy when you want the theme to follow a parent context (e.g. dark/light mode toggle in your app's own CSS).
|
|
93
|
+
|
|
94
|
+
## Orb visual states
|
|
95
|
+
|
|
96
|
+
The orb gets a different look per phase:
|
|
97
|
+
|
|
98
|
+
| State | Visual | CSS class | When |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| Idle | Pearl gradient, breathing rings | (default) | Not connected |
|
|
101
|
+
| Connecting | Amber pulse | `.connecting` | Establishing WebRTC |
|
|
102
|
+
| Active | Soft green glow | `.active` | Connected, listening |
|
|
103
|
+
| User speaking | Emerald glow | `.user-speaking` | User is talking |
|
|
104
|
+
| Agent speaking | Rose pulse | `.speaking` | Bot TTS playing |
|
|
105
|
+
| Thinking | Violet pulse | `.thinking` | Waiting for LLM response |
|
|
106
|
+
| **Idle warning** | **Orange blink** | `.idle-warning` | User silent — call will timeout soon |
|
|
107
|
+
|
|
108
|
+
The idle warning state is driven by the server's `session.idleWarning` event and clears when the user speaks or the call ends.
|
|
109
|
+
|
|
110
|
+
## Building on top of presets
|
|
111
|
+
|
|
112
|
+
Start from a preset and modify selectively:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { PRESETS } from "@pinecall/web";
|
|
116
|
+
import type { VoiceWidgetTheme } from "@pinecall/web";
|
|
117
|
+
|
|
118
|
+
const brandTheme: Partial<VoiceWidgetTheme> = {
|
|
119
|
+
...PRESETS.midnight,
|
|
120
|
+
colorAccent: "255, 87, 34", // brand orange
|
|
121
|
+
ringColor: "255, 87, 34",
|
|
122
|
+
bubbleUserColor: "#ffccbc",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
<VoiceWidget agent="mara" preset="midnight" theme={brandTheme} />;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## What's next
|
|
129
|
+
|
|
130
|
+
- [Props reference](/web/widget/props) — everything else the widget accepts
|
|
131
|
+
- [`useVoiceSession` hook](/web/widget/use-voice-session-hook) — for fully custom UIs that bypass the orb entirely
|