@bnagaraju7/chat-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/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/cdn/garuda-chat-widget.iife.global.js +166 -0
- package/dist/index.cjs +1336 -0
- package/dist/index.d.mts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.mjs +1316 -0
- package/package.json +55 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1316 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/GarudaChatWidget.tsx
|
|
4
|
+
import { useState as useState4, useRef as useRef3, useEffect as useEffect3, useMemo } from "react";
|
|
5
|
+
import {
|
|
6
|
+
X,
|
|
7
|
+
Send,
|
|
8
|
+
ChevronRight,
|
|
9
|
+
FileText,
|
|
10
|
+
Bot,
|
|
11
|
+
Star,
|
|
12
|
+
RefreshCcw,
|
|
13
|
+
Plus,
|
|
14
|
+
ShieldCheck,
|
|
15
|
+
CheckCircle2,
|
|
16
|
+
Mic,
|
|
17
|
+
MicOff,
|
|
18
|
+
CircleStop,
|
|
19
|
+
Phone,
|
|
20
|
+
PhoneOff
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
23
|
+
import ReactMarkdown from "react-markdown";
|
|
24
|
+
import remarkGfm from "remark-gfm";
|
|
25
|
+
|
|
26
|
+
// src/services/api-client.ts
|
|
27
|
+
function createApiClient(config) {
|
|
28
|
+
const baseUrl = config.apiBaseUrl.replace(/\/$/, "");
|
|
29
|
+
return async function apiClient(endpoint, options = {}) {
|
|
30
|
+
const { body, headers, skipAuth = false, quiet = false, ...rest } = options;
|
|
31
|
+
const requestHeaders = {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
...headers
|
|
34
|
+
};
|
|
35
|
+
if (config.authToken && !skipAuth) {
|
|
36
|
+
requestHeaders["Authorization"] = `Bearer ${config.authToken}`;
|
|
37
|
+
}
|
|
38
|
+
if (config.organisationId) {
|
|
39
|
+
requestHeaders["organisation_id"] = config.organisationId;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
43
|
+
...rest,
|
|
44
|
+
headers: requestHeaders,
|
|
45
|
+
body: body ? JSON.stringify(body) : void 0
|
|
46
|
+
});
|
|
47
|
+
const data = await response.json().catch(() => ({}));
|
|
48
|
+
if (response.status === 401) {
|
|
49
|
+
if (config.authToken && !skipAuth) {
|
|
50
|
+
config.onUnauthorized?.();
|
|
51
|
+
}
|
|
52
|
+
throw new Error(data.detail || "Unauthorized");
|
|
53
|
+
}
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
if (!quiet) {
|
|
56
|
+
console.error("[GarudaChat] Backend error:", data);
|
|
57
|
+
}
|
|
58
|
+
let message = "Something went wrong";
|
|
59
|
+
if (typeof data.detail === "string") {
|
|
60
|
+
message = data.detail;
|
|
61
|
+
} else if (Array.isArray(data.detail)) {
|
|
62
|
+
message = data.detail.map((e) => e.msg).join(", ");
|
|
63
|
+
} else if (data.message) {
|
|
64
|
+
message = data.message;
|
|
65
|
+
}
|
|
66
|
+
throw new Error(message);
|
|
67
|
+
}
|
|
68
|
+
return data;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (!quiet) {
|
|
71
|
+
console.error("[GarudaChat] API error:", error);
|
|
72
|
+
}
|
|
73
|
+
throw new Error(error.message || "Failed to connect to server");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/services/chat.service.ts
|
|
79
|
+
var CHAT_ENDPOINT = "/api/v1/agents/chat";
|
|
80
|
+
function createChatService(apiClient) {
|
|
81
|
+
return {
|
|
82
|
+
sendMessage: (payload, skipAuth = false) => apiClient(CHAT_ENDPOINT, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
body: payload,
|
|
85
|
+
skipAuth
|
|
86
|
+
})
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/services/voice.service.ts
|
|
91
|
+
var VOICE_TOKEN_ENDPOINT = "/api/v1/call/getToken";
|
|
92
|
+
var CALL_SUBMIT_OTP_ENDPOINT = "/api/v1/call/submit-otp";
|
|
93
|
+
function createVoiceService(config) {
|
|
94
|
+
const baseUrl = config.apiBaseUrl.replace(/\/$/, "");
|
|
95
|
+
return {
|
|
96
|
+
getTo: () => config.supportPhone ?? "",
|
|
97
|
+
getToken: async () => {
|
|
98
|
+
const resp = await fetch(`${baseUrl}${VOICE_TOKEN_ENDPOINT}`, { method: "POST" });
|
|
99
|
+
if (!resp.ok) {
|
|
100
|
+
throw new Error("Failed to fetch voice token");
|
|
101
|
+
}
|
|
102
|
+
const data = await resp.json();
|
|
103
|
+
return data?.token || data?.accessToken || data?.access_token;
|
|
104
|
+
},
|
|
105
|
+
submitOtp: async (code, opts = {}) => {
|
|
106
|
+
const body = new URLSearchParams({ code });
|
|
107
|
+
if (opts.callSid) body.set("call_sid", opts.callSid);
|
|
108
|
+
if (opts.clientRef) body.set("client_ref", opts.clientRef);
|
|
109
|
+
const resp = await fetch(`${baseUrl}${CALL_SUBMIT_OTP_ENDPOINT}`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
112
|
+
body
|
|
113
|
+
});
|
|
114
|
+
return resp.json();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/hooks/useVoiceInput.ts
|
|
120
|
+
import { useRef, useState } from "react";
|
|
121
|
+
function resolveSpeechRecognitionLocale() {
|
|
122
|
+
const browserLang = navigator.language || navigator.languages?.[0] || "en-US";
|
|
123
|
+
return browserLang;
|
|
124
|
+
}
|
|
125
|
+
function useVoiceInput(onResult) {
|
|
126
|
+
const recognitionRef = useRef(null);
|
|
127
|
+
const [isListening, setIsListening] = useState(false);
|
|
128
|
+
const start = () => {
|
|
129
|
+
if (recognitionRef.current) return;
|
|
130
|
+
const SpeechRecognitionImpl = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
131
|
+
if (!SpeechRecognitionImpl) {
|
|
132
|
+
console.error("[GarudaChat] SpeechRecognition is not supported in this browser.");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const recognition = new SpeechRecognitionImpl();
|
|
136
|
+
const speechLocale = resolveSpeechRecognitionLocale();
|
|
137
|
+
recognition.lang = speechLocale;
|
|
138
|
+
recognition.continuous = true;
|
|
139
|
+
recognition.interimResults = true;
|
|
140
|
+
recognition.onstart = () => {
|
|
141
|
+
setIsListening(true);
|
|
142
|
+
};
|
|
143
|
+
recognition.onresult = (event) => {
|
|
144
|
+
let interimTranscript = "";
|
|
145
|
+
let finalTranscript = "";
|
|
146
|
+
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
147
|
+
const result = event.results[i];
|
|
148
|
+
const transcript = result[0].transcript;
|
|
149
|
+
if (result.isFinal) {
|
|
150
|
+
finalTranscript += transcript;
|
|
151
|
+
} else {
|
|
152
|
+
interimTranscript += transcript;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const text = finalTranscript + interimTranscript;
|
|
156
|
+
if (text.trim()) {
|
|
157
|
+
onResult(text);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
recognition.onerror = (event) => {
|
|
161
|
+
console.error("[GarudaChat] SpeechRecognition error:", event.error);
|
|
162
|
+
setIsListening(false);
|
|
163
|
+
};
|
|
164
|
+
recognition.onend = () => {
|
|
165
|
+
setIsListening(false);
|
|
166
|
+
recognitionRef.current = null;
|
|
167
|
+
};
|
|
168
|
+
recognitionRef.current = recognition;
|
|
169
|
+
try {
|
|
170
|
+
recognition.start();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error("[GarudaChat] Failed to start SpeechRecognition:", error);
|
|
173
|
+
recognitionRef.current = null;
|
|
174
|
+
setIsListening(false);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const stop = () => {
|
|
178
|
+
if (recognitionRef.current) {
|
|
179
|
+
try {
|
|
180
|
+
recognitionRef.current.stop();
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
recognitionRef.current = null;
|
|
184
|
+
}
|
|
185
|
+
setIsListening(false);
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
start,
|
|
189
|
+
stop,
|
|
190
|
+
isListening
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/hooks/useVoiceCall.ts
|
|
195
|
+
import { useState as useState2, useRef as useRef2, useCallback, useEffect } from "react";
|
|
196
|
+
import { Device, Call } from "@twilio/voice-sdk";
|
|
197
|
+
var { Opus, PCMU } = Call.Codec;
|
|
198
|
+
function useVoiceCall(voiceService) {
|
|
199
|
+
const [voiceCall, setVoiceCall] = useState2({
|
|
200
|
+
isActive: false,
|
|
201
|
+
duration: 0,
|
|
202
|
+
status: "idle",
|
|
203
|
+
clientRef: "",
|
|
204
|
+
callSid: "",
|
|
205
|
+
isMuted: false
|
|
206
|
+
});
|
|
207
|
+
const timerRef = useRef2(null);
|
|
208
|
+
const deviceRef = useRef2(null);
|
|
209
|
+
const activeCallRef = useRef2(null);
|
|
210
|
+
const activeRef = useRef2(false);
|
|
211
|
+
const updateStatus = useCallback((status) => {
|
|
212
|
+
setVoiceCall((prev) => ({ ...prev, status }));
|
|
213
|
+
}, []);
|
|
214
|
+
const updateDuration = useCallback(() => {
|
|
215
|
+
setVoiceCall((prev) => ({ ...prev, duration: prev.duration + 1 }));
|
|
216
|
+
}, []);
|
|
217
|
+
const resetState = useCallback(() => {
|
|
218
|
+
setVoiceCall({
|
|
219
|
+
isActive: false,
|
|
220
|
+
duration: 0,
|
|
221
|
+
status: "idle",
|
|
222
|
+
clientRef: "",
|
|
223
|
+
callSid: "",
|
|
224
|
+
isMuted: false
|
|
225
|
+
});
|
|
226
|
+
}, []);
|
|
227
|
+
const clearTimer = useCallback(() => {
|
|
228
|
+
if (timerRef.current) {
|
|
229
|
+
clearInterval(timerRef.current);
|
|
230
|
+
timerRef.current = null;
|
|
231
|
+
}
|
|
232
|
+
}, []);
|
|
233
|
+
const endVoiceCall = useCallback(async () => {
|
|
234
|
+
activeRef.current = false;
|
|
235
|
+
clearTimer();
|
|
236
|
+
try {
|
|
237
|
+
activeCallRef.current?.disconnect();
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
activeCallRef.current = null;
|
|
241
|
+
resetState();
|
|
242
|
+
}, [clearTimer, resetState]);
|
|
243
|
+
const wireCall = useCallback(
|
|
244
|
+
(call) => {
|
|
245
|
+
activeCallRef.current = call;
|
|
246
|
+
call.on("accept", () => {
|
|
247
|
+
if (!activeRef.current) return;
|
|
248
|
+
const callSid = call.parameters && call.parameters.CallSid || "";
|
|
249
|
+
updateStatus("in-call");
|
|
250
|
+
setVoiceCall((prev) => ({ ...prev, callSid }));
|
|
251
|
+
clearTimer();
|
|
252
|
+
timerRef.current = setInterval(updateDuration, 1e3);
|
|
253
|
+
});
|
|
254
|
+
call.on("disconnect", () => {
|
|
255
|
+
if (!activeRef.current) return;
|
|
256
|
+
activeRef.current = false;
|
|
257
|
+
clearTimer();
|
|
258
|
+
activeCallRef.current = null;
|
|
259
|
+
resetState();
|
|
260
|
+
});
|
|
261
|
+
call.on("error", (error) => {
|
|
262
|
+
console.error("[GarudaChat] Call error:", error);
|
|
263
|
+
endVoiceCall();
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
[updateDuration, updateStatus, clearTimer, resetState, endVoiceCall]
|
|
267
|
+
);
|
|
268
|
+
const handleIncoming = useCallback(
|
|
269
|
+
(call) => {
|
|
270
|
+
activeRef.current = true;
|
|
271
|
+
const callSid = call.parameters && call.parameters.CallSid || "";
|
|
272
|
+
clearTimer();
|
|
273
|
+
setVoiceCall({
|
|
274
|
+
isActive: true,
|
|
275
|
+
duration: 0,
|
|
276
|
+
status: "in-call",
|
|
277
|
+
clientRef: "",
|
|
278
|
+
callSid,
|
|
279
|
+
isMuted: false
|
|
280
|
+
});
|
|
281
|
+
call.accept();
|
|
282
|
+
wireCall(call);
|
|
283
|
+
},
|
|
284
|
+
[clearTimer, wireCall]
|
|
285
|
+
);
|
|
286
|
+
const handleIncomingRef = useRef2(handleIncoming);
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
handleIncomingRef.current = handleIncoming;
|
|
289
|
+
}, [handleIncoming]);
|
|
290
|
+
const initDevice = useCallback(async () => {
|
|
291
|
+
if (deviceRef.current) return deviceRef.current;
|
|
292
|
+
try {
|
|
293
|
+
const token = await voiceService.getToken();
|
|
294
|
+
if (!token) return null;
|
|
295
|
+
const device = new Device(token, {
|
|
296
|
+
logLevel: 1,
|
|
297
|
+
codecPreferences: [Opus, PCMU]
|
|
298
|
+
});
|
|
299
|
+
deviceRef.current = device;
|
|
300
|
+
device.on("error", (error) => {
|
|
301
|
+
console.error("[GarudaChat] Device error:", error);
|
|
302
|
+
});
|
|
303
|
+
device.on("incoming", (call) => {
|
|
304
|
+
handleIncomingRef.current(call);
|
|
305
|
+
});
|
|
306
|
+
await device.register();
|
|
307
|
+
return device;
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error("[GarudaChat] Device init failed:", err);
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}, [voiceService]);
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
initDevice();
|
|
315
|
+
return () => {
|
|
316
|
+
try {
|
|
317
|
+
deviceRef.current?.disconnectAll();
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
deviceRef.current?.destroy();
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
deviceRef.current = null;
|
|
325
|
+
activeCallRef.current = null;
|
|
326
|
+
if (timerRef.current) {
|
|
327
|
+
clearInterval(timerRef.current);
|
|
328
|
+
timerRef.current = null;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}, [initDevice]);
|
|
332
|
+
const startVoiceCall = useCallback(async () => {
|
|
333
|
+
if (activeRef.current) return;
|
|
334
|
+
const device = await initDevice();
|
|
335
|
+
if (!device) {
|
|
336
|
+
console.error("[GarudaChat] Device not available");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const clientRef = "ref_" + Date.now() + "_" + Math.random().toString(36).slice(2, 10);
|
|
340
|
+
activeRef.current = true;
|
|
341
|
+
setVoiceCall({
|
|
342
|
+
isActive: true,
|
|
343
|
+
duration: 0,
|
|
344
|
+
status: "connecting",
|
|
345
|
+
clientRef,
|
|
346
|
+
callSid: "",
|
|
347
|
+
isMuted: false
|
|
348
|
+
});
|
|
349
|
+
try {
|
|
350
|
+
const call = await device.connect({
|
|
351
|
+
params: {
|
|
352
|
+
To: voiceService.getTo(),
|
|
353
|
+
clientRef
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
wireCall(call);
|
|
357
|
+
} catch (err) {
|
|
358
|
+
console.error("[GarudaChat] Failed to start call:", err);
|
|
359
|
+
activeRef.current = false;
|
|
360
|
+
resetState();
|
|
361
|
+
}
|
|
362
|
+
}, [initDevice, wireCall, resetState, voiceService]);
|
|
363
|
+
const toggleMute = useCallback(() => {
|
|
364
|
+
const call = activeCallRef.current;
|
|
365
|
+
if (!call) return;
|
|
366
|
+
setVoiceCall((prev) => {
|
|
367
|
+
const nextMuted = !prev.isMuted;
|
|
368
|
+
try {
|
|
369
|
+
call.mute(nextMuted);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error("[GarudaChat] Toggle mute failed:", err);
|
|
372
|
+
}
|
|
373
|
+
return { ...prev, isMuted: nextMuted };
|
|
374
|
+
});
|
|
375
|
+
}, []);
|
|
376
|
+
return {
|
|
377
|
+
voiceCall,
|
|
378
|
+
startVoiceCall,
|
|
379
|
+
endVoiceCall,
|
|
380
|
+
toggleMute
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/utils/cleanSanityMarkdown.ts
|
|
385
|
+
function cleanSanityMarkdown(rawMarkdown) {
|
|
386
|
+
let text = rawMarkdown;
|
|
387
|
+
text = text.replace(/<\/?table[^>]*>/gi, "\n").replace(/<\/?thead[^>]*>/gi, "").replace(/<\/?tbody[^>]*>/gi, "").replace(/<\/?tr[^>]*>/gi, "\n").replace(/<\/?t[dh][^>]*>/gi, " ").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/ /gi, " ");
|
|
388
|
+
text = text.split("\n").filter((line) => !/^\|[\s\-:|]+\|$/.test(line.trim())).join("\n");
|
|
389
|
+
text = text.split("\n").map((line) => {
|
|
390
|
+
if (line.trim().startsWith("|") && line.trim().endsWith("|")) {
|
|
391
|
+
return line.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim()).filter(Boolean).join(" - ");
|
|
392
|
+
}
|
|
393
|
+
return line;
|
|
394
|
+
}).join("\n");
|
|
395
|
+
const lines = text.split("\n");
|
|
396
|
+
const cleanedLines = [];
|
|
397
|
+
let bufferList = [];
|
|
398
|
+
lines.forEach((line) => {
|
|
399
|
+
const trimmed = line.trim();
|
|
400
|
+
if (!trimmed) return;
|
|
401
|
+
const isHeading = /^\*\*.*:\*\*/.test(trimmed);
|
|
402
|
+
const isListItem = /^(\-|\*|\+)?\s*(\d+\.|\w\.)?\s/.test(trimmed);
|
|
403
|
+
if (isHeading && isListItem) {
|
|
404
|
+
if (bufferList.length > 0) {
|
|
405
|
+
cleanedLines.push(...bufferList);
|
|
406
|
+
bufferList = [];
|
|
407
|
+
}
|
|
408
|
+
cleanedLines.push(trimmed.replace(/^[-*+]\s*/, ""));
|
|
409
|
+
} else if (isHeading) {
|
|
410
|
+
if (bufferList.length > 0) {
|
|
411
|
+
cleanedLines.push(...bufferList);
|
|
412
|
+
bufferList = [];
|
|
413
|
+
}
|
|
414
|
+
cleanedLines.push(trimmed);
|
|
415
|
+
} else if (isListItem) {
|
|
416
|
+
bufferList.push(trimmed.replace(/^([-*+]?\s*\d*\.*\s)/, ""));
|
|
417
|
+
} else {
|
|
418
|
+
if (bufferList.length > 0) {
|
|
419
|
+
cleanedLines.push(...bufferList);
|
|
420
|
+
bufferList = [];
|
|
421
|
+
}
|
|
422
|
+
cleanedLines.push(trimmed);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
if (bufferList.length > 0) cleanedLines.push(...bufferList);
|
|
426
|
+
return cleanedLines.join("\n");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/components/ShadowHost.tsx
|
|
430
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
431
|
+
import { createPortal } from "react-dom";
|
|
432
|
+
|
|
433
|
+
// src/styles/widget-css.generated.ts
|
|
434
|
+
var WIDGET_CSS = `*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-6{bottom:1.5rem}.bottom-full{bottom:100%}.left-0{left:0}.left-3{left:.75rem}.right-0{right:0}.right-14{right:3.5rem}.right-2{right:.5rem}.right-3{right:.75rem}.right-6{right:1.5rem}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-1{margin-left:.25rem}.mr-1{margin-right:.25rem}.mt-0{margin-top:0}.mt-0\\.5{margin-top:.125rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-1\\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-\\[750px\\]{height:750px}.h-full{height:100%}.max-h-\\[90vh\\]{max-height:90vh}.min-h-\\[12px\\]{min-height:12px}.min-h-\\[28px\\]{min-height:28px}.w-1{width:.25rem}.w-1\\.5{width:.375rem}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-64{width:16rem}.w-auto{width:auto}.w-full{width:100%}.max-w-\\[240px\\]{max-width:240px}.max-w-\\[500px\\]{max-width:500px}.max-w-\\[85\\%\\]{max-width:85%}.max-w-\\[90\\%\\]{max-width:90%}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}@keyframes ping{75%,to{transform:scale(2);opacity:0}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\\.5{gap:.625rem}.gap-3{gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-tl-sm{border-top-left-radius:.125rem}.rounded-tr-sm{border-top-right-radius:.125rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-blue-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-foreground{--tw-border-opacity:1;border-color:rgb(248 250 252/var(--tw-border-opacity,1))}.border-foreground\\/10{border-color:rgba(248,250,252,.1)}.border-green-500{--tw-border-opacity:1;border-color:rgb(34 197 94/var(--tw-border-opacity,1))}.border-green-500\\/20{border-color:rgba(34,197,94,.2)}.border-primary{--tw-border-opacity:1;border-color:rgb(0 240 255/var(--tw-border-opacity,1))}.border-primary\\/20{border-color:rgba(0,240,255,.2)}.border-primary\\/30{border-color:rgba(0,240,255,.3)}.border-primary\\/40{border-color:rgba(0,240,255,.4)}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity,1))}.border-red-500{--tw-border-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity,1))}.border-red-500\\/20{border-color:rgba(239,68,68,.2)}.border-red-500\\/30{border-color:rgba(239,68,68,.3)}.border-transparent{border-color:transparent}.border-white{--tw-border-opacity:1;border-color:rgb(255 255 255/var(--tw-border-opacity,1))}.border-white\\/10{border-color:hsla(0,0%,100%,.1)}.border-white\\/15{border-color:hsla(0,0%,100%,.15)}.border-white\\/20{border-color:hsla(0,0%,100%,.2)}.border-white\\/5{border-color:hsla(0,0%,100%,.05)}.border-yellow-500{--tw-border-opacity:1;border-color:rgb(234 179 8/var(--tw-border-opacity,1))}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity,1))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-foreground{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.bg-green-400{--tw-bg-opacity:1;background-color:rgb(74 222 128/var(--tw-bg-opacity,1))}.bg-green-500{--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity,1))}.bg-green-500\\/10{background-color:rgba(34,197,94,.1)}.bg-navy-700{--tw-bg-opacity:1;background-color:rgb(17 29 54/var(--tw-bg-opacity,1))}.bg-navy-800{--tw-bg-opacity:1;background-color:rgb(10 19 36/var(--tw-bg-opacity,1))}.bg-navy-800\\/50{background-color:rgba(10,19,36,.5)}.bg-navy-800\\/80{background-color:rgba(10,19,36,.8)}.bg-navy-800\\/90{background-color:rgba(10,19,36,.9)}.bg-navy-900{--tw-bg-opacity:1;background-color:rgb(5 11 20/var(--tw-bg-opacity,1))}.bg-navy-900\\/50{background-color:rgba(5,11,20,.5)}.bg-navy-900\\/95{background-color:rgba(5,11,20,.95)}.bg-primary{--tw-bg-opacity:1;background-color:rgb(0 240 255/var(--tw-bg-opacity,1))}.bg-primary\\/10{background-color:rgba(0,240,255,.1)}.bg-red-400{--tw-bg-opacity:1;background-color:rgb(248 113 113/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-red-500\\/10{background-color:rgba(239,68,68,.1)}.bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-400{--tw-bg-opacity:1;background-color:rgb(250 204 21/var(--tw-bg-opacity,1))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgb(234 179 8/var(--tw-bg-opacity,1))}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-gradient-to-tr{background-image:linear-gradient(to top right,var(--tw-gradient-stops))}.from-navy-900{--tw-gradient-from:#050b14 var(--tw-gradient-from-position);--tw-gradient-to:rgba(5,11,20,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-navy-900\\/50{--tw-gradient-from:rgba(5,11,20,.5) var(--tw-gradient-from-position);--tw-gradient-to:rgba(5,11,20,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-primary{--tw-gradient-from:#00f0ff var(--tw-gradient-from-position);--tw-gradient-to:rgba(0,240,255,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-primary\\/10{--tw-gradient-from:rgba(0,240,255,.1) var(--tw-gradient-from-position);--tw-gradient-to:rgba(0,240,255,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-primary\\/30{--tw-gradient-from:rgba(0,240,255,.3) var(--tw-gradient-from-position);--tw-gradient-to:rgba(0,240,255,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-cyan-500{--tw-gradient-to:#06b6d4 var(--tw-gradient-to-position)}.to-cyan-500\\/10{--tw-gradient-to:rgba(6,182,212,.1) var(--tw-gradient-to-position)}.to-navy-900{--tw-gradient-to:#050b14 var(--tw-gradient-to-position)}.to-teal-400{--tw-gradient-to:#2dd4bf var(--tw-gradient-to-position)}.to-teal-500{--tw-gradient-to:#14b8a6 var(--tw-gradient-to-position)}.to-teal-500\\/30{--tw-gradient-to:rgba(20,184,166,.3) var(--tw-gradient-to-position)}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-top:0;padding-bottom:0}.py-0\\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-0{padding-bottom:0}.pb-0\\.5{padding-bottom:.125rem}.pl-5{padding-left:1.25rem}.pr-24{padding-right:6rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\\[10px\\]{font-size:10px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.tracking-\\[0\\.35em\\]{letter-spacing:.35em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-foreground{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity,1))}.text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-green-500{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity,1))}.text-navy-900{--tw-text-opacity:1;color:rgb(5 11 20/var(--tw-text-opacity,1))}.text-primary{--tw-text-opacity:1;color:rgb(0 240 255/var(--tw-text-opacity,1))}.text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(107 114 128/var(--tw-placeholder-opacity,1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity:1;color:rgb(107 114 128/var(--tw-placeholder-opacity,1))}.opacity-0{opacity:0}.opacity-60{opacity:.6}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-2xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-\\[0_0_15px_rgba\\(0\\2c 0\\2c 0\\2c 0\\.5\\)\\]{--tw-shadow:0 0 15px rgba(0,0,0,.5);--tw-shadow-colored:0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\\[0_0_15px_rgba\\(32\\2c 201\\2c 151\\2c 0\\.2\\)\\]{--tw-shadow:0 0 15px rgba(32,201,151,.2);--tw-shadow-colored:0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\\[0_0_30px_rgba\\(32\\2c 201\\2c 151\\2c 0\\.8\\)\\]{--tw-shadow:0 0 30px rgba(32,201,151,.8);--tw-shadow-colored:0 0 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\\[0_20px_50px_rgba\\(0\\2c 0\\2c 0\\2c 0\\.5\\)\\]{--tw-shadow:0 20px 50px rgba(0,0,0,.5);--tw-shadow-colored:0 20px 50px var(--tw-shadow-color)}.shadow-\\[0_20px_50px_rgba\\(0\\2c 0\\2c 0\\2c 0\\.5\\)\\],.shadow-inner{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 rgba(0,0,0,.05);--tw-shadow-colored:inset 0 2px 4px 0 var(--tw-shadow-color)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-primary{--tw-shadow-color:#00f0ff;--tw-shadow:var(--tw-shadow-colored)}.shadow-primary\\/10{--tw-shadow-color:rgba(0,240,255,.1);--tw-shadow:var(--tw-shadow-colored)}.shadow-red-500{--tw-shadow-color:#ef4444;--tw-shadow:var(--tw-shadow-colored)}.shadow-red-500\\/30{--tw-shadow-color:rgba(239,68,68,.3);--tw-shadow:var(--tw-shadow-colored)}.ring-primary{--tw-ring-opacity:1;--tw-ring-color:rgb(0 240 255/var(--tw-ring-opacity,1))}.drop-shadow-\\[0_0_5px_rgba\\(250\\2c 204\\2c 21\\2c 0\\.5\\)\\]{--tw-drop-shadow:drop-shadow(0 0 5px rgba(250,204,21,.5))}.drop-shadow-\\[0_0_5px_rgba\\(250\\2c 204\\2c 21\\2c 0\\.5\\)\\],.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-2xl{--tw-backdrop-blur:blur(40px)}.backdrop-blur-2xl,.backdrop-blur-md{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-md{--tw-backdrop-blur:blur(12px)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-blur-xl{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-xl{--tw-backdrop-blur:blur(24px)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:host{all:initial;display:block;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,sans-serif}*,:after,:before{box-sizing:border-box}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#0a1324}::-webkit-scrollbar-thumb{background:rgba(49,46,129,.5);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:rgba(79,70,229,.8)}.chat-markdown p{margin:.4em 0;line-height:1.6}.chat-markdown p:first-child{margin-top:0}.chat-markdown p:last-child{margin-bottom:0}.chat-markdown ol,.chat-markdown ul{margin:.4em 0;padding-left:1.5em}.chat-markdown li{margin:.2em 0;line-height:1.5}.chat-markdown strong{font-weight:600}.chat-markdown code{background:hsla(0,0%,100%,.1);padding:.15em .4em;border-radius:4px;font-size:.9em}.hover\\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\\:scale-105:hover,.hover\\:scale-110:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1}.hover\\:border-green-500\\/40:hover{border-color:rgba(34,197,94,.4)}.hover\\:border-primary\\/40:hover{border-color:rgba(0,240,255,.4)}.hover\\:border-primary\\/50:hover{border-color:rgba(0,240,255,.5)}.hover\\:border-red-400\\/20:hover{border-color:hsla(0,91%,71%,.2)}.hover\\:border-red-500\\/40:hover{border-color:rgba(239,68,68,.4)}.hover\\:bg-green-400\\/10:hover{background-color:rgba(74,222,128,.1)}.hover\\:bg-green-500\\/20:hover{background-color:rgba(34,197,94,.2)}.hover\\:bg-navy-600:hover{--tw-bg-opacity:1;background-color:rgb(26 44 82/var(--tw-bg-opacity,1))}.hover\\:bg-navy-700:hover{--tw-bg-opacity:1;background-color:rgb(17 29 54/var(--tw-bg-opacity,1))}.hover\\:bg-primary\\/10:hover{background-color:rgba(0,240,255,.1)}.hover\\:bg-primary\\/20:hover{background-color:rgba(0,240,255,.2)}.hover\\:bg-primary\\/90:hover{background-color:rgba(0,240,255,.9)}.hover\\:bg-red-400\\/10:hover{background-color:hsla(0,91%,71%,.1)}.hover\\:bg-red-500\\/20:hover{background-color:rgba(239,68,68,.2)}.hover\\:bg-red-600:hover{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.hover\\:bg-white\\/5:hover{background-color:hsla(0,0%,100%,.05)}.hover\\:bg-yellow-600:hover{--tw-bg-opacity:1;background-color:rgb(202 138 4/var(--tw-bg-opacity,1))}.hover\\:text-green-400:hover{--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity,1))}.hover\\:text-primary\\/80:hover{color:rgba(0,240,255,.8)}.hover\\:text-red-400:hover{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.hover\\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\\:text-yellow-400\\/50:hover{color:rgba(250,204,21,.5)}.hover\\:opacity-80:hover{opacity:.8}.hover\\:opacity-90:hover{opacity:.9}.hover\\:shadow-\\[0_0_10px_rgba\\(32\\2c 201\\2c 151\\2c 0\\.15\\)\\]:hover{--tw-shadow:0 0 10px rgba(32,201,151,.15);--tw-shadow-colored:0 0 10px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\\:shadow-\\[0_0_15px_rgba\\(32\\2c 201\\2c 151\\2c 0\\.1\\)\\]:hover{--tw-shadow:0 0 15px rgba(32,201,151,.1);--tw-shadow-colored:0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\\:border-primary:focus{--tw-border-opacity:1;border-color:rgb(0 240 255/var(--tw-border-opacity,1))}.focus\\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\\:ring-primary:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(0 240 255/var(--tw-ring-opacity,1))}.focus\\:ring-primary\\/50:focus{--tw-ring-color:rgba(0,240,255,.5)}.active\\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\\:translate-x-1{--tw-translate-x:0.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\\:text-primary{--tw-text-opacity:1;color:rgb(0 240 255/var(--tw-text-opacity,1))}.group:hover .group-hover\\:opacity-100{opacity:1}@media (min-width:640px){.sm\\:left-auto{left:auto}.sm\\:right-6{right:1.5rem}.sm\\:w-full{width:100%}}`;
|
|
435
|
+
|
|
436
|
+
// src/components/ShadowHost.tsx
|
|
437
|
+
function ShadowHost({ children }) {
|
|
438
|
+
const [mountNode, setMountNode] = useState3(null);
|
|
439
|
+
useEffect2(() => {
|
|
440
|
+
const host = document.createElement("div");
|
|
441
|
+
document.body.appendChild(host);
|
|
442
|
+
const shadowRoot = host.attachShadow({ mode: "open" });
|
|
443
|
+
const style = document.createElement("style");
|
|
444
|
+
style.textContent = WIDGET_CSS;
|
|
445
|
+
shadowRoot.appendChild(style);
|
|
446
|
+
const mount = document.createElement("div");
|
|
447
|
+
shadowRoot.appendChild(mount);
|
|
448
|
+
setMountNode(mount);
|
|
449
|
+
return () => {
|
|
450
|
+
document.body.removeChild(host);
|
|
451
|
+
};
|
|
452
|
+
}, []);
|
|
453
|
+
if (!mountNode) return null;
|
|
454
|
+
return createPortal(children, mountNode);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/components/GarudaChatWidget.tsx
|
|
458
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
459
|
+
function extractStripeUrl(text) {
|
|
460
|
+
const match = text.match(
|
|
461
|
+
/https?:\/\/checkout\.stripe\.com\/c\/pay\/[a-zA-Z0-9_#%-]+(?:\?[a-zA-Z0-9_&=%-]+)?(?:#[a-zA-Z0-9_%-]+)?/
|
|
462
|
+
);
|
|
463
|
+
return match ? match[0] : null;
|
|
464
|
+
}
|
|
465
|
+
function extractPlanName(text) {
|
|
466
|
+
const match = text.match(/for\s+([A-Za-z\s]+?)(?:\s*:|$|https)/);
|
|
467
|
+
return match ? match[1].trim() : null;
|
|
468
|
+
}
|
|
469
|
+
function extractBilling(text) {
|
|
470
|
+
const lower = text.toLowerCase();
|
|
471
|
+
if (lower.includes("yearly") || lower.includes("/year")) return "yearly";
|
|
472
|
+
if (lower.includes("monthly") || lower.includes("/month")) return "monthly";
|
|
473
|
+
return "";
|
|
474
|
+
}
|
|
475
|
+
function extractAmount(text) {
|
|
476
|
+
const match = text.match(/\$([0-9,]+\.?[0-9]*)/);
|
|
477
|
+
if (!match) return 0;
|
|
478
|
+
return parseFloat(match[1].replace(/,/g, ""));
|
|
479
|
+
}
|
|
480
|
+
function cleanResponse(text) {
|
|
481
|
+
if (!text) return "";
|
|
482
|
+
text = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
483
|
+
text = text.replace(/<think>[\s\S]*/gi, "");
|
|
484
|
+
text = text.trim();
|
|
485
|
+
return cleanSanityMarkdown(text);
|
|
486
|
+
}
|
|
487
|
+
function GarudaChatWidget({ config }) {
|
|
488
|
+
return /* @__PURE__ */ jsx(ShadowHost, { children: /* @__PURE__ */ jsx(ChatWidgetInner, { config }) });
|
|
489
|
+
}
|
|
490
|
+
function ChatWidgetInner({ config }) {
|
|
491
|
+
const user = config.user ?? null;
|
|
492
|
+
const pathname = typeof window !== "undefined" ? window.location.pathname : "";
|
|
493
|
+
const apiClient = useMemo(
|
|
494
|
+
() => createApiClient(config),
|
|
495
|
+
[config.apiBaseUrl, config.organisationId, config.authToken, config.onUnauthorized]
|
|
496
|
+
);
|
|
497
|
+
const chatService = useMemo(() => createChatService(apiClient), [apiClient]);
|
|
498
|
+
const voiceService = useMemo(
|
|
499
|
+
() => createVoiceService(config),
|
|
500
|
+
[config.apiBaseUrl, config.supportPhone]
|
|
501
|
+
);
|
|
502
|
+
const [isOpen, setIsOpen] = useState4(false);
|
|
503
|
+
const { start, stop, isListening } = useVoiceInput((text) => {
|
|
504
|
+
setInput(text);
|
|
505
|
+
});
|
|
506
|
+
const [currentSessionId, setCurrentSessionId] = useState4(null);
|
|
507
|
+
const [input, setInput] = useState4("");
|
|
508
|
+
const [rating, setRating] = useState4(0);
|
|
509
|
+
const [feedback, setFeedback] = useState4("");
|
|
510
|
+
const [feedbackSubmitted, setFeedbackSubmitted] = useState4(false);
|
|
511
|
+
const [isBotTyping, setIsBotTyping] = useState4(false);
|
|
512
|
+
const messagesEndRef = useRef3(null);
|
|
513
|
+
const { voiceCall, startVoiceCall, endVoiceCall, toggleMute } = useVoiceCall(voiceService);
|
|
514
|
+
const [typedCode, setTypedCode] = useState4("");
|
|
515
|
+
const [callStatus, setCallStatus] = useState4("");
|
|
516
|
+
const [showCallPopup, setShowCallPopup] = useState4(false);
|
|
517
|
+
const [callCopied, setCallCopied] = useState4(false);
|
|
518
|
+
const userName = user?.fullname?.trim() || "";
|
|
519
|
+
const [resolved, setResolved] = useState4(null);
|
|
520
|
+
const [isSubmittingRating, setIsSubmittingRating] = useState4(false);
|
|
521
|
+
const initialWelcomeMessage = useMemo(
|
|
522
|
+
() => ({
|
|
523
|
+
id: "msg_init",
|
|
524
|
+
sender: "bot",
|
|
525
|
+
type: "text",
|
|
526
|
+
text: `\u{1F44B} Welcome ${userName} to GarudaVPN Support.
|
|
527
|
+
|
|
528
|
+
I'm your AI Support Assistant. I can help with:
|
|
529
|
+
\u2022 Subscription Plans
|
|
530
|
+
\u2022 Billing Questions
|
|
531
|
+
\u2022 Refund Requests
|
|
532
|
+
\u2022 VPN Connection & Network Issues
|
|
533
|
+
\u2022 Device Management
|
|
534
|
+
|
|
535
|
+
How can I help you today?`
|
|
536
|
+
}),
|
|
537
|
+
[userName]
|
|
538
|
+
);
|
|
539
|
+
const [messages, setMessages] = useState4([initialWelcomeMessage]);
|
|
540
|
+
useEffect3(() => {
|
|
541
|
+
setMessages((prev) => {
|
|
542
|
+
const hasInitialMessage = prev.some((message) => message.id === "msg_init");
|
|
543
|
+
if (!hasInitialMessage) return prev;
|
|
544
|
+
return prev.map((message) => message.id === "msg_init" ? initialWelcomeMessage : message);
|
|
545
|
+
});
|
|
546
|
+
}, [initialWelcomeMessage]);
|
|
547
|
+
useEffect3(() => {
|
|
548
|
+
if (messages.length === 1 && messages[0].id === "msg_init" && !messages.some((m) => m.type === "suggestions")) {
|
|
549
|
+
let suggestions = [];
|
|
550
|
+
if (pathname.includes("/billing")) {
|
|
551
|
+
suggestions = ["Show invoices", "Payment history", "Next billing date", "Refund request"];
|
|
552
|
+
} else if (pathname.includes("/devices")) {
|
|
553
|
+
suggestions = ["Remove device", "Device limit", "Login issue"];
|
|
554
|
+
} else if (pathname.includes("/subscriptions")) {
|
|
555
|
+
suggestions = ["Upgrade plan", "Compare plans", "Renew subscription", "Cancel subscription"];
|
|
556
|
+
} else if (!user) {
|
|
557
|
+
suggestions = ["Why is my VPN not connecting?", "Compare plans", "Login issue", "Install guide"];
|
|
558
|
+
} else {
|
|
559
|
+
suggestions = [
|
|
560
|
+
"Why is my VPN not connecting?",
|
|
561
|
+
"How do I request a refund?",
|
|
562
|
+
"Raise a ticket",
|
|
563
|
+
"I am facing network issues"
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
const newMsg = {
|
|
567
|
+
id: `msg_${Date.now()}`,
|
|
568
|
+
sender: "bot",
|
|
569
|
+
type: "suggestions",
|
|
570
|
+
payload: suggestions
|
|
571
|
+
};
|
|
572
|
+
setMessages([...messages, newMsg]);
|
|
573
|
+
}
|
|
574
|
+
}, [pathname, messages]);
|
|
575
|
+
useEffect3(() => {
|
|
576
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
577
|
+
}, [messages, isOpen]);
|
|
578
|
+
const startNewChat = () => {
|
|
579
|
+
setCurrentSessionId(crypto.randomUUID());
|
|
580
|
+
setMessages([initialWelcomeMessage]);
|
|
581
|
+
setFeedbackSubmitted(false);
|
|
582
|
+
setRating(0);
|
|
583
|
+
setResolved(null);
|
|
584
|
+
};
|
|
585
|
+
const handleMicClick = () => {
|
|
586
|
+
if (isListening) {
|
|
587
|
+
stop();
|
|
588
|
+
} else {
|
|
589
|
+
start();
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
const handleSend = async (text) => {
|
|
593
|
+
if (!text.trim()) return;
|
|
594
|
+
const userMsg = {
|
|
595
|
+
id: `msg_${Date.now()}`,
|
|
596
|
+
sender: "user",
|
|
597
|
+
text,
|
|
598
|
+
type: "text"
|
|
599
|
+
};
|
|
600
|
+
const updatedMessages = [...messages.filter((m) => m.type !== "suggestions"), userMsg];
|
|
601
|
+
setMessages(updatedMessages);
|
|
602
|
+
setInput("");
|
|
603
|
+
setIsBotTyping(true);
|
|
604
|
+
try {
|
|
605
|
+
const response = await chatService.sendMessage(
|
|
606
|
+
{
|
|
607
|
+
message: text,
|
|
608
|
+
session_id: currentSessionId ?? void 0
|
|
609
|
+
},
|
|
610
|
+
!config.authToken
|
|
611
|
+
);
|
|
612
|
+
setIsBotTyping(false);
|
|
613
|
+
const finalMessages = [...updatedMessages];
|
|
614
|
+
const cleanedMessage = cleanResponse(response.response);
|
|
615
|
+
if (response.session_id) {
|
|
616
|
+
setCurrentSessionId(response.session_id);
|
|
617
|
+
}
|
|
618
|
+
if (response.payment_link) {
|
|
619
|
+
const link = response.payment_link;
|
|
620
|
+
if (!link.amount) {
|
|
621
|
+
link.amount = extractAmount(cleanedMessage) || extractAmount(response.response) || 0;
|
|
622
|
+
}
|
|
623
|
+
if (!link.plan) {
|
|
624
|
+
link.plan = extractPlanName(cleanedMessage) || link.plan || "Subscription";
|
|
625
|
+
}
|
|
626
|
+
if (!link.billing) {
|
|
627
|
+
link.billing = extractBilling(cleanedMessage) || link.billing || "";
|
|
628
|
+
}
|
|
629
|
+
const cleanText = cleanedMessage.replace(/https?:\/\/checkout\.stripe\.com\/[^\s)]+/gi, "").replace(/\[.*?\]\(.*?\)/gi, "").trim();
|
|
630
|
+
if (cleanText) {
|
|
631
|
+
finalMessages.push({ id: crypto.randomUUID(), sender: "bot", type: "text", text: cleanText });
|
|
632
|
+
}
|
|
633
|
+
finalMessages.push({ id: crypto.randomUUID(), sender: "bot", type: "payment", payload: response.payment_link });
|
|
634
|
+
} else {
|
|
635
|
+
const stripeUrl = extractStripeUrl(cleanedMessage) || extractStripeUrl(response.response);
|
|
636
|
+
if (stripeUrl) {
|
|
637
|
+
const cleanText = cleanedMessage.replace(/https?:\/\/checkout\.stripe\.com\/[^\s)]+/gi, "").replace(/\[.*?\]\(.*?\)/gi, "").trim();
|
|
638
|
+
if (cleanText) {
|
|
639
|
+
finalMessages.push({ id: crypto.randomUUID(), sender: "bot", type: "text", text: cleanText });
|
|
640
|
+
}
|
|
641
|
+
finalMessages.push({
|
|
642
|
+
id: crypto.randomUUID(),
|
|
643
|
+
sender: "bot",
|
|
644
|
+
type: "payment",
|
|
645
|
+
payload: {
|
|
646
|
+
url: stripeUrl,
|
|
647
|
+
plan: extractPlanName(cleanedMessage) || "Subscription",
|
|
648
|
+
billing: extractBilling(cleanedMessage) || "",
|
|
649
|
+
amount: extractAmount(cleanedMessage) || 0,
|
|
650
|
+
text: "Complete your payment"
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
} else {
|
|
654
|
+
finalMessages.push({ id: crypto.randomUUID(), sender: "bot", type: "text", text: cleanedMessage });
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (response.followups && response.followups.length > 0) {
|
|
658
|
+
finalMessages.push({ id: crypto.randomUUID(), sender: "bot", type: "suggestions", payload: response.followups });
|
|
659
|
+
}
|
|
660
|
+
setMessages(finalMessages);
|
|
661
|
+
} catch (error) {
|
|
662
|
+
setIsBotTyping(false);
|
|
663
|
+
console.error("[GarudaChat] AI chat request failed:", error);
|
|
664
|
+
const errorMsg = {
|
|
665
|
+
id: `msg_${Date.now()}_err`,
|
|
666
|
+
sender: "bot",
|
|
667
|
+
type: "error",
|
|
668
|
+
text: "Something went wrong. Please try again after some time.",
|
|
669
|
+
payload: { retryText: text }
|
|
670
|
+
};
|
|
671
|
+
setMessages([...updatedMessages, errorMsg]);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const retrySend = (retryText) => {
|
|
675
|
+
setMessages((prev) => prev.filter((m) => m.type !== "error"));
|
|
676
|
+
handleSend(retryText);
|
|
677
|
+
};
|
|
678
|
+
const handleEndChat = () => {
|
|
679
|
+
if (messages.length <= 1) return;
|
|
680
|
+
if (!messages.some((m) => m.type === "resolution" || m.type === "rating")) {
|
|
681
|
+
setMessages([...messages, { id: `msg_${Date.now()}_end`, sender: "bot", type: "resolution" }]);
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
const handleResolution = (isResolved) => {
|
|
685
|
+
setResolved(isResolved);
|
|
686
|
+
const filtered = messages.filter((m) => m.type !== "resolution" && m.type !== "rating");
|
|
687
|
+
setMessages([...filtered, { id: `msg_${Date.now()}_rate`, sender: "bot", type: "rating" }]);
|
|
688
|
+
};
|
|
689
|
+
const submitRating = async () => {
|
|
690
|
+
if (rating === 0 || !currentSessionId || isSubmittingRating) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
setIsSubmittingRating(true);
|
|
694
|
+
try {
|
|
695
|
+
const response = await fetch(`${config.apiBaseUrl.replace(/\/$/, "")}/api/v1/ratings/respond`, {
|
|
696
|
+
method: "POST",
|
|
697
|
+
headers: {
|
|
698
|
+
accept: "application/json",
|
|
699
|
+
"Content-Type": "application/json"
|
|
700
|
+
},
|
|
701
|
+
body: JSON.stringify({
|
|
702
|
+
session_id: currentSessionId,
|
|
703
|
+
user_id: user?.id || "",
|
|
704
|
+
user_email: user?.email || "",
|
|
705
|
+
rating,
|
|
706
|
+
content: feedback || "",
|
|
707
|
+
issue_solved: resolved === true ? "yes" : resolved === false ? "no" : ""
|
|
708
|
+
})
|
|
709
|
+
});
|
|
710
|
+
if (!response.ok) {
|
|
711
|
+
throw new Error(`Rating submission failed: ${response.status}`);
|
|
712
|
+
}
|
|
713
|
+
const data = await response.json();
|
|
714
|
+
if (data.status === "success") {
|
|
715
|
+
setFeedbackSubmitted(true);
|
|
716
|
+
const filteredMessages = messages.filter((m) => m.type !== "rating");
|
|
717
|
+
const responseMessage = {
|
|
718
|
+
id: `msg_${Date.now()}_response`,
|
|
719
|
+
sender: "bot",
|
|
720
|
+
type: "text",
|
|
721
|
+
text: data.response
|
|
722
|
+
};
|
|
723
|
+
if (resolved === false) {
|
|
724
|
+
setMessages([
|
|
725
|
+
...filteredMessages,
|
|
726
|
+
responseMessage,
|
|
727
|
+
{
|
|
728
|
+
id: `msg_${Date.now()}_opts`,
|
|
729
|
+
sender: "bot",
|
|
730
|
+
type: "suggestions",
|
|
731
|
+
payload: ["Raise a ticket", "Browse Help Articles"]
|
|
732
|
+
}
|
|
733
|
+
]);
|
|
734
|
+
} else {
|
|
735
|
+
setMessages([...filteredMessages, responseMessage]);
|
|
736
|
+
}
|
|
737
|
+
setRating(0);
|
|
738
|
+
setFeedback("");
|
|
739
|
+
setResolved(null);
|
|
740
|
+
}
|
|
741
|
+
} catch (error) {
|
|
742
|
+
console.error("[GarudaChat] Failed to submit rating:", error);
|
|
743
|
+
} finally {
|
|
744
|
+
setIsSubmittingRating(false);
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
const isCallConnected = voiceCall.isActive && voiceCall.status === "in-call";
|
|
748
|
+
const appendDigit = (digit) => {
|
|
749
|
+
if (!isCallConnected) {
|
|
750
|
+
setCallStatus("Wait for the call to connect before entering the code.");
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
setTypedCode((prev) => {
|
|
754
|
+
const next = (prev + digit).slice(-6);
|
|
755
|
+
setCallStatus(next.length === 6 ? "Press Enter to send the code." : "");
|
|
756
|
+
return next;
|
|
757
|
+
});
|
|
758
|
+
};
|
|
759
|
+
const submitCode = async () => {
|
|
760
|
+
if (!isCallConnected) {
|
|
761
|
+
setCallStatus("Wait for the call to connect before entering the code.");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (!typedCode) {
|
|
765
|
+
setCallStatus("Enter the 6-digit code first.");
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const code = typedCode;
|
|
769
|
+
setTypedCode("");
|
|
770
|
+
setCallStatus("Code sent - verifying...");
|
|
771
|
+
try {
|
|
772
|
+
const data = await voiceService.submitOtp(code, {
|
|
773
|
+
callSid: voiceCall.callSid || void 0,
|
|
774
|
+
clientRef: voiceCall.clientRef || void 0
|
|
775
|
+
});
|
|
776
|
+
if (data && data.status === "ok") {
|
|
777
|
+
setCallStatus(
|
|
778
|
+
data.detail === "verified" ? "Code verified! The bot will continue shortly..." : "Code received by server - verifying..."
|
|
779
|
+
);
|
|
780
|
+
} else {
|
|
781
|
+
const detail = data && data.detail || "unknown";
|
|
782
|
+
setCallStatus(detail === "incorrect_code" ? "Incorrect code. Try again." : "Submit warning: " + detail);
|
|
783
|
+
}
|
|
784
|
+
} catch (err) {
|
|
785
|
+
console.error("[GarudaChat] submit-otp error:", err);
|
|
786
|
+
setCallStatus("Submit error: " + (err.message || "unknown"));
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
const resendCode = async () => {
|
|
790
|
+
if (!voiceCall.clientRef && !voiceCall.callSid) {
|
|
791
|
+
setCallStatus("No active call to resend the code.");
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
setCallStatus("Sending a new code...");
|
|
795
|
+
try {
|
|
796
|
+
const data = await voiceService.submitOtp("RESEND", {
|
|
797
|
+
callSid: voiceCall.callSid || void 0,
|
|
798
|
+
clientRef: voiceCall.clientRef || void 0
|
|
799
|
+
});
|
|
800
|
+
if (data && data.status === "ok") {
|
|
801
|
+
setCallStatus("New code sent to your email. Enter it on the keypad.");
|
|
802
|
+
} else {
|
|
803
|
+
setCallStatus("Resend failed: " + (data && data.detail || "unknown"));
|
|
804
|
+
}
|
|
805
|
+
} catch (err) {
|
|
806
|
+
setCallStatus("Resend error: " + (err.message || "unknown"));
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
useEffect3(() => {
|
|
810
|
+
if (!isCallConnected) return;
|
|
811
|
+
const onKeyDown = (e) => {
|
|
812
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
813
|
+
if (/^[0-9#*]$/.test(e.key)) {
|
|
814
|
+
e.preventDefault();
|
|
815
|
+
appendDigit(e.key);
|
|
816
|
+
} else if (e.key === "Enter") {
|
|
817
|
+
e.preventDefault();
|
|
818
|
+
submitCode();
|
|
819
|
+
} else if (e.key === "Backspace") {
|
|
820
|
+
e.preventDefault();
|
|
821
|
+
setTypedCode((prev) => prev.slice(0, -1));
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
document.addEventListener("keydown", onKeyDown);
|
|
825
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
826
|
+
}, [isCallConnected, typedCode, voiceCall]);
|
|
827
|
+
useEffect3(() => {
|
|
828
|
+
if (!voiceCall.isActive) {
|
|
829
|
+
setTypedCode("");
|
|
830
|
+
setCallStatus("");
|
|
831
|
+
}
|
|
832
|
+
}, [voiceCall.isActive]);
|
|
833
|
+
useEffect3(() => {
|
|
834
|
+
if (voiceCall.status === "in-call" && voiceCall.isActive) {
|
|
835
|
+
setCallStatus(
|
|
836
|
+
voiceCall.callSid ? `Connected (CallSid: ${voiceCall.callSid}). When the bot asks for the code, type it on the keypad and press Enter.` : "Connected. When the bot asks for the code, type it on the keypad and press Enter."
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
}, [voiceCall.status, voiceCall.isActive]);
|
|
840
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
841
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: !isOpen && /* @__PURE__ */ jsxs(
|
|
842
|
+
motion.div,
|
|
843
|
+
{
|
|
844
|
+
initial: { scale: 0, opacity: 0 },
|
|
845
|
+
animate: { scale: 1, opacity: 1 },
|
|
846
|
+
exit: { scale: 0, opacity: 0 },
|
|
847
|
+
className: "fixed bottom-6 right-6 z-50 flex items-center group",
|
|
848
|
+
children: [
|
|
849
|
+
/* @__PURE__ */ jsx("div", { className: "absolute bottom-full mb-4 right-0 bg-navy-800 text-foreground px-4 py-2 rounded-xl text-sm whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none shadow-[0_0_15px_rgba(0,0,0,0.5)] border border-primary/20", children: "How can I help you?" }),
|
|
850
|
+
/* @__PURE__ */ jsx(
|
|
851
|
+
"button",
|
|
852
|
+
{
|
|
853
|
+
onClick: () => setIsOpen(true),
|
|
854
|
+
className: "w-16 h-16 bg-gradient-to-tr from-primary to-teal-400 text-navy-900 rounded-full flex items-center justify-center shadow-[0_0_30px_rgba(32,201,151,0.8)] hover:scale-105 transition-all duration-300 border border-white/20 animate-pulse",
|
|
855
|
+
children: /* @__PURE__ */ jsx(Bot, { size: 32 })
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
]
|
|
859
|
+
}
|
|
860
|
+
) }),
|
|
861
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsxs(
|
|
862
|
+
motion.div,
|
|
863
|
+
{
|
|
864
|
+
initial: { opacity: 0, y: 20, scale: 0.95 },
|
|
865
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
866
|
+
exit: { opacity: 0, y: 20, scale: 0.95 },
|
|
867
|
+
className: "fixed bottom-6 left-3 right-3 sm:left-auto sm:right-6 z-50 w-auto sm:w-full max-w-[500px] h-[750px] max-h-[90vh] bg-navy-900 backdrop-blur-2xl border border-foreground/10 shadow-[0_20px_50px_rgba(0,0,0,0.5)] shadow-primary/10 rounded-3xl flex flex-col overflow-hidden",
|
|
868
|
+
children: [
|
|
869
|
+
/* @__PURE__ */ jsxs("div", { className: "h-16 border-b border-white/10 bg-navy-800/50 flex items-center justify-between px-5 shrink-0", children: [
|
|
870
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
871
|
+
/* @__PURE__ */ jsx(
|
|
872
|
+
"button",
|
|
873
|
+
{
|
|
874
|
+
onClick: startNewChat,
|
|
875
|
+
className: "text-gray-400 hover:text-white transition-colors",
|
|
876
|
+
title: "New chat",
|
|
877
|
+
children: /* @__PURE__ */ jsx(Plus, { size: 20 })
|
|
878
|
+
}
|
|
879
|
+
),
|
|
880
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
881
|
+
/* @__PURE__ */ jsx("div", { className: "w-10 h-10 bg-gradient-to-br from-primary/30 to-teal-500/30 rounded-xl flex items-center justify-center border border-primary/20 shadow-inner", children: /* @__PURE__ */ jsx(ShieldCheck, { size: 20, className: "text-primary" }) }),
|
|
882
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
883
|
+
/* @__PURE__ */ jsx("h3", { className: "font-bold text-sm text-white", children: "Aegis AI Support" }),
|
|
884
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
885
|
+
/* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse" }),
|
|
886
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-400 font-medium tracking-wide", children: "ONLINE" })
|
|
887
|
+
] })
|
|
888
|
+
] })
|
|
889
|
+
] })
|
|
890
|
+
] }),
|
|
891
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
892
|
+
config.supportPhone && /* @__PURE__ */ jsx(
|
|
893
|
+
"button",
|
|
894
|
+
{
|
|
895
|
+
onClick: () => setShowCallPopup(true),
|
|
896
|
+
className: "flex items-center justify-center text-gray-400 hover:text-green-400 transition-colors p-2 hover:bg-green-400/10 rounded-full",
|
|
897
|
+
title: "Voice Call",
|
|
898
|
+
children: /* @__PURE__ */ jsx(Phone, { size: 18 })
|
|
899
|
+
}
|
|
900
|
+
),
|
|
901
|
+
/* @__PURE__ */ jsx(
|
|
902
|
+
"button",
|
|
903
|
+
{
|
|
904
|
+
onClick: handleEndChat,
|
|
905
|
+
className: "text-xs font-bold text-gray-400 hover:text-red-400 transition-colors px-3 py-1.5 hover:bg-red-400/10 rounded-lg mr-1 border border-transparent hover:border-red-400/20",
|
|
906
|
+
children: "End Chat"
|
|
907
|
+
}
|
|
908
|
+
),
|
|
909
|
+
/* @__PURE__ */ jsx(
|
|
910
|
+
"button",
|
|
911
|
+
{
|
|
912
|
+
onClick: () => setIsOpen(false),
|
|
913
|
+
className: "text-gray-400 hover:text-white transition-colors p-2 hover:bg-white/5 rounded-full",
|
|
914
|
+
children: /* @__PURE__ */ jsx(X, { size: 20 })
|
|
915
|
+
}
|
|
916
|
+
)
|
|
917
|
+
] })
|
|
918
|
+
] }),
|
|
919
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden relative", children: [
|
|
920
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col h-full w-full relative z-0 bg-gradient-to-b from-navy-900/50 to-navy-900", children: [
|
|
921
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto overflow-x-hidden p-5 space-y-5 custom-scrollbar", children: [
|
|
922
|
+
messages.map((msg) => /* @__PURE__ */ jsxs("div", { id: msg.id, className: `flex flex-col ${msg.sender === "user" ? "items-end" : "items-start"}`, children: [
|
|
923
|
+
msg.sender === "bot" && msg.type !== "system" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-1 ml-1", children: [
|
|
924
|
+
/* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-primary" }),
|
|
925
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold uppercase tracking-wide text-primary", children: "AI Assistant" })
|
|
926
|
+
] }),
|
|
927
|
+
msg.type === "text" && /* @__PURE__ */ jsx(
|
|
928
|
+
"div",
|
|
929
|
+
{
|
|
930
|
+
className: `max-w-[85%] rounded-2xl px-5 py-3 text-base shadow-sm break-words overflow-hidden whitespace-pre-wrap chat-markdown ${msg.sender === "user" ? "bg-gradient-to-r from-primary to-teal-400 text-navy-900 font-medium rounded-tr-sm" : "bg-navy-800/80 backdrop-blur-sm border border-white/10 text-foreground rounded-tl-sm"}`,
|
|
931
|
+
children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: msg.text })
|
|
932
|
+
}
|
|
933
|
+
),
|
|
934
|
+
msg.type === "error" && /* @__PURE__ */ jsxs("div", { className: "max-w-[85%] rounded-2xl px-5 py-3 border border-red-500/30 bg-red-500/10 text-foreground rounded-tl-sm", children: [
|
|
935
|
+
/* @__PURE__ */ jsx("p", { className: "text-base text-red-300 flex items-start gap-2", children: /* @__PURE__ */ jsx("span", { children: msg.text }) }),
|
|
936
|
+
/* @__PURE__ */ jsxs(
|
|
937
|
+
"button",
|
|
938
|
+
{
|
|
939
|
+
onClick: () => retrySend(msg.payload?.retryText),
|
|
940
|
+
className: "mt-3 flex items-center gap-2 px-4 py-2 bg-primary text-navy-900 rounded-xl text-sm font-bold hover:bg-primary/90 transition-colors",
|
|
941
|
+
children: [
|
|
942
|
+
/* @__PURE__ */ jsx(RefreshCcw, { size: 14 }),
|
|
943
|
+
"Try Again"
|
|
944
|
+
]
|
|
945
|
+
}
|
|
946
|
+
)
|
|
947
|
+
] }),
|
|
948
|
+
msg.type === "suggestions" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-3 max-w-[90%]", children: msg.payload.map((suggestion, idx) => /* @__PURE__ */ jsx(
|
|
949
|
+
"button",
|
|
950
|
+
{
|
|
951
|
+
onClick: () => {
|
|
952
|
+
handleSend(suggestion);
|
|
953
|
+
},
|
|
954
|
+
className: "px-4 py-2 bg-navy-800/50 border border-primary/30 text-primary rounded-xl text-sm hover:bg-primary/10 hover:border-primary/50 transition-all font-medium shadow-sm hover:shadow-[0_0_10px_rgba(32,201,151,0.15)]",
|
|
955
|
+
children: suggestion
|
|
956
|
+
},
|
|
957
|
+
idx
|
|
958
|
+
)) }),
|
|
959
|
+
msg.type === "resolution" && /* @__PURE__ */ jsxs("div", { className: "mt-3 w-full max-w-[85%] bg-navy-800/80 backdrop-blur-md border border-white/10 rounded-2xl p-5 shadow-lg", children: [
|
|
960
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-foreground font-medium mb-4 flex items-center gap-2", children: [
|
|
961
|
+
/* @__PURE__ */ jsx(CheckCircle2, { size: 18, className: "text-primary" }),
|
|
962
|
+
" Was your issue resolved?"
|
|
963
|
+
] }),
|
|
964
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
|
|
965
|
+
/* @__PURE__ */ jsx(
|
|
966
|
+
"button",
|
|
967
|
+
{
|
|
968
|
+
onClick: () => handleResolution(true),
|
|
969
|
+
className: "flex-1 py-2 bg-green-500/10 text-green-500 border border-green-500/20 rounded-xl text-sm font-bold hover:bg-green-500/20 hover:border-green-500/40 transition-all shadow-sm",
|
|
970
|
+
children: "Yes"
|
|
971
|
+
}
|
|
972
|
+
),
|
|
973
|
+
/* @__PURE__ */ jsx(
|
|
974
|
+
"button",
|
|
975
|
+
{
|
|
976
|
+
onClick: () => handleResolution(false),
|
|
977
|
+
className: "flex-1 py-2 bg-red-500/10 text-red-500 border border-red-500/20 rounded-xl text-sm font-bold hover:bg-red-500/20 hover:border-red-500/40 transition-all shadow-sm",
|
|
978
|
+
children: "No"
|
|
979
|
+
}
|
|
980
|
+
)
|
|
981
|
+
] })
|
|
982
|
+
] }),
|
|
983
|
+
msg.type === "rating" && /* @__PURE__ */ jsxs("div", { className: "mt-3 w-full max-w-[85%] bg-navy-800/80 backdrop-blur-md border border-white/10 rounded-2xl p-5 shadow-lg", children: [
|
|
984
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-foreground font-medium mb-3", children: "Rate your experience" }),
|
|
985
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1 mb-4", children: [1, 2, 3, 4, 5].map((star) => /* @__PURE__ */ jsx(
|
|
986
|
+
"button",
|
|
987
|
+
{
|
|
988
|
+
onClick: () => setRating(star),
|
|
989
|
+
className: `transition-all hover:scale-110 ${rating >= star ? "text-yellow-400 drop-shadow-[0_0_5px_rgba(250,204,21,0.5)]" : "text-gray-600 hover:text-yellow-400/50"}`,
|
|
990
|
+
children: /* @__PURE__ */ jsx(Star, { size: 24, fill: "currentColor" })
|
|
991
|
+
},
|
|
992
|
+
star
|
|
993
|
+
)) }),
|
|
994
|
+
/* @__PURE__ */ jsx(
|
|
995
|
+
"textarea",
|
|
996
|
+
{
|
|
997
|
+
value: feedback,
|
|
998
|
+
onChange: (e) => setFeedback(e.target.value),
|
|
999
|
+
className: "w-full bg-navy-900/50 border border-white/10 rounded-xl p-3 text-sm text-foreground focus:border-primary focus:ring-1 focus:ring-primary focus:outline-none resize-none transition-all placeholder-gray-500",
|
|
1000
|
+
placeholder: "Leave feedback (optional)...",
|
|
1001
|
+
rows: 2
|
|
1002
|
+
}
|
|
1003
|
+
),
|
|
1004
|
+
/* @__PURE__ */ jsx(
|
|
1005
|
+
"button",
|
|
1006
|
+
{
|
|
1007
|
+
onClick: submitRating,
|
|
1008
|
+
disabled: rating === 0 || isSubmittingRating,
|
|
1009
|
+
className: "mt-3 w-full py-2.5 bg-primary text-navy-900 font-bold rounded-xl text-sm disabled:opacity-50 disabled:cursor-not-allowed hover:bg-primary/90 transition-colors shadow-[0_0_15px_rgba(32,201,151,0.2)]",
|
|
1010
|
+
children: isSubmittingRating ? "Submitting..." : "Submit Feedback"
|
|
1011
|
+
}
|
|
1012
|
+
)
|
|
1013
|
+
] }),
|
|
1014
|
+
msg.type === "payment" && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[85%] mt-3 overflow-hidden", children: /* @__PURE__ */ jsxs("div", { className: "rounded-2xl border border-primary/30 bg-gradient-to-br from-primary/10 to-cyan-500/10 p-5 overflow-hidden break-words", children: [
|
|
1015
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
|
|
1016
|
+
/* @__PURE__ */ jsx("h3", { className: "text-xl font-bold", children: msg.payload.plan }),
|
|
1017
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: "Stripe Secure Checkout" })
|
|
1018
|
+
] }),
|
|
1019
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 mb-5", children: [
|
|
1020
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
1021
|
+
/* @__PURE__ */ jsx("span", { children: "Plan" }),
|
|
1022
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold", children: msg.payload.plan })
|
|
1023
|
+
] }),
|
|
1024
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
1025
|
+
/* @__PURE__ */ jsx("span", { children: "Billing" }),
|
|
1026
|
+
/* @__PURE__ */ jsx("span", { className: "capitalize", children: msg.payload.billing })
|
|
1027
|
+
] }),
|
|
1028
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
1029
|
+
/* @__PURE__ */ jsx("span", { children: "Amount" }),
|
|
1030
|
+
/* @__PURE__ */ jsxs("span", { className: "text-primary font-bold text-lg", children: [
|
|
1031
|
+
"$",
|
|
1032
|
+
msg.payload.amount
|
|
1033
|
+
] })
|
|
1034
|
+
] })
|
|
1035
|
+
] }),
|
|
1036
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1037
|
+
/* @__PURE__ */ jsx(
|
|
1038
|
+
"a",
|
|
1039
|
+
{
|
|
1040
|
+
href: msg.payload.url,
|
|
1041
|
+
target: "_blank",
|
|
1042
|
+
rel: "noopener noreferrer",
|
|
1043
|
+
className: "w-full rounded-xl bg-primary py-3 font-bold text-navy-900 hover:opacity-90 text-center block",
|
|
1044
|
+
children: "Continue to Secure Payment"
|
|
1045
|
+
}
|
|
1046
|
+
),
|
|
1047
|
+
/* @__PURE__ */ jsx(
|
|
1048
|
+
"button",
|
|
1049
|
+
{
|
|
1050
|
+
onClick: () => handleSend("I have completed the payment"),
|
|
1051
|
+
className: "w-full rounded-xl border border-primary/30 py-2.5 font-semibold text-primary hover:bg-primary/10 text-sm",
|
|
1052
|
+
children: "\u2713 Payment Done"
|
|
1053
|
+
}
|
|
1054
|
+
)
|
|
1055
|
+
] })
|
|
1056
|
+
] }) }),
|
|
1057
|
+
msg.type === "article" && /* @__PURE__ */ jsxs(
|
|
1058
|
+
"div",
|
|
1059
|
+
{
|
|
1060
|
+
onClick: () => {
|
|
1061
|
+
setIsOpen(false);
|
|
1062
|
+
config.onNavigateToKnowledgeBase?.();
|
|
1063
|
+
},
|
|
1064
|
+
className: "mt-3 w-full max-w-[85%] bg-navy-800/80 backdrop-blur-md border border-white/10 rounded-2xl p-4 hover:border-primary/40 hover:shadow-[0_0_15px_rgba(32,201,151,0.1)] transition-all cursor-pointer group",
|
|
1065
|
+
children: [
|
|
1066
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 mb-3", children: [
|
|
1067
|
+
/* @__PURE__ */ jsx("div", { className: "p-2 bg-primary/10 text-primary rounded-xl shrink-0 mt-0.5", children: /* @__PURE__ */ jsx(FileText, { size: 18 }) }),
|
|
1068
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-bold text-foreground group-hover:text-primary transition-colors line-clamp-2 leading-tight", children: msg.payload.title })
|
|
1069
|
+
] }),
|
|
1070
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 line-clamp-2 mb-3 leading-relaxed", children: msg.payload.content }),
|
|
1071
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-white/5 pt-3", children: [
|
|
1072
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[10px] text-gray-500 font-medium uppercase tracking-wider", children: [
|
|
1073
|
+
msg.payload.readTime,
|
|
1074
|
+
" read"
|
|
1075
|
+
] }),
|
|
1076
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-primary font-bold flex items-center group-hover:translate-x-1 transition-transform", children: [
|
|
1077
|
+
"Read Article ",
|
|
1078
|
+
/* @__PURE__ */ jsx(ChevronRight, { size: 14, className: "ml-1" })
|
|
1079
|
+
] })
|
|
1080
|
+
] })
|
|
1081
|
+
]
|
|
1082
|
+
}
|
|
1083
|
+
),
|
|
1084
|
+
msg.type === "refund" && /* @__PURE__ */ jsxs("div", { className: "mt-3 w-full max-w-[85%] bg-navy-800/80 backdrop-blur-md border border-white/10 rounded-2xl p-5 shadow-lg", children: [
|
|
1085
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
|
|
1086
|
+
/* @__PURE__ */ jsx("div", { className: "w-10 h-10 bg-primary/10 rounded-xl flex items-center justify-center", children: /* @__PURE__ */ jsx(RefreshCcw, { size: 20, className: "text-primary" }) }),
|
|
1087
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-bold text-foreground", children: "Refund Request" })
|
|
1088
|
+
] }),
|
|
1089
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-300 mb-5 leading-relaxed", children: "Your current plan is eligible for a refund under our 30-day guarantee." }),
|
|
1090
|
+
/* @__PURE__ */ jsx(
|
|
1091
|
+
"button",
|
|
1092
|
+
{
|
|
1093
|
+
onClick: () => handleSend("Create Refund Request"),
|
|
1094
|
+
className: "w-full py-2.5 bg-primary/10 text-primary border border-primary/20 rounded-xl text-sm font-bold hover:bg-primary/20 transition-colors shadow-sm",
|
|
1095
|
+
children: "Proceed with Refund"
|
|
1096
|
+
}
|
|
1097
|
+
)
|
|
1098
|
+
] })
|
|
1099
|
+
] }, msg.id)),
|
|
1100
|
+
isBotTyping && /* @__PURE__ */ jsx(motion.div, { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0 }, className: "flex items-start", children: /* @__PURE__ */ jsx("div", { className: "bg-navy-800/80 backdrop-blur-sm border border-white/10 rounded-2xl rounded-tl-sm px-5 py-4 z-10", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1101
|
+
/* @__PURE__ */ jsx(
|
|
1102
|
+
motion.div,
|
|
1103
|
+
{
|
|
1104
|
+
className: "w-2 h-2 rounded-full bg-primary",
|
|
1105
|
+
animate: { y: [0, -5, 0] },
|
|
1106
|
+
transition: { duration: 0.6, repeat: Infinity, delay: 0 }
|
|
1107
|
+
}
|
|
1108
|
+
),
|
|
1109
|
+
/* @__PURE__ */ jsx(
|
|
1110
|
+
motion.div,
|
|
1111
|
+
{
|
|
1112
|
+
className: "w-2 h-2 rounded-full bg-primary",
|
|
1113
|
+
animate: { y: [0, -5, 0] },
|
|
1114
|
+
transition: { duration: 0.6, repeat: Infinity, delay: 0.2 }
|
|
1115
|
+
}
|
|
1116
|
+
),
|
|
1117
|
+
/* @__PURE__ */ jsx(
|
|
1118
|
+
motion.div,
|
|
1119
|
+
{
|
|
1120
|
+
className: "w-2 h-2 rounded-full bg-primary",
|
|
1121
|
+
animate: { y: [0, -5, 0] },
|
|
1122
|
+
transition: { duration: 0.6, repeat: Infinity, delay: 0.4 }
|
|
1123
|
+
}
|
|
1124
|
+
)
|
|
1125
|
+
] }) }) }),
|
|
1126
|
+
/* @__PURE__ */ jsx("div", { ref: messagesEndRef })
|
|
1127
|
+
] }),
|
|
1128
|
+
/* @__PURE__ */ jsxs("div", { className: "p-4 border-t border-white/10 bg-navy-800/80 backdrop-blur-md shrink-0", children: [
|
|
1129
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
|
|
1130
|
+
/* @__PURE__ */ jsx(
|
|
1131
|
+
"input",
|
|
1132
|
+
{
|
|
1133
|
+
type: "text",
|
|
1134
|
+
value: input,
|
|
1135
|
+
onChange: (e) => setInput(e.target.value),
|
|
1136
|
+
onKeyDown: (e) => {
|
|
1137
|
+
if (e.key === "Enter") {
|
|
1138
|
+
handleSend(input);
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
placeholder: isListening ? "Listening..." : "Ask Aegis AI anything...",
|
|
1142
|
+
className: "w-full bg-navy-900 border border-white/10 rounded-2xl py-3.5 pl-5 pr-24 text-base text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/50 transition-all shadow-inner"
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
/* @__PURE__ */ jsx(
|
|
1146
|
+
"button",
|
|
1147
|
+
{
|
|
1148
|
+
type: "button",
|
|
1149
|
+
onClick: handleMicClick,
|
|
1150
|
+
className: `absolute right-14 w-10 h-10 rounded-xl flex items-center justify-center transition-all ${isListening ? "bg-red-500 text-white animate-pulse" : "bg-navy-700 text-gray-300 hover:text-white"}`,
|
|
1151
|
+
children: isListening ? /* @__PURE__ */ jsx(CircleStop, { size: 30 }) : /* @__PURE__ */ jsx(Mic, { size: 18 })
|
|
1152
|
+
}
|
|
1153
|
+
),
|
|
1154
|
+
/* @__PURE__ */ jsx(
|
|
1155
|
+
"button",
|
|
1156
|
+
{
|
|
1157
|
+
onClick: () => handleSend(input),
|
|
1158
|
+
disabled: !input.trim(),
|
|
1159
|
+
className: "absolute right-2 w-10 h-10 bg-gradient-to-r from-primary to-teal-400 text-navy-900 rounded-xl flex items-center justify-center disabled:opacity-50",
|
|
1160
|
+
children: /* @__PURE__ */ jsx(Send, { size: 16 })
|
|
1161
|
+
}
|
|
1162
|
+
)
|
|
1163
|
+
] }),
|
|
1164
|
+
/* @__PURE__ */ jsx("div", { className: "text-center mt-2", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-500", children: "Aegis AI can make mistakes. Verify important info." }) })
|
|
1165
|
+
] })
|
|
1166
|
+
] }),
|
|
1167
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: showCallPopup && config.supportPhone && /* @__PURE__ */ jsx(
|
|
1168
|
+
motion.div,
|
|
1169
|
+
{
|
|
1170
|
+
initial: { opacity: 0 },
|
|
1171
|
+
animate: { opacity: 1 },
|
|
1172
|
+
exit: { opacity: 0 },
|
|
1173
|
+
className: "absolute inset-0 z-50 bg-navy-900/95 backdrop-blur-xl flex items-center justify-center p-6",
|
|
1174
|
+
children: /* @__PURE__ */ jsxs(
|
|
1175
|
+
motion.div,
|
|
1176
|
+
{
|
|
1177
|
+
initial: { scale: 0.9, opacity: 0 },
|
|
1178
|
+
animate: { scale: 1, opacity: 1 },
|
|
1179
|
+
exit: { scale: 0.9, opacity: 0 },
|
|
1180
|
+
transition: { type: "spring", bounce: 0, duration: 0.3 },
|
|
1181
|
+
className: "w-full max-w-sm bg-navy-800/90 backdrop-blur-md border border-white/10 rounded-3xl p-6 shadow-2xl text-center",
|
|
1182
|
+
children: [
|
|
1183
|
+
/* @__PURE__ */ jsx("div", { className: "w-14 h-14 bg-gradient-to-br from-primary/30 to-teal-500/30 rounded-full flex items-center justify-center mx-auto mb-4 border border-primary/20", children: /* @__PURE__ */ jsx(Phone, { size: 24, className: "text-primary" }) }),
|
|
1184
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-bold text-white mb-2", children: "Voice Support" }),
|
|
1185
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400 mb-5 leading-relaxed", children: "If you want this chatbot experience in voice, then call to this number:" }),
|
|
1186
|
+
/* @__PURE__ */ jsx(
|
|
1187
|
+
"a",
|
|
1188
|
+
{
|
|
1189
|
+
href: `tel:${config.supportPhone.replace(/[^+\d]/g, "")}`,
|
|
1190
|
+
className: "text-2xl font-bold text-primary tracking-wider hover:opacity-80 transition-opacity",
|
|
1191
|
+
children: config.supportPhone
|
|
1192
|
+
}
|
|
1193
|
+
),
|
|
1194
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-3 mt-6", children: [
|
|
1195
|
+
/* @__PURE__ */ jsx(
|
|
1196
|
+
"button",
|
|
1197
|
+
{
|
|
1198
|
+
onClick: () => {
|
|
1199
|
+
navigator.clipboard.writeText(config.supportPhone);
|
|
1200
|
+
setCallCopied(true);
|
|
1201
|
+
setTimeout(() => setCallCopied(false), 2e3);
|
|
1202
|
+
},
|
|
1203
|
+
className: "flex-1 py-2.5 bg-navy-700 text-gray-200 border border-white/10 rounded-xl text-sm font-bold hover:bg-navy-600 transition-colors",
|
|
1204
|
+
children: callCopied ? "Copied!" : "Copy Number"
|
|
1205
|
+
}
|
|
1206
|
+
),
|
|
1207
|
+
/* @__PURE__ */ jsx(
|
|
1208
|
+
"button",
|
|
1209
|
+
{
|
|
1210
|
+
onClick: () => setShowCallPopup(false),
|
|
1211
|
+
className: "flex-1 py-2.5 bg-navy-700 text-gray-200 border border-white/10 rounded-xl text-sm font-bold hover:bg-navy-600 transition-colors",
|
|
1212
|
+
children: "Close"
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
] }),
|
|
1216
|
+
/* @__PURE__ */ jsx(
|
|
1217
|
+
"button",
|
|
1218
|
+
{
|
|
1219
|
+
onClick: () => {
|
|
1220
|
+
setShowCallPopup(false);
|
|
1221
|
+
startVoiceCall();
|
|
1222
|
+
},
|
|
1223
|
+
className: "mt-3 w-full py-2.5 bg-primary text-navy-900 rounded-xl text-sm font-bold hover:bg-primary/90 transition-colors shadow-[0_0_15px_rgba(32,201,151,0.2)]",
|
|
1224
|
+
children: "Call Now"
|
|
1225
|
+
}
|
|
1226
|
+
)
|
|
1227
|
+
]
|
|
1228
|
+
}
|
|
1229
|
+
)
|
|
1230
|
+
}
|
|
1231
|
+
) }),
|
|
1232
|
+
voiceCall.isActive && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-50 bg-navy-900/98 backdrop-blur-xl flex items-center justify-center overflow-hidden p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2.5 w-full max-w-[240px]", children: [
|
|
1233
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
1234
|
+
/* @__PURE__ */ jsx("div", { className: "w-16 h-16 bg-gradient-to-br from-primary/30 to-teal-500/30 rounded-full flex items-center justify-center border-2 border-primary/30", children: /* @__PURE__ */ jsx(Phone, { size: 24, className: "text-primary" }) }),
|
|
1235
|
+
voiceCall.status === "in-call" && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full border-2 border-primary/40 animate-ping" })
|
|
1236
|
+
] }),
|
|
1237
|
+
/* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
1238
|
+
/* @__PURE__ */ jsx("h3", { className: "font-bold text-white text-sm", children: "Aegis AI Support" }),
|
|
1239
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-400 mt-0.5", children: [
|
|
1240
|
+
voiceCall.status === "connecting" && "Connecting...",
|
|
1241
|
+
voiceCall.status === "in-call" && `In call \xB7 ${String(Math.floor(voiceCall.duration / 60)).padStart(2, "0")}:${String(
|
|
1242
|
+
voiceCall.duration % 60
|
|
1243
|
+
).padStart(2, "0")}`
|
|
1244
|
+
] })
|
|
1245
|
+
] }),
|
|
1246
|
+
isCallConnected && /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col items-center gap-2", children: [
|
|
1247
|
+
/* @__PURE__ */ jsx("div", { className: "text-xl tracking-[0.35em] min-h-[28px] text-center text-white border-b border-white/15 pb-0.5 w-full", children: typedCode }),
|
|
1248
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1.5 w-full", children: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"].map((d) => /* @__PURE__ */ jsx(
|
|
1249
|
+
"button",
|
|
1250
|
+
{
|
|
1251
|
+
onClick: () => appendDigit(d),
|
|
1252
|
+
className: "py-1 text-base font-semibold bg-navy-800 hover:bg-navy-700 text-white rounded-lg border border-white/10 active:scale-95 transition-all",
|
|
1253
|
+
children: d
|
|
1254
|
+
},
|
|
1255
|
+
d
|
|
1256
|
+
)) }),
|
|
1257
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-1.5 w-full", children: [
|
|
1258
|
+
/* @__PURE__ */ jsx(
|
|
1259
|
+
"button",
|
|
1260
|
+
{
|
|
1261
|
+
onClick: () => {
|
|
1262
|
+
setTypedCode("");
|
|
1263
|
+
setCallStatus("");
|
|
1264
|
+
},
|
|
1265
|
+
className: "py-1 text-xs bg-navy-800 text-gray-300 rounded-lg border border-white/10 hover:bg-navy-700 transition-colors",
|
|
1266
|
+
children: "Clear"
|
|
1267
|
+
}
|
|
1268
|
+
),
|
|
1269
|
+
/* @__PURE__ */ jsx(
|
|
1270
|
+
"button",
|
|
1271
|
+
{
|
|
1272
|
+
onClick: submitCode,
|
|
1273
|
+
className: "py-1 text-xs font-bold bg-primary text-navy-900 rounded-lg hover:bg-primary/90 transition-colors",
|
|
1274
|
+
children: "Enter \u2713"
|
|
1275
|
+
}
|
|
1276
|
+
)
|
|
1277
|
+
] }),
|
|
1278
|
+
/* @__PURE__ */ jsx(
|
|
1279
|
+
"button",
|
|
1280
|
+
{
|
|
1281
|
+
onClick: resendCode,
|
|
1282
|
+
className: "py-0.5 text-[10px] text-primary underline underline-offset-4 hover:text-primary/80 transition-colors",
|
|
1283
|
+
children: "Resend code"
|
|
1284
|
+
}
|
|
1285
|
+
),
|
|
1286
|
+
callStatus && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-center text-gray-400 min-h-[12px] leading-tight", children: callStatus })
|
|
1287
|
+
] }),
|
|
1288
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1289
|
+
isCallConnected && /* @__PURE__ */ jsx(
|
|
1290
|
+
"button",
|
|
1291
|
+
{
|
|
1292
|
+
onClick: toggleMute,
|
|
1293
|
+
className: `w-11 h-11 rounded-full flex items-center justify-center transition-colors ${voiceCall.isMuted ? "bg-yellow-500 hover:bg-yellow-600" : "bg-navy-700 hover:bg-navy-600"}`,
|
|
1294
|
+
title: voiceCall.isMuted ? "Unmute" : "Mute",
|
|
1295
|
+
children: voiceCall.isMuted ? /* @__PURE__ */ jsx(MicOff, { size: 18, className: "text-white" }) : /* @__PURE__ */ jsx(Mic, { size: 18, className: "text-white" })
|
|
1296
|
+
}
|
|
1297
|
+
),
|
|
1298
|
+
/* @__PURE__ */ jsx(
|
|
1299
|
+
"button",
|
|
1300
|
+
{
|
|
1301
|
+
onClick: endVoiceCall,
|
|
1302
|
+
className: "w-12 h-12 bg-red-500 hover:bg-red-600 rounded-full flex items-center justify-center transition-colors shadow-lg shadow-red-500/30",
|
|
1303
|
+
children: /* @__PURE__ */ jsx(PhoneOff, { size: 20, className: "text-white" })
|
|
1304
|
+
}
|
|
1305
|
+
)
|
|
1306
|
+
] })
|
|
1307
|
+
] }) })
|
|
1308
|
+
] })
|
|
1309
|
+
]
|
|
1310
|
+
}
|
|
1311
|
+
) })
|
|
1312
|
+
] });
|
|
1313
|
+
}
|
|
1314
|
+
export {
|
|
1315
|
+
GarudaChatWidget
|
|
1316
|
+
};
|