@skippr/live-agent-sdk 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,709 +1,3 @@
1
- // src/components/LiveAgent.tsx
2
- import { LiveKitRoom, RoomAudioRenderer } from "@livekit/components-react";
3
- import { useCallback as useCallback6, useMemo as useMemo2, useState as useState4 } from "react";
4
-
5
- // src/context/LiveAgentContext.tsx
6
- import { createContext } from "react";
7
- var LiveAgentContext = createContext(null);
8
-
9
- // src/hooks/useSession.ts
10
- import { useCallback, useState } from "react";
11
- var API_URL = "https://skipprapi-production.up.railway.app";
12
- function useSession({ organizationId, agentId }) {
13
- const [connection, setConnection] = useState(null);
14
- const [shouldConnect, setShouldConnect] = useState(false);
15
- const [isStarting, setIsStarting] = useState(false);
16
- const [error, setError] = useState("");
17
- const [sessionId, setSessionId] = useState(null);
18
- const startSession = useCallback(async () => {
19
- setIsStarting(true);
20
- setError("");
21
- try {
22
- const createResp = await fetch(`${API_URL}/v1/sessions`, {
23
- method: "POST",
24
- headers: { "Content-Type": "application/json" },
25
- body: JSON.stringify({ organizationId, agentId })
26
- });
27
- if (!createResp.ok) {
28
- throw new Error(`Failed to create session: ${createResp.status}`);
29
- }
30
- const { session } = await createResp.json();
31
- const startResp = await fetch(`${API_URL}/v1/sessions/${session.id}/start`, {
32
- method: "POST"
33
- });
34
- if (!startResp.ok) {
35
- throw new Error(`Failed to start session: ${startResp.status}`);
36
- }
37
- const { connection: conn } = await startResp.json();
38
- setSessionId(session.id);
39
- setConnection({
40
- livekitUrl: conn.livekitUrl,
41
- token: conn.token
42
- });
43
- setShouldConnect(true);
44
- } catch (e) {
45
- setError(e instanceof Error ? e.message : "Failed to start session");
46
- } finally {
47
- setIsStarting(false);
48
- }
49
- }, [organizationId, agentId]);
50
- const disconnect = useCallback(async () => {
51
- if (sessionId) {
52
- try {
53
- await fetch(`${API_URL}/v1/sessions/${sessionId}/complete`, {
54
- method: "POST",
55
- headers: { "Content-Type": "application/json" },
56
- body: JSON.stringify({})
57
- });
58
- } catch {}
59
- }
60
- setError("");
61
- setShouldConnect(false);
62
- setConnection(null);
63
- setSessionId(null);
64
- }, [sessionId]);
65
- return { connection, shouldConnect, isStarting, error, startSession, disconnect };
66
- }
67
-
68
- // src/components/Sidebar.tsx
69
- import { useConnectionState } from "@livekit/components-react";
70
- import { ConnectionState } from "livekit-client";
71
- import { useEffect as useEffect3 } from "react";
72
-
73
- // src/hooks/useLiveAgent.ts
74
- import { use } from "react";
75
- function useLiveAgent() {
76
- const ctx = use(LiveAgentContext);
77
- if (!ctx) {
78
- throw new Error("useLiveAgent must be used within a <LiveAgent> provider");
79
- }
80
- const { connection, shouldConnect, ...publicValue } = ctx;
81
- return publicValue;
82
- }
83
-
84
- // src/hooks/usePhaseUpdates.ts
85
- import { useCallback as useCallback2 } from "react";
86
-
87
- // src/hooks/useAgentState.ts
88
- import { useRemoteParticipants } from "@livekit/components-react";
89
- import { useEffect, useState as useState2 } from "react";
90
- function useAgentState(attributeKey, parse, initial) {
91
- const [value, setValue] = useState2(initial);
92
- const remoteParticipants = useRemoteParticipants();
93
- useEffect(() => {
94
- const agentParticipant = remoteParticipants.find((p) => p.attributes?.[attributeKey]);
95
- if (agentParticipant) {
96
- const attr = agentParticipant.attributes?.[attributeKey];
97
- if (attr) {
98
- const parsed = parse(attr);
99
- if (parsed)
100
- setValue(parsed);
101
- }
102
- }
103
- const handlers = new Map;
104
- for (const p of remoteParticipants) {
105
- const handler = (changedAttributes) => {
106
- if (changedAttributes[attributeKey]) {
107
- const parsed = parse(changedAttributes[attributeKey]);
108
- if (parsed)
109
- setValue(parsed);
110
- } else if (p.attributes?.[attributeKey]) {
111
- const parsed = parse(p.attributes[attributeKey]);
112
- if (parsed)
113
- setValue(parsed);
114
- }
115
- };
116
- handlers.set(p, handler);
117
- p.on("attributesChanged", handler);
118
- }
119
- return () => {
120
- for (const [p, handler] of handlers) {
121
- p.off("attributesChanged", handler);
122
- }
123
- };
124
- }, [remoteParticipants, attributeKey, parse]);
125
- return value;
126
- }
127
-
128
- // src/hooks/usePhaseUpdates.ts
129
- function parsePhases(json) {
130
- try {
131
- const data = JSON.parse(json);
132
- if (data.type === "phase_update" && Array.isArray(data.phases)) {
133
- return data.phases;
134
- }
135
- } catch {}
136
- return null;
137
- }
138
- function usePhaseUpdates() {
139
- const parse = useCallback2(parsePhases, []);
140
- const phases = useAgentState("phases", parse, []);
141
- return { phases };
142
- }
143
-
144
- // src/hooks/useQuestionUpdates.ts
145
- import { useCallback as useCallback3 } from "react";
146
- function parseQuestions(json) {
147
- try {
148
- const data = JSON.parse(json);
149
- if (data.type === "question_update" && Array.isArray(data.questions)) {
150
- return data.questions;
151
- }
152
- } catch {}
153
- return null;
154
- }
155
- function useQuestionUpdates() {
156
- const parse = useCallback3(parseQuestions, []);
157
- const questions = useAgentState("questions", parse, []);
158
- return { questions };
159
- }
160
-
161
- // src/hooks/useTranscriptMessages.ts
162
- import {
163
- useLocalParticipant,
164
- useTrackTranscription,
165
- useVoiceAssistant
166
- } from "@livekit/components-react";
167
- import { Track } from "livekit-client";
168
- import { useMemo } from "react";
169
- function segmentsToMessages(segments, role) {
170
- return segments.filter((s) => s.final && s.text.trim().length > 0).map((s) => ({
171
- id: s.id,
172
- role,
173
- content: s.text
174
- }));
175
- }
176
- function useTranscriptMessages() {
177
- const { agentTranscriptions, state } = useVoiceAssistant();
178
- const { localParticipant } = useLocalParticipant();
179
- const localMicTrack = useMemo(() => localParticipant.getTrackPublication(Track.Source.Microphone) ? {
180
- participant: localParticipant,
181
- source: Track.Source.Microphone,
182
- publication: localParticipant.getTrackPublication(Track.Source.Microphone)
183
- } : undefined, [localParticipant]);
184
- const { segments: userSegments } = useTrackTranscription(localMicTrack);
185
- const messages = useMemo(() => {
186
- const agent = segmentsToMessages(agentTranscriptions, "assistant");
187
- const user = segmentsToMessages(userSegments, "user");
188
- const timeMap = new Map;
189
- for (const s of agentTranscriptions)
190
- timeMap.set(s.id, s.firstReceivedTime);
191
- for (const s of userSegments)
192
- timeMap.set(s.id, s.firstReceivedTime);
193
- return [...agent, ...user].sort((a, b) => (timeMap.get(a.id) ?? 0) - (timeMap.get(b.id) ?? 0));
194
- }, [agentTranscriptions, userSegments]);
195
- return { messages, agentState: state };
196
- }
197
-
198
- // src/lib/constants.ts
199
- var SIDEBAR_WIDTH = 600;
200
-
201
- // src/lib/utils.ts
202
- import { clsx } from "clsx";
203
- import { twMerge } from "tailwind-merge";
204
- function cn(...inputs) {
205
- return twMerge(clsx(inputs));
206
- }
207
-
208
- // src/components/ChatHeader.tsx
209
- import { Bot, X } from "lucide-react";
210
-
211
- // src/components/ui/button.tsx
212
- import { forwardRef } from "react";
213
- import { jsxDEV } from "react/jsx-dev-runtime";
214
- var variantClasses = {
215
- default: "skippr:bg-primary skippr:text-primary-foreground skippr:hover:bg-primary/90",
216
- destructive: "skippr:bg-destructive skippr:text-white skippr:hover:bg-destructive/90",
217
- outline: "skippr:border skippr:border-input skippr:bg-background skippr:shadow-xs skippr:hover:bg-accent skippr:hover:text-accent-foreground",
218
- secondary: "skippr:bg-secondary skippr:text-secondary-foreground skippr:hover:bg-secondary/80",
219
- ghost: "skippr:hover:bg-accent skippr:hover:text-accent-foreground"
220
- };
221
- var sizeClasses = {
222
- default: "skippr:h-9 skippr:px-4 skippr:py-2",
223
- xs: "skippr:h-6 skippr:gap-1 skippr:rounded-md skippr:px-2 skippr:text-xs",
224
- sm: "skippr:h-8 skippr:gap-1.5 skippr:rounded-md skippr:px-3",
225
- lg: "skippr:h-10 skippr:rounded-md skippr:px-6",
226
- icon: "skippr:size-9",
227
- "icon-xs": "skippr:size-6",
228
- "icon-sm": "skippr:size-8",
229
- "icon-lg": "skippr:size-10"
230
- };
231
- var Button = forwardRef(({ className, variant = "default", size = "default", ...props }, ref) => {
232
- return /* @__PURE__ */ jsxDEV("button", {
233
- className: cn("skippr:inline-flex skippr:items-center skippr:justify-center skippr:gap-2 skippr:whitespace-nowrap skippr:rounded-md skippr:text-sm skippr:font-medium skippr:ring-offset-background skippr:transition-all skippr:focus-visible:outline-none skippr:focus-visible:ring-2 skippr:focus-visible:ring-ring skippr:focus-visible:ring-offset-2 skippr:disabled:pointer-events-none skippr:disabled:opacity-50 skippr:shrink-0 skippr:[&_svg]:pointer-events-none skippr:[&_svg:not([class*='size-'])]:size-4 skippr:[&_svg]:shrink-0", variantClasses[variant], sizeClasses[size], className),
234
- ref,
235
- ...props
236
- }, undefined, false, undefined, this);
237
- });
238
- Button.displayName = "Button";
239
-
240
- // src/components/ChatHeader.tsx
241
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
242
- function ChatHeader({ onClose }) {
243
- return /* @__PURE__ */ jsxDEV2("div", {
244
- className: "skippr:flex skippr:items-center skippr:gap-3 skippr:bg-primary skippr:px-4 skippr:py-3 skippr:text-primary-foreground",
245
- children: [
246
- /* @__PURE__ */ jsxDEV2("div", {
247
- className: "skippr:flex skippr:size-6 skippr:items-center skippr:justify-center skippr:rounded-full skippr:bg-primary-foreground/20",
248
- children: /* @__PURE__ */ jsxDEV2(Bot, {
249
- className: "skippr:size-3.5 skippr:text-primary-foreground"
250
- }, undefined, false, undefined, this)
251
- }, undefined, false, undefined, this),
252
- /* @__PURE__ */ jsxDEV2("div", {
253
- className: "skippr:flex-1",
254
- children: /* @__PURE__ */ jsxDEV2("p", {
255
- className: "skippr:text-sm skippr:font-semibold skippr:leading-none",
256
- children: "AI Agent"
257
- }, undefined, false, undefined, this)
258
- }, undefined, false, undefined, this),
259
- /* @__PURE__ */ jsxDEV2(Button, {
260
- variant: "ghost",
261
- size: "icon-xs",
262
- onClick: onClose,
263
- className: "skippr:text-primary-foreground skippr:hover:bg-primary-foreground/20 skippr:hover:text-primary-foreground",
264
- children: /* @__PURE__ */ jsxDEV2(X, {
265
- className: "skippr:size-4"
266
- }, undefined, false, undefined, this)
267
- }, undefined, false, undefined, this)
268
- ]
269
- }, undefined, true, undefined, this);
270
- }
271
-
272
- // src/components/MeetingControls.tsx
273
- import { useLocalParticipant as useLocalParticipant2 } from "@livekit/components-react";
274
- import { ScreenSharePresets } from "livekit-client";
275
- import { Mic, MicOff, Monitor, MonitorOff, PhoneOff } from "lucide-react";
276
- import { useCallback as useCallback4, useEffect as useEffect2, useState as useState3 } from "react";
277
-
278
- // src/lib/format.ts
279
- function formatTime(seconds) {
280
- const m = Math.floor(seconds / 60).toString().padStart(2, "0");
281
- const s = (seconds % 60).toString().padStart(2, "0");
282
- return `${m}:${s}`;
283
- }
284
-
285
- // src/components/MeetingControls.tsx
286
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
287
- function MeetingControls({ onHangUp }) {
288
- const { localParticipant } = useLocalParticipant2();
289
- const isMuted = !localParticipant.isMicrophoneEnabled;
290
- const isScreenSharing = localParticipant.isScreenShareEnabled;
291
- const [elapsed, setElapsed] = useState3(0);
292
- useEffect2(() => {
293
- const id = setInterval(() => setElapsed((t) => t + 1), 1000);
294
- return () => clearInterval(id);
295
- }, []);
296
- const toggleMute = useCallback4(async () => {
297
- try {
298
- await localParticipant.setMicrophoneEnabled(isMuted);
299
- } catch (e) {
300
- console.error("Failed to toggle microphone:", e);
301
- }
302
- }, [localParticipant, isMuted]);
303
- const toggleScreenShare = useCallback4(async () => {
304
- try {
305
- await localParticipant.setScreenShareEnabled(!isScreenSharing, {
306
- video: { displaySurface: "browser" },
307
- resolution: ScreenSharePresets.h1080fps30.resolution,
308
- contentHint: "detail"
309
- });
310
- } catch (e) {
311
- console.error("Failed to toggle screen share:", e);
312
- }
313
- }, [localParticipant, isScreenSharing]);
314
- return /* @__PURE__ */ jsxDEV3("div", {
315
- className: "skippr:flex skippr:items-center skippr:justify-between skippr:border-b skippr:px-4 skippr:py-3",
316
- children: [
317
- /* @__PURE__ */ jsxDEV3("div", {
318
- className: "skippr:flex skippr:items-center skippr:gap-2",
319
- children: [
320
- /* @__PURE__ */ jsxDEV3(Button, {
321
- size: "icon-sm",
322
- variant: isMuted ? "destructive" : "outline",
323
- onClick: toggleMute,
324
- "aria-label": isMuted ? "Unmute" : "Mute",
325
- children: isMuted ? /* @__PURE__ */ jsxDEV3(MicOff, {
326
- className: "skippr:size-4"
327
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3(Mic, {
328
- className: "skippr:size-4"
329
- }, undefined, false, undefined, this)
330
- }, undefined, false, undefined, this),
331
- /* @__PURE__ */ jsxDEV3(Button, {
332
- size: "icon-sm",
333
- variant: isScreenSharing ? "default" : "outline",
334
- onClick: toggleScreenShare,
335
- "aria-label": isScreenSharing ? "Stop sharing" : "Share screen",
336
- children: isScreenSharing ? /* @__PURE__ */ jsxDEV3(MonitorOff, {
337
- className: "skippr:size-4"
338
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3(Monitor, {
339
- className: "skippr:size-4"
340
- }, undefined, false, undefined, this)
341
- }, undefined, false, undefined, this)
342
- ]
343
- }, undefined, true, undefined, this),
344
- /* @__PURE__ */ jsxDEV3("span", {
345
- className: "skippr:text-sm skippr:font-medium skippr:tabular-nums skippr:text-muted-foreground",
346
- children: formatTime(elapsed)
347
- }, undefined, false, undefined, this),
348
- /* @__PURE__ */ jsxDEV3(Button, {
349
- size: "icon-sm",
350
- variant: "destructive",
351
- onClick: onHangUp,
352
- "aria-label": "Hang up",
353
- children: /* @__PURE__ */ jsxDEV3(PhoneOff, {
354
- className: "skippr:size-4"
355
- }, undefined, false, undefined, this)
356
- }, undefined, false, undefined, this)
357
- ]
358
- }, undefined, true, undefined, this);
359
- }
360
-
361
- // src/components/MessageList.tsx
362
- import { useCallback as useCallback5 } from "react";
363
-
364
- // src/components/ChatMessage.tsx
365
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
366
- function ChatMessage({ message }) {
367
- const isUser = message.role === "user";
368
- return /* @__PURE__ */ jsxDEV4("div", {
369
- className: cn("skippr:flex skippr:w-full skippr:px-4 skippr:py-1", isUser ? "skippr:justify-end" : "skippr:justify-start"),
370
- children: /* @__PURE__ */ jsxDEV4("div", {
371
- 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"),
372
- children: message.content
373
- }, undefined, false, undefined, this)
374
- }, undefined, false, undefined, this);
375
- }
376
-
377
- // src/components/TypingIndicator.tsx
378
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
379
- function TypingIndicator() {
380
- return /* @__PURE__ */ jsxDEV5("div", {
381
- className: "skippr:flex skippr:items-center skippr:gap-1 skippr:px-4 skippr:py-3",
382
- children: /* @__PURE__ */ jsxDEV5("div", {
383
- 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",
384
- children: [
385
- /* @__PURE__ */ jsxDEV5("span", {
386
- className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:0ms]"
387
- }, undefined, false, undefined, this),
388
- /* @__PURE__ */ jsxDEV5("span", {
389
- className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:150ms]"
390
- }, undefined, false, undefined, this),
391
- /* @__PURE__ */ jsxDEV5("span", {
392
- className: "skippr:size-1.5 skippr:animate-bounce skippr:rounded-full skippr:bg-muted-foreground/60 skippr:[animation-delay:300ms]"
393
- }, undefined, false, undefined, this)
394
- ]
395
- }, undefined, true, undefined, this)
396
- }, undefined, false, undefined, this);
397
- }
398
-
399
- // src/components/MessageList.tsx
400
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
401
- function MessageList({ messages, isStreaming }) {
402
- const scrollRef = useCallback5((node) => {
403
- node?.scrollIntoView({ behavior: "smooth" });
404
- }, []);
405
- const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
406
- const showTyping = isStreaming && lastMessage?.role === "assistant" && lastMessage.content === "";
407
- return /* @__PURE__ */ jsxDEV6("div", {
408
- className: "skippr:flex-1 skippr:overflow-y-auto",
409
- children: /* @__PURE__ */ jsxDEV6("div", {
410
- className: "skippr:flex skippr:flex-col skippr:gap-1 skippr:py-3",
411
- children: [
412
- messages.map((message) => /* @__PURE__ */ jsxDEV6(ChatMessage, {
413
- message
414
- }, message.id, false, undefined, this)),
415
- showTyping && /* @__PURE__ */ jsxDEV6(TypingIndicator, {}, undefined, false, undefined, this),
416
- /* @__PURE__ */ jsxDEV6("div", {
417
- ref: scrollRef
418
- }, `scroll-${messages.length}`, false, undefined, this)
419
- ]
420
- }, undefined, true, undefined, this)
421
- }, undefined, false, undefined, this);
422
- }
423
-
424
- // src/components/QuickActions.tsx
425
- import { Loader2, MessageCircleQuestion } from "lucide-react";
426
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
427
- function QuickActions({ onStartSession, isStarting, error }) {
428
- return /* @__PURE__ */ jsxDEV7("div", {
429
- 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",
430
- children: [
431
- /* @__PURE__ */ jsxDEV7("p", {
432
- className: "skippr:mb-1 skippr:text-sm skippr:text-muted-foreground",
433
- children: "How can I help you today?"
434
- }, undefined, false, undefined, this),
435
- /* @__PURE__ */ jsxDEV7(Button, {
436
- variant: "outline",
437
- className: "skippr:h-auto skippr:flex-col skippr:gap-1.5 skippr:whitespace-normal skippr:py-3 skippr:text-xs",
438
- onClick: onStartSession,
439
- disabled: isStarting,
440
- children: [
441
- isStarting ? /* @__PURE__ */ jsxDEV7(Loader2, {
442
- className: "skippr:size-4 skippr:animate-spin skippr:text-primary"
443
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV7(MessageCircleQuestion, {
444
- className: "skippr:size-4 skippr:text-primary"
445
- }, undefined, false, undefined, this),
446
- isStarting ? "Starting..." : "Start Session"
447
- ]
448
- }, undefined, true, undefined, this),
449
- error && /* @__PURE__ */ jsxDEV7("p", {
450
- className: "skippr:text-xs skippr:text-destructive",
451
- children: error
452
- }, undefined, false, undefined, this)
453
- ]
454
- }, undefined, true, undefined, this);
455
- }
456
-
457
- // src/components/SessionAgenda.tsx
458
- import { Check, Circle, Loader2 as Loader22 } from "lucide-react";
459
- import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
460
- function SessionAgenda({ phases, questions = [] }) {
461
- if (phases.length === 0) {
462
- return /* @__PURE__ */ jsxDEV8("div", {
463
- className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
464
- children: [
465
- /* @__PURE__ */ jsxDEV8("h3", {
466
- className: "skippr:text-sm skippr:font-semibold",
467
- children: "Agenda"
468
- }, undefined, false, undefined, this),
469
- /* @__PURE__ */ jsxDEV8("p", {
470
- className: "skippr:text-xs skippr:text-muted-foreground",
471
- children: "Waiting for session..."
472
- }, undefined, false, undefined, this)
473
- ]
474
- }, undefined, true, undefined, this);
475
- }
476
- return /* @__PURE__ */ jsxDEV8("div", {
477
- className: "skippr:flex skippr:flex-col skippr:gap-3 skippr:p-4",
478
- children: [
479
- /* @__PURE__ */ jsxDEV8("h3", {
480
- className: "skippr:text-sm skippr:font-semibold",
481
- children: "Agenda"
482
- }, undefined, false, undefined, this),
483
- /* @__PURE__ */ jsxDEV8("ul", {
484
- className: "skippr:flex skippr:flex-col skippr:gap-2",
485
- children: phases.map((phase) => {
486
- const phaseQuestions = questions.filter((q) => q.phaseName === phase.name);
487
- const answeredCount = phaseQuestions.filter((q) => q.status === "answered").length;
488
- const totalCount = phaseQuestions.length;
489
- return /* @__PURE__ */ jsxDEV8("li", {
490
- className: "skippr:flex skippr:flex-col skippr:gap-0.5",
491
- children: [
492
- /* @__PURE__ */ jsxDEV8("div", {
493
- className: "skippr:flex skippr:items-center skippr:gap-2 skippr:text-sm",
494
- children: [
495
- /* @__PURE__ */ jsxDEV8(PhaseIcon, {
496
- status: phase.status
497
- }, undefined, false, undefined, this),
498
- /* @__PURE__ */ jsxDEV8("span", {
499
- className: cn(phase.status === "completed" && "skippr:text-muted-foreground skippr:line-through", phase.status === "active" && "skippr:font-medium skippr:text-primary"),
500
- children: phase.name
501
- }, undefined, false, undefined, this)
502
- ]
503
- }, undefined, true, undefined, this),
504
- totalCount > 0 && /* @__PURE__ */ jsxDEV8("span", {
505
- className: "skippr:ml-6 skippr:text-xs skippr:text-muted-foreground",
506
- children: [
507
- answeredCount,
508
- "/",
509
- totalCount,
510
- " answered"
511
- ]
512
- }, undefined, true, undefined, this)
513
- ]
514
- }, phase.name, true, undefined, this);
515
- })
516
- }, undefined, false, undefined, this)
517
- ]
518
- }, undefined, true, undefined, this);
519
- }
520
- function PhaseIcon({ status }) {
521
- if (status === "completed") {
522
- return /* @__PURE__ */ jsxDEV8("div", {
523
- className: "skippr:flex skippr:size-4 skippr:shrink-0 skippr:items-center skippr:justify-center skippr:rounded-full skippr:bg-primary",
524
- children: /* @__PURE__ */ jsxDEV8(Check, {
525
- className: "skippr:size-2.5 skippr:text-primary-foreground",
526
- strokeWidth: 3
527
- }, undefined, false, undefined, this)
528
- }, undefined, false, undefined, this);
529
- }
530
- if (status === "active") {
531
- return /* @__PURE__ */ jsxDEV8(Loader22, {
532
- className: "skippr:size-4 skippr:shrink-0 skippr:text-primary skippr:animate-spin"
533
- }, undefined, false, undefined, this);
534
- }
535
- return /* @__PURE__ */ jsxDEV8(Circle, {
536
- className: "skippr:size-4 skippr:shrink-0 skippr:text-muted-foreground"
537
- }, undefined, false, undefined, this);
538
- }
539
-
540
- // src/components/Sidebar.tsx
541
- import { jsxDEV as jsxDEV9, Fragment } from "react/jsx-dev-runtime";
542
- function Sidebar() {
543
- const { isConnected, isStarting, error, startSession, disconnect, isPanelOpen, closePanel } = useLiveAgent();
544
- useEffect3(() => {
545
- document.body.style.transition = "margin-right 300ms ease-in-out";
546
- document.body.style.marginRight = isPanelOpen ? `${SIDEBAR_WIDTH}px` : "0px";
547
- }, [isPanelOpen]);
548
- useEffect3(() => {
549
- return () => {
550
- document.body.style.marginRight = "";
551
- document.body.style.transition = "";
552
- };
553
- }, []);
554
- return /* @__PURE__ */ jsxDEV9("div", {
555
- 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"),
556
- style: { width: isPanelOpen ? SIDEBAR_WIDTH : undefined },
557
- children: isConnected ? /* @__PURE__ */ jsxDEV9(Fragment, {
558
- children: [
559
- /* @__PURE__ */ jsxDEV9(ChatHeader, {
560
- onClose: closePanel
561
- }, undefined, false, undefined, this),
562
- /* @__PURE__ */ jsxDEV9(ConnectedContent, {
563
- onDisconnect: disconnect
564
- }, undefined, false, undefined, this)
565
- ]
566
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV9(Fragment, {
567
- children: [
568
- /* @__PURE__ */ jsxDEV9(ChatHeader, {
569
- onClose: closePanel
570
- }, undefined, false, undefined, this),
571
- /* @__PURE__ */ jsxDEV9(QuickActions, {
572
- onStartSession: startSession,
573
- isStarting,
574
- error
575
- }, undefined, false, undefined, this)
576
- ]
577
- }, undefined, true, undefined, this)
578
- }, undefined, false, undefined, this);
579
- }
580
- function ConnectedContent({ onDisconnect }) {
581
- const connectionState = useConnectionState();
582
- const isConnected = connectionState === ConnectionState.Connected;
583
- const { messages, agentState } = useTranscriptMessages();
584
- const { phases } = usePhaseUpdates();
585
- const { questions } = useQuestionUpdates();
586
- if (!isConnected) {
587
- return /* @__PURE__ */ jsxDEV9("div", {
588
- className: "skippr:flex skippr:flex-1 skippr:items-center skippr:justify-center",
589
- children: /* @__PURE__ */ jsxDEV9("p", {
590
- className: "skippr:text-sm skippr:text-muted-foreground",
591
- children: "Connecting..."
592
- }, undefined, false, undefined, this)
593
- }, undefined, false, undefined, this);
594
- }
595
- const isAgentSpeaking = agentState === "speaking";
596
- return /* @__PURE__ */ jsxDEV9(Fragment, {
597
- children: [
598
- /* @__PURE__ */ jsxDEV9(MeetingControls, {
599
- onHangUp: onDisconnect
600
- }, undefined, false, undefined, this),
601
- /* @__PURE__ */ jsxDEV9("div", {
602
- className: "skippr:flex skippr:min-h-0 skippr:flex-1",
603
- children: [
604
- /* @__PURE__ */ jsxDEV9("div", {
605
- className: "skippr:w-[260px] skippr:shrink-0 skippr:overflow-y-auto skippr:border-r",
606
- children: /* @__PURE__ */ jsxDEV9(SessionAgenda, {
607
- phases,
608
- questions
609
- }, undefined, false, undefined, this)
610
- }, undefined, false, undefined, this),
611
- /* @__PURE__ */ jsxDEV9("div", {
612
- className: "skippr:flex skippr:min-w-0 skippr:flex-1 skippr:flex-col",
613
- children: /* @__PURE__ */ jsxDEV9(MessageList, {
614
- messages,
615
- isStreaming: isAgentSpeaking
616
- }, undefined, false, undefined, this)
617
- }, undefined, false, undefined, this)
618
- ]
619
- }, undefined, true, undefined, this)
620
- ]
621
- }, undefined, true, undefined, this);
622
- }
623
-
624
- // src/components/SidebarTrigger.tsx
625
- import { MessageCircle, X as X2 } from "lucide-react";
626
- import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
627
- var TRIGGER_GAP = 16;
628
- var TRIGGER_DEFAULT_RIGHT = 24;
629
- function SidebarTrigger() {
630
- const { isPanelOpen, togglePanel } = useLiveAgent();
631
- return /* @__PURE__ */ jsxDEV10(Button, {
632
- size: "icon-lg",
633
- onClick: togglePanel,
634
- className: "skippr:fixed skippr:bottom-6 skippr:z-[9998] skippr:size-14 skippr:rounded-full skippr:shadow-lg skippr:transition-all skippr:duration-300",
635
- style: { right: isPanelOpen ? SIDEBAR_WIDTH + TRIGGER_GAP : TRIGGER_DEFAULT_RIGHT },
636
- title: isPanelOpen ? "Close chat" : "Chat with us",
637
- children: isPanelOpen ? /* @__PURE__ */ jsxDEV10(X2, {
638
- className: "skippr:size-6"
639
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV10(MessageCircle, {
640
- className: "skippr:size-6"
641
- }, undefined, false, undefined, this)
642
- }, undefined, false, undefined, this);
643
- }
644
-
645
- // src/components/LiveAgent.tsx
646
- import { jsxDEV as jsxDEV11, Fragment as Fragment2 } from "react/jsx-dev-runtime";
647
- function LiveAgent({
648
- organizationId,
649
- agentId,
650
- defaultOpen = false,
651
- children
652
- }) {
653
- const { connection, shouldConnect, isStarting, error, startSession, disconnect } = useSession({
654
- organizationId,
655
- agentId
656
- });
657
- const [isPanelOpen, setIsPanelOpen] = useState4(defaultOpen);
658
- const openPanel = useCallback6(() => setIsPanelOpen(true), []);
659
- const closePanel = useCallback6(() => setIsPanelOpen(false), []);
660
- const togglePanel = useCallback6(() => setIsPanelOpen((prev) => !prev), []);
661
- const isConnected = connection !== null;
662
- const ctx = useMemo2(() => ({
663
- connection,
664
- shouldConnect,
665
- isConnected,
666
- isStarting,
667
- error,
668
- startSession,
669
- disconnect,
670
- isPanelOpen,
671
- openPanel,
672
- closePanel,
673
- togglePanel
674
- }), [
675
- connection,
676
- shouldConnect,
677
- isConnected,
678
- isStarting,
679
- error,
680
- startSession,
681
- disconnect,
682
- isPanelOpen,
683
- openPanel,
684
- closePanel,
685
- togglePanel
686
- ]);
687
- const widgetContent = /* @__PURE__ */ jsxDEV11(Fragment2, {
688
- children: [
689
- connection && /* @__PURE__ */ jsxDEV11(RoomAudioRenderer, {}, undefined, false, undefined, this),
690
- /* @__PURE__ */ jsxDEV11(SidebarTrigger, {}, undefined, false, undefined, this),
691
- /* @__PURE__ */ jsxDEV11(Sidebar, {}, undefined, false, undefined, this),
692
- children
693
- ]
694
- }, undefined, true, undefined, this);
695
- return /* @__PURE__ */ jsxDEV11(LiveAgentContext.Provider, {
696
- value: ctx,
697
- children: connection ? /* @__PURE__ */ jsxDEV11(LiveKitRoom, {
698
- serverUrl: connection.livekitUrl,
699
- token: connection.token,
700
- connect: shouldConnect,
701
- audio: true,
702
- onDisconnected: disconnect,
703
- children: widgetContent
704
- }, undefined, false, undefined, this) : widgetContent
705
- }, undefined, false, undefined, this);
706
- }
707
1
  export {
708
2
  useLiveAgent,
709
3
  LiveAgent