@cuekit-ai/react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1110 @@
1
+ // src/utils/JsxEncoder.ts
2
+ function generateStableDOMId(element) {
3
+ const tagName = element.tagName.toLowerCase();
4
+ const text = (element.textContent || "").trim().substring(0, 50);
5
+ let sibling = element.previousElementSibling;
6
+ let position = 1;
7
+ while (sibling) {
8
+ if (sibling.tagName === element.tagName) {
9
+ position++;
10
+ }
11
+ sibling = sibling.previousElementSibling;
12
+ }
13
+ const path = getElementPath(element);
14
+ const idString = `${tagName}[${position}]_(${text})_${path}`;
15
+ let hash = 0;
16
+ for (let i = 0; i < idString.length; i++) {
17
+ const char = idString.charCodeAt(i);
18
+ hash = (hash << 5) - hash + char;
19
+ hash |= 0;
20
+ }
21
+ return hash.toString(36);
22
+ }
23
+ function getElementPath(element) {
24
+ if (element.id) {
25
+ return `id(${element.id})`;
26
+ }
27
+ if (element.tagName.toLowerCase() === "body") {
28
+ return "/body";
29
+ }
30
+ let ix = 0;
31
+ const siblings = element.parentNode?.children || new HTMLCollection();
32
+ for (let i = 0; i < siblings.length; i++) {
33
+ const sibling = siblings[i];
34
+ if (sibling === element) {
35
+ return `${getElementPath(element.parentNode)}/${element.tagName}[${ix + 1}]`;
36
+ }
37
+ if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
38
+ ix++;
39
+ }
40
+ }
41
+ return "not_found";
42
+ }
43
+
44
+ // src/utils/patchReact.ts
45
+ function getInteractiveElementsFromDOM() {
46
+ const interactiveElements = [];
47
+ const elementQuery = 'button, [role="button"], a, [data-onclick-id], [data-on-press-id]';
48
+ document.querySelectorAll(elementQuery).forEach((element) => {
49
+ const textContent = element.textContent?.trim();
50
+ const accessibilityLabel = element.getAttribute("aria-label");
51
+ const testId = element.getAttribute("data-testid");
52
+ if ((textContent || accessibilityLabel || testId) && !element.hasAttribute("data-cuekit-ignore")) {
53
+ interactiveElements.push({
54
+ element,
55
+ text: textContent || accessibilityLabel || testId || "No Text",
56
+ type: element.tagName.toLowerCase()
57
+ });
58
+ }
59
+ });
60
+ return interactiveElements;
61
+ }
62
+ function flushCapturedElements() {
63
+ const domElements = getInteractiveElementsFromDOM();
64
+ const result = domElements.map(({ element, text, type }) => {
65
+ const elementId = generateStableDOMId(element);
66
+ return {
67
+ elementType: type,
68
+ childrenValue: text,
69
+ elementId,
70
+ element
71
+ };
72
+ });
73
+ const uniqueMap = /* @__PURE__ */ new Map();
74
+ for (const item of result) {
75
+ if (!uniqueMap.has(item.elementId)) {
76
+ uniqueMap.set(item.elementId, item);
77
+ }
78
+ }
79
+ const elements = Array.from(uniqueMap.values());
80
+ console.log("Cuekit Captured Elements:", elements);
81
+ return elements;
82
+ }
83
+
84
+ // src/core/IntentStore.ts
85
+ var store = {
86
+ screenMetadata: {},
87
+ allElementsData: []
88
+ };
89
+ var GlobalStore = {
90
+ // 🔹 Screen Metadata Methods
91
+ setMetadata(screen, metadata) {
92
+ store.screenMetadata[screen] = metadata;
93
+ },
94
+ getMetadata(screen) {
95
+ return store.screenMetadata[screen];
96
+ },
97
+ clearMetadata(screen) {
98
+ delete store.screenMetadata[screen];
99
+ },
100
+ // 🔹 Generic Store Access Methods
101
+ setData(key, value) {
102
+ store[key] = value;
103
+ },
104
+ getData(key) {
105
+ return store[key];
106
+ },
107
+ clearData(key) {
108
+ delete store[key];
109
+ },
110
+ // 🔹 Element Data Management
111
+ setElement(elementData) {
112
+ const index = store.allElementsData.findIndex((e) => e.elementId === elementData.elementId);
113
+ if (index >= 0) {
114
+ console.log("Updating existing element");
115
+ store.allElementsData[index] = elementData;
116
+ } else {
117
+ console.log("Adding new element");
118
+ store.allElementsData.push(elementData);
119
+ }
120
+ },
121
+ getElementById(elementId) {
122
+ const match = store.allElementsData.find((e) => e.elementId === elementId);
123
+ if (!match) {
124
+ console.warn(`[GlobalStore] No element found for ID: ${elementId}`);
125
+ console.log("All elements in store:", store.allElementsData);
126
+ }
127
+ return match;
128
+ },
129
+ deleteElementById(id) {
130
+ store.allElementsData = store.allElementsData.filter((e) => e.elementId !== id);
131
+ },
132
+ clearAllElements() {
133
+ store.allElementsData = [];
134
+ }
135
+ };
136
+
137
+ // src/hook/useTtsManager.ts
138
+ var TtsManager = class {
139
+ constructor() {
140
+ this.isSpeaking = false;
141
+ this.utterance = null;
142
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
143
+ this.utterance = new SpeechSynthesisUtterance();
144
+ this.utterance.onstart = () => {
145
+ this.isSpeaking = true;
146
+ };
147
+ this.utterance.onend = () => {
148
+ this.isSpeaking = false;
149
+ };
150
+ this.utterance.onerror = () => {
151
+ this.isSpeaking = false;
152
+ };
153
+ }
154
+ }
155
+ speak(text) {
156
+ if (!text || !this.utterance || typeof window === "undefined" || !("speechSynthesis" in window)) {
157
+ return;
158
+ }
159
+ console.log("\u{1F508} Speaking:", text);
160
+ window.speechSynthesis.cancel();
161
+ this.utterance.text = text;
162
+ window.speechSynthesis.speak(this.utterance);
163
+ }
164
+ stop() {
165
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
166
+ window.speechSynthesis.cancel();
167
+ }
168
+ }
169
+ getIsSpeaking() {
170
+ return this.isSpeaking;
171
+ }
172
+ };
173
+ var ttsManager = new TtsManager();
174
+
175
+ // src/core/navigation.ts
176
+ var navigation;
177
+ var setNavigator = (navigator2) => {
178
+ navigation = navigator2;
179
+ };
180
+ var navigate = (path, params) => {
181
+ let fullPath = path;
182
+ if (params) {
183
+ const searchParams = new URLSearchParams(params).toString();
184
+ if (searchParams) {
185
+ fullPath += `?${searchParams}`;
186
+ }
187
+ }
188
+ if (navigation) {
189
+ navigation.push(fullPath);
190
+ } else {
191
+ window.location.href = fullPath;
192
+ }
193
+ };
194
+ var getCurrentPath = () => {
195
+ return window.location.pathname;
196
+ };
197
+ var getSearchParams = () => {
198
+ if (navigation) {
199
+ return navigation.query;
200
+ }
201
+ return new URLSearchParams(window.location.search);
202
+ };
203
+ var safeNavigate = (name, params = {}) => {
204
+ if (name) {
205
+ navigate(name, params);
206
+ } else {
207
+ console.warn("[CueKit] route name not provided");
208
+ }
209
+ };
210
+ function getCurrentScreenName() {
211
+ try {
212
+ const path = getCurrentPath();
213
+ return path || "UnknownScreen";
214
+ } catch (e) {
215
+ return "UnknownScreen";
216
+ }
217
+ }
218
+ function getCurrentRouteParams() {
219
+ try {
220
+ const params = {};
221
+ const searchParams = getSearchParams();
222
+ if (searchParams instanceof URLSearchParams) {
223
+ searchParams.forEach((value, key) => {
224
+ params[key] = value;
225
+ });
226
+ } else {
227
+ return searchParams;
228
+ }
229
+ return params;
230
+ } catch (e) {
231
+ return {};
232
+ }
233
+ }
234
+ function onStateChange() {
235
+ const routeName = getCurrentScreenName();
236
+ const params = getCurrentRouteParams();
237
+ if (params && params.metadata) {
238
+ try {
239
+ const metadata = JSON.parse(params.metadata);
240
+ GlobalStore.setMetadata(routeName, metadata);
241
+ } catch (error) {
242
+ console.error("Failed to parse metadata from URL:", error);
243
+ }
244
+ }
245
+ }
246
+ var handleNavigationAndClick = (routeName, elementHash) => {
247
+ safeNavigate(routeName);
248
+ const observer = new MutationObserver((mutationsList, observer2) => {
249
+ const elements = flushCapturedElements();
250
+ const elementToClick = elements.find((el) => el.elementId === elementHash);
251
+ if (elementToClick && elementToClick.element) {
252
+ ;
253
+ elementToClick.element.click();
254
+ observer2.disconnect();
255
+ }
256
+ });
257
+ observer.observe(document.body, { childList: true, subtree: true });
258
+ };
259
+
260
+ // src/utils/network.ts
261
+ var isConnected = async () => {
262
+ return navigator.onLine;
263
+ };
264
+
265
+ // src/useVoice.ts
266
+ var processVoice = async (transcript, apiKey, appId, deviceId) => {
267
+ try {
268
+ const elementsArray = flushCapturedElements();
269
+ const lowerTranscript = transcript.toLowerCase().trim();
270
+ const hasInternet = await isConnected();
271
+ if (!hasInternet) {
272
+ ttsManager.speak("No Internet found. Please connect to the internet.");
273
+ return null;
274
+ }
275
+ console.log("API CALL STARTED:", lowerTranscript);
276
+ const transformedArray = elementsArray.map(({ elementId, childrenValue, elementType }) => ({
277
+ hash: elementId,
278
+ text: childrenValue,
279
+ componentType: elementType.toLowerCase()
280
+ }));
281
+ const uniqueMap = /* @__PURE__ */ new Map();
282
+ transformedArray.forEach((item) => {
283
+ if (!uniqueMap.has(item.hash)) {
284
+ uniqueMap.set(item.hash, item);
285
+ }
286
+ });
287
+ const body = {
288
+ components: Array.from(uniqueMap.values()),
289
+ userInput: lowerTranscript,
290
+ deviceId,
291
+ screen: getCurrentScreenName()
292
+ };
293
+ console.log("body", body);
294
+ try {
295
+ const response = await fetch(
296
+ `http://ec2-15-207-222-65.ap-south-1.compute.amazonaws.com/components/app/${appId}/app-intents`,
297
+ {
298
+ method: "POST",
299
+ headers: {
300
+ "Content-Type": "application/json",
301
+ "X-API-Key": apiKey ?? ""
302
+ },
303
+ body: JSON.stringify(body)
304
+ }
305
+ );
306
+ const data = await response.json();
307
+ if (!response.ok) {
308
+ console.log("Response Status:", response.status);
309
+ console.log("Response Error:", data);
310
+ throw new Error(`Request failed with status ${response.status}`);
311
+ }
312
+ const targetScreen = data?.routeName;
313
+ const currentScreen = getCurrentScreenName();
314
+ if (targetScreen && !currentScreen.toLowerCase().includes(targetScreen.toLowerCase())) {
315
+ GlobalStore.setData("reTrigger", true);
316
+ GlobalStore.setData("transcript", lowerTranscript);
317
+ safeNavigate(targetScreen);
318
+ if (data.text) {
319
+ ttsManager.speak(data.text);
320
+ }
321
+ return data;
322
+ }
323
+ const { actionType, component: selectedElement, claudeError, errorMessage, text } = data;
324
+ if (claudeError) {
325
+ console.log(errorMessage);
326
+ ttsManager.speak(errorMessage);
327
+ return data;
328
+ }
329
+ switch (actionType) {
330
+ case "navigate":
331
+ if (selectedElement?.hash && targetScreen) {
332
+ if (text) {
333
+ ttsManager.speak(text);
334
+ }
335
+ handleNavigationAndClick(targetScreen, selectedElement.hash);
336
+ } else {
337
+ safeNavigate(data?.routeName, {});
338
+ if (text) {
339
+ ttsManager.speak(text);
340
+ }
341
+ }
342
+ break;
343
+ case "click":
344
+ if (selectedElement?.hash) {
345
+ const res = elementsArray.find((el) => el.elementId === selectedElement?.hash);
346
+ if (res?.elementId === selectedElement?.hash && res?.element instanceof HTMLElement) {
347
+ res.element.click();
348
+ }
349
+ if (text) {
350
+ ttsManager.speak(text);
351
+ }
352
+ }
353
+ break;
354
+ case "audio_only":
355
+ if (text) {
356
+ ttsManager.speak(text);
357
+ }
358
+ break;
359
+ default:
360
+ break;
361
+ }
362
+ return data;
363
+ } catch (error) {
364
+ console.log("Fetch Error:", error);
365
+ }
366
+ } catch (err) {
367
+ console.error("Cuekit - processVoice() error:", err);
368
+ return null;
369
+ }
370
+ };
371
+
372
+ // src/Provider/CuekitProvider.tsx
373
+ import React, { createContext, useContext, useEffect, useState } from "react";
374
+
375
+ // src/core/globals.ts
376
+ var _apiKey = "";
377
+ var setApiKey = (key) => _apiKey = key;
378
+
379
+ // src/core/init.ts
380
+ function InitCuekit(apiKey, navigator2) {
381
+ setApiKey(apiKey);
382
+ if (navigator2) {
383
+ setNavigator(navigator2);
384
+ }
385
+ }
386
+
387
+ // src/Provider/CuekitProvider.tsx
388
+ if (typeof window !== "undefined" && !globalThis.CuekitStore) {
389
+ globalThis.CuekitStore = {
390
+ intent: null,
391
+ lastText: "",
392
+ lastClickedLabel: "",
393
+ apiKey: void 0,
394
+ appId: void 0,
395
+ deviceId: void 0,
396
+ update(data) {
397
+ Object.assign(globalThis.CuekitStore, data);
398
+ }
399
+ };
400
+ }
401
+ var QubeContext = createContext({
402
+ apiKey: "",
403
+ appId: ""
404
+ });
405
+ var getUniqueId = () => {
406
+ let id = localStorage.getItem("cuekit_device_id");
407
+ if (!id) {
408
+ id = Date.now().toString(36) + Math.random().toString(36).substring(2);
409
+ localStorage.setItem("cuekit_device_id", id);
410
+ }
411
+ return id;
412
+ };
413
+ var CuekitProvider = ({
414
+ apiKey,
415
+ deviceId = "",
416
+ appId,
417
+ children,
418
+ navigator: navigator2
419
+ }) => {
420
+ const [internalDeviceId, setInternalDeviceId] = useState(deviceId);
421
+ useEffect(() => {
422
+ InitCuekit(apiKey, navigator2);
423
+ }, [apiKey, navigator2]);
424
+ useEffect(() => {
425
+ const updateGlobalStore = (id) => {
426
+ if (globalThis.CuekitStore) {
427
+ globalThis.CuekitStore.update({
428
+ apiKey,
429
+ appId,
430
+ deviceId: id
431
+ });
432
+ }
433
+ };
434
+ if (!internalDeviceId) {
435
+ const id = getUniqueId();
436
+ setInternalDeviceId(id);
437
+ updateGlobalStore(id);
438
+ } else {
439
+ updateGlobalStore(internalDeviceId);
440
+ }
441
+ }, [internalDeviceId, apiKey, appId]);
442
+ useEffect(() => {
443
+ const handleRouteChange = () => {
444
+ onStateChange();
445
+ const reTrigger = GlobalStore.getData("reTrigger");
446
+ if (reTrigger) {
447
+ const transcript = GlobalStore.getData("transcript");
448
+ if (typeof transcript === "string" && transcript) {
449
+ setTimeout(() => {
450
+ processVoice(transcript, apiKey, appId, internalDeviceId);
451
+ }, 100);
452
+ }
453
+ GlobalStore.clearData("reTrigger");
454
+ GlobalStore.clearData("transcript");
455
+ }
456
+ };
457
+ window.addEventListener("popstate", handleRouteChange);
458
+ const originalPushState = history.pushState;
459
+ history.pushState = function(...args) {
460
+ originalPushState.apply(this, args);
461
+ handleRouteChange();
462
+ };
463
+ handleRouteChange();
464
+ return () => {
465
+ window.removeEventListener("popstate", handleRouteChange);
466
+ history.pushState = originalPushState;
467
+ };
468
+ }, [apiKey, appId, internalDeviceId]);
469
+ return /* @__PURE__ */ React.createElement(
470
+ QubeContext.Provider,
471
+ {
472
+ value: {
473
+ apiKey,
474
+ appId
475
+ }
476
+ },
477
+ children
478
+ );
479
+ };
480
+
481
+ // src/hook/useAudioController.ts
482
+ import { useCallback, useEffect as useEffect2, useRef } from "react";
483
+ var AudioController = class {
484
+ constructor() {
485
+ this.audioInstance = null;
486
+ }
487
+ setAudioInstance(audio) {
488
+ this.audioInstance = audio;
489
+ }
490
+ stopPlayback() {
491
+ if (this.audioInstance) {
492
+ this.audioInstance.pause();
493
+ this.audioInstance.currentTime = 0;
494
+ this.audioInstance = null;
495
+ }
496
+ }
497
+ isPlaying() {
498
+ return !!this.audioInstance && !this.audioInstance.paused;
499
+ }
500
+ };
501
+ var audioController = new AudioController();
502
+ var useAudioController = () => {
503
+ const audioRef = useRef(null);
504
+ const stop = useCallback(() => {
505
+ audioController.stopPlayback();
506
+ }, []);
507
+ const isPlaying = useCallback(() => audioController.isPlaying(), []);
508
+ const play = useCallback((url) => {
509
+ return new Promise((resolve, reject) => {
510
+ if (audioRef.current) {
511
+ audioController.stopPlayback();
512
+ }
513
+ const audio = new Audio(url);
514
+ audioRef.current = audio;
515
+ audioController.setAudioInstance(audio);
516
+ audio.oncanplaythrough = () => {
517
+ audio.play().catch(reject);
518
+ };
519
+ audio.onended = () => {
520
+ audioController.stopPlayback();
521
+ resolve();
522
+ };
523
+ audio.onerror = (err) => {
524
+ audioController.stopPlayback();
525
+ reject(err);
526
+ };
527
+ });
528
+ }, []);
529
+ useEffect2(() => {
530
+ return () => {
531
+ audioController.stopPlayback();
532
+ };
533
+ }, []);
534
+ return {
535
+ stop,
536
+ isPlaying,
537
+ play
538
+ };
539
+ };
540
+
541
+ // src/Components/MicButton.tsx
542
+ import React3, { useState as useState4, useEffect as useEffect4 } from "react";
543
+ import { createPortal } from "react-dom";
544
+
545
+ // src/hook/useVoice.ts
546
+ import { useState as useState3, useCallback as useCallback3 } from "react";
547
+
548
+ // src/hook/useVoiceRecognition.ts
549
+ import { useState as useState2, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef2 } from "react";
550
+ var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
551
+ var recognition = SpeechRecognition ? new SpeechRecognition() : null;
552
+ if (recognition) {
553
+ recognition.continuous = true;
554
+ recognition.interimResults = true;
555
+ }
556
+ var useVoiceRecognition = (lang = "en-US") => {
557
+ const [isListening, setIsListening] = useState2(false);
558
+ const [speechText, setSpeechText] = useState2("");
559
+ const speechTextRef = useRef2("");
560
+ const silenceTimerRef = useRef2(null);
561
+ const stopListening = useCallback2(() => {
562
+ return new Promise((resolve) => {
563
+ if (recognition) {
564
+ recognition.stop();
565
+ }
566
+ if (silenceTimerRef.current) {
567
+ clearTimeout(silenceTimerRef.current);
568
+ }
569
+ resolve();
570
+ });
571
+ }, []);
572
+ const startListening = useCallback2(() => {
573
+ return new Promise((resolve, reject) => {
574
+ if (!recognition) {
575
+ reject(new Error("Speech recognition not supported"));
576
+ return;
577
+ }
578
+ setSpeechText("");
579
+ speechTextRef.current = "";
580
+ recognition.lang = lang;
581
+ recognition.onresult = (event) => {
582
+ if (silenceTimerRef.current) {
583
+ clearTimeout(silenceTimerRef.current);
584
+ }
585
+ const transcript = Array.from(event.results).map((result) => result[0]).map((result) => result.transcript).join("");
586
+ setSpeechText(transcript);
587
+ speechTextRef.current = transcript;
588
+ silenceTimerRef.current = setTimeout(() => {
589
+ stopListening();
590
+ }, 1500);
591
+ };
592
+ recognition.onerror = (event) => {
593
+ reject(event.error);
594
+ setIsListening(false);
595
+ };
596
+ recognition.onend = () => {
597
+ setIsListening(false);
598
+ if (silenceTimerRef.current) {
599
+ clearTimeout(silenceTimerRef.current);
600
+ }
601
+ resolve(speechTextRef.current);
602
+ };
603
+ recognition.start();
604
+ setIsListening(true);
605
+ });
606
+ }, [lang, stopListening]);
607
+ useEffect3(() => {
608
+ return () => {
609
+ if (recognition) {
610
+ recognition.stop();
611
+ }
612
+ if (silenceTimerRef.current) {
613
+ clearTimeout(silenceTimerRef.current);
614
+ }
615
+ };
616
+ }, []);
617
+ return {
618
+ isListening,
619
+ startListening,
620
+ stopListening,
621
+ speechText
622
+ };
623
+ };
624
+
625
+ // src/ttsEngine.ts
626
+ var TtsManager2 = class {
627
+ constructor() {
628
+ this.isSpeaking = false;
629
+ this.utterance = null;
630
+ this.onEndCallback = null;
631
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
632
+ this.utterance = new SpeechSynthesisUtterance();
633
+ this.utterance.onstart = () => {
634
+ this.isSpeaking = true;
635
+ };
636
+ this.utterance.onend = () => {
637
+ this.isSpeaking = false;
638
+ if (this.onEndCallback) {
639
+ this.onEndCallback();
640
+ }
641
+ };
642
+ this.utterance.onerror = () => {
643
+ this.isSpeaking = false;
644
+ if (this.onEndCallback) {
645
+ this.onEndCallback();
646
+ }
647
+ };
648
+ }
649
+ }
650
+ speak(text, onEnd) {
651
+ if (!text || !this.utterance || typeof window === "undefined" || !("speechSynthesis" in window)) {
652
+ if (onEnd) {
653
+ onEnd();
654
+ }
655
+ return;
656
+ }
657
+ console.log("\u{1F508} Speaking:", text);
658
+ window.speechSynthesis.cancel();
659
+ this.utterance.text = text;
660
+ this.onEndCallback = onEnd || null;
661
+ window.speechSynthesis.speak(this.utterance);
662
+ }
663
+ stop() {
664
+ if (typeof window !== "undefined" && "speechSynthesis" in window) {
665
+ window.speechSynthesis.cancel();
666
+ }
667
+ }
668
+ getIsSpeaking() {
669
+ return this.isSpeaking;
670
+ }
671
+ };
672
+ var ttsManager2 = new TtsManager2();
673
+
674
+ // src/hook/useVoice.ts
675
+ var useVoice = () => {
676
+ const [isListening, setIsListening] = useState3(false);
677
+ const [transcript, setTranscript] = useState3("");
678
+ const {
679
+ startListening: startRecognition,
680
+ stopListening: stopRecognition,
681
+ speechText
682
+ } = useVoiceRecognition();
683
+ const start = useCallback3(
684
+ async (greeting = "Hello, how can I help you?") => {
685
+ try {
686
+ ttsManager2.speak(greeting, async () => {
687
+ const text = await startRecognition();
688
+ setTranscript(text);
689
+ setIsListening(false);
690
+ });
691
+ setIsListening(true);
692
+ } catch (error) {
693
+ console.error("Error starting voice recognition:", error);
694
+ setIsListening(false);
695
+ }
696
+ },
697
+ [startRecognition]
698
+ );
699
+ const stop = useCallback3(async () => {
700
+ try {
701
+ await stopRecognition();
702
+ setIsListening(false);
703
+ } catch (error) {
704
+ console.error("Error stopping voice recognition:", error);
705
+ }
706
+ }, [stopRecognition]);
707
+ return {
708
+ isListening,
709
+ transcript: speechText,
710
+ start,
711
+ stop
712
+ };
713
+ };
714
+
715
+ // src/Components/MicButton.tsx
716
+ import { Mic, Loader2, Volume2, X, Sparkles } from "lucide-react";
717
+
718
+ // src/Components/BorderGlow.tsx
719
+ import React2 from "react";
720
+ var BorderGlow = ({ isActive }) => {
721
+ if (!isActive) return null;
722
+ return /* @__PURE__ */ React2.createElement("div", { className: "fixed inset-0 pointer-events-none z-[10000] overflow-hidden" }, /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 right-0 bottom-0 w-1 bg-gradient-to-b from-cyan-500 via-primary to-blue-600 animate-pulse opacity-80" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 right-0 bottom-0 w-2 bg-gradient-to-b from-cyan-400 via-primary to-blue-500 blur-sm animate-pulse opacity-60" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 right-0 bottom-0 w-4 bg-gradient-to-b from-cyan-300 via-primary to-blue-400 blur-md animate-pulse opacity-40" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 bottom-0 left-0 w-1 bg-gradient-to-b from-accent via-primary to-blue-600 animate-pulse opacity-80" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 bottom-0 left-0 w-2 bg-gradient-to-b from-accent via-primary to-blue-500 blur-sm animate-pulse opacity-60" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 bottom-0 left-0 w-4 bg-gradient-to-b from-accent via-primary to-blue-400 blur-md animate-pulse opacity-40" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 left-0 w-8 h-8 bg-accent rounded-br-full blur-lg animate-pulse opacity-60" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute top-0 right-0 w-8 h-8 bg-cyan-500 rounded-bl-full blur-lg animate-pulse opacity-60" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute bottom-0 right-0 w-8 h-8 bg-primary rounded-tl-full blur-lg animate-pulse opacity-60" }), /* @__PURE__ */ React2.createElement("div", { className: "absolute bottom-0 left-0 w-8 h-8 bg-blue-600 rounded-tr-full blur-lg animate-pulse opacity-60" }));
723
+ };
724
+ var BorderGlow_default = BorderGlow;
725
+
726
+ // src/Components/MicButton.tsx
727
+ if (typeof document !== "undefined") {
728
+ const style = document.createElement("style");
729
+ style.textContent = `
730
+ @keyframes slideUp {
731
+ 0% { transform: translateY(100%); }
732
+ 100% { transform: translateY(0); }
733
+ }
734
+ @keyframes slideInUp {
735
+ 0% { transform: translateY(100%); }
736
+ 100% { transform: translateY(0); }
737
+ }
738
+ @keyframes pulse {
739
+ 0%, 100% { opacity: 1; }
740
+ 50% { opacity: 0.5; }
741
+ }
742
+ @keyframes ping {
743
+ 75%, 100% { transform: scale(2); opacity: 0; }
744
+ }
745
+ @keyframes bounce {
746
+ 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); }
747
+ 50% { transform: none; animation-timing-function: cubic-bezier(0, 0, 0.2, 1); }
748
+ }
749
+ `;
750
+ if (!document.head.querySelector("style[data-mic-button-animations]")) {
751
+ style.setAttribute("data-mic-button-animations", "true");
752
+ document.head.appendChild(style);
753
+ }
754
+ }
755
+ var WaveAnimation = () => {
756
+ return /* @__PURE__ */ React3.createElement("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none" }, /* @__PURE__ */ React3.createElement("div", { className: "absolute inset-0 flex items-center justify-center" }, /* @__PURE__ */ React3.createElement(
757
+ "div",
758
+ {
759
+ className: "absolute w-24 h-24 rounded-full border-2 border-blue-500 opacity-40",
760
+ style: {
761
+ animationDelay: "0s",
762
+ animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
763
+ }
764
+ }
765
+ ), /* @__PURE__ */ React3.createElement(
766
+ "div",
767
+ {
768
+ className: "absolute w-32 h-32 rounded-full border-2 border-cyan-400 opacity-30",
769
+ style: {
770
+ animationDelay: "0.5s",
771
+ animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
772
+ }
773
+ }
774
+ ), /* @__PURE__ */ React3.createElement(
775
+ "div",
776
+ {
777
+ className: "absolute w-40 h-40 rounded-full border-2 border-purple-400 opacity-20",
778
+ style: {
779
+ animationDelay: "1s",
780
+ animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
781
+ }
782
+ }
783
+ )));
784
+ };
785
+ var UnifiedModal = ({
786
+ text,
787
+ isVisible,
788
+ onClose,
789
+ mode = "response"
790
+ }) => {
791
+ const [displayedText, setDisplayedText] = useState4("");
792
+ const [currentWordIndex, setCurrentWordIndex] = useState4(0);
793
+ const [isAnimationComplete, setIsAnimationComplete] = useState4(false);
794
+ useEffect4(() => {
795
+ if (!isVisible || !text) {
796
+ setDisplayedText("");
797
+ setCurrentWordIndex(0);
798
+ setIsAnimationComplete(false);
799
+ return;
800
+ }
801
+ const words = text.split(" ");
802
+ setDisplayedText("");
803
+ setCurrentWordIndex(0);
804
+ setIsAnimationComplete(false);
805
+ const timer = setInterval(
806
+ () => {
807
+ setCurrentWordIndex((prev) => {
808
+ if (prev >= words.length - 1) {
809
+ clearInterval(timer);
810
+ setIsAnimationComplete(true);
811
+ return prev;
812
+ }
813
+ return prev + 1;
814
+ });
815
+ },
816
+ mode === "speech" ? 380 : 250
817
+ );
818
+ return () => clearInterval(timer);
819
+ }, [text, isVisible, mode]);
820
+ useEffect4(() => {
821
+ if (isVisible && text) {
822
+ const words = text.split(" ");
823
+ setDisplayedText(words.slice(0, currentWordIndex + 1).join(" "));
824
+ }
825
+ }, [currentWordIndex, text, isVisible]);
826
+ if (!isVisible) return null;
827
+ const isSpeechMode = mode === "speech";
828
+ const isComplete = currentWordIndex >= (text?.split(" ").length || 0) - 1;
829
+ return /* @__PURE__ */ React3.createElement("div", { className: "fixed inset-x-0 bottom-0 z-[500000] flex items-end justify-center" }, /* @__PURE__ */ React3.createElement("div", { className: "fixed inset-0 bg-black/20", onClick: onClose }), /* @__PURE__ */ React3.createElement(
830
+ "div",
831
+ {
832
+ className: "relative w-full max-w-sm mx-auto",
833
+ style: {
834
+ height: "30vh",
835
+ animation: "slideUp 0.3s ease-out forwards",
836
+ transform: "translateY(100%)"
837
+ }
838
+ },
839
+ /* @__PURE__ */ React3.createElement(
840
+ "div",
841
+ {
842
+ className: "absolute -top-4 -right-4 w-16 h-16 bg-gradient-to-br from-blue-500/40 to-blue-500/20 rounded-full blur-xl",
843
+ style: { animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite" }
844
+ }
845
+ ),
846
+ /* @__PURE__ */ React3.createElement(
847
+ "div",
848
+ {
849
+ className: "absolute -bottom-4 -left-4 w-12 h-12 bg-gradient-to-br from-blue-500/30 to-blue-500/10 rounded-full blur-lg",
850
+ style: {
851
+ animationDelay: "1s",
852
+ animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
853
+ }
854
+ }
855
+ ),
856
+ /* @__PURE__ */ React3.createElement("div", { className: "relative bg-white rounded-t-3xl border-t border-x border-blue-500/30 shadow-2xl h-full flex flex-col overflow-hidden" }, /* @__PURE__ */ React3.createElement("div", { className: "relative bg-gradient-to-r from-blue-500/10 via-blue-500/5 to-blue-500/10 p-4 border-b border-blue-500/20 flex-shrink-0" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React3.createElement("div", { className: "relative" }, /* @__PURE__ */ React3.createElement("div", { className: "w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-500 rounded-xl flex items-center justify-center shadow-lg" }, isSpeechMode ? /* @__PURE__ */ React3.createElement(Mic, { className: "w-4 h-4 text-white" }) : /* @__PURE__ */ React3.createElement(Sparkles, { className: "w-4 h-4 text-white" })), /* @__PURE__ */ React3.createElement(
857
+ "div",
858
+ {
859
+ className: "absolute -inset-1 bg-gradient-to-br from-blue-500 to-blue-500 rounded-xl opacity-30",
860
+ style: { animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite" }
861
+ }
862
+ )), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("h3", { className: "text-base font-semibold text-gray-800" }, isSpeechMode ? "Voice Input" : "AI Assistant"), /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-2 mt-0.5" }, isSpeechMode ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("div", { className: "flex gap-1" }, /* @__PURE__ */ React3.createElement(
863
+ "div",
864
+ {
865
+ className: "w-1 h-1 bg-blue-500 rounded-full",
866
+ style: {
867
+ animationDelay: "0ms",
868
+ animation: "bounce 1s infinite"
869
+ }
870
+ }
871
+ ), /* @__PURE__ */ React3.createElement(
872
+ "div",
873
+ {
874
+ className: "w-1 h-1 bg-blue-500 rounded-full",
875
+ style: {
876
+ animationDelay: "150ms",
877
+ animation: "bounce 1s infinite"
878
+ }
879
+ }
880
+ ), /* @__PURE__ */ React3.createElement(
881
+ "div",
882
+ {
883
+ className: "w-1 h-1 bg-blue-500 rounded-full",
884
+ style: {
885
+ animationDelay: "300ms",
886
+ animation: "bounce 1s infinite"
887
+ }
888
+ }
889
+ )), /* @__PURE__ */ React3.createElement("span", { className: "text-xs text-gray-600" }, "Listening...")) : /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Volume2, { className: "w-3 h-3 text-blue-500" }), /* @__PURE__ */ React3.createElement("span", { className: "text-xs text-gray-600" }, "Speaking..."))))), /* @__PURE__ */ React3.createElement(
890
+ "button",
891
+ {
892
+ onClick: onClose,
893
+ className: "w-8 h-8 rounded-xl bg-white/10 hover:bg-white/20 flex items-center justify-center transition-all duration-200 hover:scale-105"
894
+ },
895
+ /* @__PURE__ */ React3.createElement(X, { className: "w-4 h-4 text-gray-600" })
896
+ ))), /* @__PURE__ */ React3.createElement("div", { className: "flex-1 p-4 overflow-y-auto" }, /* @__PURE__ */ React3.createElement("div", { className: "min-h-[80px] flex items-center justify-center" }, displayedText ? /* @__PURE__ */ React3.createElement("div", { className: "w-full text-center" }, /* @__PURE__ */ React3.createElement("p", { className: "text-sm leading-relaxed font-light tracking-wide text-gray-800" }, displayedText, !isComplete && /* @__PURE__ */ React3.createElement(
897
+ "span",
898
+ {
899
+ className: "inline-block w-0.5 h-5 bg-gradient-to-t from-blue-500 to-blue-500 ml-2 align-text-bottom rounded-full",
900
+ style: { animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite" }
901
+ }
902
+ ))) : /* @__PURE__ */ React3.createElement("div", { className: "w-full text-center" }, /* @__PURE__ */ React3.createElement("p", { className: "text-sm text-gray-600 font-light italic" }, isSpeechMode ? "Start speaking..." : "Generating response...")))), /* @__PURE__ */ React3.createElement("div", { className: "px-4 pb-4 flex-shrink-0" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between text-xs text-gray-500" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React3.createElement(
903
+ "div",
904
+ {
905
+ className: "w-2 h-2 bg-blue-500 rounded-full",
906
+ style: { animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite" }
907
+ }
908
+ ), /* @__PURE__ */ React3.createElement("span", null, isSpeechMode ? "Listening for input..." : "Response delivered")), /* @__PURE__ */ React3.createElement("span", null, (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })))), /* @__PURE__ */ React3.createElement("div", { className: "absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-blue-500/50 to-transparent" }))
909
+ ));
910
+ };
911
+ var MicButton = ({
912
+ hasText = false,
913
+ text = "CueKit VC",
914
+ textStyle,
915
+ imageSource,
916
+ imageStyle,
917
+ buttonStyle,
918
+ screenPosition = "bottom-right",
919
+ buttonSize = 60
920
+ }) => {
921
+ const { isListening, start, stop, transcript } = useVoice();
922
+ const [micState, setMicState] = useState4("idle");
923
+ const [showModal, setShowModal] = useState4(false);
924
+ const [modalMode, setModalMode] = useState4("speech");
925
+ const [modalText, setModalText] = useState4("");
926
+ const [userTranscript, setUserTranscript] = useState4("");
927
+ const [isAudioOnlyResponse, setIsAudioOnlyResponse] = useState4(false);
928
+ const [showBodyGlow, setShowBodyGlow] = useState4(false);
929
+ const handlePress = async () => {
930
+ if (isListening) {
931
+ await stop();
932
+ setMicState("idle");
933
+ setShowModal(false);
934
+ setShowBodyGlow(false);
935
+ } else {
936
+ await start("Hello, how can I help you?");
937
+ setMicState("listening");
938
+ setShowModal(true);
939
+ setModalMode("speech");
940
+ setModalText("");
941
+ setUserTranscript("");
942
+ setIsAudioOnlyResponse(false);
943
+ setShowBodyGlow(true);
944
+ }
945
+ };
946
+ React3.useEffect(() => {
947
+ if (isListening && transcript) {
948
+ setUserTranscript(transcript);
949
+ setModalText(transcript);
950
+ }
951
+ }, [isListening, transcript]);
952
+ React3.useEffect(() => {
953
+ if (!isListening && transcript) {
954
+ const { apiKey, appId, deviceId } = globalThis.CuekitStore;
955
+ setMicState("thinking");
956
+ processVoice(transcript, apiKey, appId, deviceId).then((data) => {
957
+ if (data) {
958
+ setMicState("replying");
959
+ if (data.actionType === "audio_only") {
960
+ setIsAudioOnlyResponse(true);
961
+ setModalText(data.text || "Processing audio response...");
962
+ setModalMode("response");
963
+ setMicState("idle");
964
+ } else {
965
+ setIsAudioOnlyResponse(false);
966
+ setTimeout(() => {
967
+ setModalText(data.text || "Processing audio response...");
968
+ setModalMode("response");
969
+ setMicState("idle");
970
+ const words = data.text.split(" ");
971
+ const animationTime = words.length * 250 + 1e3;
972
+ setTimeout(() => {
973
+ setShowBodyGlow(false);
974
+ setShowModal(false);
975
+ }, animationTime);
976
+ }, 1500);
977
+ }
978
+ } else {
979
+ setMicState("replying");
980
+ setTimeout(() => {
981
+ const response = "I understand you said: " + transcript + ".";
982
+ setModalText(response);
983
+ setModalMode("response");
984
+ setMicState("idle");
985
+ const words = response.split(" ");
986
+ const animationTime = words.length * 250 + 1e3;
987
+ setTimeout(() => {
988
+ setShowModal(false);
989
+ setShowBodyGlow(false);
990
+ }, animationTime);
991
+ }, 1500);
992
+ }
993
+ }).catch((error) => {
994
+ console.error("Error processing voice:", error);
995
+ setMicState("idle");
996
+ setShowModal(false);
997
+ });
998
+ }
999
+ }, [isListening, transcript]);
1000
+ const getIcon = () => {
1001
+ switch (micState) {
1002
+ case "listening":
1003
+ return /* @__PURE__ */ React3.createElement(Mic, { className: "w-7 h-7 text-white drop-shadow-lg" });
1004
+ case "thinking":
1005
+ return /* @__PURE__ */ React3.createElement(Loader2, { className: "w-7 h-7 text-white drop-shadow-lg animate-spin" });
1006
+ case "replying":
1007
+ return /* @__PURE__ */ React3.createElement(Volume2, { className: "w-7 h-7 text-white drop-shadow-lg animate-pulse" });
1008
+ default:
1009
+ return /* @__PURE__ */ React3.createElement(Mic, { className: "w-7 h-7 text-white drop-shadow-lg" });
1010
+ }
1011
+ };
1012
+ const getButtonClasses = () => {
1013
+ switch (micState) {
1014
+ case "listening":
1015
+ return "scale-110 shadow-2xl shadow-blue-500/60";
1016
+ case "thinking":
1017
+ return "scale-105 shadow-xl shadow-purple-400/40 animate-pulse";
1018
+ case "replying":
1019
+ return "scale-105 shadow-xl shadow-cyan-400/40";
1020
+ default:
1021
+ return "hover:scale-105 shadow-lg shadow-blue-500/30 hover:shadow-xl hover:shadow-blue-500/40";
1022
+ }
1023
+ };
1024
+ const shouldShowWaves = micState === "listening" || micState === "replying";
1025
+ const getPositionStyle = () => {
1026
+ const positionStyles = {
1027
+ position: "fixed",
1028
+ // <-- Ensure button is fixed to the viewport
1029
+ bottom: 20,
1030
+ zIndex: 9999
1031
+ };
1032
+ switch (screenPosition) {
1033
+ case "bottom-center":
1034
+ return {
1035
+ ...positionStyles,
1036
+ left: "50%",
1037
+ transform: "translateX(-50%)"
1038
+ };
1039
+ case "bottom-left":
1040
+ return {
1041
+ ...positionStyles,
1042
+ left: 20
1043
+ };
1044
+ case "bottom-right":
1045
+ default:
1046
+ return {
1047
+ ...positionStyles,
1048
+ right: 20
1049
+ };
1050
+ }
1051
+ };
1052
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("div", { className: "relative flex items-center justify-center", style: getPositionStyle() }, shouldShowWaves && /* @__PURE__ */ React3.createElement(WaveAnimation, null), /* @__PURE__ */ React3.createElement(
1053
+ "button",
1054
+ {
1055
+ "data-testid": "ignore",
1056
+ onClick: handlePress,
1057
+ disabled: micState === "thinking" || micState === "replying",
1058
+ className: `relative flex items-center justify-center w-16 h-16 rounded-full bg-gradient-to-br from-blue-500 via-cyan-400 to-purple-400 transition-all duration-500 border-0 shadow-2xl transform hover:scale-105 ${getButtonClasses()}`,
1059
+ style: {
1060
+ ...buttonStyle
1061
+ },
1062
+ "aria-label": micState === "thinking" ? "Processing..." : micState === "replying" ? "AI is responding..." : isListening ? "Stop listening" : "Start listening"
1063
+ },
1064
+ /* @__PURE__ */ React3.createElement("div", { className: "relative z-10" }, getIcon()),
1065
+ micState === "listening" && /* @__PURE__ */ React3.createElement("div", { className: "absolute inset-0 rounded-full bg-blue-500 opacity-30 animate-ping" }),
1066
+ micState === "thinking" && /* @__PURE__ */ React3.createElement(
1067
+ "div",
1068
+ {
1069
+ className: "absolute inset-[-2px] rounded-full border-2 animate-pulse",
1070
+ style: {
1071
+ borderColor: "rgba(59, 130, 246, 0.6)",
1072
+ animationDuration: "2s"
1073
+ }
1074
+ }
1075
+ ),
1076
+ micState === "replying" && /* @__PURE__ */ React3.createElement("div", { className: "absolute inset-0 rounded-full bg-blue-500 opacity-25 animate-pulse" })
1077
+ )), showModal && typeof document !== "undefined" && createPortal(
1078
+ /* @__PURE__ */ React3.createElement(
1079
+ UnifiedModal,
1080
+ {
1081
+ text: modalText,
1082
+ isVisible: showModal,
1083
+ mode: modalMode,
1084
+ onClose: () => {
1085
+ setShowBodyGlow(false);
1086
+ if (isAudioOnlyResponse || modalMode === "speech") {
1087
+ setShowModal(false);
1088
+ setIsAudioOnlyResponse(false);
1089
+ if (modalMode === "speech") {
1090
+ stop();
1091
+ }
1092
+ } else {
1093
+ setShowModal(false);
1094
+ }
1095
+ }
1096
+ }
1097
+ ),
1098
+ document.body
1099
+ ), showBodyGlow && typeof document !== "undefined" && createPortal(/* @__PURE__ */ React3.createElement(BorderGlow_default, { isActive: true }), document.body));
1100
+ };
1101
+ MicButton.displayName = "MicButton";
1102
+ export {
1103
+ CuekitProvider,
1104
+ InitCuekit,
1105
+ MicButton,
1106
+ onStateChange,
1107
+ processVoice,
1108
+ useAudioController,
1109
+ useVoiceRecognition
1110
+ };