@skippr/live-agent-sdk 0.13.0 → 0.15.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.
@@ -1,33 +1,175 @@
1
1
  // src/components/LiveAgent.tsx
2
2
  import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
3
- import { useCallback as useCallback5, useMemo as useMemo4, useState as useState5 } from "react";
3
+ import { useCallback as useCallback7, useMemo as useMemo4, useState as useState7 } from "react";
4
4
 
5
5
  // src/context/LiveAgentContext.tsx
6
6
  import { createContext } from "react";
7
7
  var LiveAgentContext = createContext(null);
8
8
 
9
- // src/hooks/useSession.ts
10
- import { useCallback, useState } from "react";
9
+ // src/hooks/useAuth.ts
10
+ import { useCallback, useEffect, useState } from "react";
11
11
  var API_URL = "https://skipprapi-production.up.railway.app";
12
- function resolveAuthHeaders(authToken, appKey) {
12
+ function storageKey(appKey) {
13
+ return `skippr_auth_${appKey}`;
14
+ }
15
+ function getStoredToken(appKey) {
16
+ try {
17
+ return localStorage.getItem(storageKey(appKey));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function setStoredToken(appKey, token) {
23
+ try {
24
+ localStorage.setItem(storageKey(appKey), token);
25
+ } catch (e) {
26
+ console.error("[Skippr] Failed to persist auth token:", e);
27
+ }
28
+ }
29
+ function clearStoredToken(appKey) {
30
+ try {
31
+ localStorage.removeItem(storageKey(appKey));
32
+ } catch (e) {
33
+ console.error("[Skippr] Failed to clear auth token:", e);
34
+ }
35
+ }
36
+ function useAuth({ appKey }) {
37
+ const [authToken, setAuthToken] = useState(null);
38
+ const [isValidating, setIsValidating] = useState(false);
39
+ const [isSubmitting, setIsSubmitting] = useState(false);
40
+ const [error, setError] = useState("");
41
+ const isAuthenticated = authToken !== null;
42
+ useEffect(() => {
43
+ if (!appKey)
44
+ return;
45
+ const stored = getStoredToken(appKey);
46
+ if (!stored)
47
+ return;
48
+ async function validateStoredToken() {
49
+ setIsValidating(true);
50
+ try {
51
+ const res = await fetch(`${API_URL}/v1/auth/validate-token`, {
52
+ headers: { Authorization: `Bearer ${stored}` }
53
+ });
54
+ if (res.ok) {
55
+ setAuthToken(stored);
56
+ } else {
57
+ clearStoredToken(appKey);
58
+ }
59
+ } catch {
60
+ clearStoredToken(appKey);
61
+ } finally {
62
+ setIsValidating(false);
63
+ }
64
+ }
65
+ validateStoredToken();
66
+ }, [appKey]);
67
+ useEffect(() => {
68
+ function handleLogout() {
69
+ setAuthToken(null);
70
+ setError("");
71
+ }
72
+ window.addEventListener("skippr:logout", handleLogout);
73
+ return () => window.removeEventListener("skippr:logout", handleLogout);
74
+ }, []);
75
+ const requestOtp = useCallback(async (email) => {
76
+ if (!appKey)
77
+ return false;
78
+ setIsSubmitting(true);
79
+ setError("");
80
+ try {
81
+ const resp = await fetch(`${API_URL}/v1/auth/request-otp`, {
82
+ method: "POST",
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ "X-App-Key": appKey
86
+ },
87
+ body: JSON.stringify({ email })
88
+ });
89
+ if (!resp.ok) {
90
+ const body = await resp.json().catch(() => ({}));
91
+ throw new Error(body.detail || "Failed to send verification code");
92
+ }
93
+ return true;
94
+ } catch (e) {
95
+ setError(e instanceof Error ? e.message : "Failed to send verification code");
96
+ return false;
97
+ } finally {
98
+ setIsSubmitting(false);
99
+ }
100
+ }, [appKey]);
101
+ const verifyOtp = useCallback(async (email, code) => {
102
+ if (!appKey)
103
+ return;
104
+ setIsSubmitting(true);
105
+ setError("");
106
+ try {
107
+ const resp = await fetch(`${API_URL}/v1/auth/verify-otp`, {
108
+ method: "POST",
109
+ headers: {
110
+ "Content-Type": "application/json",
111
+ "X-App-Key": appKey
112
+ },
113
+ body: JSON.stringify({ email, code, recaptchaToken: "sdk" })
114
+ });
115
+ if (!resp.ok) {
116
+ const body = await resp.json().catch(() => ({}));
117
+ throw new Error(body.detail || "Invalid or expired verification code");
118
+ }
119
+ const { token } = await resp.json();
120
+ setStoredToken(appKey, token);
121
+ setAuthToken(token);
122
+ } catch (e) {
123
+ setError(e instanceof Error ? e.message : "Verification failed");
124
+ return;
125
+ } finally {
126
+ setIsSubmitting(false);
127
+ }
128
+ }, [appKey]);
129
+ const logout = useCallback(() => {
130
+ if (appKey)
131
+ clearStoredToken(appKey);
132
+ setAuthToken(null);
133
+ setError("");
134
+ }, [appKey]);
135
+ return {
136
+ isAuthenticated,
137
+ isValidating,
138
+ authToken,
139
+ requestOtp,
140
+ verifyOtp,
141
+ logout,
142
+ error,
143
+ isSubmitting
144
+ };
145
+ }
146
+
147
+ // src/hooks/useSession.ts
148
+ import { useCallback as useCallback2, useState as useState2 } from "react";
149
+ var API_URL2 = "https://skipprapi-production.up.railway.app";
150
+ function resolveAuthHeaders(authToken, appKey, userToken) {
151
+ const headers = {};
13
152
  if (authToken)
14
- return { Authorization: `Bearer ${authToken}` };
153
+ headers.Authorization = `Bearer ${authToken}`;
15
154
  if (appKey)
16
- return { "X-App-Key": appKey };
17
- return {};
155
+ headers["X-App-Key"] = appKey;
156
+ if (userToken)
157
+ headers["X-User-Token"] = userToken;
158
+ return headers;
18
159
  }
19
- function useSession({ agentId, authToken, appKey }) {
20
- const [connection, setConnection] = useState(null);
21
- const [shouldConnect, setShouldConnect] = useState(false);
22
- const [isStarting, setIsStarting] = useState(false);
23
- const [error, setError] = useState("");
24
- const [sessionId, setSessionId] = useState(null);
25
- const startSession = useCallback(async () => {
160
+ function useSession({ agentId, authToken, appKey, userToken }) {
161
+ const [connection, setConnection] = useState2(null);
162
+ const [shouldConnect, setShouldConnect] = useState2(false);
163
+ const [isStarting, setIsStarting] = useState2(false);
164
+ const [error, setError] = useState2("");
165
+ const [sessionId, setSessionId] = useState2(null);
166
+ const [sessionToken, setSessionToken] = useState2(null);
167
+ const startSession = useCallback2(async () => {
26
168
  setIsStarting(true);
27
169
  setError("");
28
170
  try {
29
- const authHeaders = resolveAuthHeaders(authToken, appKey);
30
- const createResp = await fetch(`${API_URL}/v1/sessions`, {
171
+ const authHeaders = resolveAuthHeaders(authToken, appKey, userToken);
172
+ const createResp = await fetch(`${API_URL2}/v1/sessions`, {
31
173
  method: "POST",
32
174
  headers: { "Content-Type": "application/json", ...authHeaders },
33
175
  body: JSON.stringify({ agentId })
@@ -36,15 +178,17 @@ function useSession({ agentId, authToken, appKey }) {
36
178
  throw new Error(`Failed to create session: ${createResp.status}`);
37
179
  }
38
180
  const { session } = await createResp.json();
39
- const startResp = await fetch(`${API_URL}/v1/sessions/${session.id}/start`, {
181
+ const sessionHeaders = { "X-Session-Token": session.sessionToken };
182
+ const startResp = await fetch(`${API_URL2}/v1/sessions/${session.id}/start`, {
40
183
  method: "POST",
41
- headers: authHeaders
184
+ headers: sessionHeaders
42
185
  });
43
186
  if (!startResp.ok) {
44
187
  throw new Error(`Failed to start session: ${startResp.status}`);
45
188
  }
46
189
  const { connection: conn } = await startResp.json();
47
190
  setSessionId(session.id);
191
+ setSessionToken(session.sessionToken);
48
192
  setConnection({
49
193
  livekitUrl: conn.livekitUrl,
50
194
  token: conn.token
@@ -55,14 +199,14 @@ function useSession({ agentId, authToken, appKey }) {
55
199
  } finally {
56
200
  setIsStarting(false);
57
201
  }
58
- }, [agentId, authToken, appKey]);
59
- const disconnect = useCallback(async () => {
60
- if (sessionId) {
61
- const authHeaders = resolveAuthHeaders(authToken, appKey);
202
+ }, [agentId, authToken, appKey, userToken]);
203
+ const disconnect = useCallback2(async () => {
204
+ if (sessionId && sessionToken) {
205
+ const sessionHeaders = { "X-Session-Token": sessionToken };
62
206
  try {
63
- await fetch(`${API_URL}/v1/sessions/${sessionId}/complete`, {
207
+ await fetch(`${API_URL2}/v1/sessions/${sessionId}/complete`, {
64
208
  method: "POST",
65
- headers: { "Content-Type": "application/json", ...authHeaders },
209
+ headers: { "Content-Type": "application/json", ...sessionHeaders },
66
210
  body: JSON.stringify({})
67
211
  });
68
212
  } catch {}
@@ -71,14 +215,15 @@ function useSession({ agentId, authToken, appKey }) {
71
215
  setShouldConnect(false);
72
216
  setConnection(null);
73
217
  setSessionId(null);
74
- }, [sessionId, authToken, appKey]);
218
+ setSessionToken(null);
219
+ }, [sessionId, sessionToken]);
75
220
  return { connection, shouldConnect, isStarting, error, startSession, disconnect };
76
221
  }
77
222
 
78
223
  // src/components/Sidebar.tsx
79
224
  import { useConnectionState } from "@livekit/components-react";
80
225
  import { ConnectionState } from "livekit-client";
81
- import { useEffect as useEffect4 } from "react";
226
+ import { useEffect as useEffect6 } from "react";
82
227
 
83
228
  // src/hooks/useCombinedMessages.ts
84
229
  import { useVoiceAssistant } from "@livekit/components-react";
@@ -172,15 +317,15 @@ function useLiveAgent() {
172
317
  }
173
318
 
174
319
  // src/hooks/usePhaseUpdates.ts
175
- import { useCallback as useCallback2 } from "react";
320
+ import { useCallback as useCallback3 } from "react";
176
321
 
177
322
  // src/hooks/useAgentState.ts
178
323
  import { useRemoteParticipants } from "@livekit/components-react";
179
- import { useEffect, useState as useState2 } from "react";
324
+ import { useEffect as useEffect2, useState as useState3 } from "react";
180
325
  function useAgentState(attributeKey, parse, initial) {
181
- const [value, setValue] = useState2(initial);
326
+ const [value, setValue] = useState3(initial);
182
327
  const remoteParticipants = useRemoteParticipants();
183
- useEffect(() => {
328
+ useEffect2(() => {
184
329
  const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
185
330
  if (agentParticipant) {
186
331
  const attr = agentParticipant.attributes?.[attributeKey];
@@ -226,13 +371,13 @@ function parsePhases(json) {
226
371
  return null;
227
372
  }
228
373
  function usePhaseUpdates() {
229
- const parse = useCallback2(parsePhases, []);
374
+ const parse = useCallback3(parsePhases, []);
230
375
  const phases = useAgentState("phases", parse, []);
231
376
  return { phases };
232
377
  }
233
378
 
234
379
  // src/hooks/useQuestionUpdates.ts
235
- import { useCallback as useCallback3 } from "react";
380
+ import { useCallback as useCallback4 } from "react";
236
381
  function parseQuestions(json) {
237
382
  try {
238
383
  const data = JSON.parse(json);
@@ -243,7 +388,7 @@ function parseQuestions(json) {
243
388
  return null;
244
389
  }
245
390
  function useQuestionUpdates() {
246
- const parse = useCallback3(parseQuestions, []);
391
+ const parse = useCallback4(parseQuestions, []);
247
392
  const questions = useAgentState("questions", parse, []);
248
393
  return { questions };
249
394
  }
@@ -384,8 +529,14 @@ var Check = createLucideIcon("check", __iconNode5);
384
529
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/circle.js
385
530
  var __iconNode6 = [["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]];
386
531
  var Circle = createLucideIcon("circle", __iconNode6);
387
- // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
532
+ // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mail.js
388
533
  var __iconNode7 = [
534
+ ["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7", key: "132q7q" }],
535
+ ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2", key: "izxlao" }]
536
+ ];
537
+ var Mail = createLucideIcon("mail", __iconNode7);
538
+ // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/message-circle.js
539
+ var __iconNode8 = [
389
540
  [
390
541
  "path",
391
542
  {
@@ -394,9 +545,9 @@ var __iconNode7 = [
394
545
  }
395
546
  ]
396
547
  ];
397
- var MessageCircle = createLucideIcon("message-circle", __iconNode7);
548
+ var MessageCircle = createLucideIcon("message-circle", __iconNode8);
398
549
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic-off.js
399
- var __iconNode8 = [
550
+ var __iconNode9 = [
400
551
  ["path", { d: "M12 19v3", key: "npa21l" }],
401
552
  ["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33", key: "1gzdoj" }],
402
553
  ["path", { d: "M16.95 16.95A7 7 0 0 1 5 12v-2", key: "cqa7eg" }],
@@ -404,32 +555,32 @@ var __iconNode8 = [
404
555
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
405
556
  ["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12", key: "r2i35w" }]
406
557
  ];
407
- var MicOff = createLucideIcon("mic-off", __iconNode8);
558
+ var MicOff = createLucideIcon("mic-off", __iconNode9);
408
559
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mic.js
409
- var __iconNode9 = [
560
+ var __iconNode10 = [
410
561
  ["path", { d: "M12 19v3", key: "npa21l" }],
411
562
  ["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2", key: "1vc78b" }],
412
563
  ["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3", key: "s6n7sd" }]
413
564
  ];
414
- var Mic = createLucideIcon("mic", __iconNode9);
565
+ var Mic = createLucideIcon("mic", __iconNode10);
415
566
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor-off.js
416
- var __iconNode10 = [
567
+ var __iconNode11 = [
417
568
  ["path", { d: "M12 17v4", key: "1riwvh" }],
418
569
  ["path", { d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826", key: "cv7jms" }],
419
570
  ["path", { d: "m2 2 20 20", key: "1ooewy" }],
420
571
  ["path", { d: "M8 21h8", key: "1ev6f3" }],
421
572
  ["path", { d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042", key: "z8ni2w" }]
422
573
  ];
423
- var MonitorOff = createLucideIcon("monitor-off", __iconNode10);
574
+ var MonitorOff = createLucideIcon("monitor-off", __iconNode11);
424
575
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.js
425
- var __iconNode11 = [
576
+ var __iconNode12 = [
426
577
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
427
578
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
428
579
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
429
580
  ];
430
- var Monitor = createLucideIcon("monitor", __iconNode11);
581
+ var Monitor = createLucideIcon("monitor", __iconNode12);
431
582
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/phone-off.js
432
- var __iconNode12 = [
583
+ var __iconNode13 = [
433
584
  [
434
585
  "path",
435
586
  {
@@ -446,13 +597,13 @@ var __iconNode12 = [
446
597
  }
447
598
  ]
448
599
  ];
449
- var PhoneOff = createLucideIcon("phone-off", __iconNode12);
600
+ var PhoneOff = createLucideIcon("phone-off", __iconNode13);
450
601
  // ../../node_modules/.bun/lucide-react@0.563.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/x.js
451
- var __iconNode13 = [
602
+ var __iconNode14 = [
452
603
  ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
453
604
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
454
605
  ];
455
- var X = createLucideIcon("x", __iconNode13);
606
+ var X = createLucideIcon("x", __iconNode14);
456
607
  // src/components/ui/button.tsx
457
608
  import { forwardRef as forwardRef3 } from "react";
458
609
  import { jsx } from "react/jsx-runtime";
@@ -514,10 +665,257 @@ function ChatHeader({ onClose }) {
514
665
  });
515
666
  }
516
667
 
668
+ // src/components/LoginFlow.tsx
669
+ import { useCallback as useCallback5, useEffect as useEffect3, useRef, useState as useState4 } from "react";
670
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
671
+ var OTP_LENGTH = 6;
672
+ var DIGIT_KEYS = ["d0", "d1", "d2", "d3", "d4", "d5"];
673
+ function LoginFlow({ requestOtp, verifyOtp, error, isSubmitting }) {
674
+ const [step, setStep] = useState4("email");
675
+ const [email, setEmail] = useState4("");
676
+ const handleRequestOtp = useCallback5(async (emailValue) => {
677
+ const success = await requestOtp(emailValue);
678
+ if (success)
679
+ setStep("otp");
680
+ }, [requestOtp]);
681
+ const handleVerifyOtp = useCallback5(async (code) => {
682
+ await verifyOtp(email, code);
683
+ }, [verifyOtp, email]);
684
+ const handleBack = useCallback5(() => {
685
+ setStep("email");
686
+ }, []);
687
+ const handleResend = useCallback5(async () => {
688
+ await requestOtp(email);
689
+ }, [requestOtp, email]);
690
+ if (step === "otp") {
691
+ return /* @__PURE__ */ jsx3(OtpStep, {
692
+ email,
693
+ onSubmit: handleVerifyOtp,
694
+ onResend: handleResend,
695
+ onBack: handleBack,
696
+ error,
697
+ isSubmitting
698
+ });
699
+ }
700
+ return /* @__PURE__ */ jsx3(EmailStep, {
701
+ email,
702
+ onEmailChange: setEmail,
703
+ onSubmit: handleRequestOtp,
704
+ error,
705
+ isSubmitting
706
+ });
707
+ }
708
+ function EmailStep({ email, onEmailChange, onSubmit, error, isSubmitting }) {
709
+ function handleSubmit(e) {
710
+ e.preventDefault();
711
+ if (email.trim())
712
+ onSubmit(email.trim());
713
+ }
714
+ return /* @__PURE__ */ jsxs2("div", {
715
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:px-4 skippr:py-4",
716
+ children: [
717
+ /* @__PURE__ */ jsxs2("div", {
718
+ className: "skippr:mb-4 skippr:text-center",
719
+ children: [
720
+ /* @__PURE__ */ jsx3(Mail, {
721
+ className: "skippr:mx-auto skippr:mb-2 skippr:size-6 skippr:text-primary"
722
+ }),
723
+ /* @__PURE__ */ jsx3("p", {
724
+ className: "skippr:text-sm skippr:font-medium skippr:text-foreground",
725
+ children: "Sign in to continue"
726
+ }),
727
+ /* @__PURE__ */ jsx3("p", {
728
+ className: "skippr:mt-1 skippr:text-xs skippr:text-muted-foreground",
729
+ children: "Your email will be used to identify you across sessions"
730
+ })
731
+ ]
732
+ }),
733
+ /* @__PURE__ */ jsxs2("form", {
734
+ onSubmit: handleSubmit,
735
+ className: "skippr:flex skippr:flex-col skippr:gap-3",
736
+ children: [
737
+ /* @__PURE__ */ jsx3("input", {
738
+ type: "email",
739
+ placeholder: "you@example.com",
740
+ value: email,
741
+ onChange: (e) => onEmailChange(e.target.value),
742
+ disabled: isSubmitting,
743
+ required: true,
744
+ className: "skippr:w-full skippr:rounded-md skippr:border skippr:border-border skippr:bg-background skippr:px-3 skippr:py-2 skippr:text-sm skippr:text-foreground skippr:placeholder-muted-foreground skippr:outline-none focus:skippr:ring-2 focus:skippr:ring-primary/30 focus:skippr:border-primary disabled:skippr:opacity-50"
745
+ }),
746
+ /* @__PURE__ */ jsx3(Button, {
747
+ type: "submit",
748
+ disabled: isSubmitting || !email.trim(),
749
+ className: "skippr:w-full",
750
+ children: isSubmitting ? /* @__PURE__ */ jsx3(LoaderCircle, {
751
+ className: "skippr:size-4 skippr:animate-spin"
752
+ }) : "Continue"
753
+ }),
754
+ error && /* @__PURE__ */ jsx3("p", {
755
+ className: "skippr:text-xs skippr:text-center skippr:text-destructive",
756
+ children: error
757
+ })
758
+ ]
759
+ })
760
+ ]
761
+ });
762
+ }
763
+ function OtpStep({ email, onSubmit, onResend, onBack, error, isSubmitting }) {
764
+ const [digits, setDigits] = useState4(Array(OTP_LENGTH).fill(""));
765
+ const [resendCooldown, setResendCooldown] = useState4(0);
766
+ const inputRefs = useRef([]);
767
+ const submittedRef = useRef(false);
768
+ useEffect3(() => {
769
+ inputRefs.current[0]?.focus();
770
+ }, []);
771
+ useEffect3(() => {
772
+ if (error)
773
+ submittedRef.current = false;
774
+ }, [error]);
775
+ useEffect3(() => {
776
+ if (resendCooldown <= 0)
777
+ return;
778
+ const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000);
779
+ return () => clearTimeout(timer);
780
+ }, [resendCooldown]);
781
+ const submitCode = useCallback5((code) => {
782
+ if (submittedRef.current || isSubmitting)
783
+ return;
784
+ submittedRef.current = true;
785
+ onSubmit(code);
786
+ }, [onSubmit, isSubmitting]);
787
+ const handleDigitChange = useCallback5((index2, value) => {
788
+ const digit = value.replace(/\D/g, "").slice(-1);
789
+ const newDigits = [...digits];
790
+ newDigits[index2] = digit;
791
+ setDigits(newDigits);
792
+ if (digit && index2 < OTP_LENGTH - 1) {
793
+ inputRefs.current[index2 + 1]?.focus();
794
+ }
795
+ if (digit && index2 === OTP_LENGTH - 1) {
796
+ const code = newDigits.join("");
797
+ if (code.length === OTP_LENGTH)
798
+ submitCode(code);
799
+ }
800
+ }, [digits, submitCode]);
801
+ const handleKeyDown = useCallback5((index2, e) => {
802
+ if (e.key === "Backspace" && !digits[index2] && index2 > 0) {
803
+ inputRefs.current[index2 - 1]?.focus();
804
+ }
805
+ }, [digits]);
806
+ const handlePaste = useCallback5((e) => {
807
+ e.preventDefault();
808
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, OTP_LENGTH);
809
+ if (pasted.length > 0) {
810
+ const newDigits = Array(OTP_LENGTH).fill("");
811
+ for (let i = 0;i < pasted.length; i++) {
812
+ newDigits[i] = pasted[i];
813
+ }
814
+ setDigits(newDigits);
815
+ if (pasted.length === OTP_LENGTH)
816
+ submitCode(pasted);
817
+ else
818
+ inputRefs.current[pasted.length]?.focus();
819
+ }
820
+ }, [submitCode]);
821
+ function handleSubmit(e) {
822
+ e.preventDefault();
823
+ const code = digits.join("");
824
+ if (code.length === OTP_LENGTH)
825
+ submitCode(code);
826
+ }
827
+ function handleResend() {
828
+ onResend();
829
+ setResendCooldown(30);
830
+ setDigits(Array(OTP_LENGTH).fill(""));
831
+ submittedRef.current = false;
832
+ inputRefs.current[0]?.focus();
833
+ }
834
+ return /* @__PURE__ */ jsxs2("div", {
835
+ className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:px-4 skippr:py-4",
836
+ children: [
837
+ /* @__PURE__ */ jsxs2("div", {
838
+ className: "skippr:mb-4 skippr:text-center",
839
+ children: [
840
+ /* @__PURE__ */ jsx3("p", {
841
+ className: "skippr:text-sm skippr:font-medium skippr:text-foreground",
842
+ children: "Enter verification code"
843
+ }),
844
+ /* @__PURE__ */ jsxs2("p", {
845
+ className: "skippr:mt-1 skippr:text-xs skippr:text-muted-foreground",
846
+ children: [
847
+ "We sent a 6-digit code to",
848
+ " ",
849
+ /* @__PURE__ */ jsx3("span", {
850
+ className: "skippr:font-medium skippr:text-foreground",
851
+ children: email
852
+ })
853
+ ]
854
+ })
855
+ ]
856
+ }),
857
+ /* @__PURE__ */ jsxs2("form", {
858
+ onSubmit: handleSubmit,
859
+ className: "skippr:flex skippr:flex-col skippr:gap-3",
860
+ children: [
861
+ /* @__PURE__ */ jsx3("div", {
862
+ className: "skippr:flex skippr:justify-center skippr:gap-1.5",
863
+ children: digits.map((digit, index2) => /* @__PURE__ */ jsx3("input", {
864
+ ref: (el) => {
865
+ inputRefs.current[index2] = el;
866
+ },
867
+ type: "text",
868
+ inputMode: "numeric",
869
+ maxLength: 1,
870
+ value: digit,
871
+ onChange: (e) => handleDigitChange(index2, e.target.value),
872
+ onKeyDown: (e) => handleKeyDown(index2, e),
873
+ onPaste: handlePaste,
874
+ disabled: isSubmitting,
875
+ className: "skippr:h-10 skippr:w-10 skippr:rounded-md skippr:border skippr:border-border skippr:bg-background skippr:text-center skippr:text-sm skippr:font-semibold skippr:text-foreground skippr:outline-none focus:skippr:ring-2 focus:skippr:ring-primary/30 focus:skippr:border-primary disabled:skippr:opacity-50"
876
+ }, DIGIT_KEYS[index2]))
877
+ }),
878
+ error && /* @__PURE__ */ jsx3("p", {
879
+ className: "skippr:text-xs skippr:text-center skippr:text-destructive",
880
+ children: error
881
+ }),
882
+ /* @__PURE__ */ jsx3(Button, {
883
+ type: "submit",
884
+ disabled: isSubmitting || digits.join("").length !== OTP_LENGTH,
885
+ className: "skippr:w-full",
886
+ children: isSubmitting ? /* @__PURE__ */ jsx3(LoaderCircle, {
887
+ className: "skippr:size-4 skippr:animate-spin"
888
+ }) : "Verify"
889
+ }),
890
+ /* @__PURE__ */ jsxs2("div", {
891
+ className: "skippr:flex skippr:items-center skippr:justify-between skippr:text-xs",
892
+ children: [
893
+ /* @__PURE__ */ jsx3("button", {
894
+ type: "button",
895
+ onClick: onBack,
896
+ disabled: isSubmitting,
897
+ className: "skippr:text-muted-foreground hover:skippr:text-foreground skippr:transition-colors disabled:skippr:opacity-50",
898
+ children: "Change email"
899
+ }),
900
+ /* @__PURE__ */ jsx3("button", {
901
+ type: "button",
902
+ onClick: handleResend,
903
+ disabled: isSubmitting || resendCooldown > 0,
904
+ className: "skippr:text-primary hover:skippr:opacity-80 skippr:transition-opacity disabled:skippr:opacity-50",
905
+ children: resendCooldown > 0 ? `Resend in ${resendCooldown}s` : "Resend code"
906
+ })
907
+ ]
908
+ })
909
+ ]
910
+ })
911
+ ]
912
+ });
913
+ }
914
+
517
915
  // src/components/MeetingControls.tsx
518
916
  import { useLocalParticipant as useLocalParticipant3 } from "@livekit/components-react";
519
917
  import { ScreenSharePresets } from "livekit-client";
520
- import { useCallback as useCallback4, useEffect as useEffect2, useRef, useState as useState3 } from "react";
918
+ import { useCallback as useCallback6, useEffect as useEffect4, useRef as useRef2, useState as useState5 } from "react";
521
919
 
522
920
  // src/lib/format.ts
523
921
  function formatTime(seconds) {
@@ -531,12 +929,12 @@ function parseNumber(s) {
531
929
  }
532
930
 
533
931
  // src/components/SessionWarningBanner.tsx
534
- import { jsx as jsx3 } from "react/jsx-runtime";
932
+ import { jsx as jsx4 } from "react/jsx-runtime";
535
933
  var SESSION_WARNING_THRESHOLD_SECS = 60;
536
934
  function SessionWarningBanner({ remaining }) {
537
935
  if (remaining === null || remaining <= 0 || remaining > SESSION_WARNING_THRESHOLD_SECS)
538
936
  return null;
539
- return /* @__PURE__ */ jsx3("div", {
937
+ return /* @__PURE__ */ jsx4("div", {
540
938
  "data-testid": "session-warning-banner",
541
939
  className: "skippr:bg-red-50 skippr:px-4 skippr:py-1.5 skippr:text-center skippr:text-xs skippr:font-medium skippr:text-red-700",
542
940
  children: "Session ending soon"
@@ -544,15 +942,15 @@ function SessionWarningBanner({ remaining }) {
544
942
  }
545
943
 
546
944
  // src/components/MeetingControls.tsx
547
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
945
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
548
946
  function MeetingControls({ onHangUp }) {
549
947
  const maxCallDuration = useAgentState("maxCallDuration", parseNumber, null);
550
948
  const { localParticipant } = useLocalParticipant3();
551
949
  const isMuted = !localParticipant.isMicrophoneEnabled;
552
950
  const isScreenSharing = localParticipant.isScreenShareEnabled;
553
- const endTimeRef = useRef(null);
554
- const [remaining, setRemaining] = useState3(null);
555
- useEffect2(() => {
951
+ const endTimeRef = useRef2(null);
952
+ const [remaining, setRemaining] = useState5(null);
953
+ useEffect4(() => {
556
954
  if (maxCallDuration === null || endTimeRef.current !== null)
557
955
  return;
558
956
  endTimeRef.current = Date.now() + maxCallDuration * 1000;
@@ -564,14 +962,14 @@ function MeetingControls({ onHangUp }) {
564
962
  const id = setInterval(tick, 1000);
565
963
  return () => clearInterval(id);
566
964
  }, [maxCallDuration]);
567
- const toggleMute = useCallback4(async () => {
965
+ const toggleMute = useCallback6(async () => {
568
966
  try {
569
967
  await localParticipant.setMicrophoneEnabled(isMuted);
570
968
  } catch (e) {
571
969
  console.error("Failed to toggle microphone:", e);
572
970
  }
573
971
  }, [localParticipant, isMuted]);
574
- const toggleScreenShare = useCallback4(async () => {
972
+ const toggleScreenShare = useCallback6(async () => {
575
973
  try {
576
974
  await localParticipant.setScreenShareEnabled(!isScreenSharing, {
577
975
  video: { displaySurface: "browser" },
@@ -582,54 +980,54 @@ function MeetingControls({ onHangUp }) {
582
980
  console.error("Failed to toggle screen share:", e);
583
981
  }
584
982
  }, [localParticipant, isScreenSharing]);
585
- useEffect2(() => {
983
+ useEffect4(() => {
586
984
  toggleMute().then(() => toggleScreenShare());
587
985
  }, []);
588
- return /* @__PURE__ */ jsxs2("div", {
986
+ return /* @__PURE__ */ jsxs3("div", {
589
987
  children: [
590
- /* @__PURE__ */ jsx4(SessionWarningBanner, {
988
+ /* @__PURE__ */ jsx5(SessionWarningBanner, {
591
989
  remaining
592
990
  }),
593
- /* @__PURE__ */ jsxs2("div", {
991
+ /* @__PURE__ */ jsxs3("div", {
594
992
  className: "skippr:flex skippr:items-center skippr:justify-between skippr:border-b skippr:px-4 skippr:py-3",
595
993
  children: [
596
- /* @__PURE__ */ jsxs2("div", {
994
+ /* @__PURE__ */ jsxs3("div", {
597
995
  className: "skippr:flex skippr:items-center skippr:gap-2",
598
996
  children: [
599
- /* @__PURE__ */ jsx4(Button, {
997
+ /* @__PURE__ */ jsx5(Button, {
600
998
  size: "icon-sm",
601
999
  variant: isMuted ? "destructive" : "outline",
602
1000
  onClick: toggleMute,
603
1001
  "aria-label": isMuted ? "Unmute" : "Mute",
604
- children: isMuted ? /* @__PURE__ */ jsx4(MicOff, {
1002
+ children: isMuted ? /* @__PURE__ */ jsx5(MicOff, {
605
1003
  className: "skippr:size-4"
606
- }) : /* @__PURE__ */ jsx4(Mic, {
1004
+ }) : /* @__PURE__ */ jsx5(Mic, {
607
1005
  className: "skippr:size-4"
608
1006
  })
609
1007
  }),
610
- /* @__PURE__ */ jsx4(Button, {
1008
+ /* @__PURE__ */ jsx5(Button, {
611
1009
  size: "icon-sm",
612
1010
  variant: isScreenSharing ? "default" : "outline",
613
1011
  onClick: toggleScreenShare,
614
1012
  "aria-label": isScreenSharing ? "Stop sharing" : "Share screen",
615
- children: isScreenSharing ? /* @__PURE__ */ jsx4(MonitorOff, {
1013
+ children: isScreenSharing ? /* @__PURE__ */ jsx5(MonitorOff, {
616
1014
  className: "skippr:size-4"
617
- }) : /* @__PURE__ */ jsx4(Monitor, {
1015
+ }) : /* @__PURE__ */ jsx5(Monitor, {
618
1016
  className: "skippr:size-4"
619
1017
  })
620
1018
  })
621
1019
  ]
622
1020
  }),
623
- remaining !== null && /* @__PURE__ */ jsx4("span", {
1021
+ remaining !== null && /* @__PURE__ */ jsx5("span", {
624
1022
  className: cn("skippr:text-sm skippr:font-medium skippr:tabular-nums", remaining <= SESSION_WARNING_THRESHOLD_SECS ? "skippr:text-red-600 skippr:animate-pulse" : "skippr:text-muted-foreground"),
625
1023
  children: formatTime(remaining)
626
1024
  }),
627
- /* @__PURE__ */ jsx4(Button, {
1025
+ /* @__PURE__ */ jsx5(Button, {
628
1026
  size: "icon-sm",
629
1027
  variant: "destructive",
630
1028
  onClick: onHangUp,
631
1029
  "aria-label": "Hang up",
632
- children: /* @__PURE__ */ jsx4(PhoneOff, {
1030
+ children: /* @__PURE__ */ jsx5(PhoneOff, {
633
1031
  className: "skippr:size-4"
634
1032
  })
635
1033
  })
@@ -640,13 +1038,13 @@ function MeetingControls({ onHangUp }) {
640
1038
  }
641
1039
 
642
1040
  // src/components/MessageList.tsx
643
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
1041
+ import { useEffect as useEffect5, useRef as useRef3 } from "react";
644
1042
 
645
1043
  // src/components/ChatInput.tsx
646
- import { useState as useState4 } from "react";
647
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1044
+ import { useState as useState6 } from "react";
1045
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
648
1046
  function ChatInput({ sendChatMessage, isSendingChat }) {
649
- const [inputText, setInputText] = useState4("");
1047
+ const [inputText, setInputText] = useState6("");
650
1048
  const canSend = inputText.trim().length > 0 && !isSendingChat;
651
1049
  function handleSubmit(e) {
652
1050
  e.preventDefault();
@@ -656,11 +1054,11 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
656
1054
  setInputText("");
657
1055
  sendChatMessage(text).catch(() => setInputText(text));
658
1056
  }
659
- return /* @__PURE__ */ jsxs3("form", {
1057
+ return /* @__PURE__ */ jsxs4("form", {
660
1058
  onSubmit: handleSubmit,
661
1059
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:border-t skippr:border-border skippr:px-3 skippr:py-2",
662
1060
  children: [
663
- /* @__PURE__ */ jsx5("input", {
1061
+ /* @__PURE__ */ jsx6("input", {
664
1062
  type: "text",
665
1063
  value: inputText,
666
1064
  onChange: (e) => setInputText(e.target.value),
@@ -668,12 +1066,12 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
668
1066
  className: cn("skippr:flex-1 skippr:rounded-lg skippr:border skippr:border-border skippr:bg-background", "skippr:px-3 skippr:py-2 skippr:text-sm skippr:text-foreground", "skippr:placeholder:text-muted-foreground skippr:outline-none", "skippr:focus:ring-1 skippr:focus:ring-ring"),
669
1067
  disabled: isSendingChat
670
1068
  }),
671
- /* @__PURE__ */ jsx5("button", {
1069
+ /* @__PURE__ */ jsx6("button", {
672
1070
  type: "submit",
673
1071
  disabled: !canSend,
674
1072
  "aria-label": "Send message",
675
1073
  className: cn("skippr:flex skippr:size-9 skippr:shrink-0 skippr:items-center skippr:justify-center", "skippr:rounded-lg skippr:bg-primary skippr:text-primary-foreground", "skippr:transition-opacity", !canSend && "skippr:opacity-50 skippr:cursor-not-allowed"),
676
- children: /* @__PURE__ */ jsx5(SendHorizontal, {
1074
+ children: /* @__PURE__ */ jsx6(SendHorizontal, {
677
1075
  className: "skippr:size-4"
678
1076
  })
679
1077
  })
@@ -682,12 +1080,12 @@ function ChatInput({ sendChatMessage, isSendingChat }) {
682
1080
  }
683
1081
 
684
1082
  // src/components/ChatMessage.tsx
685
- import { jsx as jsx6 } from "react/jsx-runtime";
1083
+ import { jsx as jsx7 } from "react/jsx-runtime";
686
1084
  function ChatMessage({ message }) {
687
1085
  const isUser = message.role === "user";
688
- return /* @__PURE__ */ jsx6("div", {
1086
+ return /* @__PURE__ */ jsx7("div", {
689
1087
  className: cn("skippr:flex skippr:w-full skippr:px-4 skippr:py-1", isUser ? "skippr:justify-end" : "skippr:justify-start"),
690
- children: /* @__PURE__ */ jsx6("div", {
1088
+ children: /* @__PURE__ */ jsx7("div", {
691
1089
  className: cn("skippr:max-w-[85%] skippr:whitespace-pre-wrap skippr:rounded-2xl skippr:px-4 skippr:py-2.5 skippr:text-sm skippr:leading-relaxed", isUser ? "skippr:rounded-br-sm skippr:bg-primary skippr:text-primary-foreground" : "skippr:rounded-bl-sm skippr:bg-muted skippr:text-foreground"),
692
1090
  children: message.content
693
1091
  })
@@ -695,20 +1093,20 @@ function ChatMessage({ message }) {
695
1093
  }
696
1094
 
697
1095
  // src/components/TypingIndicator.tsx
698
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1096
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
699
1097
  function TypingIndicator() {
700
- return /* @__PURE__ */ jsx7("div", {
1098
+ return /* @__PURE__ */ jsx8("div", {
701
1099
  className: "skippr:flex skippr:items-center skippr:gap-1 skippr:px-4 skippr:py-3",
702
- children: /* @__PURE__ */ jsxs4("div", {
1100
+ children: /* @__PURE__ */ jsxs5("div", {
703
1101
  className: "skippr:flex skippr:items-center skippr:gap-1 skippr:rounded-2xl skippr:rounded-bl-sm skippr:bg-muted skippr:px-4 skippr:py-2.5",
704
1102
  children: [
705
- /* @__PURE__ */ jsx7("span", {
1103
+ /* @__PURE__ */ jsx8("span", {
706
1104
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:0ms]"
707
1105
  }),
708
- /* @__PURE__ */ jsx7("span", {
1106
+ /* @__PURE__ */ jsx8("span", {
709
1107
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:150ms]"
710
1108
  }),
711
- /* @__PURE__ */ jsx7("span", {
1109
+ /* @__PURE__ */ jsx8("span", {
712
1110
  className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:300ms]"
713
1111
  })
714
1112
  ]
@@ -717,38 +1115,38 @@ function TypingIndicator() {
717
1115
  }
718
1116
 
719
1117
  // src/components/MessageList.tsx
720
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1118
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
721
1119
  function MessageList({
722
1120
  messages,
723
1121
  isStreaming,
724
1122
  sendChatMessage,
725
1123
  isSendingChat
726
1124
  }) {
727
- const scrollRef = useRef2(null);
1125
+ const scrollRef = useRef3(null);
728
1126
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
729
- useEffect3(() => {
1127
+ useEffect5(() => {
730
1128
  scrollRef.current?.scrollIntoView({ behavior: "smooth" });
731
1129
  }, [messages.length, lastMessage?.content]);
732
1130
  const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
733
- return /* @__PURE__ */ jsxs5("div", {
1131
+ return /* @__PURE__ */ jsxs6("div", {
734
1132
  className: "skippr:flex skippr:min-h-0 skippr:flex-1 skippr:flex-col",
735
1133
  children: [
736
- /* @__PURE__ */ jsx8("div", {
1134
+ /* @__PURE__ */ jsx9("div", {
737
1135
  className: "skippr:min-h-0 skippr:flex-1 skippr:overflow-y-auto",
738
- children: /* @__PURE__ */ jsxs5("div", {
1136
+ children: /* @__PURE__ */ jsxs6("div", {
739
1137
  className: "skippr:flex skippr:flex-col skippr:gap-1 skippr:py-3",
740
1138
  children: [
741
- messages.map((message) => /* @__PURE__ */ jsx8(ChatMessage, {
1139
+ messages.map((message) => /* @__PURE__ */ jsx9(ChatMessage, {
742
1140
  message
743
1141
  }, message.id)),
744
- showTyping && /* @__PURE__ */ jsx8(TypingIndicator, {}),
745
- /* @__PURE__ */ jsx8("div", {
1142
+ showTyping && /* @__PURE__ */ jsx9(TypingIndicator, {}),
1143
+ /* @__PURE__ */ jsx9("div", {
746
1144
  ref: scrollRef
747
1145
  })
748
1146
  ]
749
1147
  })
750
1148
  }),
751
- /* @__PURE__ */ jsx8(ChatInput, {
1149
+ /* @__PURE__ */ jsx9(ChatInput, {
752
1150
  sendChatMessage,
753
1151
  isSendingChat
754
1152
  })
@@ -757,30 +1155,30 @@ function MessageList({
757
1155
  }
758
1156
 
759
1157
  // src/components/QuickActions.tsx
760
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1158
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
761
1159
  function QuickActions({ onStartSession, isStarting, error }) {
762
- return /* @__PURE__ */ jsxs6("div", {
1160
+ return /* @__PURE__ */ jsxs7("div", {
763
1161
  className: "skippr:flex skippr:flex-1 skippr:flex-col skippr:items-center skippr:gap-6 skippr:overflow-y-auto skippr:px-4 skippr:py-4",
764
1162
  children: [
765
- /* @__PURE__ */ jsx9("p", {
1163
+ /* @__PURE__ */ jsx10("p", {
766
1164
  className: "skippr:mb-1 skippr:text-sm skippr:text-muted-foreground",
767
1165
  children: "How can I help you today?"
768
1166
  }),
769
- /* @__PURE__ */ jsxs6(Button, {
1167
+ /* @__PURE__ */ jsxs7(Button, {
770
1168
  variant: "outline",
771
1169
  className: "skippr:h-auto skippr:flex-col skippr:gap-1.5 skippr:whitespace-normal skippr:py-3 skippr:text-xs",
772
1170
  onClick: onStartSession,
773
1171
  disabled: isStarting,
774
1172
  children: [
775
- isStarting ? /* @__PURE__ */ jsx9(LoaderCircle, {
1173
+ isStarting ? /* @__PURE__ */ jsx10(LoaderCircle, {
776
1174
  className: "skippr:size-4 skippr:animate-spin skippr:text-primary"
777
- }) : /* @__PURE__ */ jsx9(MessageCircleQuestionMark, {
1175
+ }) : /* @__PURE__ */ jsx10(MessageCircleQuestionMark, {
778
1176
  className: "skippr:size-4 skippr:text-primary"
779
1177
  }),
780
1178
  isStarting ? "Starting..." : "Start Session"
781
1179
  ]
782
1180
  }),
783
- error && /* @__PURE__ */ jsx9("p", {
1181
+ error && /* @__PURE__ */ jsx10("p", {
784
1182
  className: "skippr:text-xs skippr:text-destructive",
785
1183
  children: error
786
1184
  })
@@ -789,52 +1187,52 @@ function QuickActions({ onStartSession, isStarting, error }) {
789
1187
  }
790
1188
 
791
1189
  // src/components/SessionAgenda.tsx
792
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1190
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
793
1191
  function SessionAgenda({ phases, questions = [] }) {
794
1192
  if (phases.length === 0) {
795
- return /* @__PURE__ */ jsxs7("div", {
1193
+ return /* @__PURE__ */ jsxs8("div", {
796
1194
  className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
797
1195
  children: [
798
- /* @__PURE__ */ jsx10("h3", {
1196
+ /* @__PURE__ */ jsx11("h3", {
799
1197
  className: "skippr:text-sm skippr:font-semibold",
800
1198
  children: "Agenda"
801
1199
  }),
802
- /* @__PURE__ */ jsx10("p", {
1200
+ /* @__PURE__ */ jsx11("p", {
803
1201
  className: "skippr:text-xs skippr:text-muted-foreground",
804
1202
  children: "Waiting for session..."
805
1203
  })
806
1204
  ]
807
1205
  });
808
1206
  }
809
- return /* @__PURE__ */ jsxs7("div", {
1207
+ return /* @__PURE__ */ jsxs8("div", {
810
1208
  className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
811
1209
  children: [
812
- /* @__PURE__ */ jsx10("h3", {
1210
+ /* @__PURE__ */ jsx11("h3", {
813
1211
  className: "skippr:text-sm skippr:font-semibold",
814
1212
  children: "Agenda"
815
1213
  }),
816
- /* @__PURE__ */ jsx10("ul", {
1214
+ /* @__PURE__ */ jsx11("ul", {
817
1215
  className: "skippr:flex skippr:flex-col skippr:gap-2",
818
1216
  children: phases.map((phase) => {
819
1217
  const phaseQuestions = questions.filter((q) => q.phaseName === phase.name);
820
1218
  const answeredCount = phaseQuestions.filter((q) => q.status === "answered").length;
821
1219
  const totalCount = phaseQuestions.length;
822
- return /* @__PURE__ */ jsxs7("li", {
1220
+ return /* @__PURE__ */ jsxs8("li", {
823
1221
  className: "skippr:flex skippr:flex-col skippr:gap-0.5",
824
1222
  children: [
825
- /* @__PURE__ */ jsxs7("div", {
1223
+ /* @__PURE__ */ jsxs8("div", {
826
1224
  className: "skippr:flex skippr:items-center skippr:gap-2 skippr:text-sm",
827
1225
  children: [
828
- /* @__PURE__ */ jsx10(PhaseIcon, {
1226
+ /* @__PURE__ */ jsx11(PhaseIcon, {
829
1227
  status: phase.status
830
1228
  }),
831
- /* @__PURE__ */ jsx10("span", {
1229
+ /* @__PURE__ */ jsx11("span", {
832
1230
  className: cn(phase.status === "completed" && "skippr:text-muted-foreground skippr:line-through", phase.status === "active" && "skippr:font-medium skippr:text-primary"),
833
1231
  children: phase.name
834
1232
  })
835
1233
  ]
836
1234
  }),
837
- totalCount > 0 && /* @__PURE__ */ jsxs7("span", {
1235
+ totalCount > 0 && /* @__PURE__ */ jsxs8("span", {
838
1236
  className: "skippr:ml-6 skippr:text-xs skippr:text-muted-foreground",
839
1237
  children: [
840
1238
  answeredCount,
@@ -852,62 +1250,78 @@ function SessionAgenda({ phases, questions = [] }) {
852
1250
  }
853
1251
  function PhaseIcon({ status }) {
854
1252
  if (status === "completed") {
855
- return /* @__PURE__ */ jsx10("div", {
1253
+ return /* @__PURE__ */ jsx11("div", {
856
1254
  className: "skippr:flex skippr:size-4 skippr:shrink-0 skippr:items-center skippr:justify-center skippr:rounded-full skippr:bg-primary",
857
- children: /* @__PURE__ */ jsx10(Check, {
1255
+ children: /* @__PURE__ */ jsx11(Check, {
858
1256
  className: "skippr:size-2.5 skippr:text-primary-foreground",
859
1257
  strokeWidth: 3
860
1258
  })
861
1259
  });
862
1260
  }
863
1261
  if (status === "active") {
864
- return /* @__PURE__ */ jsx10(LoaderCircle, {
1262
+ return /* @__PURE__ */ jsx11(LoaderCircle, {
865
1263
  className: "skippr:size-4 skippr:shrink-0 skippr:text-primary skippr:animate-spin"
866
1264
  });
867
1265
  }
868
- return /* @__PURE__ */ jsx10(Circle, {
1266
+ return /* @__PURE__ */ jsx11(Circle, {
869
1267
  className: "skippr:size-4 skippr:shrink-0 skippr:text-muted-foreground"
870
1268
  });
871
1269
  }
872
1270
 
873
1271
  // src/components/Sidebar.tsx
874
- import { jsx as jsx11, jsxs as jsxs8, Fragment } from "react/jsx-runtime";
1272
+ import { jsx as jsx12, jsxs as jsxs9, Fragment } from "react/jsx-runtime";
875
1273
  function Sidebar() {
876
- const { isConnected, isStarting, error, startSession, disconnect, isPanelOpen, closePanel } = useLiveAgent();
877
- useEffect4(() => {
1274
+ const {
1275
+ isConnected,
1276
+ isStarting,
1277
+ error,
1278
+ startSession,
1279
+ disconnect,
1280
+ isPanelOpen,
1281
+ closePanel,
1282
+ isAuthenticated,
1283
+ isValidating,
1284
+ authError,
1285
+ requestOtp,
1286
+ verifyOtp,
1287
+ isAuthSubmitting
1288
+ } = useLiveAgent();
1289
+ useEffect6(() => {
878
1290
  document.body.style.transition = "margin-right 300ms ease-in-out";
879
1291
  document.body.style.marginRight = isPanelOpen ? `${SIDEBAR_WIDTH}px` : "0px";
880
1292
  }, [isPanelOpen]);
881
- useEffect4(() => {
1293
+ useEffect6(() => {
882
1294
  return () => {
883
1295
  document.body.style.marginRight = "";
884
1296
  document.body.style.transition = "";
885
1297
  };
886
1298
  }, []);
887
- return /* @__PURE__ */ jsx11("div", {
1299
+ return /* @__PURE__ */ jsxs9("div", {
888
1300
  className: cn("skippr:fixed skippr:top-0 skippr:right-0 skippr:h-full skippr:z-[9999]", "skippr:bg-background skippr:border-l skippr:border-border", "skippr:flex skippr:flex-col", "skippr:transition-all skippr:duration-300 skippr:ease-in-out skippr:overflow-hidden", !isPanelOpen && "skippr:w-0 skippr:border-l-0"),
889
1301
  style: { width: isPanelOpen ? SIDEBAR_WIDTH : undefined },
890
- children: isConnected ? /* @__PURE__ */ jsxs8(Fragment, {
891
- children: [
892
- /* @__PURE__ */ jsx11(ChatHeader, {
893
- onClose: closePanel
894
- }),
895
- /* @__PURE__ */ jsx11(ConnectedContent, {
896
- onDisconnect: disconnect
897
- })
898
- ]
899
- }) : /* @__PURE__ */ jsxs8(Fragment, {
900
- children: [
901
- /* @__PURE__ */ jsx11(ChatHeader, {
902
- onClose: closePanel
903
- }),
904
- /* @__PURE__ */ jsx11(QuickActions, {
905
- onStartSession: startSession,
906
- isStarting,
907
- error
1302
+ children: [
1303
+ /* @__PURE__ */ jsx12(ChatHeader, {
1304
+ onClose: closePanel
1305
+ }),
1306
+ isConnected ? /* @__PURE__ */ jsx12(ConnectedContent, {
1307
+ onDisconnect: disconnect
1308
+ }) : isValidating ? /* @__PURE__ */ jsx12("div", {
1309
+ className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
1310
+ children: /* @__PURE__ */ jsx12("p", {
1311
+ className: "skippr:text-sm skippr:text-muted-foreground",
1312
+ children: "Loading..."
908
1313
  })
909
- ]
910
- })
1314
+ }) : isAuthenticated ? /* @__PURE__ */ jsx12(QuickActions, {
1315
+ onStartSession: startSession,
1316
+ isStarting,
1317
+ error
1318
+ }) : /* @__PURE__ */ jsx12(LoginFlow, {
1319
+ requestOtp,
1320
+ verifyOtp,
1321
+ error: authError,
1322
+ isSubmitting: isAuthSubmitting
1323
+ })
1324
+ ]
911
1325
  });
912
1326
  }
913
1327
  function ConnectedContent({ onDisconnect }) {
@@ -917,33 +1331,33 @@ function ConnectedContent({ onDisconnect }) {
917
1331
  const { phases } = usePhaseUpdates();
918
1332
  const { questions } = useQuestionUpdates();
919
1333
  if (!isConnected) {
920
- return /* @__PURE__ */ jsx11("div", {
1334
+ return /* @__PURE__ */ jsx12("div", {
921
1335
  className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
922
- children: /* @__PURE__ */ jsx11("p", {
1336
+ children: /* @__PURE__ */ jsx12("p", {
923
1337
  className: "skippr:text-sm skippr:text-muted-foreground",
924
1338
  children: "Connecting..."
925
1339
  })
926
1340
  });
927
1341
  }
928
1342
  const isAgentSpeaking = agentState === "speaking";
929
- return /* @__PURE__ */ jsxs8(Fragment, {
1343
+ return /* @__PURE__ */ jsxs9(Fragment, {
930
1344
  children: [
931
- /* @__PURE__ */ jsx11(MeetingControls, {
1345
+ /* @__PURE__ */ jsx12(MeetingControls, {
932
1346
  onHangUp: onDisconnect
933
1347
  }),
934
- /* @__PURE__ */ jsxs8("div", {
1348
+ /* @__PURE__ */ jsxs9("div", {
935
1349
  className: "skippr:flex skippr:min-h-0 skippr:flex-1",
936
1350
  children: [
937
- /* @__PURE__ */ jsx11("div", {
1351
+ /* @__PURE__ */ jsx12("div", {
938
1352
  className: "skippr:w-[260px] skippr:shrink-0 skippr:overflow-y-auto skippr:border-r",
939
- children: /* @__PURE__ */ jsx11(SessionAgenda, {
1353
+ children: /* @__PURE__ */ jsx12(SessionAgenda, {
940
1354
  phases,
941
1355
  questions
942
1356
  })
943
1357
  }),
944
- /* @__PURE__ */ jsx11("div", {
1358
+ /* @__PURE__ */ jsx12("div", {
945
1359
  className: "skippr:flex skippr:min-w-0 skippr:flex-1 skippr:flex-col",
946
- children: /* @__PURE__ */ jsx11(MessageList, {
1360
+ children: /* @__PURE__ */ jsx12(MessageList, {
947
1361
  messages: allMessages,
948
1362
  isStreaming: isAgentSpeaking,
949
1363
  sendChatMessage,
@@ -957,44 +1371,49 @@ function ConnectedContent({ onDisconnect }) {
957
1371
  }
958
1372
 
959
1373
  // src/components/SidebarTrigger.tsx
960
- import { jsx as jsx12 } from "react/jsx-runtime";
1374
+ import { jsx as jsx13 } from "react/jsx-runtime";
961
1375
  var TRIGGER_GAP = 16;
962
1376
  var TRIGGER_DEFAULT_RIGHT = 24;
963
1377
  function SidebarTrigger() {
964
1378
  const { isPanelOpen, togglePanel } = useLiveAgent();
965
- return /* @__PURE__ */ jsx12(Button, {
1379
+ return /* @__PURE__ */ jsx13(Button, {
966
1380
  size: "icon-lg",
967
1381
  onClick: togglePanel,
968
1382
  className: "skippr:fixed skippr:bottom-6 skippr:z-[9998] skippr:size-14 skippr:rounded-full skippr:shadow-lg skippr:transition-all skippr:duration-300",
969
1383
  style: { right: isPanelOpen ? SIDEBAR_WIDTH + TRIGGER_GAP : TRIGGER_DEFAULT_RIGHT },
970
1384
  title: isPanelOpen ? "Close chat" : "Chat with us",
971
- children: isPanelOpen ? /* @__PURE__ */ jsx12(X, {
1385
+ children: isPanelOpen ? /* @__PURE__ */ jsx13(X, {
972
1386
  className: "skippr:size-6"
973
- }) : /* @__PURE__ */ jsx12(MessageCircle, {
1387
+ }) : /* @__PURE__ */ jsx13(MessageCircle, {
974
1388
  className: "skippr:size-6"
975
1389
  })
976
1390
  });
977
1391
  }
978
1392
 
979
1393
  // src/components/LiveAgent.tsx
980
- import { jsx as jsx13, jsxs as jsxs9, Fragment as Fragment2 } from "react/jsx-runtime";
1394
+ import { jsx as jsx14, jsxs as jsxs10, Fragment as Fragment2 } from "react/jsx-runtime";
981
1395
  function LiveAgent({
982
1396
  agentId,
983
- authToken,
1397
+ authToken: authTokenProp,
984
1398
  appKey,
1399
+ userToken,
985
1400
  defaultOpen = false,
986
1401
  children
987
1402
  }) {
1403
+ const auth = useAuth({ appKey });
1404
+ const effectiveAuthToken = authTokenProp || auth.authToken || undefined;
988
1405
  const { connection, shouldConnect, isStarting, error, startSession, disconnect } = useSession({
989
1406
  agentId,
990
- authToken,
991
- appKey
1407
+ authToken: effectiveAuthToken,
1408
+ appKey,
1409
+ userToken
992
1410
  });
993
- const [isPanelOpen, setIsPanelOpen] = useState5(defaultOpen);
994
- const openPanel = useCallback5(() => setIsPanelOpen(true), []);
995
- const closePanel = useCallback5(() => setIsPanelOpen(false), []);
996
- const togglePanel = useCallback5(() => setIsPanelOpen((prev) => !prev), []);
1411
+ const [isPanelOpen, setIsPanelOpen] = useState7(defaultOpen);
1412
+ const openPanel = useCallback7(() => setIsPanelOpen(true), []);
1413
+ const closePanel = useCallback7(() => setIsPanelOpen(false), []);
1414
+ const togglePanel = useCallback7(() => setIsPanelOpen((prev) => !prev), []);
997
1415
  const isConnected = connection !== null;
1416
+ const isAuthenticated = !!userToken || !!authTokenProp || auth.isAuthenticated;
998
1417
  const ctx = useMemo4(() => ({
999
1418
  connection,
1000
1419
  shouldConnect,
@@ -1006,7 +1425,14 @@ function LiveAgent({
1006
1425
  isPanelOpen,
1007
1426
  openPanel,
1008
1427
  closePanel,
1009
- togglePanel
1428
+ togglePanel,
1429
+ isAuthenticated,
1430
+ isValidating: auth.isValidating,
1431
+ authError: auth.error,
1432
+ requestOtp: auth.requestOtp,
1433
+ verifyOtp: auth.verifyOtp,
1434
+ logoutAuth: auth.logout,
1435
+ isAuthSubmitting: auth.isSubmitting
1010
1436
  }), [
1011
1437
  connection,
1012
1438
  shouldConnect,
@@ -1018,19 +1444,26 @@ function LiveAgent({
1018
1444
  isPanelOpen,
1019
1445
  openPanel,
1020
1446
  closePanel,
1021
- togglePanel
1447
+ togglePanel,
1448
+ isAuthenticated,
1449
+ auth.isValidating,
1450
+ auth.error,
1451
+ auth.requestOtp,
1452
+ auth.verifyOtp,
1453
+ auth.logout,
1454
+ auth.isSubmitting
1022
1455
  ]);
1023
- const widgetContent = /* @__PURE__ */ jsxs9(Fragment2, {
1456
+ const widgetContent = /* @__PURE__ */ jsxs10(Fragment2, {
1024
1457
  children: [
1025
- connection && /* @__PURE__ */ jsx13(RoomAudioRenderer, {}),
1026
- /* @__PURE__ */ jsx13(SidebarTrigger, {}),
1027
- /* @__PURE__ */ jsx13(Sidebar, {}),
1458
+ connection && /* @__PURE__ */ jsx14(RoomAudioRenderer, {}),
1459
+ /* @__PURE__ */ jsx14(SidebarTrigger, {}),
1460
+ /* @__PURE__ */ jsx14(Sidebar, {}),
1028
1461
  children
1029
1462
  ]
1030
1463
  });
1031
- return /* @__PURE__ */ jsx13(LiveAgentContext.Provider, {
1464
+ return /* @__PURE__ */ jsx14(LiveAgentContext.Provider, {
1032
1465
  value: ctx,
1033
- children: connection ? /* @__PURE__ */ jsx13(LiveKitRoom, {
1466
+ children: connection ? /* @__PURE__ */ jsx14(LiveKitRoom, {
1034
1467
  serverUrl: connection.livekitUrl,
1035
1468
  token: connection.token,
1036
1469
  connect: shouldConnect,