@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.
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // src/chat/react.tsx
6
+
7
+ // src/chat/ChatSession.ts
8
+ var INITIAL_STATE = {
9
+ status: "idle",
10
+ error: null,
11
+ messages: [],
12
+ typing: false,
13
+ streamingText: "",
14
+ sessionId: null
15
+ };
16
+ var ChatSession = class extends EventTarget {
17
+ constructor(opts) {
18
+ super();
19
+ this.opts = opts;
20
+ }
21
+ opts;
22
+ state = { ...INITIAL_STATE };
23
+ listeners = /* @__PURE__ */ new Set();
24
+ ws = null;
25
+ reconnectTimer = null;
26
+ msgCounter = 0;
27
+ /** Read-only snapshot of current state (stable ref until next mutation). */
28
+ getState() {
29
+ return this.state;
30
+ }
31
+ /** Subscribe to ANY state change (for React useSyncExternalStore). */
32
+ subscribe(listener) {
33
+ this.listeners.add(listener);
34
+ return () => {
35
+ this.listeners.delete(listener);
36
+ };
37
+ }
38
+ setState(patch) {
39
+ const prev = this.state;
40
+ this.state = { ...prev, ...patch };
41
+ for (const l of this.listeners) l();
42
+ if (patch.status !== void 0 && patch.status !== prev.status) {
43
+ this.dispatchEvent(
44
+ new CustomEvent("status", { detail: { status: this.state.status } })
45
+ );
46
+ }
47
+ if (patch.error !== void 0 && patch.error !== null && patch.error !== prev.error) {
48
+ this.dispatchEvent(
49
+ new CustomEvent("error", { detail: { error: this.state.error } })
50
+ );
51
+ }
52
+ this.dispatchEvent(
53
+ new CustomEvent("change", { detail: { state: this.state } })
54
+ );
55
+ }
56
+ setMessages(updater) {
57
+ const next = updater(this.state.messages);
58
+ this.setState({ messages: next });
59
+ const last = next[next.length - 1];
60
+ if (last) {
61
+ this.dispatchEvent(
62
+ new CustomEvent("message", { detail: { message: last } })
63
+ );
64
+ }
65
+ }
66
+ // ── Connection ──────────────────────────────────────────────────────
67
+ async connect() {
68
+ if (this.ws) return;
69
+ try {
70
+ this.setState({
71
+ status: "connecting",
72
+ error: null
73
+ });
74
+ const base = (this.opts.server ?? "https://voice.pinecall.io").replace(
75
+ /\/$/,
76
+ ""
77
+ );
78
+ let token;
79
+ let chatServer;
80
+ if (this.opts.tokenProvider) {
81
+ const t = await this.opts.tokenProvider();
82
+ token = t.token;
83
+ chatServer = t.server;
84
+ } else {
85
+ const tRes = await fetch(
86
+ `${base}/chat/token?agent_id=${encodeURIComponent(this.opts.agent)}`
87
+ );
88
+ if (!tRes.ok) {
89
+ const body = await tRes.text();
90
+ throw new Error(`Token: ${tRes.status} ${body}`);
91
+ }
92
+ const t = await tRes.json();
93
+ token = t.token;
94
+ chatServer = t.server;
95
+ }
96
+ const wsBase = (chatServer || base).replace(/^http:/, "ws:").replace(/^https:/, "wss:");
97
+ const ws = new WebSocket(`${wsBase}/chat/ws?token=${token}`);
98
+ this.ws = ws;
99
+ ws.onopen = () => {
100
+ };
101
+ ws.onmessage = (evt) => this.handleMessage(evt);
102
+ ws.onerror = () => {
103
+ this.setState({ error: "WebSocket error", status: "error" });
104
+ };
105
+ ws.onclose = (evt) => {
106
+ this.ws = null;
107
+ if (this.state.status === "connected") {
108
+ this.setState({ status: "idle" });
109
+ }
110
+ };
111
+ } catch (err) {
112
+ this.setState({
113
+ error: err instanceof Error ? err.message : String(err),
114
+ status: "error"
115
+ });
116
+ this.ws = null;
117
+ }
118
+ }
119
+ handleMessage(evt) {
120
+ let d;
121
+ try {
122
+ d = JSON.parse(evt.data);
123
+ } catch {
124
+ return;
125
+ }
126
+ switch (d.event) {
127
+ case "chat.connected":
128
+ this.setState({
129
+ status: "connected",
130
+ sessionId: d.session_id ?? null
131
+ });
132
+ break;
133
+ case "chat.token":
134
+ case "llm.chat.token":
135
+ this.setState({
136
+ typing: true,
137
+ streamingText: d.text ?? ""
138
+ });
139
+ this.setMessages((prev) => {
140
+ const idx = prev.findIndex(
141
+ (m) => m.messageId === d.message_id && m.isStreaming
142
+ );
143
+ if (idx >= 0) {
144
+ return prev.map(
145
+ (m, i) => i === idx ? { ...m, text: d.text ?? "" } : m
146
+ );
147
+ }
148
+ return [
149
+ ...prev,
150
+ {
151
+ id: ++this.msgCounter,
152
+ role: "bot",
153
+ text: d.text ?? "",
154
+ messageId: d.message_id,
155
+ isStreaming: true
156
+ }
157
+ ];
158
+ });
159
+ break;
160
+ case "chat.done":
161
+ case "llm.chat.done":
162
+ this.setState({
163
+ typing: false,
164
+ streamingText: ""
165
+ });
166
+ this.setMessages((prev) => {
167
+ const idx = prev.findIndex(
168
+ (m) => m.messageId === d.message_id && m.isStreaming
169
+ );
170
+ if (idx >= 0) {
171
+ return prev.map(
172
+ (m, i) => i === idx ? { ...m, text: d.text ?? m.text, isStreaming: false } : m
173
+ );
174
+ }
175
+ return [
176
+ ...prev,
177
+ {
178
+ id: ++this.msgCounter,
179
+ role: "bot",
180
+ text: d.text ?? "",
181
+ messageId: d.message_id,
182
+ isStreaming: false
183
+ }
184
+ ];
185
+ });
186
+ break;
187
+ case "chat.error":
188
+ case "llm.chat.error":
189
+ this.setState({
190
+ typing: false,
191
+ error: d.error ?? "Unknown error"
192
+ });
193
+ break;
194
+ case "error":
195
+ this.setState({
196
+ error: d.error ?? "Unknown error",
197
+ status: "error"
198
+ });
199
+ break;
200
+ }
201
+ this.dispatchEvent(new CustomEvent("event", { detail: d }));
202
+ }
203
+ // ── Actions ─────────────────────────────────────────────────────────
204
+ /** Send a text message to the agent. */
205
+ send(text) {
206
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
207
+ const trimmed = text.trim();
208
+ if (!trimmed) return;
209
+ this.setMessages((prev) => [
210
+ ...prev,
211
+ {
212
+ id: ++this.msgCounter,
213
+ role: "user",
214
+ text: trimmed
215
+ }
216
+ ]);
217
+ this.ws.send(JSON.stringify({ event: "message", text: trimmed }));
218
+ }
219
+ /**
220
+ * Set or clear a keyed context block in the LLM system prompt.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * session.setContext("form", JSON.stringify({ name: "Juan" }));
225
+ * session.setContext("form", null); // clear
226
+ * ```
227
+ */
228
+ setContext(key, value) {
229
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
230
+ this.ws.send(JSON.stringify({ event: "set_context", key, value }));
231
+ }
232
+ /** Disconnect the chat session. */
233
+ disconnect() {
234
+ if (this.reconnectTimer) {
235
+ clearTimeout(this.reconnectTimer);
236
+ this.reconnectTimer = null;
237
+ }
238
+ if (this.ws) {
239
+ this.ws.close();
240
+ this.ws = null;
241
+ }
242
+ this.setState({ status: "idle", typing: false, streamingText: "" });
243
+ }
244
+ /** Tear down the session and clear subscribers. Do not reuse after this. */
245
+ destroy() {
246
+ this.disconnect();
247
+ this.setState({ status: "destroyed" });
248
+ this.listeners.clear();
249
+ }
250
+ };
251
+
252
+ // src/chat/react.tsx
253
+ function usePinecallChat(opts) {
254
+ const [session, setSession] = react.useState(
255
+ () => new ChatSession(opts)
256
+ );
257
+ const state = react.useSyncExternalStore(
258
+ react.useCallback((cb) => session.subscribe(cb), [session]),
259
+ react.useCallback(() => session.getState(), [session])
260
+ );
261
+ react.useEffect(() => {
262
+ let activeSession = session;
263
+ if (activeSession.getState().status === "destroyed") {
264
+ activeSession = new ChatSession(opts);
265
+ setSession(activeSession);
266
+ }
267
+ if (opts.autoConnect !== false) {
268
+ activeSession.connect();
269
+ }
270
+ return () => {
271
+ activeSession.destroy();
272
+ };
273
+ }, []);
274
+ return {
275
+ messages: state.messages,
276
+ send: react.useCallback((text) => session.send(text), [session]),
277
+ connected: state.status === "connected",
278
+ typing: state.typing,
279
+ streamingText: state.streamingText,
280
+ error: state.error,
281
+ setContext: react.useCallback(
282
+ (key, value) => session.setContext(key, value),
283
+ [session]
284
+ ),
285
+ connect: react.useCallback(() => session.connect(), [session]),
286
+ disconnect: react.useCallback(() => session.disconnect(), [session])
287
+ };
288
+ }
289
+
290
+ exports.usePinecallChat = usePinecallChat;
291
+ //# sourceMappingURL=react.cjs.map
292
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/ChatSession.ts","../../src/chat/react.tsx"],"names":["useState","useSyncExternalStore","useCallback","useEffect"],"mappings":"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","file":"react.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","/**\n * @pinecall/web/chat/react\n *\n * React hook for Pinecall text chat.\n *\n * @example\n * ```tsx\n * import { usePinecallChat } from \"@pinecall/web/chat/react\";\n *\n * function Chat() {\n * const { messages, send, connected, typing } = usePinecallChat({\n * agent: \"florencia\",\n * });\n *\n * return (\n * <div>\n * {messages.map((m) => (\n * <p key={m.id}>{m.role}: {m.text}</p>\n * ))}\n * <input onKeyDown={(e) => {\n * if (e.key === \"Enter\") {\n * send(e.currentTarget.value);\n * e.currentTarget.value = \"\";\n * }\n * }} />\n * </div>\n * );\n * }\n * ```\n */\nimport { useSyncExternalStore, useState, useCallback, useEffect } from \"react\";\nimport { ChatSession } from \"./ChatSession\";\nimport type { ChatSessionOptions, ChatMessage } from \"./types\";\n\nexport interface UsePinecallChatReturn {\n /** All messages in the conversation. */\n messages: ChatMessage[];\n /** Send a text message. */\n send: (text: string) => void;\n /** True when connected to the server. */\n connected: boolean;\n /** True while the bot is streaming a response. */\n typing: boolean;\n /** Partial text of the current response being streamed. */\n streamingText: string;\n /** Current error, if any. */\n error: string | null;\n /** Inject dynamic context into the LLM prompt. */\n setContext: (key: string, value: string | null) => void;\n /** Connect to the chat server. */\n connect: () => void;\n /** Disconnect from the chat server. */\n disconnect: () => void;\n}\n\n/**\n * React hook for Pinecall text chat.\n *\n * Creates a ChatSession and exposes reactive state via useSyncExternalStore.\n * The session auto-connects on mount and disconnects on unmount.\n */\nexport function usePinecallChat(\n opts: ChatSessionOptions & {\n /** Set to false to disable auto-connect on mount. @default true */\n autoConnect?: boolean;\n },\n): UsePinecallChatReturn {\n // Use state for the session so React re-renders when it changes\n const [session, setSession] = useState<ChatSession>(\n () => new ChatSession(opts),\n );\n\n // useSyncExternalStore for reactive state\n const state = useSyncExternalStore(\n useCallback((cb: () => void) => session.subscribe(cb), [session]),\n useCallback(() => session.getState(), [session]),\n );\n\n // Auto-connect on mount, destroy on unmount\n useEffect(() => {\n // In Strict Mode, the previous session was destroyed.\n // Create a fresh one if the current session was destroyed.\n let activeSession = session;\n if (activeSession.getState().status === \"destroyed\") {\n activeSession = new ChatSession(opts);\n setSession(activeSession);\n }\n\n if (opts.autoConnect !== false) {\n activeSession.connect();\n }\n return () => {\n activeSession.destroy();\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return {\n messages: state.messages,\n send: useCallback((text: string) => session.send(text), [session]),\n connected: state.status === \"connected\",\n typing: state.typing,\n streamingText: state.streamingText,\n error: state.error,\n setContext: useCallback(\n (key: string, value: string | null) => session.setContext(key, value),\n [session],\n ),\n connect: useCallback(() => session.connect(), [session]),\n disconnect: useCallback(() => session.disconnect(), [session]),\n };\n}\n\n// Re-export core types for convenience\nexport type { ChatSessionOptions, ChatMessage, ChatSessionState } from \"./types\";\n"]}
@@ -0,0 +1,35 @@
1
+ import { c as ChatMessage, C as ChatSessionOptions } from '../types-W0229iUB.cjs';
2
+ export { a as ChatSessionState } from '../types-W0229iUB.cjs';
3
+
4
+ interface UsePinecallChatReturn {
5
+ /** All messages in the conversation. */
6
+ messages: ChatMessage[];
7
+ /** Send a text message. */
8
+ send: (text: string) => void;
9
+ /** True when connected to the server. */
10
+ connected: boolean;
11
+ /** True while the bot is streaming a response. */
12
+ typing: boolean;
13
+ /** Partial text of the current response being streamed. */
14
+ streamingText: string;
15
+ /** Current error, if any. */
16
+ error: string | null;
17
+ /** Inject dynamic context into the LLM prompt. */
18
+ setContext: (key: string, value: string | null) => void;
19
+ /** Connect to the chat server. */
20
+ connect: () => void;
21
+ /** Disconnect from the chat server. */
22
+ disconnect: () => void;
23
+ }
24
+ /**
25
+ * React hook for Pinecall text chat.
26
+ *
27
+ * Creates a ChatSession and exposes reactive state via useSyncExternalStore.
28
+ * The session auto-connects on mount and disconnects on unmount.
29
+ */
30
+ declare function usePinecallChat(opts: ChatSessionOptions & {
31
+ /** Set to false to disable auto-connect on mount. @default true */
32
+ autoConnect?: boolean;
33
+ }): UsePinecallChatReturn;
34
+
35
+ export { ChatMessage, ChatSessionOptions, type UsePinecallChatReturn, usePinecallChat };
@@ -0,0 +1,35 @@
1
+ import { c as ChatMessage, C as ChatSessionOptions } from '../types-W0229iUB.js';
2
+ export { a as ChatSessionState } from '../types-W0229iUB.js';
3
+
4
+ interface UsePinecallChatReturn {
5
+ /** All messages in the conversation. */
6
+ messages: ChatMessage[];
7
+ /** Send a text message. */
8
+ send: (text: string) => void;
9
+ /** True when connected to the server. */
10
+ connected: boolean;
11
+ /** True while the bot is streaming a response. */
12
+ typing: boolean;
13
+ /** Partial text of the current response being streamed. */
14
+ streamingText: string;
15
+ /** Current error, if any. */
16
+ error: string | null;
17
+ /** Inject dynamic context into the LLM prompt. */
18
+ setContext: (key: string, value: string | null) => void;
19
+ /** Connect to the chat server. */
20
+ connect: () => void;
21
+ /** Disconnect from the chat server. */
22
+ disconnect: () => void;
23
+ }
24
+ /**
25
+ * React hook for Pinecall text chat.
26
+ *
27
+ * Creates a ChatSession and exposes reactive state via useSyncExternalStore.
28
+ * The session auto-connects on mount and disconnects on unmount.
29
+ */
30
+ declare function usePinecallChat(opts: ChatSessionOptions & {
31
+ /** Set to false to disable auto-connect on mount. @default true */
32
+ autoConnect?: boolean;
33
+ }): UsePinecallChatReturn;
34
+
35
+ export { ChatMessage, ChatSessionOptions, type UsePinecallChatReturn, usePinecallChat };
@@ -0,0 +1,4 @@
1
+ export { usePinecallChat } from '../chunk-LHZA26Z5.js';
2
+ import '../chunk-MCAQMGBG.js';
3
+ //# sourceMappingURL=react.js.map
4
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"react.js"}
@@ -0,0 +1,43 @@
1
+ import { ChatSession } from './chunk-MCAQMGBG.js';
2
+ import { useState, useSyncExternalStore, useCallback, useEffect } from 'react';
3
+
4
+ function usePinecallChat(opts) {
5
+ const [session, setSession] = useState(
6
+ () => new ChatSession(opts)
7
+ );
8
+ const state = useSyncExternalStore(
9
+ useCallback((cb) => session.subscribe(cb), [session]),
10
+ useCallback(() => session.getState(), [session])
11
+ );
12
+ useEffect(() => {
13
+ let activeSession = session;
14
+ if (activeSession.getState().status === "destroyed") {
15
+ activeSession = new ChatSession(opts);
16
+ setSession(activeSession);
17
+ }
18
+ if (opts.autoConnect !== false) {
19
+ activeSession.connect();
20
+ }
21
+ return () => {
22
+ activeSession.destroy();
23
+ };
24
+ }, []);
25
+ return {
26
+ messages: state.messages,
27
+ send: useCallback((text) => session.send(text), [session]),
28
+ connected: state.status === "connected",
29
+ typing: state.typing,
30
+ streamingText: state.streamingText,
31
+ error: state.error,
32
+ setContext: useCallback(
33
+ (key, value) => session.setContext(key, value),
34
+ [session]
35
+ ),
36
+ connect: useCallback(() => session.connect(), [session]),
37
+ disconnect: useCallback(() => session.disconnect(), [session])
38
+ };
39
+ }
40
+
41
+ export { usePinecallChat };
42
+ //# sourceMappingURL=chunk-LHZA26Z5.js.map
43
+ //# sourceMappingURL=chunk-LHZA26Z5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chat/react.tsx"],"names":[],"mappings":";;;AA6DO,SAAS,gBACd,IAAA,EAIuB;AAEvB,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,QAAA;AAAA,IAC5B,MAAM,IAAI,WAAA,CAAY,IAAI;AAAA,GAC5B;AAGA,EAAA,MAAM,KAAA,GAAQ,oBAAA;AAAA,IACZ,WAAA,CAAY,CAAC,EAAA,KAAmB,OAAA,CAAQ,UAAU,EAAE,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAAA,IAChE,YAAY,MAAM,OAAA,CAAQ,UAAS,EAAG,CAAC,OAAO,CAAC;AAAA,GACjD;AAGA,EAAA,SAAA,CAAU,MAAM;AAGd,IAAA,IAAI,aAAA,GAAgB,OAAA;AACpB,IAAA,IAAI,aAAA,CAAc,QAAA,EAAS,CAAE,MAAA,KAAW,WAAA,EAAa;AACnD,MAAA,aAAA,GAAgB,IAAI,YAAY,IAAI,CAAA;AACpC,MAAA,UAAA,CAAW,aAAa,CAAA;AAAA,IAC1B;AAEA,IAAA,IAAI,IAAA,CAAK,gBAAgB,KAAA,EAAO;AAC9B,MAAA,aAAA,CAAc,OAAA,EAAQ;AAAA,IACxB;AACA,IAAA,OAAO,MAAM;AACX,MAAA,aAAA,CAAc,OAAA,EAAQ;AAAA,IACxB,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,UAAU,KAAA,CAAM,QAAA;AAAA,IAChB,IAAA,EAAM,WAAA,CAAY,CAAC,IAAA,KAAiB,OAAA,CAAQ,KAAK,IAAI,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAAA,IACjE,SAAA,EAAW,MAAM,MAAA,KAAW,WAAA;AAAA,IAC5B,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,eAAe,KAAA,CAAM,aAAA;AAAA,IACrB,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,UAAA,EAAY,WAAA;AAAA,MACV,CAAC,GAAA,EAAa,KAAA,KAAyB,OAAA,CAAQ,UAAA,CAAW,KAAK,KAAK,CAAA;AAAA,MACpE,CAAC,OAAO;AAAA,KACV;AAAA,IACA,OAAA,EAAS,YAAY,MAAM,OAAA,CAAQ,SAAQ,EAAG,CAAC,OAAO,CAAC,CAAA;AAAA,IACvD,UAAA,EAAY,YAAY,MAAM,OAAA,CAAQ,YAAW,EAAG,CAAC,OAAO,CAAC;AAAA,GAC/D;AACF","file":"chunk-LHZA26Z5.js","sourcesContent":["/**\n * @pinecall/web/chat/react\n *\n * React hook for Pinecall text chat.\n *\n * @example\n * ```tsx\n * import { usePinecallChat } from \"@pinecall/web/chat/react\";\n *\n * function Chat() {\n * const { messages, send, connected, typing } = usePinecallChat({\n * agent: \"florencia\",\n * });\n *\n * return (\n * <div>\n * {messages.map((m) => (\n * <p key={m.id}>{m.role}: {m.text}</p>\n * ))}\n * <input onKeyDown={(e) => {\n * if (e.key === \"Enter\") {\n * send(e.currentTarget.value);\n * e.currentTarget.value = \"\";\n * }\n * }} />\n * </div>\n * );\n * }\n * ```\n */\nimport { useSyncExternalStore, useState, useCallback, useEffect } from \"react\";\nimport { ChatSession } from \"./ChatSession\";\nimport type { ChatSessionOptions, ChatMessage } from \"./types\";\n\nexport interface UsePinecallChatReturn {\n /** All messages in the conversation. */\n messages: ChatMessage[];\n /** Send a text message. */\n send: (text: string) => void;\n /** True when connected to the server. */\n connected: boolean;\n /** True while the bot is streaming a response. */\n typing: boolean;\n /** Partial text of the current response being streamed. */\n streamingText: string;\n /** Current error, if any. */\n error: string | null;\n /** Inject dynamic context into the LLM prompt. */\n setContext: (key: string, value: string | null) => void;\n /** Connect to the chat server. */\n connect: () => void;\n /** Disconnect from the chat server. */\n disconnect: () => void;\n}\n\n/**\n * React hook for Pinecall text chat.\n *\n * Creates a ChatSession and exposes reactive state via useSyncExternalStore.\n * The session auto-connects on mount and disconnects on unmount.\n */\nexport function usePinecallChat(\n opts: ChatSessionOptions & {\n /** Set to false to disable auto-connect on mount. @default true */\n autoConnect?: boolean;\n },\n): UsePinecallChatReturn {\n // Use state for the session so React re-renders when it changes\n const [session, setSession] = useState<ChatSession>(\n () => new ChatSession(opts),\n );\n\n // useSyncExternalStore for reactive state\n const state = useSyncExternalStore(\n useCallback((cb: () => void) => session.subscribe(cb), [session]),\n useCallback(() => session.getState(), [session]),\n );\n\n // Auto-connect on mount, destroy on unmount\n useEffect(() => {\n // In Strict Mode, the previous session was destroyed.\n // Create a fresh one if the current session was destroyed.\n let activeSession = session;\n if (activeSession.getState().status === \"destroyed\") {\n activeSession = new ChatSession(opts);\n setSession(activeSession);\n }\n\n if (opts.autoConnect !== false) {\n activeSession.connect();\n }\n return () => {\n activeSession.destroy();\n };\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n return {\n messages: state.messages,\n send: useCallback((text: string) => session.send(text), [session]),\n connected: state.status === \"connected\",\n typing: state.typing,\n streamingText: state.streamingText,\n error: state.error,\n setContext: useCallback(\n (key: string, value: string | null) => session.setContext(key, value),\n [session],\n ),\n connect: useCallback(() => session.connect(), [session]),\n disconnect: useCallback(() => session.disconnect(), [session]),\n };\n}\n\n// Re-export core types for convenience\nexport type { ChatSessionOptions, ChatMessage, ChatSessionState } from \"./types\";\n"]}