@guidekit/react 0.1.0-beta.1

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.cjs ADDED
@@ -0,0 +1,1233 @@
1
+ 'use strict';
2
+
3
+ var chunkEQGJ2LTR_cjs = require('./chunk-EQGJ2LTR.cjs');
4
+ var core = require('@guidekit/core');
5
+ var react = require('react');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ var SSR_SNAPSHOT = {
9
+ status: { isReady: false, agentState: { status: "idle" }, error: null },
10
+ voice: { isListening: false, isSpeaking: false }};
11
+ var SSR_SUBSCRIBE = (_listener) => () => {
12
+ };
13
+ function useGuideKitCore() {
14
+ return react.useContext(chunkEQGJ2LTR_cjs.GuideKitContext);
15
+ }
16
+ function GuideKitProvider(props) {
17
+ const {
18
+ children,
19
+ tokenEndpoint,
20
+ stt,
21
+ tts,
22
+ llm,
23
+ agent,
24
+ contentMap,
25
+ options,
26
+ theme,
27
+ locale: _locale,
28
+ instanceId,
29
+ rootElement,
30
+ onError,
31
+ onEvent,
32
+ onReady,
33
+ onBeforeLLMCall
34
+ } = props;
35
+ const coreRef = react.useRef(null);
36
+ const initCalled = react.useRef(false);
37
+ if (coreRef.current === null) {
38
+ const coreOptions = {
39
+ tokenEndpoint,
40
+ stt,
41
+ tts,
42
+ llm,
43
+ agent,
44
+ contentMap,
45
+ options,
46
+ instanceId,
47
+ rootElement,
48
+ onError,
49
+ onEvent,
50
+ onReady,
51
+ onBeforeLLMCall
52
+ };
53
+ coreRef.current = new core.GuideKitCore(coreOptions);
54
+ }
55
+ react.useEffect(() => {
56
+ const core = coreRef.current;
57
+ if (!core) return;
58
+ if (typeof window === "undefined") return;
59
+ if (initCalled.current) return;
60
+ initCalled.current = true;
61
+ core.init().catch((initErr) => {
62
+ if (options?.debug) {
63
+ console.error("[GuideKit:React] init() failed", initErr);
64
+ }
65
+ if (initErr && typeof initErr === "object" && "message" in initErr) {
66
+ onError?.(initErr);
67
+ }
68
+ });
69
+ return () => {
70
+ initCalled.current = false;
71
+ core.destroy().catch((destroyErr) => {
72
+ if (options?.debug) {
73
+ console.error("[GuideKit:React] destroy() failed", destroyErr);
74
+ }
75
+ });
76
+ };
77
+ }, []);
78
+ return /* @__PURE__ */ jsxRuntime.jsxs(chunkEQGJ2LTR_cjs.GuideKitContext.Provider, { value: coreRef.current, children: [
79
+ children,
80
+ /* @__PURE__ */ jsxRuntime.jsx(GuideKitWidget, { theme, consentRequired: options?.consentRequired, instanceId })
81
+ ] });
82
+ }
83
+ function useGuideKitStatus() {
84
+ const core = useGuideKitCore();
85
+ const subscribe = react.useCallback(
86
+ (listener) => core ? core.subscribe(listener) : SSR_SUBSCRIBE(),
87
+ [core]
88
+ );
89
+ const getSnapshot = react.useCallback(
90
+ () => core ? core.getSnapshot().status : SSR_SNAPSHOT.status,
91
+ [core]
92
+ );
93
+ return react.useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT.status);
94
+ }
95
+ function useGuideKitVoice() {
96
+ const core = useGuideKitCore();
97
+ const subscribe = react.useCallback(
98
+ (listener) => core ? core.subscribe(listener) : SSR_SUBSCRIBE(),
99
+ [core]
100
+ );
101
+ const getSnapshot = react.useCallback(
102
+ () => core ? core.getSnapshot().voice : SSR_SNAPSHOT.voice,
103
+ [core]
104
+ );
105
+ const voiceSlice = react.useSyncExternalStore(
106
+ subscribe,
107
+ getSnapshot,
108
+ () => SSR_SNAPSHOT.voice
109
+ );
110
+ const startListening = react.useCallback(() => {
111
+ if (core) {
112
+ core.startListening().catch((err) => {
113
+ console.error("[GuideKit] Failed to start listening:", err);
114
+ });
115
+ }
116
+ }, [core]);
117
+ const stopListening = react.useCallback(() => {
118
+ if (core) {
119
+ core.stopListening();
120
+ }
121
+ }, [core]);
122
+ const sendText = react.useCallback(
123
+ (text) => {
124
+ if (!core) {
125
+ return Promise.reject(
126
+ new Error("GuideKit not initialised. Wrap your app in <GuideKitProvider>.")
127
+ );
128
+ }
129
+ return core.sendText(text);
130
+ },
131
+ [core]
132
+ );
133
+ return {
134
+ ...voiceSlice,
135
+ startListening,
136
+ stopListening,
137
+ sendText
138
+ };
139
+ }
140
+ function useGuideKitActions() {
141
+ const core = useGuideKitCore();
142
+ const highlight = react.useCallback(
143
+ (sectionId, options) => {
144
+ core?.highlight({
145
+ sectionId,
146
+ selector: options?.selector,
147
+ tooltip: options?.tooltip,
148
+ position: options?.position
149
+ });
150
+ },
151
+ [core]
152
+ );
153
+ const dismissHighlight = react.useCallback(() => {
154
+ core?.dismissHighlight();
155
+ }, [core]);
156
+ const scrollToSection = react.useCallback(
157
+ (sectionId, offset) => {
158
+ core?.scrollToSection(sectionId, offset);
159
+ },
160
+ [core]
161
+ );
162
+ const startTour = react.useCallback(
163
+ (sectionIds, mode) => {
164
+ core?.startTour(sectionIds, mode);
165
+ },
166
+ [core]
167
+ );
168
+ const navigate = react.useCallback(
169
+ (href) => {
170
+ core?.navigate(href).catch((err) => {
171
+ console.error("[GuideKit] Navigation failed:", err);
172
+ });
173
+ },
174
+ [core]
175
+ );
176
+ return { highlight, dismissHighlight, scrollToSection, startTour, navigate };
177
+ }
178
+ function useGuideKitContext() {
179
+ const core = useGuideKitCore();
180
+ const setPageContext = react.useCallback(
181
+ (context) => {
182
+ core?.setPageContext(context);
183
+ },
184
+ [core]
185
+ );
186
+ const addContext = react.useCallback(
187
+ (key, value) => {
188
+ core?.setPageContext({ [key]: value });
189
+ },
190
+ [core]
191
+ );
192
+ const registerAction = react.useCallback(
193
+ (actionId, action) => {
194
+ core?.registerAction(actionId, action);
195
+ },
196
+ [core]
197
+ );
198
+ return { setPageContext, addContext, registerAction };
199
+ }
200
+ function useGuideKit() {
201
+ const status = useGuideKitStatus();
202
+ const voice = useGuideKitVoice();
203
+ const actions = useGuideKitActions();
204
+ const ctx = useGuideKitContext();
205
+ return {
206
+ ...status,
207
+ ...voice,
208
+ ...actions,
209
+ ...ctx
210
+ };
211
+ }
212
+ var WIDGET_STYLES = (
213
+ /* css */
214
+ `
215
+ :host {
216
+ --gk-primary: #6366f1;
217
+ --gk-primary-hover: #4f46e5;
218
+ --gk-primary-active: #4338ca;
219
+ --gk-bg: #ffffff;
220
+ --gk-bg-secondary: #f8fafc;
221
+ --gk-text: #1e293b;
222
+ --gk-text-secondary: #64748b;
223
+ --gk-border: #e2e8f0;
224
+ --gk-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
225
+ --gk-radius: 16px;
226
+ --gk-fab-size: 56px;
227
+ --gk-panel-width: 380px;
228
+ --gk-panel-height: 520px;
229
+ --gk-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
230
+
231
+ all: initial;
232
+ font-family: var(--gk-font);
233
+ position: fixed;
234
+ z-index: 2147483647;
235
+ bottom: 24px;
236
+ right: 24px;
237
+ }
238
+
239
+ @media (prefers-reduced-motion: reduce) {
240
+ *, *::before, *::after {
241
+ animation-duration: 0.01ms !important;
242
+ transition-duration: 0.01ms !important;
243
+ }
244
+ }
245
+
246
+ /* ----- Floating Action Button ----- */
247
+
248
+ .gk-fab {
249
+ width: var(--gk-fab-size);
250
+ height: var(--gk-fab-size);
251
+ border-radius: 50%;
252
+ border: none;
253
+ background: var(--gk-primary);
254
+ color: #fff;
255
+ cursor: pointer;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ box-shadow: 0 4px 16px rgba(99, 102, 241, 0.35);
260
+ transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.15s ease;
261
+ outline: none;
262
+ position: relative;
263
+ }
264
+
265
+ .gk-fab:hover {
266
+ background: var(--gk-primary-hover);
267
+ transform: scale(1.05);
268
+ box-shadow: 0 6px 24px rgba(99, 102, 241, 0.45);
269
+ }
270
+
271
+ .gk-fab:active {
272
+ background: var(--gk-primary-active);
273
+ transform: scale(0.97);
274
+ }
275
+
276
+ .gk-fab:focus-visible {
277
+ outline: 2px solid var(--gk-primary);
278
+ outline-offset: 3px;
279
+ }
280
+
281
+ .gk-fab svg {
282
+ width: 24px;
283
+ height: 24px;
284
+ fill: currentColor;
285
+ transition: transform 0.2s ease;
286
+ }
287
+
288
+ .gk-fab[aria-expanded="true"] svg {
289
+ transform: rotate(45deg);
290
+ }
291
+
292
+ /* ----- Panel ----- */
293
+
294
+ .gk-panel {
295
+ position: absolute;
296
+ bottom: calc(var(--gk-fab-size) + 16px);
297
+ right: 0;
298
+ width: var(--gk-panel-width);
299
+ height: var(--gk-panel-height);
300
+ background: var(--gk-bg);
301
+ border-radius: var(--gk-radius);
302
+ box-shadow: var(--gk-shadow);
303
+ display: flex;
304
+ flex-direction: column;
305
+ overflow: hidden;
306
+ opacity: 0;
307
+ transform: translateY(12px) scale(0.95);
308
+ pointer-events: none;
309
+ transition: opacity 0.2s ease, transform 0.2s ease;
310
+ }
311
+
312
+ .gk-panel[data-open="true"] {
313
+ opacity: 1;
314
+ transform: translateY(0) scale(1);
315
+ pointer-events: auto;
316
+ }
317
+
318
+ /* ----- Header ----- */
319
+
320
+ .gk-header {
321
+ display: flex;
322
+ align-items: center;
323
+ justify-content: space-between;
324
+ padding: 16px 20px;
325
+ border-bottom: 1px solid var(--gk-border);
326
+ background: var(--gk-bg);
327
+ flex-shrink: 0;
328
+ }
329
+
330
+ .gk-header-title {
331
+ font-size: 15px;
332
+ font-weight: 600;
333
+ color: var(--gk-text);
334
+ margin: 0;
335
+ }
336
+
337
+ .gk-header-status {
338
+ font-size: 12px;
339
+ color: var(--gk-text-secondary);
340
+ display: flex;
341
+ align-items: center;
342
+ gap: 6px;
343
+ }
344
+
345
+ .gk-status-dot {
346
+ width: 8px;
347
+ height: 8px;
348
+ border-radius: 50%;
349
+ background: #94a3b8;
350
+ flex-shrink: 0;
351
+ }
352
+
353
+ .gk-status-dot[data-ready="true"] {
354
+ background: #22c55e;
355
+ }
356
+
357
+ .gk-close-btn {
358
+ width: 28px;
359
+ height: 28px;
360
+ border-radius: 8px;
361
+ border: none;
362
+ background: transparent;
363
+ color: var(--gk-text-secondary);
364
+ cursor: pointer;
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: center;
368
+ transition: background 0.15s ease, color 0.15s ease;
369
+ outline: none;
370
+ flex-shrink: 0;
371
+ }
372
+
373
+ .gk-close-btn:hover {
374
+ background: var(--gk-bg-secondary);
375
+ color: var(--gk-text);
376
+ }
377
+
378
+ .gk-close-btn:focus-visible {
379
+ outline: 2px solid var(--gk-primary);
380
+ outline-offset: -2px;
381
+ }
382
+
383
+ .gk-close-btn svg {
384
+ width: 16px;
385
+ height: 16px;
386
+ fill: currentColor;
387
+ }
388
+
389
+ /* ----- Transcript ----- */
390
+
391
+ .gk-transcript {
392
+ flex: 1;
393
+ overflow-y: auto;
394
+ padding: 16px 20px;
395
+ display: flex;
396
+ flex-direction: column;
397
+ gap: 12px;
398
+ scroll-behavior: smooth;
399
+ }
400
+
401
+ .gk-transcript::-webkit-scrollbar {
402
+ width: 4px;
403
+ }
404
+
405
+ .gk-transcript::-webkit-scrollbar-thumb {
406
+ background: var(--gk-border);
407
+ border-radius: 2px;
408
+ }
409
+
410
+ .gk-empty-state {
411
+ flex: 1;
412
+ display: flex;
413
+ flex-direction: column;
414
+ align-items: center;
415
+ justify-content: center;
416
+ gap: 8px;
417
+ color: var(--gk-text-secondary);
418
+ text-align: center;
419
+ padding: 32px 16px;
420
+ }
421
+
422
+ .gk-empty-state-icon {
423
+ width: 40px;
424
+ height: 40px;
425
+ border-radius: 12px;
426
+ background: var(--gk-bg-secondary);
427
+ display: flex;
428
+ align-items: center;
429
+ justify-content: center;
430
+ margin-bottom: 4px;
431
+ }
432
+
433
+ .gk-empty-state-icon svg {
434
+ width: 20px;
435
+ height: 20px;
436
+ fill: var(--gk-text-secondary);
437
+ }
438
+
439
+ .gk-empty-state p {
440
+ margin: 0;
441
+ font-size: 13px;
442
+ line-height: 1.5;
443
+ }
444
+
445
+ /* ----- Message Bubbles ----- */
446
+
447
+ .gk-message {
448
+ max-width: 85%;
449
+ padding: 10px 14px;
450
+ border-radius: 12px;
451
+ font-size: 14px;
452
+ line-height: 1.5;
453
+ word-wrap: break-word;
454
+ white-space: pre-wrap;
455
+ }
456
+
457
+ .gk-message[data-role="user"] {
458
+ align-self: flex-end;
459
+ background: var(--gk-primary);
460
+ color: #fff;
461
+ border-bottom-right-radius: 4px;
462
+ }
463
+
464
+ .gk-message[data-role="assistant"] {
465
+ align-self: flex-start;
466
+ background: var(--gk-bg-secondary);
467
+ color: var(--gk-text);
468
+ border-bottom-left-radius: 4px;
469
+ }
470
+
471
+ /* ----- Processing indicator ----- */
472
+
473
+ .gk-processing {
474
+ align-self: flex-start;
475
+ display: flex;
476
+ gap: 4px;
477
+ padding: 12px 16px;
478
+ }
479
+
480
+ .gk-dot {
481
+ width: 6px;
482
+ height: 6px;
483
+ border-radius: 50%;
484
+ background: var(--gk-text-secondary);
485
+ animation: gk-bounce 1.4s ease-in-out infinite;
486
+ }
487
+
488
+ .gk-dot:nth-child(2) { animation-delay: 0.16s; }
489
+ .gk-dot:nth-child(3) { animation-delay: 0.32s; }
490
+
491
+ @keyframes gk-bounce {
492
+ 0%, 80%, 100% { transform: translateY(0); }
493
+ 40% { transform: translateY(-6px); }
494
+ }
495
+
496
+ /* ----- Input Area ----- */
497
+
498
+ .gk-input-area {
499
+ display: flex;
500
+ align-items: flex-end;
501
+ gap: 8px;
502
+ padding: 12px 16px;
503
+ border-top: 1px solid var(--gk-border);
504
+ background: var(--gk-bg);
505
+ flex-shrink: 0;
506
+ }
507
+
508
+ .gk-input {
509
+ flex: 1;
510
+ min-height: 40px;
511
+ max-height: 120px;
512
+ padding: 8px 14px;
513
+ border: 1px solid var(--gk-border);
514
+ border-radius: 12px;
515
+ background: var(--gk-bg);
516
+ color: var(--gk-text);
517
+ font-family: var(--gk-font);
518
+ font-size: 14px;
519
+ line-height: 1.5;
520
+ resize: none;
521
+ outline: none;
522
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
523
+ }
524
+
525
+ .gk-input::placeholder {
526
+ color: var(--gk-text-secondary);
527
+ }
528
+
529
+ .gk-input:focus {
530
+ border-color: var(--gk-primary);
531
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
532
+ }
533
+
534
+ .gk-send-btn {
535
+ width: 40px;
536
+ height: 40px;
537
+ border-radius: 12px;
538
+ border: none;
539
+ background: var(--gk-primary);
540
+ color: #fff;
541
+ cursor: pointer;
542
+ display: flex;
543
+ align-items: center;
544
+ justify-content: center;
545
+ flex-shrink: 0;
546
+ transition: background 0.15s ease, transform 0.1s ease;
547
+ outline: none;
548
+ }
549
+
550
+ .gk-send-btn:hover:not(:disabled) {
551
+ background: var(--gk-primary-hover);
552
+ }
553
+
554
+ .gk-send-btn:active:not(:disabled) {
555
+ transform: scale(0.93);
556
+ }
557
+
558
+ .gk-send-btn:disabled {
559
+ opacity: 0.5;
560
+ cursor: not-allowed;
561
+ }
562
+
563
+ .gk-send-btn:focus-visible {
564
+ outline: 2px solid var(--gk-primary);
565
+ outline-offset: 3px;
566
+ }
567
+
568
+ .gk-send-btn svg {
569
+ width: 18px;
570
+ height: 18px;
571
+ fill: currentColor;
572
+ }
573
+
574
+ /* ----- Mic Button ----- */
575
+
576
+ .gk-mic-btn {
577
+ width: 40px;
578
+ height: 40px;
579
+ border-radius: 12px;
580
+ border: none;
581
+ background: transparent;
582
+ color: var(--gk-text-secondary);
583
+ cursor: pointer;
584
+ display: flex;
585
+ align-items: center;
586
+ justify-content: center;
587
+ flex-shrink: 0;
588
+ transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease;
589
+ outline: none;
590
+ }
591
+
592
+ .gk-mic-btn:hover:not(:disabled) {
593
+ background: var(--gk-bg-secondary);
594
+ color: var(--gk-text);
595
+ }
596
+
597
+ .gk-mic-btn:active:not(:disabled) {
598
+ transform: scale(0.93);
599
+ }
600
+
601
+ .gk-mic-btn:disabled {
602
+ opacity: 0.4;
603
+ cursor: not-allowed;
604
+ }
605
+
606
+ .gk-mic-btn:focus-visible {
607
+ outline: 2px solid var(--gk-primary);
608
+ outline-offset: 3px;
609
+ }
610
+
611
+ .gk-mic-btn svg {
612
+ width: 20px;
613
+ height: 20px;
614
+ fill: currentColor;
615
+ }
616
+
617
+ .gk-mic-btn[data-active="true"] {
618
+ background: #fee2e2;
619
+ color: #dc2626;
620
+ }
621
+
622
+ .gk-mic-btn[data-active="true"]:hover {
623
+ background: #fecaca;
624
+ }
625
+
626
+ /* Pulse animation for active mic */
627
+ @keyframes gk-pulse {
628
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.4); }
629
+ 50% { box-shadow: 0 0 0 6px rgba(220, 38, 38, 0); }
630
+ }
631
+
632
+ .gk-mic-btn[data-active="true"] {
633
+ animation: gk-pulse 1.5s ease-in-out infinite;
634
+ }
635
+
636
+ /* ----- Voice Degraded Banner ----- */
637
+
638
+ .gk-voice-notice {
639
+ padding: 6px 16px;
640
+ background: #fffbeb;
641
+ color: #92400e;
642
+ font-size: 12px;
643
+ line-height: 1.4;
644
+ border-top: 1px solid #fde68a;
645
+ flex-shrink: 0;
646
+ display: flex;
647
+ align-items: center;
648
+ gap: 6px;
649
+ }
650
+
651
+ /* ----- Error Banner ----- */
652
+
653
+ .gk-error {
654
+ padding: 8px 16px;
655
+ background: #fef2f2;
656
+ color: #dc2626;
657
+ font-size: 12px;
658
+ line-height: 1.4;
659
+ border-top: 1px solid #fecaca;
660
+ flex-shrink: 0;
661
+ }
662
+
663
+ /* ----- Mobile Responsive: Bottom Sheet ----- */
664
+
665
+ @media (hover: none) and (pointer: coarse), (max-width: 768px) {
666
+ :host {
667
+ bottom: 16px !important;
668
+ right: 16px !important;
669
+ left: auto !important;
670
+ }
671
+
672
+ .gk-panel {
673
+ position: fixed;
674
+ bottom: 0;
675
+ left: 0;
676
+ right: 0;
677
+ width: 100%;
678
+ height: 70vh;
679
+ max-height: 70vh;
680
+ border-radius: var(--gk-radius) var(--gk-radius) 0 0;
681
+ transform: translateY(100%);
682
+ padding-bottom: env(safe-area-inset-bottom, 0px);
683
+ }
684
+
685
+ .gk-panel[data-open="true"] {
686
+ transform: translateY(0);
687
+ }
688
+
689
+ .gk-fab {
690
+ bottom: 16px;
691
+ right: 16px;
692
+ }
693
+
694
+ .gk-input-area {
695
+ padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
696
+ }
697
+
698
+ /* Touch targets min 44x44 */
699
+ .gk-send-btn,
700
+ .gk-mic-btn,
701
+ .gk-close-btn {
702
+ min-width: 44px;
703
+ min-height: 44px;
704
+ }
705
+ }
706
+
707
+ /* ----- Privacy Consent Dialog ----- */
708
+
709
+ .gk-consent-dialog {
710
+ position: absolute;
711
+ bottom: calc(var(--gk-fab-size) + 16px);
712
+ right: 0;
713
+ width: var(--gk-panel-width);
714
+ background: var(--gk-bg);
715
+ border-radius: var(--gk-radius);
716
+ box-shadow: var(--gk-shadow);
717
+ padding: 24px;
718
+ opacity: 0;
719
+ transform: translateY(12px) scale(0.95);
720
+ pointer-events: none;
721
+ transition: opacity 0.2s ease, transform 0.2s ease;
722
+ }
723
+
724
+ .gk-consent-dialog[data-open="true"] {
725
+ opacity: 1;
726
+ transform: translateY(0) scale(1);
727
+ pointer-events: auto;
728
+ }
729
+
730
+ .gk-consent-message {
731
+ font-size: 14px;
732
+ line-height: 1.6;
733
+ color: var(--gk-text);
734
+ margin: 0 0 20px 0;
735
+ }
736
+
737
+ .gk-consent-actions {
738
+ display: flex;
739
+ gap: 10px;
740
+ justify-content: flex-end;
741
+ }
742
+
743
+ .gk-consent-btn {
744
+ padding: 8px 20px;
745
+ border-radius: 10px;
746
+ font-family: var(--gk-font);
747
+ font-size: 14px;
748
+ font-weight: 500;
749
+ cursor: pointer;
750
+ transition: background 0.15s ease, transform 0.1s ease;
751
+ outline: none;
752
+ border: none;
753
+ }
754
+
755
+ .gk-consent-btn:focus-visible {
756
+ outline: 2px solid var(--gk-primary);
757
+ outline-offset: 2px;
758
+ }
759
+
760
+ .gk-consent-btn:active {
761
+ transform: scale(0.97);
762
+ }
763
+
764
+ .gk-consent-btn--decline {
765
+ background: var(--gk-bg-secondary);
766
+ color: var(--gk-text-secondary);
767
+ border: 1px solid var(--gk-border);
768
+ }
769
+
770
+ .gk-consent-btn--decline:hover {
771
+ background: var(--gk-border);
772
+ color: var(--gk-text);
773
+ }
774
+
775
+ .gk-consent-btn--accept {
776
+ background: var(--gk-primary);
777
+ color: #fff;
778
+ }
779
+
780
+ .gk-consent-btn--accept:hover {
781
+ background: var(--gk-primary-hover);
782
+ }
783
+
784
+ /* ----- High Contrast (Windows) ----- */
785
+
786
+ @media (forced-colors: active) {
787
+ .gk-fab,
788
+ .gk-send-btn,
789
+ .gk-mic-btn {
790
+ border: 2px solid ButtonText;
791
+ }
792
+
793
+ .gk-panel {
794
+ border: 1px solid ButtonText;
795
+ }
796
+
797
+ .gk-message[data-role="user"] {
798
+ border: 1px solid Highlight;
799
+ }
800
+
801
+ .gk-message[data-role="assistant"] {
802
+ border: 1px solid ButtonText;
803
+ }
804
+
805
+ .gk-consent-dialog {
806
+ border: 1px solid ButtonText;
807
+ }
808
+
809
+ .gk-consent-btn {
810
+ border: 2px solid ButtonText;
811
+ }
812
+ }
813
+ `
814
+ );
815
+ var ChatIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [
816
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z" }),
817
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 9h10v2H7zm0-3h10v2H7z" })
818
+ ] });
819
+ var CloseIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) });
820
+ var SendIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) });
821
+ var MicIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" }) });
822
+ var MicOffIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z" }) });
823
+ var SparkleIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2L9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2z" }) });
824
+ function GuideKitWidget({ theme, consentRequired, instanceId }) {
825
+ const core = useGuideKitCore();
826
+ const { isReady, agentState } = useGuideKitStatus();
827
+ const [isOpen, setIsOpen] = react.useState(false);
828
+ const [messages, setMessages] = react.useState([]);
829
+ const [inputValue, setInputValue] = react.useState("");
830
+ const [isSending, setIsSending] = react.useState(false);
831
+ const [isVoiceActive, setIsVoiceActive] = react.useState(false);
832
+ const consentStorageKey = `guidekit-consent:${instanceId ?? "default"}`;
833
+ const [hasConsent, setHasConsent] = react.useState(() => {
834
+ if (!consentRequired) return true;
835
+ if (typeof window === "undefined") return false;
836
+ try {
837
+ return localStorage.getItem(consentStorageKey) === "granted";
838
+ } catch {
839
+ return false;
840
+ }
841
+ });
842
+ const [showConsentDialog, setShowConsentDialog] = react.useState(false);
843
+ const shadowHostRef = react.useRef(null);
844
+ const shadowRootRef = react.useRef(null);
845
+ const shadowContainerRef = react.useRef(null);
846
+ const transcriptRef = react.useRef(null);
847
+ const inputRef = react.useRef(null);
848
+ const fabRef = react.useRef(null);
849
+ const msgIdRef = react.useRef(0);
850
+ const t = react.useCallback(
851
+ (key) => {
852
+ if (core) {
853
+ return core.i18n.t(key) ?? key;
854
+ }
855
+ return key;
856
+ },
857
+ [core]
858
+ );
859
+ const [shadowReady, setShadowReady] = react.useState(false);
860
+ react.useEffect(() => {
861
+ const host = shadowHostRef.current;
862
+ if (!host || shadowRootRef.current) return;
863
+ const shadow = host.attachShadow({ mode: "open" });
864
+ shadowRootRef.current = shadow;
865
+ const styleEl = document.createElement("style");
866
+ styleEl.textContent = WIDGET_STYLES;
867
+ shadow.appendChild(styleEl);
868
+ const container = document.createElement("div");
869
+ shadow.appendChild(container);
870
+ shadowContainerRef.current = container;
871
+ setShadowReady(true);
872
+ return () => {
873
+ shadowRootRef.current = null;
874
+ shadowContainerRef.current = null;
875
+ setShadowReady(false);
876
+ };
877
+ }, []);
878
+ react.useEffect(() => {
879
+ const container = shadowContainerRef.current;
880
+ if (!container) return;
881
+ const host = shadowHostRef.current;
882
+ if (!host) return;
883
+ if (theme?.primaryColor) {
884
+ host.style.setProperty("--gk-primary", theme.primaryColor);
885
+ }
886
+ if (theme?.borderRadius) {
887
+ host.style.setProperty("--gk-radius", theme.borderRadius);
888
+ }
889
+ const pos = theme?.position ?? "bottom-right";
890
+ host.style.removeProperty("top");
891
+ host.style.removeProperty("bottom");
892
+ host.style.removeProperty("left");
893
+ host.style.removeProperty("right");
894
+ switch (pos) {
895
+ case "bottom-right":
896
+ host.style.bottom = "24px";
897
+ host.style.right = "24px";
898
+ break;
899
+ case "bottom-left":
900
+ host.style.bottom = "24px";
901
+ host.style.left = "24px";
902
+ break;
903
+ case "top-right":
904
+ host.style.top = "24px";
905
+ host.style.right = "24px";
906
+ break;
907
+ case "top-left":
908
+ host.style.top = "24px";
909
+ host.style.left = "24px";
910
+ break;
911
+ }
912
+ }, [theme, shadowReady]);
913
+ react.useEffect(() => {
914
+ const el = transcriptRef.current;
915
+ if (el) {
916
+ el.scrollTop = el.scrollHeight;
917
+ }
918
+ }, [messages, isSending]);
919
+ react.useEffect(() => {
920
+ if (isOpen && inputRef.current) {
921
+ const timer = setTimeout(() => inputRef.current?.focus(), 100);
922
+ return () => clearTimeout(timer);
923
+ }
924
+ }, [isOpen]);
925
+ const handleSend = react.useCallback(async () => {
926
+ const text = inputValue.trim();
927
+ if (!text || !core || isSending) return;
928
+ const userMsg = {
929
+ id: `msg-${++msgIdRef.current}`,
930
+ role: "user",
931
+ content: text,
932
+ timestamp: Date.now()
933
+ };
934
+ setMessages((prev) => [...prev, userMsg]);
935
+ setInputValue("");
936
+ setIsSending(true);
937
+ try {
938
+ const response = await core.sendText(text);
939
+ const assistantMsg = {
940
+ id: `msg-${++msgIdRef.current}`,
941
+ role: "assistant",
942
+ content: response,
943
+ timestamp: Date.now()
944
+ };
945
+ setMessages((prev) => [...prev, assistantMsg]);
946
+ } catch (err) {
947
+ const errorContent = err instanceof Error ? err.message : "Something went wrong. Please try again.";
948
+ const errorMsg = {
949
+ id: `msg-${++msgIdRef.current}`,
950
+ role: "assistant",
951
+ content: `Error: ${errorContent}`,
952
+ timestamp: Date.now()
953
+ };
954
+ setMessages((prev) => [...prev, errorMsg]);
955
+ } finally {
956
+ setIsSending(false);
957
+ }
958
+ }, [inputValue, core, isSending]);
959
+ const handleMicToggle = react.useCallback(async () => {
960
+ if (!core) return;
961
+ if (isVoiceActive) {
962
+ core.stopListening();
963
+ setIsVoiceActive(false);
964
+ } else {
965
+ try {
966
+ await core.startListening();
967
+ setIsVoiceActive(true);
968
+ } catch (err) {
969
+ console.error("[GuideKit] Failed to start voice:", err);
970
+ setIsVoiceActive(false);
971
+ }
972
+ }
973
+ }, [core, isVoiceActive]);
974
+ const handleKeyDown = react.useCallback(
975
+ (e) => {
976
+ if (e.key === "Enter" && !e.shiftKey) {
977
+ e.preventDefault();
978
+ handleSend();
979
+ }
980
+ if (e.key === "Escape") {
981
+ setIsOpen(false);
982
+ fabRef.current?.focus();
983
+ }
984
+ },
985
+ [handleSend]
986
+ );
987
+ const handlePanelKeyDown = react.useCallback(
988
+ (e) => {
989
+ if (e.key === "Escape") {
990
+ setIsOpen(false);
991
+ fabRef.current?.focus();
992
+ }
993
+ },
994
+ []
995
+ );
996
+ const handleConsentAccept = react.useCallback(() => {
997
+ try {
998
+ localStorage.setItem(consentStorageKey, "granted");
999
+ } catch {
1000
+ }
1001
+ setHasConsent(true);
1002
+ setShowConsentDialog(false);
1003
+ setIsOpen(true);
1004
+ }, [consentStorageKey]);
1005
+ const handleConsentDecline = react.useCallback(() => {
1006
+ setShowConsentDialog(false);
1007
+ fabRef.current?.focus();
1008
+ }, []);
1009
+ const handleConsentKeyDown = react.useCallback(
1010
+ (e) => {
1011
+ if (e.key === "Escape") {
1012
+ setShowConsentDialog(false);
1013
+ fabRef.current?.focus();
1014
+ }
1015
+ },
1016
+ []
1017
+ );
1018
+ const togglePanel = react.useCallback(() => {
1019
+ if (consentRequired && !hasConsent) {
1020
+ setShowConsentDialog((prev) => !prev);
1021
+ return;
1022
+ }
1023
+ setIsOpen((prev) => !prev);
1024
+ }, [consentRequired, hasConsent]);
1025
+ const isProcessing = agentState.status === "processing";
1026
+ const hasVoice = core?.hasVoice ?? false;
1027
+ const isListeningState = agentState.status === "listening";
1028
+ const isSpeakingState = agentState.status === "speaking";
1029
+ react.useEffect(() => {
1030
+ if (!isListeningState && isVoiceActive) {
1031
+ if (!isSpeakingState && !isProcessing) {
1032
+ setIsVoiceActive(false);
1033
+ }
1034
+ }
1035
+ }, [isListeningState, isSpeakingState, isProcessing, isVoiceActive]);
1036
+ const statusLabel = isReady ? isListeningState ? t("statusListening") : isSpeakingState ? t("statusSpeaking") : t("statusOnline") : t("statusConnecting");
1037
+ const createPortalRef = react.useRef(null);
1038
+ const [portalReady, setPortalReady] = react.useState(false);
1039
+ react.useEffect(() => {
1040
+ if (typeof window === "undefined") return;
1041
+ import('react-dom').then((mod) => {
1042
+ createPortalRef.current = mod.createPortal;
1043
+ setPortalReady(true);
1044
+ });
1045
+ }, []);
1046
+ const widgetUI = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1047
+ /* @__PURE__ */ jsxRuntime.jsxs(
1048
+ "div",
1049
+ {
1050
+ className: "gk-panel",
1051
+ "data-open": isOpen ? "true" : "false",
1052
+ role: "dialog",
1053
+ "aria-label": t("widgetTitle"),
1054
+ "aria-hidden": !isOpen,
1055
+ onKeyDown: handlePanelKeyDown,
1056
+ children: [
1057
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gk-header", children: [
1058
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1059
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gk-header-title", children: t("widgetTitle") }),
1060
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gk-header-status", children: [
1061
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "gk-status-dot", "data-ready": isReady ? "true" : "false" }),
1062
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: statusLabel })
1063
+ ] })
1064
+ ] }),
1065
+ /* @__PURE__ */ jsxRuntime.jsx(
1066
+ "button",
1067
+ {
1068
+ className: "gk-close-btn",
1069
+ onClick: () => {
1070
+ setIsOpen(false);
1071
+ fabRef.current?.focus();
1072
+ },
1073
+ "aria-label": t("closePanel"),
1074
+ tabIndex: isOpen ? 0 : -1,
1075
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
1076
+ }
1077
+ )
1078
+ ] }),
1079
+ /* @__PURE__ */ jsxRuntime.jsx(
1080
+ "div",
1081
+ {
1082
+ className: "gk-transcript",
1083
+ ref: transcriptRef,
1084
+ role: "log",
1085
+ "aria-live": "polite",
1086
+ "aria-label": "Conversation transcript",
1087
+ children: messages.length === 0 && !isSending ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gk-empty-state", children: [
1088
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gk-empty-state-icon", children: /* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, {}) }),
1089
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: t("emptyStateMessage") })
1090
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1091
+ messages.map((msg) => /* @__PURE__ */ jsxRuntime.jsx(
1092
+ "div",
1093
+ {
1094
+ className: "gk-message",
1095
+ "data-role": msg.role,
1096
+ role: msg.role === "assistant" ? "status" : void 0,
1097
+ children: msg.content
1098
+ },
1099
+ msg.id
1100
+ )),
1101
+ isProcessing && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gk-processing", role: "status", "aria-label": "Processing", children: [
1102
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gk-dot" }),
1103
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gk-dot" }),
1104
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gk-dot" })
1105
+ ] })
1106
+ ] })
1107
+ }
1108
+ ),
1109
+ agentState.status === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "gk-error", role: "alert", children: agentState.error?.message ?? "An error occurred." }),
1110
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gk-input-area", children: [
1111
+ hasVoice && /* @__PURE__ */ jsxRuntime.jsx(
1112
+ "button",
1113
+ {
1114
+ className: "gk-mic-btn",
1115
+ onClick: handleMicToggle,
1116
+ disabled: !isReady || isSending,
1117
+ "data-active": isVoiceActive || isListeningState ? "true" : "false",
1118
+ "aria-label": isVoiceActive ? t("stopVoice") : t("startVoice"),
1119
+ tabIndex: isOpen ? 0 : -1,
1120
+ children: isVoiceActive || isListeningState ? /* @__PURE__ */ jsxRuntime.jsx(MicOffIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(MicIcon, {})
1121
+ }
1122
+ ),
1123
+ /* @__PURE__ */ jsxRuntime.jsx(
1124
+ "textarea",
1125
+ {
1126
+ className: "gk-input",
1127
+ ref: inputRef,
1128
+ value: inputValue,
1129
+ onChange: (e) => setInputValue(e.target.value),
1130
+ onKeyDown: handleKeyDown,
1131
+ placeholder: isListeningState ? t("listeningPlaceholder") : t("inputPlaceholder"),
1132
+ "aria-label": t("sendMessage"),
1133
+ rows: 1,
1134
+ disabled: !isReady || isSending,
1135
+ tabIndex: isOpen ? 0 : -1
1136
+ }
1137
+ ),
1138
+ /* @__PURE__ */ jsxRuntime.jsx(
1139
+ "button",
1140
+ {
1141
+ className: "gk-send-btn",
1142
+ onClick: handleSend,
1143
+ disabled: !isReady || isSending || !inputValue.trim(),
1144
+ "aria-label": t("sendMessage"),
1145
+ tabIndex: isOpen ? 0 : -1,
1146
+ children: /* @__PURE__ */ jsxRuntime.jsx(SendIcon, {})
1147
+ }
1148
+ )
1149
+ ] })
1150
+ ]
1151
+ }
1152
+ ),
1153
+ consentRequired && !hasConsent && /* @__PURE__ */ jsxRuntime.jsxs(
1154
+ "div",
1155
+ {
1156
+ className: "gk-consent-dialog",
1157
+ "data-open": showConsentDialog ? "true" : "false",
1158
+ role: "dialog",
1159
+ "aria-label": "Privacy consent",
1160
+ "aria-hidden": !showConsentDialog,
1161
+ onKeyDown: handleConsentKeyDown,
1162
+ children: [
1163
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "gk-consent-message", children: "This assistant uses AI to help you navigate this site. Your questions will be processed by an AI service." }),
1164
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "gk-consent-actions", children: [
1165
+ /* @__PURE__ */ jsxRuntime.jsx(
1166
+ "button",
1167
+ {
1168
+ className: "gk-consent-btn gk-consent-btn--decline",
1169
+ onClick: handleConsentDecline,
1170
+ tabIndex: showConsentDialog ? 0 : -1,
1171
+ children: "Decline"
1172
+ }
1173
+ ),
1174
+ /* @__PURE__ */ jsxRuntime.jsx(
1175
+ "button",
1176
+ {
1177
+ className: "gk-consent-btn gk-consent-btn--accept",
1178
+ onClick: handleConsentAccept,
1179
+ tabIndex: showConsentDialog ? 0 : -1,
1180
+ children: "Accept"
1181
+ }
1182
+ )
1183
+ ] })
1184
+ ]
1185
+ }
1186
+ ),
1187
+ /* @__PURE__ */ jsxRuntime.jsx(
1188
+ "button",
1189
+ {
1190
+ className: "gk-fab",
1191
+ ref: fabRef,
1192
+ onClick: togglePanel,
1193
+ "aria-label": isOpen ? t("closeAssistant") : t("openAssistant"),
1194
+ "aria-expanded": isOpen || showConsentDialog,
1195
+ "aria-haspopup": "dialog",
1196
+ children: isOpen ? /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(ChatIcon, {})
1197
+ }
1198
+ )
1199
+ ] });
1200
+ if (typeof window === "undefined") {
1201
+ return null;
1202
+ }
1203
+ return /* @__PURE__ */ jsxRuntime.jsx(
1204
+ "div",
1205
+ {
1206
+ ref: shadowHostRef,
1207
+ id: "guidekit-widget",
1208
+ style: {
1209
+ // The host element itself is positioned via :host in Shadow DOM CSS,
1210
+ // but we also set fixed positioning here as a fallback.
1211
+ position: "fixed",
1212
+ zIndex: 2147483647,
1213
+ bottom: "24px",
1214
+ right: "24px",
1215
+ // Ensure the host doesn't interfere with page layout
1216
+ margin: 0,
1217
+ padding: 0,
1218
+ border: "none",
1219
+ background: "none"
1220
+ },
1221
+ children: shadowReady && portalReady && shadowContainerRef.current && createPortalRef.current ? createPortalRef.current(widgetUI, shadowContainerRef.current) : null
1222
+ }
1223
+ );
1224
+ }
1225
+
1226
+ exports.GuideKitProvider = GuideKitProvider;
1227
+ exports.useGuideKit = useGuideKit;
1228
+ exports.useGuideKitActions = useGuideKitActions;
1229
+ exports.useGuideKitContext = useGuideKitContext;
1230
+ exports.useGuideKitStatus = useGuideKitStatus;
1231
+ exports.useGuideKitVoice = useGuideKitVoice;
1232
+ //# sourceMappingURL=index.cjs.map
1233
+ //# sourceMappingURL=index.cjs.map