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