@pinecall/web 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 +168 -0
- package/dist/chat/index.cjs +250 -0
- package/dist/chat/index.cjs.map +1 -0
- package/dist/chat/index.d.cts +48 -0
- package/dist/chat/index.d.ts +48 -0
- package/dist/chat/index.js +3 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/react.cjs +292 -0
- package/dist/chat/react.cjs.map +1 -0
- package/dist/chat/react.d.cts +35 -0
- package/dist/chat/react.d.ts +35 -0
- package/dist/chat/react.js +4 -0
- package/dist/chat/react.js.map +1 -0
- package/dist/chunk-LHZA26Z5.js +43 -0
- package/dist/chunk-LHZA26Z5.js.map +1 -0
- package/dist/chunk-LPRH4KOL.js +552 -0
- package/dist/chunk-LPRH4KOL.js.map +1 -0
- package/dist/chunk-MCAQMGBG.js +248 -0
- package/dist/chunk-MCAQMGBG.js.map +1 -0
- package/dist/core/index.cjs +554 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +100 -0
- package/dist/core/index.d.ts +100 -0
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +4351 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +434 -0
- package/dist/index.d.ts +434 -0
- package/dist/index.js +3510 -0
- package/dist/index.js.map +1 -0
- package/dist/types-CKfTJcH8.d.cts +144 -0
- package/dist/types-CKfTJcH8.d.ts +144 -0
- package/dist/types-W0229iUB.d.cts +61 -0
- package/dist/types-W0229iUB.d.ts +61 -0
- package/package.json +113 -0
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# @pinecall/web
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@pinecall/web)
|
|
4
|
+
|
|
5
|
+
The web client for [Pinecall](https://pinecall.io) agents — real-time WebRTC **voice**, text **chat**, and drop-in **React** widgets, in one package.
|
|
6
|
+
|
|
7
|
+
> **Migrating from `@pinecall/voice-core` / `@pinecall/voice-widget` / `@pinecall/chat-core`?** They are now a single package. See [Entry points](#entry-points) below — the React widget moves to the package root, vanilla voice to `/core`, and chat to `/chat` + `/chat/react`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @pinecall/web
|
|
13
|
+
# React is a peer dep — only needed for the widget + chat/react entries
|
|
14
|
+
npm install react react-dom
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Entry points
|
|
18
|
+
|
|
19
|
+
| Import | What | Needs React |
|
|
20
|
+
|--------|------|-------------|
|
|
21
|
+
| `@pinecall/web` | React widgets — `VoiceWidget`, `ContactHub`, `ChatView`, `useVoice`, `useVoiceSession`, presets | ✅ |
|
|
22
|
+
| `@pinecall/web/core` | `VoiceSession` — framework-agnostic WebRTC voice client | ❌ |
|
|
23
|
+
| `@pinecall/web/chat` | `ChatSession` — framework-agnostic text chat client | ❌ |
|
|
24
|
+
| `@pinecall/web/chat/react` | `usePinecallChat` — React hook over `ChatSession` | ✅ |
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### React widget
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { VoiceWidget } from "@pinecall/web";
|
|
32
|
+
|
|
33
|
+
<VoiceWidget agent="mara" name="Mara" preset="midnight" />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Vanilla voice (any framework)
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { VoiceSession } from "@pinecall/web/core";
|
|
40
|
+
|
|
41
|
+
const session = new VoiceSession({ agent: "mara" });
|
|
42
|
+
session.subscribe(() => console.log(session.getState()));
|
|
43
|
+
await session.connect();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Chat hook
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { usePinecallChat } from "@pinecall/web/chat/react";
|
|
50
|
+
|
|
51
|
+
const chat = usePinecallChat({ agent: "florencia" });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Structure
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
web/
|
|
58
|
+
├── src/
|
|
59
|
+
│ ├── index.ts @pinecall/web — React widgets barrel
|
|
60
|
+
│ ├── core/ @pinecall/web/core — VoiceSession (vanilla)
|
|
61
|
+
│ ├── chat/ @pinecall/web/chat[/react] — ChatSession + React hook
|
|
62
|
+
│ └── widget/ React components (VoiceWidget, ContactHub, ChatView…)
|
|
63
|
+
├── docs/ diagrams + legacy changelogs
|
|
64
|
+
├── examples/react/ Demo app with preset switcher
|
|
65
|
+
├── tsup.config.ts Build (4 entries → ESM + CJS + DTS)
|
|
66
|
+
└── tsconfig.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pnpm install
|
|
73
|
+
pnpm build # build all 4 entries (ESM + CJS + DTS)
|
|
74
|
+
pnpm dev # tsup watch
|
|
75
|
+
pnpm typecheck
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Publishing
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm version <patch|minor|major>
|
|
82
|
+
pnpm release # build + npm publish
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Widget Theme & Orb States
|
|
86
|
+
|
|
87
|
+
The `<VoiceWidget>` orb cycles through visual states as the session progresses. Each state has a configurable color (RGB triplet):
|
|
88
|
+
|
|
89
|
+
| Orb State | CSS Class | Theme Property | Default Color | When |
|
|
90
|
+
|-----------|-----------|---------------|---------------|------|
|
|
91
|
+
| Idle | — | `orbFrom`/`orbMid`/`orbTo` | Pearl gradient | Not connected |
|
|
92
|
+
| Connecting | `.connecting` | `colorConnecting` | `245, 158, 11` (amber) | Establishing WebRTC |
|
|
93
|
+
| Active | `.active` | `colorActive` | `76, 175, 80` (green) | Connected, waiting |
|
|
94
|
+
| User speaking | `.user-speaking` | `colorUserSpeaking` | `52, 211, 153` (emerald) | User talking |
|
|
95
|
+
| Agent speaking | `.speaking` | `colorSpeaking` | `248, 113, 113` (rose) | Agent talking |
|
|
96
|
+
| Thinking | `.thinking` | `colorThinking` | `139, 92, 246` (violet) | Processing |
|
|
97
|
+
| **Idle warning** | `.idle-warning` | `colorWarning` | `255, 160, 0` (orange) | User silent too long, call will timeout |
|
|
98
|
+
|
|
99
|
+
### Idle Warning
|
|
100
|
+
|
|
101
|
+
When the server emits `session.idle_warning`, the orb switches to the `idle-warning` state — a blinking amber/orange animation. This warns the user that the call will end due to inactivity.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
// Customize the warning color via theme
|
|
105
|
+
<VoiceWidget
|
|
106
|
+
agent="mara"
|
|
107
|
+
theme={{ colorWarning: "255, 60, 60" }} // red warning
|
|
108
|
+
/>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The idle warning is cleared when:
|
|
112
|
+
- The user starts speaking
|
|
113
|
+
- The session disconnects
|
|
114
|
+
- `session.timeout` fires (auto-disconnect)
|
|
115
|
+
|
|
116
|
+
### Theme Presets
|
|
117
|
+
|
|
118
|
+
5 built-in presets: `dark` (default), `midnight`, `aurora`, `sunset`, `light`.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<VoiceWidget agent="mara" preset="midnight" />
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Custom Theme
|
|
125
|
+
|
|
126
|
+
Override individual colors on top of any preset:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
<VoiceWidget
|
|
130
|
+
agent="mara"
|
|
131
|
+
preset="dark"
|
|
132
|
+
theme={{
|
|
133
|
+
colorActive: "0, 200, 100",
|
|
134
|
+
colorWarning: "255, 80, 0",
|
|
135
|
+
ringColor: "100, 100, 200",
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
All theme properties accept **RGB triplets** (e.g. `"255, 160, 0"`) for use with CSS `rgba()`.
|
|
141
|
+
|
|
142
|
+
## Session Limits (via `@pinecall/sdk`)
|
|
143
|
+
|
|
144
|
+
Session limits are configured on the agent (server-side SDK) and flow through to the WebRTC widget automatically:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Server-side (agent.js)
|
|
148
|
+
const agent = pc.deploy("my-agent", {
|
|
149
|
+
// ...voice, stt, llm config...
|
|
150
|
+
sessionLimits: {
|
|
151
|
+
idle_timeout_seconds: 20, // hang up after 20s of silence
|
|
152
|
+
idle_warning_seconds: 10, // warn 10s before timeout
|
|
153
|
+
max_duration_seconds: 600, // hard cap at 10 minutes
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
agent.on("session.idle_warning", (event, call) => {
|
|
158
|
+
call.say("Are you still there?");
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The widget receives `session.idle_warning` via DataChannel and:
|
|
163
|
+
1. Switches the orb to the **idle-warning** state (blinking `colorWarning`)
|
|
164
|
+
2. On `session.timeout`, auto-disconnects and resets to idle
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/chat/ChatSession.ts
|
|
4
|
+
var INITIAL_STATE = {
|
|
5
|
+
status: "idle",
|
|
6
|
+
error: null,
|
|
7
|
+
messages: [],
|
|
8
|
+
typing: false,
|
|
9
|
+
streamingText: "",
|
|
10
|
+
sessionId: null
|
|
11
|
+
};
|
|
12
|
+
var ChatSession = class extends EventTarget {
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
super();
|
|
15
|
+
this.opts = opts;
|
|
16
|
+
}
|
|
17
|
+
opts;
|
|
18
|
+
state = { ...INITIAL_STATE };
|
|
19
|
+
listeners = /* @__PURE__ */ new Set();
|
|
20
|
+
ws = null;
|
|
21
|
+
reconnectTimer = null;
|
|
22
|
+
msgCounter = 0;
|
|
23
|
+
/** Read-only snapshot of current state (stable ref until next mutation). */
|
|
24
|
+
getState() {
|
|
25
|
+
return this.state;
|
|
26
|
+
}
|
|
27
|
+
/** Subscribe to ANY state change (for React useSyncExternalStore). */
|
|
28
|
+
subscribe(listener) {
|
|
29
|
+
this.listeners.add(listener);
|
|
30
|
+
return () => {
|
|
31
|
+
this.listeners.delete(listener);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
setState(patch) {
|
|
35
|
+
const prev = this.state;
|
|
36
|
+
this.state = { ...prev, ...patch };
|
|
37
|
+
for (const l of this.listeners) l();
|
|
38
|
+
if (patch.status !== void 0 && patch.status !== prev.status) {
|
|
39
|
+
this.dispatchEvent(
|
|
40
|
+
new CustomEvent("status", { detail: { status: this.state.status } })
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (patch.error !== void 0 && patch.error !== null && patch.error !== prev.error) {
|
|
44
|
+
this.dispatchEvent(
|
|
45
|
+
new CustomEvent("error", { detail: { error: this.state.error } })
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
this.dispatchEvent(
|
|
49
|
+
new CustomEvent("change", { detail: { state: this.state } })
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
setMessages(updater) {
|
|
53
|
+
const next = updater(this.state.messages);
|
|
54
|
+
this.setState({ messages: next });
|
|
55
|
+
const last = next[next.length - 1];
|
|
56
|
+
if (last) {
|
|
57
|
+
this.dispatchEvent(
|
|
58
|
+
new CustomEvent("message", { detail: { message: last } })
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ── Connection ──────────────────────────────────────────────────────
|
|
63
|
+
async connect() {
|
|
64
|
+
if (this.ws) return;
|
|
65
|
+
try {
|
|
66
|
+
this.setState({
|
|
67
|
+
status: "connecting",
|
|
68
|
+
error: null
|
|
69
|
+
});
|
|
70
|
+
const base = (this.opts.server ?? "https://voice.pinecall.io").replace(
|
|
71
|
+
/\/$/,
|
|
72
|
+
""
|
|
73
|
+
);
|
|
74
|
+
let token;
|
|
75
|
+
let chatServer;
|
|
76
|
+
if (this.opts.tokenProvider) {
|
|
77
|
+
const t = await this.opts.tokenProvider();
|
|
78
|
+
token = t.token;
|
|
79
|
+
chatServer = t.server;
|
|
80
|
+
} else {
|
|
81
|
+
const tRes = await fetch(
|
|
82
|
+
`${base}/chat/token?agent_id=${encodeURIComponent(this.opts.agent)}`
|
|
83
|
+
);
|
|
84
|
+
if (!tRes.ok) {
|
|
85
|
+
const body = await tRes.text();
|
|
86
|
+
throw new Error(`Token: ${tRes.status} ${body}`);
|
|
87
|
+
}
|
|
88
|
+
const t = await tRes.json();
|
|
89
|
+
token = t.token;
|
|
90
|
+
chatServer = t.server;
|
|
91
|
+
}
|
|
92
|
+
const wsBase = (chatServer || base).replace(/^http:/, "ws:").replace(/^https:/, "wss:");
|
|
93
|
+
const ws = new WebSocket(`${wsBase}/chat/ws?token=${token}`);
|
|
94
|
+
this.ws = ws;
|
|
95
|
+
ws.onopen = () => {
|
|
96
|
+
};
|
|
97
|
+
ws.onmessage = (evt) => this.handleMessage(evt);
|
|
98
|
+
ws.onerror = () => {
|
|
99
|
+
this.setState({ error: "WebSocket error", status: "error" });
|
|
100
|
+
};
|
|
101
|
+
ws.onclose = (evt) => {
|
|
102
|
+
this.ws = null;
|
|
103
|
+
if (this.state.status === "connected") {
|
|
104
|
+
this.setState({ status: "idle" });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
} catch (err) {
|
|
108
|
+
this.setState({
|
|
109
|
+
error: err instanceof Error ? err.message : String(err),
|
|
110
|
+
status: "error"
|
|
111
|
+
});
|
|
112
|
+
this.ws = null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
handleMessage(evt) {
|
|
116
|
+
let d;
|
|
117
|
+
try {
|
|
118
|
+
d = JSON.parse(evt.data);
|
|
119
|
+
} catch {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
switch (d.event) {
|
|
123
|
+
case "chat.connected":
|
|
124
|
+
this.setState({
|
|
125
|
+
status: "connected",
|
|
126
|
+
sessionId: d.session_id ?? null
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
case "chat.token":
|
|
130
|
+
case "llm.chat.token":
|
|
131
|
+
this.setState({
|
|
132
|
+
typing: true,
|
|
133
|
+
streamingText: d.text ?? ""
|
|
134
|
+
});
|
|
135
|
+
this.setMessages((prev) => {
|
|
136
|
+
const idx = prev.findIndex(
|
|
137
|
+
(m) => m.messageId === d.message_id && m.isStreaming
|
|
138
|
+
);
|
|
139
|
+
if (idx >= 0) {
|
|
140
|
+
return prev.map(
|
|
141
|
+
(m, i) => i === idx ? { ...m, text: d.text ?? "" } : m
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return [
|
|
145
|
+
...prev,
|
|
146
|
+
{
|
|
147
|
+
id: ++this.msgCounter,
|
|
148
|
+
role: "bot",
|
|
149
|
+
text: d.text ?? "",
|
|
150
|
+
messageId: d.message_id,
|
|
151
|
+
isStreaming: true
|
|
152
|
+
}
|
|
153
|
+
];
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
case "chat.done":
|
|
157
|
+
case "llm.chat.done":
|
|
158
|
+
this.setState({
|
|
159
|
+
typing: false,
|
|
160
|
+
streamingText: ""
|
|
161
|
+
});
|
|
162
|
+
this.setMessages((prev) => {
|
|
163
|
+
const idx = prev.findIndex(
|
|
164
|
+
(m) => m.messageId === d.message_id && m.isStreaming
|
|
165
|
+
);
|
|
166
|
+
if (idx >= 0) {
|
|
167
|
+
return prev.map(
|
|
168
|
+
(m, i) => i === idx ? { ...m, text: d.text ?? m.text, isStreaming: false } : m
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return [
|
|
172
|
+
...prev,
|
|
173
|
+
{
|
|
174
|
+
id: ++this.msgCounter,
|
|
175
|
+
role: "bot",
|
|
176
|
+
text: d.text ?? "",
|
|
177
|
+
messageId: d.message_id,
|
|
178
|
+
isStreaming: false
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
183
|
+
case "chat.error":
|
|
184
|
+
case "llm.chat.error":
|
|
185
|
+
this.setState({
|
|
186
|
+
typing: false,
|
|
187
|
+
error: d.error ?? "Unknown error"
|
|
188
|
+
});
|
|
189
|
+
break;
|
|
190
|
+
case "error":
|
|
191
|
+
this.setState({
|
|
192
|
+
error: d.error ?? "Unknown error",
|
|
193
|
+
status: "error"
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
this.dispatchEvent(new CustomEvent("event", { detail: d }));
|
|
198
|
+
}
|
|
199
|
+
// ── Actions ─────────────────────────────────────────────────────────
|
|
200
|
+
/** Send a text message to the agent. */
|
|
201
|
+
send(text) {
|
|
202
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
203
|
+
const trimmed = text.trim();
|
|
204
|
+
if (!trimmed) return;
|
|
205
|
+
this.setMessages((prev) => [
|
|
206
|
+
...prev,
|
|
207
|
+
{
|
|
208
|
+
id: ++this.msgCounter,
|
|
209
|
+
role: "user",
|
|
210
|
+
text: trimmed
|
|
211
|
+
}
|
|
212
|
+
]);
|
|
213
|
+
this.ws.send(JSON.stringify({ event: "message", text: trimmed }));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Set or clear a keyed context block in the LLM system prompt.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* session.setContext("form", JSON.stringify({ name: "Juan" }));
|
|
221
|
+
* session.setContext("form", null); // clear
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
setContext(key, value) {
|
|
225
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
226
|
+
this.ws.send(JSON.stringify({ event: "set_context", key, value }));
|
|
227
|
+
}
|
|
228
|
+
/** Disconnect the chat session. */
|
|
229
|
+
disconnect() {
|
|
230
|
+
if (this.reconnectTimer) {
|
|
231
|
+
clearTimeout(this.reconnectTimer);
|
|
232
|
+
this.reconnectTimer = null;
|
|
233
|
+
}
|
|
234
|
+
if (this.ws) {
|
|
235
|
+
this.ws.close();
|
|
236
|
+
this.ws = null;
|
|
237
|
+
}
|
|
238
|
+
this.setState({ status: "idle", typing: false, streamingText: "" });
|
|
239
|
+
}
|
|
240
|
+
/** Tear down the session and clear subscribers. Do not reuse after this. */
|
|
241
|
+
destroy() {
|
|
242
|
+
this.disconnect();
|
|
243
|
+
this.setState({ status: "destroyed" });
|
|
244
|
+
this.listeners.clear();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
exports.ChatSession = ChatSession;
|
|
249
|
+
//# sourceMappingURL=index.cjs.map
|
|
250
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/chat/ChatSession.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * ChatSession — Framework-agnostic text chat client for Pinecall agents.\n *\n * Mirrors VoiceSession's API patterns:\n * - session.subscribe(cb) + session.getState() — for React useSyncExternalStore\n * - session.addEventListener('status' | 'message' | 'error' | 'change', cb)\n *\n * Flow: GET /chat/token → WS /chat/ws?token=cht_xxx → bidirectional text chat\n */\nimport type {\n ChatSessionState,\n ChatSessionOptions,\n ChatMessage,\n} from \"./types\";\n\nconst INITIAL_STATE: ChatSessionState = {\n status: \"idle\",\n error: null,\n messages: [],\n typing: false,\n streamingText: \"\",\n sessionId: null,\n};\n\nexport class ChatSession extends EventTarget {\n private state: ChatSessionState = { ...INITIAL_STATE };\n private listeners = new Set<() => void>();\n\n private ws: WebSocket | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private msgCounter = 0;\n\n constructor(private opts: ChatSessionOptions) {\n super();\n }\n\n /** Read-only snapshot of current state (stable ref until next mutation). */\n getState(): Readonly<ChatSessionState> {\n return this.state;\n }\n\n /** Subscribe to ANY state change (for React useSyncExternalStore). */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private setState(patch: Partial<ChatSessionState>): void {\n const prev = this.state;\n this.state = { ...prev, ...patch };\n for (const l of this.listeners) l();\n\n if (patch.status !== undefined && patch.status !== prev.status) {\n this.dispatchEvent(\n new CustomEvent(\"status\", { detail: { status: this.state.status } }),\n );\n }\n if (\n patch.error !== undefined &&\n patch.error !== null &&\n patch.error !== prev.error\n ) {\n this.dispatchEvent(\n new CustomEvent(\"error\", { detail: { error: this.state.error } }),\n );\n }\n this.dispatchEvent(\n new CustomEvent(\"change\", { detail: { state: this.state } }),\n );\n }\n\n private setMessages(\n updater: (prev: ChatMessage[]) => ChatMessage[],\n ): void {\n const next = updater(this.state.messages);\n this.setState({ messages: next });\n const last = next[next.length - 1];\n if (last) {\n this.dispatchEvent(\n new CustomEvent(\"message\", { detail: { message: last } }),\n );\n }\n }\n\n // ── Connection ──────────────────────────────────────────────────────\n\n async connect(): Promise<void> {\n if (this.ws) return;\n\n try {\n this.setState({\n status: \"connecting\",\n error: null,\n });\n\n const base = (this.opts.server ?? \"https://voice.pinecall.io\").replace(\n /\\/$/,\n \"\",\n );\n\n // 1. Fetch chat token — use tokenProvider (backend proxy) or direct fetch\n let token: string;\n let chatServer: string;\n if (this.opts.tokenProvider) {\n const t = await this.opts.tokenProvider();\n token = t.token;\n chatServer = t.server;\n } else {\n const tRes = await fetch(\n `${base}/chat/token?agent_id=${encodeURIComponent(this.opts.agent)}`,\n );\n if (!tRes.ok) {\n const body = await tRes.text();\n throw new Error(`Token: ${tRes.status} ${body}`);\n }\n const t = await tRes.json();\n token = t.token;\n chatServer = t.server;\n }\n const wsBase = (chatServer || base)\n .replace(/^http:/, \"ws:\")\n .replace(/^https:/, \"wss:\");\n\n // 2. Open WebSocket\n const ws = new WebSocket(`${wsBase}/chat/ws?token=${token}`);\n this.ws = ws;\n\n ws.onopen = () => {\n // Wait for chat.connected event before setting status\n };\n\n ws.onmessage = (evt) => this.handleMessage(evt);\n\n ws.onerror = () => {\n this.setState({ error: \"WebSocket error\", status: \"error\" });\n };\n\n ws.onclose = (evt) => {\n this.ws = null;\n if (this.state.status === \"connected\") {\n // Unexpected disconnect\n this.setState({ status: \"idle\" });\n }\n };\n } catch (err) {\n this.setState({\n error: err instanceof Error ? err.message : String(err),\n status: \"error\",\n });\n this.ws = null;\n }\n }\n\n private handleMessage(evt: MessageEvent): void {\n let d: any;\n try {\n d = JSON.parse(evt.data);\n } catch {\n return;\n }\n\n switch (d.event) {\n case \"chat.connected\":\n this.setState({\n status: \"connected\",\n sessionId: d.session_id ?? null,\n });\n break;\n\n case \"chat.token\":\n case \"llm.chat.token\":\n // Streaming token from LLM\n this.setState({\n typing: true,\n streamingText: d.text ?? \"\",\n });\n\n // Update or create bot message\n this.setMessages((prev) => {\n const idx = prev.findIndex(\n (m) => m.messageId === d.message_id && m.isStreaming,\n );\n if (idx >= 0) {\n return prev.map((m, i) =>\n i === idx ? { ...m, text: d.text ?? \"\" } : m,\n );\n }\n return [\n ...prev,\n {\n id: ++this.msgCounter,\n role: \"bot\",\n text: d.text ?? \"\",\n messageId: d.message_id,\n isStreaming: true,\n },\n ];\n });\n break;\n\n case \"chat.done\":\n case \"llm.chat.done\":\n // LLM finished streaming\n this.setState({\n typing: false,\n streamingText: \"\",\n });\n\n this.setMessages((prev) => {\n const idx = prev.findIndex(\n (m) => m.messageId === d.message_id && m.isStreaming,\n );\n if (idx >= 0) {\n return prev.map((m, i) =>\n i === idx\n ? { ...m, text: d.text ?? m.text, isStreaming: false }\n : m,\n );\n }\n // If we missed the streaming, add the final message\n return [\n ...prev,\n {\n id: ++this.msgCounter,\n role: \"bot\",\n text: d.text ?? \"\",\n messageId: d.message_id,\n isStreaming: false,\n },\n ];\n });\n break;\n\n case \"chat.error\":\n case \"llm.chat.error\":\n this.setState({\n typing: false,\n error: d.error ?? \"Unknown error\",\n });\n break;\n\n case \"error\":\n this.setState({\n error: d.error ?? \"Unknown error\",\n status: \"error\",\n });\n break;\n }\n\n // Emit raw event for power users\n this.dispatchEvent(new CustomEvent(\"event\", { detail: d }));\n }\n\n // ── Actions ─────────────────────────────────────────────────────────\n\n /** Send a text message to the agent. */\n send(text: string): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n\n const trimmed = text.trim();\n if (!trimmed) return;\n\n // Add user message to local state immediately\n this.setMessages((prev) => [\n ...prev,\n {\n id: ++this.msgCounter,\n role: \"user\",\n text: trimmed,\n },\n ]);\n\n // Send to server\n this.ws.send(JSON.stringify({ event: \"message\", text: trimmed }));\n }\n\n /**\n * Set or clear a keyed context block in the LLM system prompt.\n *\n * @example\n * ```ts\n * session.setContext(\"form\", JSON.stringify({ name: \"Juan\" }));\n * session.setContext(\"form\", null); // clear\n * ```\n */\n setContext(key: string, value: string | null): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n this.ws.send(JSON.stringify({ event: \"set_context\", key, value }));\n }\n\n /** Disconnect the chat session. */\n disconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.setState({ status: \"idle\", typing: false, streamingText: \"\" });\n }\n\n /** Tear down the session and clear subscribers. Do not reuse after this. */\n destroy(): void {\n this.disconnect();\n this.setState({ status: \"destroyed\" });\n this.listeners.clear();\n }\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { C as ChatSessionOptions, a as ChatSessionState } from '../types-W0229iUB.cjs';
|
|
2
|
+
export { b as ChatEventType, c as ChatMessage, d as ChatStatus } from '../types-W0229iUB.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ChatSession — Framework-agnostic text chat client for Pinecall agents.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors VoiceSession's API patterns:
|
|
8
|
+
* - session.subscribe(cb) + session.getState() — for React useSyncExternalStore
|
|
9
|
+
* - session.addEventListener('status' | 'message' | 'error' | 'change', cb)
|
|
10
|
+
*
|
|
11
|
+
* Flow: GET /chat/token → WS /chat/ws?token=cht_xxx → bidirectional text chat
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare class ChatSession extends EventTarget {
|
|
15
|
+
private opts;
|
|
16
|
+
private state;
|
|
17
|
+
private listeners;
|
|
18
|
+
private ws;
|
|
19
|
+
private reconnectTimer;
|
|
20
|
+
private msgCounter;
|
|
21
|
+
constructor(opts: ChatSessionOptions);
|
|
22
|
+
/** Read-only snapshot of current state (stable ref until next mutation). */
|
|
23
|
+
getState(): Readonly<ChatSessionState>;
|
|
24
|
+
/** Subscribe to ANY state change (for React useSyncExternalStore). */
|
|
25
|
+
subscribe(listener: () => void): () => void;
|
|
26
|
+
private setState;
|
|
27
|
+
private setMessages;
|
|
28
|
+
connect(): Promise<void>;
|
|
29
|
+
private handleMessage;
|
|
30
|
+
/** Send a text message to the agent. */
|
|
31
|
+
send(text: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Set or clear a keyed context block in the LLM system prompt.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* session.setContext("form", JSON.stringify({ name: "Juan" }));
|
|
38
|
+
* session.setContext("form", null); // clear
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
setContext(key: string, value: string | null): void;
|
|
42
|
+
/** Disconnect the chat session. */
|
|
43
|
+
disconnect(): void;
|
|
44
|
+
/** Tear down the session and clear subscribers. Do not reuse after this. */
|
|
45
|
+
destroy(): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { ChatSession, ChatSessionOptions, ChatSessionState };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { C as ChatSessionOptions, a as ChatSessionState } from '../types-W0229iUB.js';
|
|
2
|
+
export { b as ChatEventType, c as ChatMessage, d as ChatStatus } from '../types-W0229iUB.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ChatSession — Framework-agnostic text chat client for Pinecall agents.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors VoiceSession's API patterns:
|
|
8
|
+
* - session.subscribe(cb) + session.getState() — for React useSyncExternalStore
|
|
9
|
+
* - session.addEventListener('status' | 'message' | 'error' | 'change', cb)
|
|
10
|
+
*
|
|
11
|
+
* Flow: GET /chat/token → WS /chat/ws?token=cht_xxx → bidirectional text chat
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare class ChatSession extends EventTarget {
|
|
15
|
+
private opts;
|
|
16
|
+
private state;
|
|
17
|
+
private listeners;
|
|
18
|
+
private ws;
|
|
19
|
+
private reconnectTimer;
|
|
20
|
+
private msgCounter;
|
|
21
|
+
constructor(opts: ChatSessionOptions);
|
|
22
|
+
/** Read-only snapshot of current state (stable ref until next mutation). */
|
|
23
|
+
getState(): Readonly<ChatSessionState>;
|
|
24
|
+
/** Subscribe to ANY state change (for React useSyncExternalStore). */
|
|
25
|
+
subscribe(listener: () => void): () => void;
|
|
26
|
+
private setState;
|
|
27
|
+
private setMessages;
|
|
28
|
+
connect(): Promise<void>;
|
|
29
|
+
private handleMessage;
|
|
30
|
+
/** Send a text message to the agent. */
|
|
31
|
+
send(text: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Set or clear a keyed context block in the LLM system prompt.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* session.setContext("form", JSON.stringify({ name: "Juan" }));
|
|
38
|
+
* session.setContext("form", null); // clear
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
setContext(key: string, value: string | null): void;
|
|
42
|
+
/** Disconnect the chat session. */
|
|
43
|
+
disconnect(): void;
|
|
44
|
+
/** Tear down the session and clear subscribers. Do not reuse after this. */
|
|
45
|
+
destroy(): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { ChatSession, ChatSessionOptions, ChatSessionState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|