@thunderphone/widget 0.2.3 → 0.3.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/dist/index.mjs CHANGED
@@ -1,94 +1,46 @@
1
- // src/ThunderPhoneWidget.tsx
2
- import { useCallback, useState as useState2 } from "react";
3
- import { LiveKitRoom } from "@livekit/components-react";
4
-
5
- // src/AudioHandler.tsx
6
- import { useEffect, useRef } from "react";
7
- import { useRoomContext } from "@livekit/components-react";
8
- import { RoomEvent, Track } from "livekit-client";
9
- import { jsx } from "react/jsx-runtime";
10
- function AudioHandler({ onAgentConnected, onDisconnected }) {
11
- const room = useRoomContext();
12
- const audioRef = useRef(null);
13
- useEffect(() => {
14
- if (room.remoteParticipants.size > 0) {
15
- onAgentConnected();
16
- }
17
- const handleParticipantConnected = () => onAgentConnected();
18
- const handleDisconnect = () => onDisconnected();
19
- const attachTrack = (track, _pub, _participant) => {
20
- if (track.kind === Track.Kind.Audio && audioRef.current) {
21
- const stream = new MediaStream([track.mediaStreamTrack]);
22
- audioRef.current.srcObject = stream;
23
- audioRef.current.play().catch(() => {
24
- });
25
- }
26
- };
27
- room.on(RoomEvent.ParticipantConnected, handleParticipantConnected);
28
- room.on(RoomEvent.Disconnected, handleDisconnect);
29
- room.on(RoomEvent.TrackSubscribed, attachTrack);
30
- for (const participant of room.remoteParticipants.values()) {
31
- for (const pub of participant.trackPublications.values()) {
32
- if (pub.track && pub.isSubscribed && pub.track.kind === Track.Kind.Audio && audioRef.current) {
33
- const stream = new MediaStream([pub.track.mediaStreamTrack]);
34
- audioRef.current.srcObject = stream;
35
- audioRef.current.play().catch(() => {
36
- });
37
- }
38
- }
39
- }
40
- return () => {
41
- room.off(RoomEvent.ParticipantConnected, handleParticipantConnected);
42
- room.off(RoomEvent.Disconnected, handleDisconnect);
43
- room.off(RoomEvent.TrackSubscribed, attachTrack);
44
- };
45
- }, [room, onAgentConnected, onDisconnected]);
46
- return /* @__PURE__ */ jsx("audio", { ref: audioRef, autoPlay: true });
47
- }
48
-
49
1
  // src/WidgetButton.tsx
50
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
51
3
  function WidgetButton({ state, muted, onClick, onMuteToggle }) {
52
4
  if (state === "connected") {
53
5
  return /* @__PURE__ */ jsxs("div", { className: "tp-button-group", children: [
54
- /* @__PURE__ */ jsx2("button", { className: "tp-button tp-button--mute", onClick: onMuteToggle, type: "button", children: muted ? /* @__PURE__ */ jsxs("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
55
- /* @__PURE__ */ jsx2("line", { x1: "1", y1: "1", x2: "23", y2: "23" }),
56
- /* @__PURE__ */ jsx2("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" }),
57
- /* @__PURE__ */ jsx2("path", { d: "M17 16.95A7 7 0 0 1 5 12v-2m14 0v2c0 .76-.13 1.49-.36 2.18" }),
58
- /* @__PURE__ */ jsx2("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
59
- /* @__PURE__ */ jsx2("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
6
+ /* @__PURE__ */ jsx("button", { className: "tp-button tp-button--mute", onClick: onMuteToggle, type: "button", children: muted ? /* @__PURE__ */ jsxs("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
7
+ /* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" }),
8
+ /* @__PURE__ */ jsx("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" }),
9
+ /* @__PURE__ */ jsx("path", { d: "M17 16.95A7 7 0 0 1 5 12v-2m14 0v2c0 .76-.13 1.49-.36 2.18" }),
10
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
11
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
60
12
  ] }) : /* @__PURE__ */ jsxs("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
61
- /* @__PURE__ */ jsx2("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
62
- /* @__PURE__ */ jsx2("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
63
- /* @__PURE__ */ jsx2("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
64
- /* @__PURE__ */ jsx2("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
13
+ /* @__PURE__ */ jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
14
+ /* @__PURE__ */ jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
15
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
16
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
65
17
  ] }) }),
66
- /* @__PURE__ */ jsx2("button", { className: "tp-button tp-button--end", onClick, type: "button", children: /* @__PURE__ */ jsx2("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx2("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) })
18
+ /* @__PURE__ */ jsx("button", { className: "tp-button tp-button--end", onClick, type: "button", children: /* @__PURE__ */ jsx("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) })
67
19
  ] });
68
20
  }
69
- return /* @__PURE__ */ jsx2(
21
+ return /* @__PURE__ */ jsx(
70
22
  "button",
71
23
  {
72
24
  className: `tp-button tp-button--start ${state === "connecting" ? "tp-button--loading" : ""}`,
73
25
  onClick,
74
26
  disabled: state === "connecting",
75
27
  type: "button",
76
- children: state === "connecting" ? /* @__PURE__ */ jsx2("svg", { className: "tp-icon tp-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx2("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }) : /* @__PURE__ */ jsxs("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
77
- /* @__PURE__ */ jsx2("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
78
- /* @__PURE__ */ jsx2("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
79
- /* @__PURE__ */ jsx2("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
80
- /* @__PURE__ */ jsx2("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
28
+ children: state === "connecting" ? /* @__PURE__ */ jsx("svg", { className: "tp-icon tp-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }) : /* @__PURE__ */ jsxs("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
29
+ /* @__PURE__ */ jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
30
+ /* @__PURE__ */ jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
31
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
32
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
81
33
  ] })
82
34
  }
83
35
  );
84
36
  }
85
37
 
86
38
  // src/WidgetStatus.tsx
87
- import { useEffect as useEffect2, useState } from "react";
88
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
39
+ import { useEffect, useState } from "react";
40
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
89
41
  function WidgetStatus({ state, agentName, errorMessage }) {
90
42
  const [elapsed, setElapsed] = useState(0);
91
- useEffect2(() => {
43
+ useEffect(() => {
92
44
  if (state !== "connected") {
93
45
  setElapsed(0);
94
46
  return;
@@ -109,14 +61,62 @@ function WidgetStatus({ state, agentName, errorMessage }) {
109
61
  error: "Unable to connect"
110
62
  };
111
63
  return /* @__PURE__ */ jsxs2("div", { className: "tp-status", children: [
112
- agentName && /* @__PURE__ */ jsx3("div", { className: "tp-status__name", children: agentName }),
64
+ agentName && /* @__PURE__ */ jsx2("div", { className: "tp-status__name", children: agentName }),
113
65
  /* @__PURE__ */ jsxs2("div", { className: `tp-status__text tp-status--${state}`, children: [
114
- state === "connected" && /* @__PURE__ */ jsx3("span", { className: "tp-status__dot" }),
66
+ state === "connected" && /* @__PURE__ */ jsx2("span", { className: "tp-status__dot" }),
115
67
  errorMessage && state === "error" ? errorMessage : statusText[state]
116
68
  ] })
117
69
  ] });
118
70
  }
119
71
 
72
+ // src/useThunderPhone.ts
73
+ import { useCallback, useState as useState2, createElement } from "react";
74
+ import { LiveKitRoom } from "@livekit/components-react";
75
+
76
+ // src/AudioHandler.tsx
77
+ import { useEffect as useEffect2, useRef } from "react";
78
+ import { useRoomContext } from "@livekit/components-react";
79
+ import { RoomEvent, Track } from "livekit-client";
80
+ import { jsx as jsx3 } from "react/jsx-runtime";
81
+ function AudioHandler({ onAgentConnected, onDisconnected }) {
82
+ const room = useRoomContext();
83
+ const audioRef = useRef(null);
84
+ useEffect2(() => {
85
+ if (room.remoteParticipants.size > 0) {
86
+ onAgentConnected();
87
+ }
88
+ const handleParticipantConnected = () => onAgentConnected();
89
+ const handleDisconnect = () => onDisconnected();
90
+ const attachTrack = (track, _pub, _participant) => {
91
+ if (track.kind === Track.Kind.Audio && audioRef.current) {
92
+ const stream = new MediaStream([track.mediaStreamTrack]);
93
+ audioRef.current.srcObject = stream;
94
+ audioRef.current.play().catch(() => {
95
+ });
96
+ }
97
+ };
98
+ room.on(RoomEvent.ParticipantConnected, handleParticipantConnected);
99
+ room.on(RoomEvent.Disconnected, handleDisconnect);
100
+ room.on(RoomEvent.TrackSubscribed, attachTrack);
101
+ for (const participant of room.remoteParticipants.values()) {
102
+ for (const pub of participant.trackPublications.values()) {
103
+ if (pub.track && pub.isSubscribed && pub.track.kind === Track.Kind.Audio && audioRef.current) {
104
+ const stream = new MediaStream([pub.track.mediaStreamTrack]);
105
+ audioRef.current.srcObject = stream;
106
+ audioRef.current.play().catch(() => {
107
+ });
108
+ }
109
+ }
110
+ }
111
+ return () => {
112
+ room.off(RoomEvent.ParticipantConnected, handleParticipantConnected);
113
+ room.off(RoomEvent.Disconnected, handleDisconnect);
114
+ room.off(RoomEvent.TrackSubscribed, attachTrack);
115
+ };
116
+ }, [room, onAgentConnected, onDisconnected]);
117
+ return /* @__PURE__ */ jsx3("audio", { ref: audioRef, autoPlay: true });
118
+ }
119
+
120
120
  // src/api.ts
121
121
  var DEFAULT_API_BASE = "https://api.thunderphone.com/v1";
122
122
  var WidgetAPIError = class extends Error {
@@ -146,98 +146,113 @@ async function createWidgetSession(apiKey, agentId, apiBase) {
146
146
  return response.json();
147
147
  }
148
148
 
149
- // src/ThunderPhoneWidget.tsx
150
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
151
- function ThunderPhoneWidget({
152
- apiKey,
153
- agentId,
154
- apiBase,
155
- onConnect,
156
- onDisconnect,
157
- onError,
158
- className
159
- }) {
149
+ // src/useThunderPhone.ts
150
+ function useThunderPhone(opts) {
160
151
  const [state, setState] = useState2("idle");
161
152
  const [session, setSession] = useState2(null);
162
153
  const [muted, setMuted] = useState2(false);
163
- const [errorMessage, setErrorMessage] = useState2();
164
- const handleConnect = useCallback(async () => {
154
+ const [error, setError] = useState2();
155
+ const handleDisconnect = useCallback(() => {
156
+ setState("disconnected");
157
+ setSession(null);
158
+ setMuted(false);
159
+ opts.onDisconnect?.();
160
+ setTimeout(() => setState("idle"), 1500);
161
+ }, [opts.onDisconnect]);
162
+ const handleAgentConnected = useCallback(() => {
163
+ setState("connected");
164
+ opts.onConnect?.();
165
+ }, [opts.onConnect]);
166
+ const connect = useCallback(async () => {
165
167
  if (state === "connecting" || state === "connected") return;
166
168
  setState("connecting");
167
- setErrorMessage(void 0);
169
+ setError(void 0);
168
170
  try {
169
- const sess = await createWidgetSession(apiKey, agentId, apiBase);
171
+ const sess = await createWidgetSession(opts.apiKey, opts.agentId, opts.apiBase);
170
172
  setSession(sess);
171
173
  } catch (err) {
172
174
  setState("error");
173
175
  if (err instanceof WidgetAPIError) {
174
- setErrorMessage(err.message);
175
- onError?.({ error: err.code, message: err.message });
176
+ setError(err.message);
177
+ opts.onError?.({ error: err.code, message: err.message });
176
178
  } else {
177
- setErrorMessage("Unable to connect.");
178
- onError?.({ error: "unknown", message: "Unable to connect." });
179
+ setError("Unable to connect.");
180
+ opts.onError?.({ error: "unknown", message: "Unable to connect." });
179
181
  }
180
182
  }
181
- }, [apiKey, agentId, apiBase, state, onError]);
182
- const handleDisconnect = useCallback(() => {
183
- setState("disconnected");
184
- setSession(null);
185
- setMuted(false);
186
- onDisconnect?.();
187
- setTimeout(() => setState("idle"), 1500);
188
- }, [onDisconnect]);
189
- const handleAgentConnected = useCallback(() => {
190
- setState("connected");
191
- onConnect?.();
192
- }, [onConnect]);
183
+ }, [opts.apiKey, opts.agentId, opts.apiBase, state, opts.onError]);
184
+ const disconnect = useCallback(() => {
185
+ handleDisconnect();
186
+ }, [handleDisconnect]);
187
+ const toggleMute = useCallback(() => setMuted((m) => !m), []);
188
+ const audio = session ? createElement(
189
+ LiveKitRoom,
190
+ {
191
+ token: session.token,
192
+ serverUrl: session.server_url,
193
+ audio: !muted,
194
+ video: false,
195
+ connect: true
196
+ },
197
+ createElement(AudioHandler, {
198
+ onAgentConnected: handleAgentConnected,
199
+ onDisconnected: handleDisconnect
200
+ })
201
+ ) : null;
202
+ return {
203
+ state,
204
+ connect,
205
+ disconnect,
206
+ toggleMute,
207
+ isMuted: muted,
208
+ error,
209
+ agentName: session?.agent_name,
210
+ audio
211
+ };
212
+ }
213
+
214
+ // src/ThunderPhoneWidget.tsx
215
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
216
+ function ThunderPhoneWidget({
217
+ apiKey,
218
+ agentId,
219
+ apiBase,
220
+ onConnect,
221
+ onDisconnect,
222
+ onError,
223
+ className
224
+ }) {
225
+ const phone = useThunderPhone({ apiKey, agentId, apiBase, onConnect, onDisconnect, onError });
193
226
  const handleClick = () => {
194
- if (state === "connected") {
195
- handleDisconnect();
196
- } else if (state === "idle" || state === "error" || state === "disconnected") {
197
- handleConnect();
227
+ if (phone.state === "connected") {
228
+ phone.disconnect();
229
+ } else if (phone.state === "idle" || phone.state === "error" || phone.state === "disconnected") {
230
+ phone.connect();
198
231
  }
199
232
  };
200
- const handleMuteToggle = useCallback(() => {
201
- setMuted((m) => !m);
202
- }, []);
203
233
  return /* @__PURE__ */ jsxs3("div", { className: `tp-widget ${className || ""}`, children: [
204
234
  /* @__PURE__ */ jsx4(
205
235
  WidgetStatus,
206
236
  {
207
- state,
208
- agentName: session?.agent_name || null,
209
- errorMessage
237
+ state: phone.state,
238
+ agentName: phone.agentName || null,
239
+ errorMessage: phone.error
210
240
  }
211
241
  ),
212
242
  /* @__PURE__ */ jsx4(
213
243
  WidgetButton,
214
244
  {
215
- state,
216
- muted,
245
+ state: phone.state,
246
+ muted: phone.isMuted,
217
247
  onClick: handleClick,
218
- onMuteToggle: handleMuteToggle
248
+ onMuteToggle: phone.toggleMute
219
249
  }
220
250
  ),
221
- session && /* @__PURE__ */ jsx4(
222
- LiveKitRoom,
223
- {
224
- token: session.token,
225
- serverUrl: session.server_url,
226
- audio: !muted,
227
- video: false,
228
- connect: true,
229
- children: /* @__PURE__ */ jsx4(
230
- AudioHandler,
231
- {
232
- onAgentConnected: handleAgentConnected,
233
- onDisconnected: handleDisconnect
234
- }
235
- )
236
- }
237
- )
251
+ phone.audio
238
252
  ] });
239
253
  }
240
254
  export {
241
- ThunderPhoneWidget
255
+ ThunderPhoneWidget,
256
+ useThunderPhone
242
257
  };
243
258
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ThunderPhoneWidget.tsx","../src/AudioHandler.tsx","../src/WidgetButton.tsx","../src/WidgetStatus.tsx","../src/api.ts"],"sourcesContent":["import { useCallback, useState } from 'react'\nimport { LiveKitRoom } from '@livekit/components-react'\nimport { AudioHandler } from './AudioHandler'\nimport { WidgetButton } from './WidgetButton'\nimport { WidgetStatus } from './WidgetStatus'\nimport { createWidgetSession, WidgetAPIError } from './api'\nimport type { ThunderPhoneWidgetProps, WidgetState, WidgetSessionResponse } from './types'\n\nexport function ThunderPhoneWidget({\n apiKey,\n agentId,\n apiBase,\n onConnect,\n onDisconnect,\n onError,\n className,\n}: ThunderPhoneWidgetProps) {\n const [state, setState] = useState<WidgetState>('idle')\n const [session, setSession] = useState<WidgetSessionResponse | null>(null)\n const [muted, setMuted] = useState(false)\n const [errorMessage, setErrorMessage] = useState<string | undefined>()\n\n const handleConnect = useCallback(async () => {\n if (state === 'connecting' || state === 'connected') return\n setState('connecting')\n setErrorMessage(undefined)\n try {\n const sess = await createWidgetSession(apiKey, agentId, apiBase)\n setSession(sess)\n } catch (err) {\n setState('error')\n if (err instanceof WidgetAPIError) {\n setErrorMessage(err.message)\n onError?.({ error: err.code, message: err.message })\n } else {\n setErrorMessage('Unable to connect.')\n onError?.({ error: 'unknown', message: 'Unable to connect.' })\n }\n }\n }, [apiKey, agentId, apiBase, state, onError])\n\n const handleDisconnect = useCallback(() => {\n setState('disconnected')\n setSession(null)\n setMuted(false)\n onDisconnect?.()\n setTimeout(() => setState('idle'), 1500)\n }, [onDisconnect])\n\n const handleAgentConnected = useCallback(() => {\n setState('connected')\n onConnect?.()\n }, [onConnect])\n\n const handleClick = () => {\n if (state === 'connected') {\n handleDisconnect()\n } else if (state === 'idle' || state === 'error' || state === 'disconnected') {\n handleConnect()\n }\n }\n\n const handleMuteToggle = useCallback(() => {\n setMuted(m => !m)\n }, [])\n\n return (\n <div className={`tp-widget ${className || ''}`}>\n <WidgetStatus\n state={state}\n agentName={session?.agent_name || null}\n errorMessage={errorMessage}\n />\n <WidgetButton\n state={state}\n muted={muted}\n onClick={handleClick}\n onMuteToggle={handleMuteToggle}\n />\n {session && (\n <LiveKitRoom\n token={session.token}\n serverUrl={session.server_url}\n audio={!muted}\n video={false}\n connect={true}\n >\n <AudioHandler\n onAgentConnected={handleAgentConnected}\n onDisconnected={handleDisconnect}\n />\n </LiveKitRoom>\n )}\n </div>\n )\n}\n","import { useEffect, useRef } from 'react'\nimport { useRoomContext } from '@livekit/components-react'\nimport { RoomEvent, Track, type RemoteTrackPublication, type RemoteParticipant } from 'livekit-client'\n\ninterface AudioHandlerProps {\n onAgentConnected: () => void\n onDisconnected: () => void\n}\n\nexport function AudioHandler({ onAgentConnected, onDisconnected }: AudioHandlerProps) {\n const room = useRoomContext()\n const audioRef = useRef<HTMLAudioElement>(null)\n\n useEffect(() => {\n if (room.remoteParticipants.size > 0) {\n onAgentConnected()\n }\n\n const handleParticipantConnected = () => onAgentConnected()\n const handleDisconnect = () => onDisconnected()\n\n const attachTrack = (\n track: { kind: Track.Kind; mediaStreamTrack: MediaStreamTrack },\n _pub: RemoteTrackPublication,\n _participant: RemoteParticipant,\n ) => {\n if (track.kind === Track.Kind.Audio && audioRef.current) {\n const stream = new MediaStream([track.mediaStreamTrack])\n audioRef.current.srcObject = stream\n audioRef.current.play().catch(() => {})\n }\n }\n\n room.on(RoomEvent.ParticipantConnected, handleParticipantConnected)\n room.on(RoomEvent.Disconnected, handleDisconnect)\n room.on(RoomEvent.TrackSubscribed, attachTrack)\n\n for (const participant of room.remoteParticipants.values()) {\n for (const pub of participant.trackPublications.values()) {\n if (pub.track && pub.isSubscribed && pub.track.kind === Track.Kind.Audio && audioRef.current) {\n const stream = new MediaStream([pub.track.mediaStreamTrack])\n audioRef.current.srcObject = stream\n audioRef.current.play().catch(() => {})\n }\n }\n }\n\n return () => {\n room.off(RoomEvent.ParticipantConnected, handleParticipantConnected)\n room.off(RoomEvent.Disconnected, handleDisconnect)\n room.off(RoomEvent.TrackSubscribed, attachTrack)\n }\n }, [room, onAgentConnected, onDisconnected])\n\n return <audio ref={audioRef} autoPlay />\n}\n","import type { WidgetState } from './types'\n\ninterface WidgetButtonProps {\n state: WidgetState\n muted: boolean\n onClick: () => void\n onMuteToggle: () => void\n}\n\nexport function WidgetButton({ state, muted, onClick, onMuteToggle }: WidgetButtonProps) {\n if (state === 'connected') {\n return (\n <div className=\"tp-button-group\">\n <button className=\"tp-button tp-button--mute\" onClick={onMuteToggle} type=\"button\">\n {muted ? (\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" />\n <path d=\"M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6\" />\n <path d=\"M17 16.95A7 7 0 0 1 5 12v-2m14 0v2c0 .76-.13 1.49-.36 2.18\" />\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\" />\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\" />\n </svg>\n ) : (\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\" />\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\" />\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\" />\n </svg>\n )}\n </button>\n <button className=\"tp-button tp-button--end\" onClick={onClick} type=\"button\">\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n </svg>\n </button>\n </div>\n )\n }\n\n return (\n <button\n className={`tp-button tp-button--start ${state === 'connecting' ? 'tp-button--loading' : ''}`}\n onClick={onClick}\n disabled={state === 'connecting'}\n type=\"button\"\n >\n {state === 'connecting' ? (\n <svg className=\"tp-icon tp-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n </svg>\n ) : (\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\" />\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\" />\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\" />\n </svg>\n )}\n </button>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { WidgetState } from './types'\n\ninterface WidgetStatusProps {\n state: WidgetState\n agentName: string | null\n errorMessage?: string\n}\n\nexport function WidgetStatus({ state, agentName, errorMessage }: WidgetStatusProps) {\n const [elapsed, setElapsed] = useState(0)\n\n useEffect(() => {\n if (state !== 'connected') {\n setElapsed(0)\n return\n }\n const interval = setInterval(() => setElapsed(s => s + 1), 1000)\n return () => clearInterval(interval)\n }, [state])\n\n const formatTime = (seconds: number) => {\n const m = Math.floor(seconds / 60)\n const s = seconds % 60\n return `${m}:${s.toString().padStart(2, '0')}`\n }\n\n const statusText: Record<WidgetState, string> = {\n idle: 'Ready',\n connecting: 'Connecting...',\n connected: formatTime(elapsed),\n disconnected: 'Disconnected',\n error: 'Unable to connect',\n }\n\n return (\n <div className=\"tp-status\">\n {agentName && <div className=\"tp-status__name\">{agentName}</div>}\n <div className={`tp-status__text tp-status--${state}`}>\n {state === 'connected' && <span className=\"tp-status__dot\" />}\n {errorMessage && state === 'error' ? errorMessage : statusText[state]}\n </div>\n </div>\n )\n}\n","import type { WidgetSessionResponse, WidgetError } from './types'\n\nconst DEFAULT_API_BASE = 'https://api.thunderphone.com/v1'\n\nexport class WidgetAPIError extends Error {\n constructor(public code: string, message: string) {\n super(message)\n this.name = 'WidgetAPIError'\n }\n}\n\nexport async function createWidgetSession(\n apiKey: string,\n agentId: number,\n apiBase?: string,\n): Promise<WidgetSessionResponse> {\n const base = apiBase || DEFAULT_API_BASE\n const response = await fetch(`${base}/widget/session`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': apiKey,\n },\n body: JSON.stringify({ agent_id: agentId }),\n })\n\n if (!response.ok) {\n const data: WidgetError = await response.json().catch(() => ({\n error: 'unknown',\n message: 'Unable to connect.',\n }))\n throw new WidgetAPIError(data.error, data.message)\n }\n\n return response.json()\n}\n"],"mappings":"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","names":["useState","jsx","useEffect","jsx","jsxs","jsx","jsxs","useState"]}
1
+ {"version":3,"sources":["../src/WidgetButton.tsx","../src/WidgetStatus.tsx","../src/useThunderPhone.ts","../src/AudioHandler.tsx","../src/api.ts","../src/ThunderPhoneWidget.tsx"],"sourcesContent":["import type { WidgetState } from './types'\n\ninterface WidgetButtonProps {\n state: WidgetState\n muted: boolean\n onClick: () => void\n onMuteToggle: () => void\n}\n\nexport function WidgetButton({ state, muted, onClick, onMuteToggle }: WidgetButtonProps) {\n if (state === 'connected') {\n return (\n <div className=\"tp-button-group\">\n <button className=\"tp-button tp-button--mute\" onClick={onMuteToggle} type=\"button\">\n {muted ? (\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\" />\n <path d=\"M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6\" />\n <path d=\"M17 16.95A7 7 0 0 1 5 12v-2m14 0v2c0 .76-.13 1.49-.36 2.18\" />\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\" />\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\" />\n </svg>\n ) : (\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\" />\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\" />\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\" />\n </svg>\n )}\n </button>\n <button className=\"tp-button tp-button--end\" onClick={onClick} type=\"button\">\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n </svg>\n </button>\n </div>\n )\n }\n\n return (\n <button\n className={`tp-button tp-button--start ${state === 'connecting' ? 'tp-button--loading' : ''}`}\n onClick={onClick}\n disabled={state === 'connecting'}\n type=\"button\"\n >\n {state === 'connecting' ? (\n <svg className=\"tp-icon tp-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n </svg>\n ) : (\n <svg className=\"tp-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\" />\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" />\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\" />\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\" />\n </svg>\n )}\n </button>\n )\n}\n","import { useEffect, useState } from 'react'\nimport type { WidgetState } from './types'\n\ninterface WidgetStatusProps {\n state: WidgetState\n agentName: string | null\n errorMessage?: string\n}\n\nexport function WidgetStatus({ state, agentName, errorMessage }: WidgetStatusProps) {\n const [elapsed, setElapsed] = useState(0)\n\n useEffect(() => {\n if (state !== 'connected') {\n setElapsed(0)\n return\n }\n const interval = setInterval(() => setElapsed(s => s + 1), 1000)\n return () => clearInterval(interval)\n }, [state])\n\n const formatTime = (seconds: number) => {\n const m = Math.floor(seconds / 60)\n const s = seconds % 60\n return `${m}:${s.toString().padStart(2, '0')}`\n }\n\n const statusText: Record<WidgetState, string> = {\n idle: 'Ready',\n connecting: 'Connecting...',\n connected: formatTime(elapsed),\n disconnected: 'Disconnected',\n error: 'Unable to connect',\n }\n\n return (\n <div className=\"tp-status\">\n {agentName && <div className=\"tp-status__name\">{agentName}</div>}\n <div className={`tp-status__text tp-status--${state}`}>\n {state === 'connected' && <span className=\"tp-status__dot\" />}\n {errorMessage && state === 'error' ? errorMessage : statusText[state]}\n </div>\n </div>\n )\n}\n","import { useCallback, useState, type ReactNode, createElement } from 'react'\nimport { LiveKitRoom } from '@livekit/components-react'\nimport { AudioHandler } from './AudioHandler'\nimport { createWidgetSession, WidgetAPIError } from './api'\nimport type { WidgetState, WidgetSessionResponse } from './types'\n\nexport interface UseThunderPhoneOptions {\n apiKey: string\n agentId: number\n apiBase?: string\n onConnect?: () => void\n onDisconnect?: () => void\n onError?: (error: { error: string; message: string }) => void\n}\n\nexport interface UseThunderPhoneReturn {\n state: WidgetState\n connect: () => void\n disconnect: () => void\n toggleMute: () => void\n isMuted: boolean\n error: string | undefined\n agentName: string | undefined\n /** Render this somewhere in your tree — it's invisible but handles audio. */\n audio: ReactNode\n}\n\nexport function useThunderPhone(opts: UseThunderPhoneOptions): UseThunderPhoneReturn {\n const [state, setState] = useState<WidgetState>('idle')\n const [session, setSession] = useState<WidgetSessionResponse | null>(null)\n const [muted, setMuted] = useState(false)\n const [error, setError] = useState<string | undefined>()\n\n const handleDisconnect = useCallback(() => {\n setState('disconnected')\n setSession(null)\n setMuted(false)\n opts.onDisconnect?.()\n setTimeout(() => setState('idle'), 1500)\n }, [opts.onDisconnect])\n\n const handleAgentConnected = useCallback(() => {\n setState('connected')\n opts.onConnect?.()\n }, [opts.onConnect])\n\n const connect = useCallback(async () => {\n if (state === 'connecting' || state === 'connected') return\n setState('connecting')\n setError(undefined)\n try {\n const sess = await createWidgetSession(opts.apiKey, opts.agentId, opts.apiBase)\n setSession(sess)\n } catch (err) {\n setState('error')\n if (err instanceof WidgetAPIError) {\n setError(err.message)\n opts.onError?.({ error: err.code, message: err.message })\n } else {\n setError('Unable to connect.')\n opts.onError?.({ error: 'unknown', message: 'Unable to connect.' })\n }\n }\n }, [opts.apiKey, opts.agentId, opts.apiBase, state, opts.onError])\n\n const disconnect = useCallback(() => {\n handleDisconnect()\n }, [handleDisconnect])\n\n const toggleMute = useCallback(() => setMuted(m => !m), [])\n\n const audio: ReactNode = session\n ? createElement(\n LiveKitRoom,\n {\n token: session.token,\n serverUrl: session.server_url,\n audio: !muted,\n video: false,\n connect: true,\n },\n createElement(AudioHandler, {\n onAgentConnected: handleAgentConnected,\n onDisconnected: handleDisconnect,\n }),\n )\n : null\n\n return {\n state,\n connect,\n disconnect,\n toggleMute,\n isMuted: muted,\n error,\n agentName: session?.agent_name,\n audio,\n }\n}\n","import { useEffect, useRef } from 'react'\nimport { useRoomContext } from '@livekit/components-react'\nimport { RoomEvent, Track, type RemoteTrackPublication, type RemoteParticipant } from 'livekit-client'\n\ninterface AudioHandlerProps {\n onAgentConnected: () => void\n onDisconnected: () => void\n}\n\nexport function AudioHandler({ onAgentConnected, onDisconnected }: AudioHandlerProps) {\n const room = useRoomContext()\n const audioRef = useRef<HTMLAudioElement>(null)\n\n useEffect(() => {\n if (room.remoteParticipants.size > 0) {\n onAgentConnected()\n }\n\n const handleParticipantConnected = () => onAgentConnected()\n const handleDisconnect = () => onDisconnected()\n\n const attachTrack = (\n track: { kind: Track.Kind; mediaStreamTrack: MediaStreamTrack },\n _pub: RemoteTrackPublication,\n _participant: RemoteParticipant,\n ) => {\n if (track.kind === Track.Kind.Audio && audioRef.current) {\n const stream = new MediaStream([track.mediaStreamTrack])\n audioRef.current.srcObject = stream\n audioRef.current.play().catch(() => {})\n }\n }\n\n room.on(RoomEvent.ParticipantConnected, handleParticipantConnected)\n room.on(RoomEvent.Disconnected, handleDisconnect)\n room.on(RoomEvent.TrackSubscribed, attachTrack)\n\n for (const participant of room.remoteParticipants.values()) {\n for (const pub of participant.trackPublications.values()) {\n if (pub.track && pub.isSubscribed && pub.track.kind === Track.Kind.Audio && audioRef.current) {\n const stream = new MediaStream([pub.track.mediaStreamTrack])\n audioRef.current.srcObject = stream\n audioRef.current.play().catch(() => {})\n }\n }\n }\n\n return () => {\n room.off(RoomEvent.ParticipantConnected, handleParticipantConnected)\n room.off(RoomEvent.Disconnected, handleDisconnect)\n room.off(RoomEvent.TrackSubscribed, attachTrack)\n }\n }, [room, onAgentConnected, onDisconnected])\n\n return <audio ref={audioRef} autoPlay />\n}\n","import type { WidgetSessionResponse, WidgetError } from './types'\n\nconst DEFAULT_API_BASE = 'https://api.thunderphone.com/v1'\n\nexport class WidgetAPIError extends Error {\n constructor(public code: string, message: string) {\n super(message)\n this.name = 'WidgetAPIError'\n }\n}\n\nexport async function createWidgetSession(\n apiKey: string,\n agentId: number,\n apiBase?: string,\n): Promise<WidgetSessionResponse> {\n const base = apiBase || DEFAULT_API_BASE\n const response = await fetch(`${base}/widget/session`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': apiKey,\n },\n body: JSON.stringify({ agent_id: agentId }),\n })\n\n if (!response.ok) {\n const data: WidgetError = await response.json().catch(() => ({\n error: 'unknown',\n message: 'Unable to connect.',\n }))\n throw new WidgetAPIError(data.error, data.message)\n }\n\n return response.json()\n}\n","import { WidgetButton } from './WidgetButton'\nimport { WidgetStatus } from './WidgetStatus'\nimport { useThunderPhone } from './useThunderPhone'\nimport type { ThunderPhoneWidgetProps } from './types'\n\nexport function ThunderPhoneWidget({\n apiKey,\n agentId,\n apiBase,\n onConnect,\n onDisconnect,\n onError,\n className,\n}: ThunderPhoneWidgetProps) {\n const phone = useThunderPhone({ apiKey, agentId, apiBase, onConnect, onDisconnect, onError })\n\n const handleClick = () => {\n if (phone.state === 'connected') {\n phone.disconnect()\n } else if (phone.state === 'idle' || phone.state === 'error' || phone.state === 'disconnected') {\n phone.connect()\n }\n }\n\n return (\n <div className={`tp-widget ${className || ''}`}>\n <WidgetStatus\n state={phone.state}\n agentName={phone.agentName || null}\n errorMessage={phone.error}\n />\n <WidgetButton\n state={phone.state}\n muted={phone.isMuted}\n onClick={handleClick}\n onMuteToggle={phone.toggleMute}\n />\n {phone.audio}\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","useState","useEffect","jsx","useState","jsx","jsxs"]}