@thunderphone/widget 0.2.3 → 0.3.1
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 +64 -2
- package/dist/index.d.mts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +155 -139
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +148 -133
- package/dist/index.mjs.map +1 -1
- package/dist/mount.global.js +156 -142
- package/dist/mount.global.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,68 @@ function App() {
|
|
|
43
43
|
| `onError` | `(error) => void` | No | Called on errors. Error has `error` (code) and `message` fields |
|
|
44
44
|
| `className` | `string` | No | Additional CSS class for the widget container |
|
|
45
45
|
|
|
46
|
+
## Headless Hook
|
|
47
|
+
|
|
48
|
+
Use the `useThunderPhone` hook to build a completely custom UI while the widget handles the voice connection.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { useThunderPhone } from '@thunderphone/widget'
|
|
52
|
+
|
|
53
|
+
function CustomCallButton() {
|
|
54
|
+
const phone = useThunderPhone({
|
|
55
|
+
apiKey: 'pk_live_your_publishable_key',
|
|
56
|
+
agentId: 123,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const handleClick = () => {
|
|
60
|
+
if (phone.state === 'connected') {
|
|
61
|
+
phone.disconnect()
|
|
62
|
+
} else {
|
|
63
|
+
phone.connect()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<>
|
|
69
|
+
<button onClick={handleClick} disabled={phone.state === 'connecting'}>
|
|
70
|
+
{phone.state === 'connecting'
|
|
71
|
+
? 'Connecting...'
|
|
72
|
+
: phone.state === 'connected'
|
|
73
|
+
? 'End call'
|
|
74
|
+
: 'Start call'}
|
|
75
|
+
</button>
|
|
76
|
+
{phone.audio}
|
|
77
|
+
</>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Important:** Always render `phone.audio` somewhere in your component tree. It's an invisible element that handles the audio connection.
|
|
83
|
+
|
|
84
|
+
### Hook Options
|
|
85
|
+
|
|
86
|
+
| Option | Type | Required | Description |
|
|
87
|
+
|--------|------|----------|-------------|
|
|
88
|
+
| `apiKey` | `string` | Yes | Your publishable API key |
|
|
89
|
+
| `agentId` | `number` | Yes | ID of the agent to connect to |
|
|
90
|
+
| `apiBase` | `string` | No | API base URL override |
|
|
91
|
+
| `onConnect` | `() => void` | No | Called when the voice session connects |
|
|
92
|
+
| `onDisconnect` | `() => void` | No | Called when the session ends |
|
|
93
|
+
| `onError` | `(error) => void` | No | Called on errors |
|
|
94
|
+
|
|
95
|
+
### Hook Return Value
|
|
96
|
+
|
|
97
|
+
| Property | Type | Description |
|
|
98
|
+
|----------|------|-------------|
|
|
99
|
+
| `state` | `'idle' \| 'connecting' \| 'connected' \| 'disconnected' \| 'error'` | Current connection state |
|
|
100
|
+
| `connect` | `() => void` | Start a voice session |
|
|
101
|
+
| `disconnect` | `() => void` | End the current session |
|
|
102
|
+
| `toggleMute` | `() => void` | Toggle microphone mute |
|
|
103
|
+
| `isMuted` | `boolean` | Whether the mic is muted |
|
|
104
|
+
| `error` | `string \| undefined` | Error message if state is `'error'` |
|
|
105
|
+
| `agentName` | `string \| undefined` | Name of the connected agent |
|
|
106
|
+
| `audio` | `ReactNode` | Invisible element — must be rendered in the tree |
|
|
107
|
+
|
|
46
108
|
## Styling
|
|
47
109
|
|
|
48
110
|
The widget uses plain CSS with `tp-` prefixed classes, so it won't conflict with your styles. You can override any of these classes:
|
|
@@ -80,8 +142,8 @@ Wildcard subdomains are supported: `*.example.com` matches `app.example.com`, `d
|
|
|
80
142
|
If you're not using a bundler, you can load the widget via script tag. This version bundles React internally so no dependencies are needed.
|
|
81
143
|
|
|
82
144
|
```html
|
|
83
|
-
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/v0.
|
|
84
|
-
<script src="https://cdn.thunderphone.com/widget/v0.
|
|
145
|
+
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/v0.3.0/style.css" />
|
|
146
|
+
<script src="https://cdn.thunderphone.com/widget/v0.3.0/widget.js"></script>
|
|
85
147
|
|
|
86
148
|
<div id="thunderphone"></div>
|
|
87
149
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
interface WidgetError {
|
|
4
5
|
error: string;
|
|
@@ -17,4 +18,28 @@ interface ThunderPhoneWidgetProps {
|
|
|
17
18
|
|
|
18
19
|
declare function ThunderPhoneWidget({ apiKey, agentId, apiBase, onConnect, onDisconnect, onError, className, }: ThunderPhoneWidgetProps): react_jsx_runtime.JSX.Element;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
interface UseThunderPhoneOptions {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
agentId: number;
|
|
24
|
+
apiBase?: string;
|
|
25
|
+
onConnect?: () => void;
|
|
26
|
+
onDisconnect?: () => void;
|
|
27
|
+
onError?: (error: {
|
|
28
|
+
error: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}) => void;
|
|
31
|
+
}
|
|
32
|
+
interface UseThunderPhoneReturn {
|
|
33
|
+
state: WidgetState;
|
|
34
|
+
connect: () => void;
|
|
35
|
+
disconnect: () => void;
|
|
36
|
+
toggleMute: () => void;
|
|
37
|
+
isMuted: boolean;
|
|
38
|
+
error: string | undefined;
|
|
39
|
+
agentName: string | undefined;
|
|
40
|
+
/** Render this somewhere in your tree — it's invisible but handles audio. */
|
|
41
|
+
audio: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
declare function useThunderPhone(opts: UseThunderPhoneOptions): UseThunderPhoneReturn;
|
|
44
|
+
|
|
45
|
+
export { ThunderPhoneWidget, type ThunderPhoneWidgetProps, type UseThunderPhoneOptions, type UseThunderPhoneReturn, type WidgetError, type WidgetState, useThunderPhone };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
interface WidgetError {
|
|
4
5
|
error: string;
|
|
@@ -17,4 +18,28 @@ interface ThunderPhoneWidgetProps {
|
|
|
17
18
|
|
|
18
19
|
declare function ThunderPhoneWidget({ apiKey, agentId, apiBase, onConnect, onDisconnect, onError, className, }: ThunderPhoneWidgetProps): react_jsx_runtime.JSX.Element;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
interface UseThunderPhoneOptions {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
agentId: number;
|
|
24
|
+
apiBase?: string;
|
|
25
|
+
onConnect?: () => void;
|
|
26
|
+
onDisconnect?: () => void;
|
|
27
|
+
onError?: (error: {
|
|
28
|
+
error: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}) => void;
|
|
31
|
+
}
|
|
32
|
+
interface UseThunderPhoneReturn {
|
|
33
|
+
state: WidgetState;
|
|
34
|
+
connect: () => void;
|
|
35
|
+
disconnect: () => void;
|
|
36
|
+
toggleMute: () => void;
|
|
37
|
+
isMuted: boolean;
|
|
38
|
+
error: string | undefined;
|
|
39
|
+
agentName: string | undefined;
|
|
40
|
+
/** Render this somewhere in your tree — it's invisible but handles audio. */
|
|
41
|
+
audio: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
declare function useThunderPhone(opts: UseThunderPhoneOptions): UseThunderPhoneReturn;
|
|
44
|
+
|
|
45
|
+
export { ThunderPhoneWidget, type ThunderPhoneWidgetProps, type UseThunderPhoneOptions, type UseThunderPhoneReturn, type WidgetError, type WidgetState, useThunderPhone };
|
package/dist/index.js
CHANGED
|
@@ -20,101 +20,54 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
ThunderPhoneWidget: () => ThunderPhoneWidget
|
|
23
|
+
ThunderPhoneWidget: () => ThunderPhoneWidget,
|
|
24
|
+
useThunderPhone: () => useThunderPhone
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(index_exports);
|
|
26
27
|
|
|
27
|
-
// src/ThunderPhoneWidget.tsx
|
|
28
|
-
var import_react3 = require("react");
|
|
29
|
-
var import_components_react2 = require("@livekit/components-react");
|
|
30
|
-
|
|
31
|
-
// src/AudioHandler.tsx
|
|
32
|
-
var import_react = require("react");
|
|
33
|
-
var import_components_react = require("@livekit/components-react");
|
|
34
|
-
var import_livekit_client = require("livekit-client");
|
|
35
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
36
|
-
function AudioHandler({ onAgentConnected, onDisconnected }) {
|
|
37
|
-
const room = (0, import_components_react.useRoomContext)();
|
|
38
|
-
const audioRef = (0, import_react.useRef)(null);
|
|
39
|
-
(0, import_react.useEffect)(() => {
|
|
40
|
-
if (room.remoteParticipants.size > 0) {
|
|
41
|
-
onAgentConnected();
|
|
42
|
-
}
|
|
43
|
-
const handleParticipantConnected = () => onAgentConnected();
|
|
44
|
-
const handleDisconnect = () => onDisconnected();
|
|
45
|
-
const attachTrack = (track, _pub, _participant) => {
|
|
46
|
-
if (track.kind === import_livekit_client.Track.Kind.Audio && audioRef.current) {
|
|
47
|
-
const stream = new MediaStream([track.mediaStreamTrack]);
|
|
48
|
-
audioRef.current.srcObject = stream;
|
|
49
|
-
audioRef.current.play().catch(() => {
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
room.on(import_livekit_client.RoomEvent.ParticipantConnected, handleParticipantConnected);
|
|
54
|
-
room.on(import_livekit_client.RoomEvent.Disconnected, handleDisconnect);
|
|
55
|
-
room.on(import_livekit_client.RoomEvent.TrackSubscribed, attachTrack);
|
|
56
|
-
for (const participant of room.remoteParticipants.values()) {
|
|
57
|
-
for (const pub of participant.trackPublications.values()) {
|
|
58
|
-
if (pub.track && pub.isSubscribed && pub.track.kind === import_livekit_client.Track.Kind.Audio && audioRef.current) {
|
|
59
|
-
const stream = new MediaStream([pub.track.mediaStreamTrack]);
|
|
60
|
-
audioRef.current.srcObject = stream;
|
|
61
|
-
audioRef.current.play().catch(() => {
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return () => {
|
|
67
|
-
room.off(import_livekit_client.RoomEvent.ParticipantConnected, handleParticipantConnected);
|
|
68
|
-
room.off(import_livekit_client.RoomEvent.Disconnected, handleDisconnect);
|
|
69
|
-
room.off(import_livekit_client.RoomEvent.TrackSubscribed, attachTrack);
|
|
70
|
-
};
|
|
71
|
-
}, [room, onAgentConnected, onDisconnected]);
|
|
72
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("audio", { ref: audioRef, autoPlay: true });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
28
|
// src/WidgetButton.tsx
|
|
76
|
-
var
|
|
29
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
77
30
|
function WidgetButton({ state, muted, onClick, onMuteToggle }) {
|
|
78
31
|
if (state === "connected") {
|
|
79
|
-
return /* @__PURE__ */ (0,
|
|
80
|
-
/* @__PURE__ */ (0,
|
|
81
|
-
/* @__PURE__ */ (0,
|
|
82
|
-
/* @__PURE__ */ (0,
|
|
83
|
-
/* @__PURE__ */ (0,
|
|
84
|
-
/* @__PURE__ */ (0,
|
|
85
|
-
/* @__PURE__ */ (0,
|
|
86
|
-
] }) : /* @__PURE__ */ (0,
|
|
87
|
-
/* @__PURE__ */ (0,
|
|
88
|
-
/* @__PURE__ */ (0,
|
|
89
|
-
/* @__PURE__ */ (0,
|
|
90
|
-
/* @__PURE__ */ (0,
|
|
32
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tp-button-group", children: [
|
|
33
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "tp-button tp-button--mute", onClick: onMuteToggle, type: "button", children: muted ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
34
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "1", y1: "1", x2: "23", y2: "23" }),
|
|
35
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" }),
|
|
36
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17 16.95A7 7 0 0 1 5 12v-2m14 0v2c0 .76-.13 1.49-.36 2.18" }),
|
|
37
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
|
|
38
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
|
|
39
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
40
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
|
|
41
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
|
|
42
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
|
|
43
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
|
|
91
44
|
] }) }),
|
|
92
|
-
/* @__PURE__ */ (0,
|
|
45
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "tp-button tp-button--end", onClick, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) })
|
|
93
46
|
] });
|
|
94
47
|
}
|
|
95
|
-
return /* @__PURE__ */ (0,
|
|
48
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
96
49
|
"button",
|
|
97
50
|
{
|
|
98
51
|
className: `tp-button tp-button--start ${state === "connecting" ? "tp-button--loading" : ""}`,
|
|
99
52
|
onClick,
|
|
100
53
|
disabled: state === "connecting",
|
|
101
54
|
type: "button",
|
|
102
|
-
children: state === "connecting" ? /* @__PURE__ */ (0,
|
|
103
|
-
/* @__PURE__ */ (0,
|
|
104
|
-
/* @__PURE__ */ (0,
|
|
105
|
-
/* @__PURE__ */ (0,
|
|
106
|
-
/* @__PURE__ */ (0,
|
|
55
|
+
children: state === "connecting" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "tp-icon tp-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "tp-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
56
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
|
|
57
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
|
|
58
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
|
|
59
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
|
|
107
60
|
] })
|
|
108
61
|
}
|
|
109
62
|
);
|
|
110
63
|
}
|
|
111
64
|
|
|
112
65
|
// src/WidgetStatus.tsx
|
|
113
|
-
var
|
|
114
|
-
var
|
|
66
|
+
var import_react = require("react");
|
|
67
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
115
68
|
function WidgetStatus({ state, agentName, errorMessage }) {
|
|
116
|
-
const [elapsed, setElapsed] = (0,
|
|
117
|
-
(0,
|
|
69
|
+
const [elapsed, setElapsed] = (0, import_react.useState)(0);
|
|
70
|
+
(0, import_react.useEffect)(() => {
|
|
118
71
|
if (state !== "connected") {
|
|
119
72
|
setElapsed(0);
|
|
120
73
|
return;
|
|
@@ -134,15 +87,63 @@ function WidgetStatus({ state, agentName, errorMessage }) {
|
|
|
134
87
|
disconnected: "Disconnected",
|
|
135
88
|
error: "Unable to connect"
|
|
136
89
|
};
|
|
137
|
-
return /* @__PURE__ */ (0,
|
|
138
|
-
agentName && /* @__PURE__ */ (0,
|
|
139
|
-
/* @__PURE__ */ (0,
|
|
140
|
-
state === "connected" && /* @__PURE__ */ (0,
|
|
90
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "tp-status", children: [
|
|
91
|
+
agentName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "tp-status__name", children: agentName }),
|
|
92
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `tp-status__text tp-status--${state}`, children: [
|
|
93
|
+
state === "connected" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "tp-status__dot" }),
|
|
141
94
|
errorMessage && state === "error" ? errorMessage : statusText[state]
|
|
142
95
|
] })
|
|
143
96
|
] });
|
|
144
97
|
}
|
|
145
98
|
|
|
99
|
+
// src/useThunderPhone.ts
|
|
100
|
+
var import_react3 = require("react");
|
|
101
|
+
var import_components_react2 = require("@livekit/components-react");
|
|
102
|
+
|
|
103
|
+
// src/AudioHandler.tsx
|
|
104
|
+
var import_react2 = require("react");
|
|
105
|
+
var import_components_react = require("@livekit/components-react");
|
|
106
|
+
var import_livekit_client = require("livekit-client");
|
|
107
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
108
|
+
function AudioHandler({ onAgentConnected, onDisconnected }) {
|
|
109
|
+
const room = (0, import_components_react.useRoomContext)();
|
|
110
|
+
const audioRef = (0, import_react2.useRef)(null);
|
|
111
|
+
(0, import_react2.useEffect)(() => {
|
|
112
|
+
if (room.remoteParticipants.size > 0) {
|
|
113
|
+
onAgentConnected();
|
|
114
|
+
}
|
|
115
|
+
const handleParticipantConnected = () => onAgentConnected();
|
|
116
|
+
const handleDisconnect = () => onDisconnected();
|
|
117
|
+
const attachTrack = (track, _pub, _participant) => {
|
|
118
|
+
if (track.kind === import_livekit_client.Track.Kind.Audio && audioRef.current) {
|
|
119
|
+
const stream = new MediaStream([track.mediaStreamTrack]);
|
|
120
|
+
audioRef.current.srcObject = stream;
|
|
121
|
+
audioRef.current.play().catch(() => {
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
room.on(import_livekit_client.RoomEvent.ParticipantConnected, handleParticipantConnected);
|
|
126
|
+
room.on(import_livekit_client.RoomEvent.Disconnected, handleDisconnect);
|
|
127
|
+
room.on(import_livekit_client.RoomEvent.TrackSubscribed, attachTrack);
|
|
128
|
+
for (const participant of room.remoteParticipants.values()) {
|
|
129
|
+
for (const pub of participant.trackPublications.values()) {
|
|
130
|
+
if (pub.track && pub.isSubscribed && pub.track.kind === import_livekit_client.Track.Kind.Audio && audioRef.current) {
|
|
131
|
+
const stream = new MediaStream([pub.track.mediaStreamTrack]);
|
|
132
|
+
audioRef.current.srcObject = stream;
|
|
133
|
+
audioRef.current.play().catch(() => {
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return () => {
|
|
139
|
+
room.off(import_livekit_client.RoomEvent.ParticipantConnected, handleParticipantConnected);
|
|
140
|
+
room.off(import_livekit_client.RoomEvent.Disconnected, handleDisconnect);
|
|
141
|
+
room.off(import_livekit_client.RoomEvent.TrackSubscribed, attachTrack);
|
|
142
|
+
};
|
|
143
|
+
}, [room, onAgentConnected, onDisconnected]);
|
|
144
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("audio", { ref: audioRef, autoPlay: true });
|
|
145
|
+
}
|
|
146
|
+
|
|
146
147
|
// src/api.ts
|
|
147
148
|
var DEFAULT_API_BASE = "https://api.thunderphone.com/v1";
|
|
148
149
|
var WidgetAPIError = class extends Error {
|
|
@@ -172,99 +173,114 @@ async function createWidgetSession(apiKey, agentId, apiBase) {
|
|
|
172
173
|
return response.json();
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
// src/
|
|
176
|
-
|
|
177
|
-
function ThunderPhoneWidget({
|
|
178
|
-
apiKey,
|
|
179
|
-
agentId,
|
|
180
|
-
apiBase,
|
|
181
|
-
onConnect,
|
|
182
|
-
onDisconnect,
|
|
183
|
-
onError,
|
|
184
|
-
className
|
|
185
|
-
}) {
|
|
176
|
+
// src/useThunderPhone.ts
|
|
177
|
+
function useThunderPhone(opts) {
|
|
186
178
|
const [state, setState] = (0, import_react3.useState)("idle");
|
|
187
179
|
const [session, setSession] = (0, import_react3.useState)(null);
|
|
188
180
|
const [muted, setMuted] = (0, import_react3.useState)(false);
|
|
189
|
-
const [
|
|
190
|
-
const
|
|
181
|
+
const [error, setError] = (0, import_react3.useState)();
|
|
182
|
+
const handleDisconnect = (0, import_react3.useCallback)(() => {
|
|
183
|
+
setState("disconnected");
|
|
184
|
+
setSession(null);
|
|
185
|
+
setMuted(false);
|
|
186
|
+
opts.onDisconnect?.();
|
|
187
|
+
setTimeout(() => setState("idle"), 1500);
|
|
188
|
+
}, [opts.onDisconnect]);
|
|
189
|
+
const handleAgentConnected = (0, import_react3.useCallback)(() => {
|
|
190
|
+
setState("connected");
|
|
191
|
+
opts.onConnect?.();
|
|
192
|
+
}, [opts.onConnect]);
|
|
193
|
+
const connect = (0, import_react3.useCallback)(async () => {
|
|
191
194
|
if (state === "connecting" || state === "connected") return;
|
|
192
195
|
setState("connecting");
|
|
193
|
-
|
|
196
|
+
setError(void 0);
|
|
194
197
|
try {
|
|
195
|
-
const sess = await createWidgetSession(apiKey, agentId, apiBase);
|
|
198
|
+
const sess = await createWidgetSession(opts.apiKey, opts.agentId, opts.apiBase);
|
|
196
199
|
setSession(sess);
|
|
197
200
|
} catch (err) {
|
|
198
201
|
setState("error");
|
|
199
202
|
if (err instanceof WidgetAPIError) {
|
|
200
|
-
|
|
201
|
-
onError?.({ error: err.code, message: err.message });
|
|
203
|
+
setError(err.message);
|
|
204
|
+
opts.onError?.({ error: err.code, message: err.message });
|
|
202
205
|
} else {
|
|
203
|
-
|
|
204
|
-
onError?.({ error: "unknown", message: "Unable to connect." });
|
|
206
|
+
setError("Unable to connect.");
|
|
207
|
+
opts.onError?.({ error: "unknown", message: "Unable to connect." });
|
|
205
208
|
}
|
|
206
209
|
}
|
|
207
|
-
}, [apiKey, agentId, apiBase, state, onError]);
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
210
|
+
}, [opts.apiKey, opts.agentId, opts.apiBase, state, opts.onError]);
|
|
211
|
+
const disconnect = (0, import_react3.useCallback)(() => {
|
|
212
|
+
handleDisconnect();
|
|
213
|
+
}, [handleDisconnect]);
|
|
214
|
+
const toggleMute = (0, import_react3.useCallback)(() => setMuted((m) => !m), []);
|
|
215
|
+
const audio = session ? (0, import_react3.createElement)(
|
|
216
|
+
import_components_react2.LiveKitRoom,
|
|
217
|
+
{
|
|
218
|
+
token: session.token,
|
|
219
|
+
serverUrl: session.server_url,
|
|
220
|
+
audio: !muted,
|
|
221
|
+
video: false,
|
|
222
|
+
connect: true
|
|
223
|
+
},
|
|
224
|
+
(0, import_react3.createElement)(AudioHandler, {
|
|
225
|
+
onAgentConnected: handleAgentConnected,
|
|
226
|
+
onDisconnected: handleDisconnect
|
|
227
|
+
})
|
|
228
|
+
) : null;
|
|
229
|
+
return {
|
|
230
|
+
state,
|
|
231
|
+
connect,
|
|
232
|
+
disconnect,
|
|
233
|
+
toggleMute,
|
|
234
|
+
isMuted: muted,
|
|
235
|
+
error,
|
|
236
|
+
agentName: session?.agent_name,
|
|
237
|
+
audio
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/ThunderPhoneWidget.tsx
|
|
242
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
243
|
+
function ThunderPhoneWidget({
|
|
244
|
+
apiKey,
|
|
245
|
+
agentId,
|
|
246
|
+
apiBase,
|
|
247
|
+
onConnect,
|
|
248
|
+
onDisconnect,
|
|
249
|
+
onError,
|
|
250
|
+
className
|
|
251
|
+
}) {
|
|
252
|
+
const phone = useThunderPhone({ apiKey, agentId, apiBase, onConnect, onDisconnect, onError });
|
|
219
253
|
const handleClick = () => {
|
|
220
|
-
if (state === "connected") {
|
|
221
|
-
|
|
222
|
-
} else if (state === "idle" || state === "error" || state === "disconnected") {
|
|
223
|
-
|
|
254
|
+
if (phone.state === "connected") {
|
|
255
|
+
phone.disconnect();
|
|
256
|
+
} else if (phone.state === "idle" || phone.state === "error" || phone.state === "disconnected") {
|
|
257
|
+
phone.connect();
|
|
224
258
|
}
|
|
225
259
|
};
|
|
226
|
-
const handleMuteToggle = (0, import_react3.useCallback)(() => {
|
|
227
|
-
setMuted((m) => !m);
|
|
228
|
-
}, []);
|
|
229
260
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `tp-widget ${className || ""}`, children: [
|
|
230
261
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
231
262
|
WidgetStatus,
|
|
232
263
|
{
|
|
233
|
-
state,
|
|
234
|
-
agentName:
|
|
235
|
-
errorMessage
|
|
264
|
+
state: phone.state,
|
|
265
|
+
agentName: phone.agentName || null,
|
|
266
|
+
errorMessage: phone.error
|
|
236
267
|
}
|
|
237
268
|
),
|
|
238
269
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
239
270
|
WidgetButton,
|
|
240
271
|
{
|
|
241
|
-
state,
|
|
242
|
-
muted,
|
|
272
|
+
state: phone.state,
|
|
273
|
+
muted: phone.isMuted,
|
|
243
274
|
onClick: handleClick,
|
|
244
|
-
onMuteToggle:
|
|
275
|
+
onMuteToggle: phone.toggleMute
|
|
245
276
|
}
|
|
246
277
|
),
|
|
247
|
-
|
|
248
|
-
import_components_react2.LiveKitRoom,
|
|
249
|
-
{
|
|
250
|
-
token: session.token,
|
|
251
|
-
serverUrl: session.server_url,
|
|
252
|
-
audio: !muted,
|
|
253
|
-
video: false,
|
|
254
|
-
connect: true,
|
|
255
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
256
|
-
AudioHandler,
|
|
257
|
-
{
|
|
258
|
-
onAgentConnected: handleAgentConnected,
|
|
259
|
-
onDisconnected: handleDisconnect
|
|
260
|
-
}
|
|
261
|
-
)
|
|
262
|
-
}
|
|
263
|
-
)
|
|
278
|
+
phone.audio
|
|
264
279
|
] });
|
|
265
280
|
}
|
|
266
281
|
// Annotate the CommonJS export names for ESM import in node:
|
|
267
282
|
0 && (module.exports = {
|
|
268
|
-
ThunderPhoneWidget
|
|
283
|
+
ThunderPhoneWidget,
|
|
284
|
+
useThunderPhone
|
|
269
285
|
});
|
|
270
286
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/ThunderPhoneWidget.tsx","../src/AudioHandler.tsx","../src/WidgetButton.tsx","../src/WidgetStatus.tsx","../src/api.ts"],"sourcesContent":["import './style.css'\n\nexport { ThunderPhoneWidget } from './ThunderPhoneWidget'\nexport type { ThunderPhoneWidgetProps, WidgetError, WidgetState } from './types'\n","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":["import_react","import_components_react","import_jsx_runtime","import_react","import_jsx_runtime","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/WidgetButton.tsx","../src/WidgetStatus.tsx","../src/useThunderPhone.ts","../src/AudioHandler.tsx","../src/api.ts","../src/ThunderPhoneWidget.tsx"],"sourcesContent":["import './style.css'\n\nexport { ThunderPhoneWidget } from './ThunderPhoneWidget'\nexport { useThunderPhone } from './useThunderPhone'\nexport type { UseThunderPhoneOptions, UseThunderPhoneReturn } from './useThunderPhone'\nexport type { ThunderPhoneWidgetProps, WidgetError, WidgetState } from './types'\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 { 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACeY;AANL,SAAS,aAAa,EAAE,OAAO,OAAO,SAAS,aAAa,GAAsB;AACvF,MAAI,UAAU,aAAa;AACzB,WACE,6CAAC,SAAI,WAAU,mBACb;AAAA,kDAAC,YAAO,WAAU,6BAA4B,SAAS,cAAc,MAAK,UACvE,kBACC,6CAAC,SAAI,WAAU,WAAU,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KACzF;AAAA,oDAAC,UAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,QACpC,4CAAC,UAAK,GAAE,0DAAyD;AAAA,QACjE,4CAAC,UAAK,GAAE,8DAA6D;AAAA,QACrE,4CAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,QACtC,4CAAC,UAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,SACvC,IAEA,6CAAC,SAAI,WAAU,WAAU,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KACzF;AAAA,oDAAC,UAAK,GAAE,wDAAuD;AAAA,QAC/D,4CAAC,UAAK,GAAE,8BAA6B;AAAA,QACrC,4CAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,QACtC,4CAAC,UAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,SACvC,GAEJ;AAAA,MACA,4CAAC,YAAO,WAAU,4BAA2B,SAAkB,MAAK,UAClE,sDAAC,SAAI,WAAU,WAAU,SAAQ,aAAY,MAAK,gBAChD,sDAAC,UAAK,GAAE,KAAI,GAAE,KAAI,OAAM,MAAK,QAAO,MAAK,IAAG,KAAI,GAClD,GACF;AAAA,OACF;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,8BAA8B,UAAU,eAAe,uBAAuB,EAAE;AAAA,MAC3F;AAAA,MACA,UAAU,UAAU;AAAA,MACpB,MAAK;AAAA,MAEJ,oBAAU,eACT,4CAAC,SAAI,WAAU,mBAAkB,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KACjG,sDAAC,UAAK,GAAE,+BAA8B,GACxC,IAEA,6CAAC,SAAI,WAAU,WAAU,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KACzF;AAAA,oDAAC,UAAK,GAAE,wDAAuD;AAAA,QAC/D,4CAAC,UAAK,GAAE,8BAA6B;AAAA,QACrC,4CAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,QACtC,4CAAC,UAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,SACvC;AAAA;AAAA,EAEJ;AAEJ;;;AC7DA,mBAAoC;AAqChB,IAAAA,sBAAA;AA5Bb,SAAS,aAAa,EAAE,OAAO,WAAW,aAAa,GAAsB;AAClF,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,CAAC;AAExC,8BAAU,MAAM;AACd,QAAI,UAAU,aAAa;AACzB,iBAAW,CAAC;AACZ;AAAA,IACF;AACA,UAAM,WAAW,YAAY,MAAM,WAAW,OAAK,IAAI,CAAC,GAAG,GAAI;AAC/D,WAAO,MAAM,cAAc,QAAQ;AAAA,EACrC,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,aAAa,CAAC,YAAoB;AACtC,UAAM,IAAI,KAAK,MAAM,UAAU,EAAE;AACjC,UAAM,IAAI,UAAU;AACpB,WAAO,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAC9C;AAEA,QAAM,aAA0C;AAAA,IAC9C,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW,WAAW,OAAO;AAAA,IAC7B,cAAc;AAAA,IACd,OAAO;AAAA,EACT;AAEA,SACE,8CAAC,SAAI,WAAU,aACZ;AAAA,iBAAa,6CAAC,SAAI,WAAU,mBAAmB,qBAAU;AAAA,IAC1D,8CAAC,SAAI,WAAW,8BAA8B,KAAK,IAChD;AAAA,gBAAU,eAAe,6CAAC,UAAK,WAAU,kBAAiB;AAAA,MAC1D,gBAAgB,UAAU,UAAU,eAAe,WAAW,KAAK;AAAA,OACtE;AAAA,KACF;AAEJ;;;AC5CA,IAAAC,gBAAqE;AACrE,IAAAC,2BAA4B;;;ACD5B,IAAAC,gBAAkC;AAClC,8BAA+B;AAC/B,4BAAsF;AAoD7E,IAAAC,sBAAA;AA7CF,SAAS,aAAa,EAAE,kBAAkB,eAAe,GAAsB;AACpF,QAAM,WAAO,wCAAe;AAC5B,QAAM,eAAW,sBAAyB,IAAI;AAE9C,+BAAU,MAAM;AACd,QAAI,KAAK,mBAAmB,OAAO,GAAG;AACpC,uBAAiB;AAAA,IACnB;AAEA,UAAM,6BAA6B,MAAM,iBAAiB;AAC1D,UAAM,mBAAmB,MAAM,eAAe;AAE9C,UAAM,cAAc,CAClB,OACA,MACA,iBACG;AACH,UAAI,MAAM,SAAS,4BAAM,KAAK,SAAS,SAAS,SAAS;AACvD,cAAM,SAAS,IAAI,YAAY,CAAC,MAAM,gBAAgB,CAAC;AACvD,iBAAS,QAAQ,YAAY;AAC7B,iBAAS,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACxC;AAAA,IACF;AAEA,SAAK,GAAG,gCAAU,sBAAsB,0BAA0B;AAClE,SAAK,GAAG,gCAAU,cAAc,gBAAgB;AAChD,SAAK,GAAG,gCAAU,iBAAiB,WAAW;AAE9C,eAAW,eAAe,KAAK,mBAAmB,OAAO,GAAG;AAC1D,iBAAW,OAAO,YAAY,kBAAkB,OAAO,GAAG;AACxD,YAAI,IAAI,SAAS,IAAI,gBAAgB,IAAI,MAAM,SAAS,4BAAM,KAAK,SAAS,SAAS,SAAS;AAC5F,gBAAM,SAAS,IAAI,YAAY,CAAC,IAAI,MAAM,gBAAgB,CAAC;AAC3D,mBAAS,QAAQ,YAAY;AAC7B,mBAAS,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACxC;AAAA,MACF;AAAA,IACF;AAEA,WAAO,MAAM;AACX,WAAK,IAAI,gCAAU,sBAAsB,0BAA0B;AACnE,WAAK,IAAI,gCAAU,cAAc,gBAAgB;AACjD,WAAK,IAAI,gCAAU,iBAAiB,WAAW;AAAA,IACjD;AAAA,EACF,GAAG,CAAC,MAAM,kBAAkB,cAAc,CAAC;AAE3C,SAAO,6CAAC,WAAM,KAAK,UAAU,UAAQ,MAAC;AACxC;;;ACrDA,IAAM,mBAAmB;AAElB,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC,YAAmB,MAAc,SAAiB;AAChD,UAAM,OAAO;AADI;AAEjB,SAAK,OAAO;AAAA,EACd;AACF;AAEA,eAAsB,oBACpB,QACA,SACA,SACgC;AAChC,QAAM,OAAO,WAAW;AACxB,QAAM,WAAW,MAAM,MAAM,GAAG,IAAI,mBAAmB;AAAA,IACrD,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,aAAa;AAAA,IACf;AAAA,IACA,MAAM,KAAK,UAAU,EAAE,UAAU,QAAQ,CAAC;AAAA,EAC5C,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,OAAoB,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO;AAAA,MAC3D,OAAO;AAAA,MACP,SAAS;AAAA,IACX,EAAE;AACF,UAAM,IAAI,eAAe,KAAK,OAAO,KAAK,OAAO;AAAA,EACnD;AAEA,SAAO,SAAS,KAAK;AACvB;;;AFRO,SAAS,gBAAgB,MAAqD;AACnF,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAsB,MAAM;AACtD,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAuC,IAAI;AACzE,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAS,KAAK;AACxC,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAA6B;AAEvD,QAAM,uBAAmB,2BAAY,MAAM;AACzC,aAAS,cAAc;AACvB,eAAW,IAAI;AACf,aAAS,KAAK;AACd,SAAK,eAAe;AACpB,eAAW,MAAM,SAAS,MAAM,GAAG,IAAI;AAAA,EACzC,GAAG,CAAC,KAAK,YAAY,CAAC;AAEtB,QAAM,2BAAuB,2BAAY,MAAM;AAC7C,aAAS,WAAW;AACpB,SAAK,YAAY;AAAA,EACnB,GAAG,CAAC,KAAK,SAAS,CAAC;AAEnB,QAAM,cAAU,2BAAY,YAAY;AACtC,QAAI,UAAU,gBAAgB,UAAU,YAAa;AACrD,aAAS,YAAY;AACrB,aAAS,MAAS;AAClB,QAAI;AACF,YAAM,OAAO,MAAM,oBAAoB,KAAK,QAAQ,KAAK,SAAS,KAAK,OAAO;AAC9E,iBAAW,IAAI;AAAA,IACjB,SAAS,KAAK;AACZ,eAAS,OAAO;AAChB,UAAI,eAAe,gBAAgB;AACjC,iBAAS,IAAI,OAAO;AACpB,aAAK,UAAU,EAAE,OAAO,IAAI,MAAM,SAAS,IAAI,QAAQ,CAAC;AAAA,MAC1D,OAAO;AACL,iBAAS,oBAAoB;AAC7B,aAAK,UAAU,EAAE,OAAO,WAAW,SAAS,qBAAqB,CAAC;AAAA,MACpE;AAAA,IACF;AAAA,EACF,GAAG,CAAC,KAAK,QAAQ,KAAK,SAAS,KAAK,SAAS,OAAO,KAAK,OAAO,CAAC;AAEjE,QAAM,iBAAa,2BAAY,MAAM;AACnC,qBAAiB;AAAA,EACnB,GAAG,CAAC,gBAAgB,CAAC;AAErB,QAAM,iBAAa,2BAAY,MAAM,SAAS,OAAK,CAAC,CAAC,GAAG,CAAC,CAAC;AAE1D,QAAM,QAAmB,cACrB;AAAA,IACE;AAAA,IACA;AAAA,MACE,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,OAAO,CAAC;AAAA,MACR,OAAO;AAAA,MACP,SAAS;AAAA,IACX;AAAA,QACA,6BAAc,cAAc;AAAA,MAC1B,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH,IACA;AAEJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,WAAW,SAAS;AAAA,IACpB;AAAA,EACF;AACF;;;AGzEI,IAAAC,sBAAA;AApBG,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,QAAQ,gBAAgB,EAAE,QAAQ,SAAS,SAAS,WAAW,cAAc,QAAQ,CAAC;AAE5F,QAAM,cAAc,MAAM;AACxB,QAAI,MAAM,UAAU,aAAa;AAC/B,YAAM,WAAW;AAAA,IACnB,WAAW,MAAM,UAAU,UAAU,MAAM,UAAU,WAAW,MAAM,UAAU,gBAAgB;AAC9F,YAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AAEA,SACE,8CAAC,SAAI,WAAW,aAAa,aAAa,EAAE,IAC1C;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,MAAM;AAAA,QACb,WAAW,MAAM,aAAa;AAAA,QAC9B,cAAc,MAAM;AAAA;AAAA,IACtB;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,SAAS;AAAA,QACT,cAAc,MAAM;AAAA;AAAA,IACtB;AAAA,IACC,MAAM;AAAA,KACT;AAEJ;","names":["import_jsx_runtime","import_react","import_components_react","import_react","import_jsx_runtime","import_jsx_runtime"]}
|