@thunderphone/widget 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,243 @@
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
+ // src/WidgetButton.tsx
50
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
51
+ function WidgetButton({ state, muted, onClick, onMuteToggle }) {
52
+ if (state === "connected") {
53
+ 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" })
60
+ ] }) : /* @__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" })
65
+ ] }) }),
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" }) }) })
67
+ ] });
68
+ }
69
+ return /* @__PURE__ */ jsx2(
70
+ "button",
71
+ {
72
+ className: `tp-button tp-button--start ${state === "connecting" ? "tp-button--loading" : ""}`,
73
+ onClick,
74
+ disabled: state === "connecting",
75
+ 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" })
81
+ ] })
82
+ }
83
+ );
84
+ }
85
+
86
+ // src/WidgetStatus.tsx
87
+ import { useEffect as useEffect2, useState } from "react";
88
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
89
+ function WidgetStatus({ state, agentName, errorMessage }) {
90
+ const [elapsed, setElapsed] = useState(0);
91
+ useEffect2(() => {
92
+ if (state !== "connected") {
93
+ setElapsed(0);
94
+ return;
95
+ }
96
+ const interval = setInterval(() => setElapsed((s) => s + 1), 1e3);
97
+ return () => clearInterval(interval);
98
+ }, [state]);
99
+ const formatTime = (seconds) => {
100
+ const m = Math.floor(seconds / 60);
101
+ const s = seconds % 60;
102
+ return `${m}:${s.toString().padStart(2, "0")}`;
103
+ };
104
+ const statusText = {
105
+ idle: "Ready",
106
+ connecting: "Connecting...",
107
+ connected: formatTime(elapsed),
108
+ disconnected: "Disconnected",
109
+ error: "Unable to connect"
110
+ };
111
+ return /* @__PURE__ */ jsxs2("div", { className: "tp-status", children: [
112
+ agentName && /* @__PURE__ */ jsx3("div", { className: "tp-status__name", children: agentName }),
113
+ /* @__PURE__ */ jsxs2("div", { className: `tp-status__text tp-status--${state}`, children: [
114
+ state === "connected" && /* @__PURE__ */ jsx3("span", { className: "tp-status__dot" }),
115
+ errorMessage && state === "error" ? errorMessage : statusText[state]
116
+ ] })
117
+ ] });
118
+ }
119
+
120
+ // src/api.ts
121
+ var DEFAULT_API_BASE = "https://api.thunderphone.com/v1";
122
+ var WidgetAPIError = class extends Error {
123
+ constructor(code, message) {
124
+ super(message);
125
+ this.code = code;
126
+ this.name = "WidgetAPIError";
127
+ }
128
+ };
129
+ async function createWidgetSession(apiKey, agentId, apiBase) {
130
+ const base = apiBase || DEFAULT_API_BASE;
131
+ const response = await fetch(`${base}/widget/session`, {
132
+ method: "POST",
133
+ headers: {
134
+ "Content-Type": "application/json",
135
+ "X-API-Key": apiKey
136
+ },
137
+ body: JSON.stringify({ agent_id: agentId })
138
+ });
139
+ if (!response.ok) {
140
+ const data = await response.json().catch(() => ({
141
+ error: "unknown",
142
+ message: "Unable to connect."
143
+ }));
144
+ throw new WidgetAPIError(data.error, data.message);
145
+ }
146
+ return response.json();
147
+ }
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
+ }) {
160
+ const [state, setState] = useState2("idle");
161
+ const [session, setSession] = useState2(null);
162
+ const [muted, setMuted] = useState2(false);
163
+ const [errorMessage, setErrorMessage] = useState2();
164
+ const handleConnect = useCallback(async () => {
165
+ if (state === "connecting" || state === "connected") return;
166
+ setState("connecting");
167
+ setErrorMessage(void 0);
168
+ try {
169
+ const sess = await createWidgetSession(apiKey, agentId, apiBase);
170
+ setSession(sess);
171
+ } catch (err) {
172
+ setState("error");
173
+ if (err instanceof WidgetAPIError) {
174
+ setErrorMessage(err.message);
175
+ onError?.({ error: err.code, message: err.message });
176
+ } else {
177
+ setErrorMessage("Unable to connect.");
178
+ onError?.({ error: "unknown", message: "Unable to connect." });
179
+ }
180
+ }
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]);
193
+ const handleClick = () => {
194
+ if (state === "connected") {
195
+ handleDisconnect();
196
+ } else if (state === "idle" || state === "error" || state === "disconnected") {
197
+ handleConnect();
198
+ }
199
+ };
200
+ const handleMuteToggle = useCallback(() => {
201
+ setMuted((m) => !m);
202
+ }, []);
203
+ return /* @__PURE__ */ jsxs3("div", { className: `tp-widget ${className || ""}`, children: [
204
+ /* @__PURE__ */ jsx4(
205
+ WidgetStatus,
206
+ {
207
+ state,
208
+ agentName: session?.agent_name || null,
209
+ errorMessage
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx4(
213
+ WidgetButton,
214
+ {
215
+ state,
216
+ muted,
217
+ onClick: handleClick,
218
+ onMuteToggle: handleMuteToggle
219
+ }
220
+ ),
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
+ )
238
+ ] });
239
+ }
240
+ export {
241
+ ThunderPhoneWidget
242
+ };
243
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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"]}
package/dist/mount.css ADDED
@@ -0,0 +1,112 @@
1
+ /* src/style.css */
2
+ .tp-widget {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ gap: 12px;
6
+ font-family:
7
+ -apple-system,
8
+ BlinkMacSystemFont,
9
+ "Segoe UI",
10
+ Roboto,
11
+ sans-serif;
12
+ }
13
+ .tp-status {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 2px;
17
+ }
18
+ .tp-status__name {
19
+ font-size: 14px;
20
+ font-weight: 500;
21
+ color: #18181b;
22
+ }
23
+ .tp-status__text {
24
+ font-size: 12px;
25
+ color: #71717a;
26
+ display: flex;
27
+ align-items: center;
28
+ gap: 6px;
29
+ }
30
+ .tp-status--connected {
31
+ color: #059669;
32
+ }
33
+ .tp-status--error {
34
+ color: #dc2626;
35
+ }
36
+ .tp-status__dot {
37
+ width: 6px;
38
+ height: 6px;
39
+ border-radius: 50%;
40
+ background: #059669;
41
+ animation: tp-pulse 2s ease-in-out infinite;
42
+ }
43
+ .tp-icon {
44
+ width: 20px;
45
+ height: 20px;
46
+ }
47
+ .tp-button-group {
48
+ display: inline-flex;
49
+ gap: 8px;
50
+ }
51
+ .tp-button {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ width: 44px;
56
+ height: 44px;
57
+ border-radius: 50%;
58
+ border: none;
59
+ cursor: pointer;
60
+ transition: background-color 0.15s, transform 0.1s;
61
+ }
62
+ .tp-button:active {
63
+ transform: scale(0.95);
64
+ }
65
+ .tp-button--start {
66
+ background: #18181b;
67
+ color: white;
68
+ }
69
+ .tp-button--start:hover {
70
+ background: #27272a;
71
+ }
72
+ .tp-button--start:disabled {
73
+ opacity: 0.5;
74
+ cursor: not-allowed;
75
+ }
76
+ .tp-button--mute {
77
+ background: #f4f4f5;
78
+ color: #18181b;
79
+ }
80
+ .tp-button--mute:hover {
81
+ background: #e4e4e7;
82
+ }
83
+ .tp-button--end {
84
+ background: #dc2626;
85
+ color: white;
86
+ }
87
+ .tp-button--end:hover {
88
+ background: #b91c1c;
89
+ }
90
+ .tp-button--loading {
91
+ opacity: 0.7;
92
+ }
93
+ .tp-spin {
94
+ animation: tp-spin 1s linear infinite;
95
+ }
96
+ @keyframes tp-spin {
97
+ from {
98
+ transform: rotate(0deg);
99
+ }
100
+ to {
101
+ transform: rotate(360deg);
102
+ }
103
+ }
104
+ @keyframes tp-pulse {
105
+ 0%, 100% {
106
+ opacity: 1;
107
+ }
108
+ 50% {
109
+ opacity: 0.5;
110
+ }
111
+ }
112
+ /*# sourceMappingURL=mount.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/style.css"],"sourcesContent":[".tp-widget {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n.tp-status {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.tp-status__name {\n font-size: 14px;\n font-weight: 500;\n color: #18181b;\n}\n\n.tp-status__text {\n font-size: 12px;\n color: #71717a;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.tp-status--connected {\n color: #059669;\n}\n\n.tp-status--error {\n color: #dc2626;\n}\n\n.tp-status__dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #059669;\n animation: tp-pulse 2s ease-in-out infinite;\n}\n\n.tp-icon {\n width: 20px;\n height: 20px;\n}\n\n.tp-button-group {\n display: inline-flex;\n gap: 8px;\n}\n\n.tp-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n border: none;\n cursor: pointer;\n transition: background-color 0.15s, transform 0.1s;\n}\n\n.tp-button:active {\n transform: scale(0.95);\n}\n\n.tp-button--start {\n background: #18181b;\n color: white;\n}\n\n.tp-button--start:hover {\n background: #27272a;\n}\n\n.tp-button--start:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.tp-button--mute {\n background: #f4f4f5;\n color: #18181b;\n}\n\n.tp-button--mute:hover {\n background: #e4e4e7;\n}\n\n.tp-button--end {\n background: #dc2626;\n color: white;\n}\n\n.tp-button--end:hover {\n background: #b91c1c;\n}\n\n.tp-button--loading {\n opacity: 0.7;\n}\n\n.tp-spin {\n animation: tp-spin 1s linear infinite;\n}\n\n@keyframes tp-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n}\n\n@keyframes tp-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n}\n"],"mappings":";AAAA,CAAC;AACC,WAAS;AACT,eAAa;AACb,OAAK;AACL;AAAA,IAAa,aAAa;AAAA,IAAE,kBAAkB;AAAA,IAAE,UAAU;AAAA,IAAE,MAAM;AAAA,IAAE;AACtE;AAEA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,OAAK;AACP;AAEA,CAAC;AACC,aAAW;AACX,eAAa;AACb,SAAO;AACT;AAEA,CAAC;AACC,aAAW;AACX,SAAO;AACP,WAAS;AACT,eAAa;AACb,OAAK;AACP;AAEA,CAAC;AACC,SAAO;AACT;AAEA,CAAC;AACC,SAAO;AACT;AAEA,CAAC;AACC,SAAO;AACP,UAAQ;AACR,iBAAe;AACf,cAAY;AACZ,aAAW,SAAS,GAAG,YAAY;AACrC;AAEA,CAAC;AACC,SAAO;AACP,UAAQ;AACV;AAEA,CAAC;AACC,WAAS;AACT,OAAK;AACP;AAEA,CAAC;AACC,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,SAAO;AACP,UAAQ;AACR,iBAAe;AACf,UAAQ;AACR,UAAQ;AACR,cAAY,iBAAiB,KAAK,EAAE,UAAU;AAChD;AAEA,CAZC,SAYS;AACR,aAAW,MAAM;AACnB;AAEA,CAAC;AACC,cAAY;AACZ,SAAO;AACT;AAEA,CALC,gBAKgB;AACf,cAAY;AACd;AAEA,CATC,gBASgB;AACf,WAAS;AACT,UAAQ;AACV;AAEA,CAAC;AACC,cAAY;AACZ,SAAO;AACT;AAEA,CALC,eAKe;AACd,cAAY;AACd;AAEA,CAAC;AACC,cAAY;AACZ,SAAO;AACT;AAEA,CALC,cAKc;AACb,cAAY;AACd;AAEA,CAAC;AACC,WAAS;AACX;AAEA,CAAC;AACC,aAAW,QAAQ,GAAG,OAAO;AAC/B;AAEA,WAJC;AAKC;AAAO,eAAW,OAAO;AAAO;AAChC;AAAK,eAAW,OAAO;AAAS;AAClC;AAEA,WA1Ea;AA2EX;AAAW,aAAS;AAAG;AACvB;AAAM,aAAS;AAAK;AACtB;","names":[]}