@huskel/sdk 0.4.2 → 0.4.6
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.css +1045 -159
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +66 -28
- package/dist/index.d.ts +66 -28
- package/dist/index.js +887 -277
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +879 -272
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -39,6 +39,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
39
39
|
var index_exports = {};
|
|
40
40
|
__export(index_exports, {
|
|
41
41
|
AIChatButton: () => AIChatButton,
|
|
42
|
+
CartBadge: () => CartBadge,
|
|
43
|
+
CartDrawer: () => CartDrawer,
|
|
42
44
|
ChatWidget: () => ChatWidget,
|
|
43
45
|
HuskelAPI: () => HuskelAPI,
|
|
44
46
|
HuskelClient: () => HuskelClient,
|
|
@@ -47,6 +49,7 @@ __export(index_exports, {
|
|
|
47
49
|
Sparkle: () => Sparkle,
|
|
48
50
|
getHuskelClient: () => getHuskelClient,
|
|
49
51
|
initHuskel: () => initHuskel,
|
|
52
|
+
useCart: () => useCart,
|
|
50
53
|
useChat: () => useChat,
|
|
51
54
|
useHuskel: () => useHuskel,
|
|
52
55
|
useIngest: () => useIngest,
|
|
@@ -99,7 +102,15 @@ var HuskelAPI = class {
|
|
|
99
102
|
});
|
|
100
103
|
if (!res.ok) {
|
|
101
104
|
const text = await res.text();
|
|
102
|
-
|
|
105
|
+
let message = text;
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(text);
|
|
108
|
+
if (parsed && typeof parsed.error === "string") {
|
|
109
|
+
message = parsed.error;
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
}
|
|
113
|
+
const err = { status: res.status, message };
|
|
103
114
|
if (res.status >= 400 && res.status < 500) {
|
|
104
115
|
log("error", `${path} failed [${res.status}]`, text);
|
|
105
116
|
throw err;
|
|
@@ -150,6 +161,52 @@ var HuskelAPI = class {
|
|
|
150
161
|
log("info", "chat query", query);
|
|
151
162
|
return this.post("/chat", { query, siteId: this.siteId, history });
|
|
152
163
|
}
|
|
164
|
+
// --- Cart System ---
|
|
165
|
+
buildHeaders() {
|
|
166
|
+
var _a, _b;
|
|
167
|
+
const headers = {
|
|
168
|
+
"Content-Type": "application/json",
|
|
169
|
+
"X-Huskel-Token": this.apiToken,
|
|
170
|
+
"X-Huskel-Site": this.siteId
|
|
171
|
+
};
|
|
172
|
+
const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
|
|
173
|
+
if (shopperId) headers["X-Huskel-Shopper-Id"] = shopperId;
|
|
174
|
+
const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
|
|
175
|
+
if (sessionId) headers["X-Huskel-Session-Id"] = sessionId;
|
|
176
|
+
return headers;
|
|
177
|
+
}
|
|
178
|
+
async getCart() {
|
|
179
|
+
const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
|
|
180
|
+
headers: this.buildHeaders()
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) throw new Error("Failed to fetch cart");
|
|
183
|
+
return res.json();
|
|
184
|
+
}
|
|
185
|
+
async clearCart() {
|
|
186
|
+
const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
|
|
187
|
+
method: "DELETE",
|
|
188
|
+
headers: this.buildHeaders()
|
|
189
|
+
});
|
|
190
|
+
if (!res.ok) throw new Error("Failed to clear cart");
|
|
191
|
+
return res.json();
|
|
192
|
+
}
|
|
193
|
+
async checkoutCart() {
|
|
194
|
+
const res = await fetch(`${this.apiUrl}/cart/checkout`, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: this.buildHeaders(),
|
|
197
|
+
body: JSON.stringify({ siteId: this.siteId })
|
|
198
|
+
});
|
|
199
|
+
if (!res.ok) throw new Error("Failed to checkout cart");
|
|
200
|
+
return res.json();
|
|
201
|
+
}
|
|
202
|
+
async getCheckoutConfig() {
|
|
203
|
+
const res = await fetch(`${this.apiUrl}/checkout/config?site_id=${this.siteId}`, {
|
|
204
|
+
method: "GET",
|
|
205
|
+
headers: this.buildHeaders()
|
|
206
|
+
});
|
|
207
|
+
if (!res.ok) throw new Error("Failed to fetch checkout config");
|
|
208
|
+
return res.json();
|
|
209
|
+
}
|
|
153
210
|
};
|
|
154
211
|
|
|
155
212
|
// src/client.ts
|
|
@@ -257,13 +314,14 @@ var _HuskelClient = class _HuskelClient {
|
|
|
257
314
|
if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl="..."> or NEXT_PUBLIC_HUSKEL_API_URL.');
|
|
258
315
|
if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken="..."> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');
|
|
259
316
|
this.shopperId = config.shopperId;
|
|
317
|
+
this.onCheckout = config.onCheckout;
|
|
260
318
|
this.initSession();
|
|
261
319
|
this.loadIngestedCache();
|
|
262
320
|
this.api = new HuskelAPI(
|
|
263
321
|
apiUrl,
|
|
264
322
|
siteId,
|
|
265
323
|
apiToken,
|
|
266
|
-
() => this.
|
|
324
|
+
() => this.getShopperId(),
|
|
267
325
|
() => this.sessionId
|
|
268
326
|
);
|
|
269
327
|
instance = this;
|
|
@@ -300,11 +358,14 @@ var _HuskelClient = class _HuskelClient {
|
|
|
300
358
|
} catch (e) {
|
|
301
359
|
}
|
|
302
360
|
}
|
|
361
|
+
reRegister() {
|
|
362
|
+
instance = this;
|
|
363
|
+
}
|
|
303
364
|
setShopperId(id) {
|
|
304
365
|
this.shopperId = id;
|
|
305
366
|
}
|
|
306
367
|
getShopperId() {
|
|
307
|
-
return this.shopperId;
|
|
368
|
+
return this.shopperId || "guest_" + this.sessionId;
|
|
308
369
|
}
|
|
309
370
|
getSessionId() {
|
|
310
371
|
return this.sessionId;
|
|
@@ -433,11 +494,17 @@ function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }) {
|
|
|
433
494
|
const clientRef = (0, import_react2.useRef)(null);
|
|
434
495
|
if (!clientRef.current) {
|
|
435
496
|
clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });
|
|
497
|
+
} else {
|
|
498
|
+
clientRef.current.reRegister();
|
|
436
499
|
}
|
|
437
500
|
(0, import_react2.useEffect)(() => {
|
|
438
501
|
var _a;
|
|
439
502
|
(_a = clientRef.current) == null ? void 0 : _a.setShopperId(shopperId);
|
|
440
503
|
}, [shopperId]);
|
|
504
|
+
(0, import_react2.useEffect)(() => {
|
|
505
|
+
var _a;
|
|
506
|
+
(_a = clientRef.current) == null ? void 0 : _a.reRegister();
|
|
507
|
+
}, []);
|
|
441
508
|
(0, import_react2.useEffect)(() => {
|
|
442
509
|
return () => {
|
|
443
510
|
var _a;
|
|
@@ -469,6 +536,7 @@ function useSearch() {
|
|
|
469
536
|
return;
|
|
470
537
|
}
|
|
471
538
|
const gen = ++genRef.current;
|
|
539
|
+
setLoading(true);
|
|
472
540
|
setError(null);
|
|
473
541
|
try {
|
|
474
542
|
const res = await client.api.searchAutocomplete(query, limit);
|
|
@@ -477,7 +545,15 @@ function useSearch() {
|
|
|
477
545
|
}
|
|
478
546
|
} catch (e) {
|
|
479
547
|
if (gen === genRef.current) {
|
|
480
|
-
|
|
548
|
+
let msg = (_b = e == null ? void 0 : e.message) != null ? _b : "Search failed";
|
|
549
|
+
try {
|
|
550
|
+
const parsed = JSON.parse(msg);
|
|
551
|
+
if (parsed && parsed.error) {
|
|
552
|
+
msg = parsed.error;
|
|
553
|
+
}
|
|
554
|
+
} catch (e2) {
|
|
555
|
+
}
|
|
556
|
+
setError(msg);
|
|
481
557
|
}
|
|
482
558
|
} finally {
|
|
483
559
|
if (gen === genRef.current) setLoading(false);
|
|
@@ -552,13 +628,13 @@ function useChat() {
|
|
|
552
628
|
const [loading, setLoading] = (0, import_react6.useState)(false);
|
|
553
629
|
const [error, setError] = (0, import_react6.useState)(null);
|
|
554
630
|
const abortRef = (0, import_react6.useRef)(null);
|
|
555
|
-
const send = (0, import_react6.useCallback)(async (query) => {
|
|
556
|
-
var _a, _b, _c;
|
|
631
|
+
const send = (0, import_react6.useCallback)(async (query, displayQuery) => {
|
|
632
|
+
var _a, _b, _c, _d, _e;
|
|
557
633
|
if (!query.trim() || loading) return;
|
|
558
634
|
(_a = abortRef.current) == null ? void 0 : _a.abort();
|
|
559
635
|
abortRef.current = new AbortController();
|
|
560
636
|
const signal = abortRef.current.signal;
|
|
561
|
-
const userMsg = { role: "user", content: query };
|
|
637
|
+
const userMsg = { role: "user", content: displayQuery != null ? displayQuery : query };
|
|
562
638
|
setMessages((prev) => [...prev, userMsg]);
|
|
563
639
|
setLoading(true);
|
|
564
640
|
setError(null);
|
|
@@ -584,9 +660,30 @@ function useChat() {
|
|
|
584
660
|
}
|
|
585
661
|
if (signal.aborted) return;
|
|
586
662
|
setSources((_b = res.sources) != null ? _b : []);
|
|
663
|
+
if (((_c = res.action) == null ? void 0 : _c.type) === "add_to_cart" || res.checkout) {
|
|
664
|
+
if (typeof window !== "undefined") {
|
|
665
|
+
window.dispatchEvent(new CustomEvent("huskel:cart_updated", { detail: res.checkout }));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (((_d = res.action) == null ? void 0 : _d.type) === "checkout") {
|
|
669
|
+
if (typeof window !== "undefined") {
|
|
670
|
+
window.dispatchEvent(new CustomEvent("huskel:trigger_checkout", { detail: res.checkout }));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (res.checkout && client.onCheckout) {
|
|
674
|
+
client.onCheckout(res.checkout);
|
|
675
|
+
}
|
|
587
676
|
} catch (e) {
|
|
588
677
|
if (signal.aborted) return;
|
|
589
|
-
|
|
678
|
+
let msg = (_e = e == null ? void 0 : e.message) != null ? _e : "Chat request failed";
|
|
679
|
+
try {
|
|
680
|
+
const parsed = JSON.parse(msg);
|
|
681
|
+
if (parsed && parsed.error) {
|
|
682
|
+
msg = parsed.error;
|
|
683
|
+
}
|
|
684
|
+
} catch (e2) {
|
|
685
|
+
}
|
|
686
|
+
setError(msg);
|
|
590
687
|
setMessages((prev) => prev.slice(0, -1));
|
|
591
688
|
} finally {
|
|
592
689
|
if (!signal.aborted) {
|
|
@@ -605,8 +702,53 @@ function useChat() {
|
|
|
605
702
|
return { messages, sources, loading, error, send, reset };
|
|
606
703
|
}
|
|
607
704
|
|
|
608
|
-
// src/
|
|
705
|
+
// src/hooks/useCart.ts
|
|
609
706
|
var import_react7 = require("react");
|
|
707
|
+
function useCart() {
|
|
708
|
+
const client = useHuskelContext();
|
|
709
|
+
const [cart, setCart] = (0, import_react7.useState)(null);
|
|
710
|
+
const [loading, setLoading] = (0, import_react7.useState)(false);
|
|
711
|
+
const shopperId = client.getShopperId();
|
|
712
|
+
const fetchCart = (0, import_react7.useCallback)(async () => {
|
|
713
|
+
if (!shopperId) return;
|
|
714
|
+
setLoading(true);
|
|
715
|
+
try {
|
|
716
|
+
const res = await client.api.getCart();
|
|
717
|
+
setCart(res);
|
|
718
|
+
} catch (e) {
|
|
719
|
+
console.error("[Huskel] Failed to fetch cart", e);
|
|
720
|
+
} finally {
|
|
721
|
+
setLoading(false);
|
|
722
|
+
}
|
|
723
|
+
}, [client, shopperId]);
|
|
724
|
+
(0, import_react7.useEffect)(() => {
|
|
725
|
+
fetchCart();
|
|
726
|
+
const handleCartUpdate = (e) => {
|
|
727
|
+
if (e.detail) {
|
|
728
|
+
setCart(e.detail);
|
|
729
|
+
} else {
|
|
730
|
+
fetchCart();
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
if (typeof window !== "undefined") {
|
|
734
|
+
window.addEventListener("huskel:cart_updated", handleCartUpdate);
|
|
735
|
+
return () => window.removeEventListener("huskel:cart_updated", handleCartUpdate);
|
|
736
|
+
}
|
|
737
|
+
}, [fetchCart, shopperId]);
|
|
738
|
+
return { cart, loading, fetchCart };
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/components/SearchBar.tsx
|
|
742
|
+
var import_react8 = require("react");
|
|
743
|
+
|
|
744
|
+
// src/utils/cn.ts
|
|
745
|
+
var import_clsx = require("clsx");
|
|
746
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
747
|
+
function cn(...inputs) {
|
|
748
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/components/SearchBar.tsx
|
|
610
752
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
611
753
|
var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "15", height: "15", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
612
754
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
|
|
@@ -615,7 +757,7 @@ var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { wi
|
|
|
615
757
|
function SearchBar({
|
|
616
758
|
placeholder = "Search products\u2026",
|
|
617
759
|
limit = 10,
|
|
618
|
-
debounceMs =
|
|
760
|
+
debounceMs = 300,
|
|
619
761
|
onSelect,
|
|
620
762
|
className,
|
|
621
763
|
inputClassName,
|
|
@@ -624,25 +766,35 @@ function SearchBar({
|
|
|
624
766
|
theme,
|
|
625
767
|
classNames = {}
|
|
626
768
|
}) {
|
|
627
|
-
const [query, setQuery] = (0,
|
|
628
|
-
const [open, setOpen] = (0,
|
|
769
|
+
const [query, setQuery] = (0, import_react8.useState)("");
|
|
770
|
+
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
771
|
+
const [isDebouncing, setIsDebouncing] = (0, import_react8.useState)(false);
|
|
629
772
|
const { results, loading, search, clear } = useSearch();
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
(0,
|
|
773
|
+
const client = useHuskelContext();
|
|
774
|
+
const timer = (0, import_react8.useRef)();
|
|
775
|
+
const wrap = (0, import_react8.useRef)(null);
|
|
776
|
+
const ignoreNextQueryChange = (0, import_react8.useRef)(false);
|
|
777
|
+
(0, import_react8.useEffect)(() => {
|
|
778
|
+
if (ignoreNextQueryChange.current) {
|
|
779
|
+
ignoreNextQueryChange.current = false;
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
633
782
|
clearTimeout(timer.current);
|
|
634
783
|
if (!query.trim()) {
|
|
635
784
|
clear();
|
|
636
785
|
setOpen(false);
|
|
786
|
+
setIsDebouncing(false);
|
|
637
787
|
return;
|
|
638
788
|
}
|
|
639
789
|
setOpen(true);
|
|
790
|
+
setIsDebouncing(true);
|
|
640
791
|
timer.current = setTimeout(() => {
|
|
792
|
+
setIsDebouncing(false);
|
|
641
793
|
search(query, limit);
|
|
642
794
|
}, debounceMs);
|
|
643
795
|
return () => clearTimeout(timer.current);
|
|
644
796
|
}, [query]);
|
|
645
|
-
(0,
|
|
797
|
+
(0, import_react8.useEffect)(() => {
|
|
646
798
|
const h = (e) => {
|
|
647
799
|
if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
|
|
648
800
|
};
|
|
@@ -650,75 +802,220 @@ function SearchBar({
|
|
|
650
802
|
return () => document.removeEventListener("mousedown", h);
|
|
651
803
|
}, []);
|
|
652
804
|
const handleSelect = (r) => {
|
|
805
|
+
if (query.trim()) {
|
|
806
|
+
client.api.searchVector(query, 1).catch(() => {
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
ignoreNextQueryChange.current = true;
|
|
653
810
|
setOpen(false);
|
|
654
811
|
setQuery(r.product.name);
|
|
655
812
|
onSelect == null ? void 0 : onSelect(r);
|
|
656
813
|
};
|
|
814
|
+
const handleCommitSearch = () => {
|
|
815
|
+
if (!query.trim()) return;
|
|
816
|
+
client.api.searchVector(query, 1).catch(() => {
|
|
817
|
+
});
|
|
818
|
+
if (results.length > 0) {
|
|
819
|
+
handleSelect(results[0]);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
657
822
|
const showDrop = open && query.trim().length > 0;
|
|
658
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
659
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className:
|
|
823
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
824
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("hsk-sb-wrap", classNames.root, className), ref: wrap, style: customStyles, children: [
|
|
660
825
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
|
|
661
826
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
662
827
|
"input",
|
|
663
828
|
{
|
|
664
|
-
className:
|
|
829
|
+
className: cn("hsk-sb-input", classNames.input, inputClassName),
|
|
665
830
|
type: "text",
|
|
666
831
|
value: query,
|
|
667
832
|
placeholder,
|
|
668
833
|
onChange: (e) => setQuery(e.target.value),
|
|
669
834
|
onFocus: () => results.length > 0 && query.trim() && setOpen(true),
|
|
835
|
+
onKeyDown: (e) => {
|
|
836
|
+
if (e.key === "Enter") {
|
|
837
|
+
handleCommitSearch();
|
|
838
|
+
}
|
|
839
|
+
},
|
|
670
840
|
autoComplete: "off",
|
|
671
841
|
spellCheck: false
|
|
672
842
|
}
|
|
673
843
|
),
|
|
674
|
-
showDrop && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className:
|
|
675
|
-
loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
|
|
676
|
-
results.length === 0
|
|
677
|
-
"
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
{
|
|
686
|
-
|
|
687
|
-
className: "hsk-sb-
|
|
688
|
-
style: {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
"
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
844
|
+
showDrop && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
|
|
845
|
+
(loading || isDebouncing) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
|
|
846
|
+
(loading || isDebouncing) && results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
847
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-skeleton-row", children: [
|
|
848
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-skeleton-icon" }),
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
|
|
850
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text1" }),
|
|
851
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text2" })
|
|
852
|
+
] })
|
|
853
|
+
] }),
|
|
854
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-skeleton-row", children: [
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-skeleton-icon" }),
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
|
|
857
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text1", style: { width: "45%" } }),
|
|
858
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text2", style: { width: "25%" } })
|
|
859
|
+
] })
|
|
860
|
+
] })
|
|
861
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
862
|
+
results.length === 0 && !loading && !isDebouncing && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-empty", children: [
|
|
863
|
+
"No results for \u201C",
|
|
864
|
+
query,
|
|
865
|
+
"\u201D"
|
|
866
|
+
] }),
|
|
867
|
+
results.map((r, i) => {
|
|
868
|
+
var _a;
|
|
869
|
+
return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
870
|
+
"div",
|
|
871
|
+
{
|
|
872
|
+
onClick: () => handleSelect(r),
|
|
873
|
+
className: "hsk-sb-fade",
|
|
874
|
+
style: { animationDelay: `${i * 18}ms` },
|
|
875
|
+
children: renderResult(r)
|
|
876
|
+
},
|
|
877
|
+
r.id
|
|
878
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
879
|
+
"div",
|
|
880
|
+
{
|
|
881
|
+
className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
|
|
882
|
+
style: { animationDelay: `${i * 18}ms` },
|
|
883
|
+
onClick: () => handleSelect(r),
|
|
884
|
+
children: [
|
|
885
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
|
|
886
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
|
|
887
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-title", children: r.product.name }),
|
|
888
|
+
(r.product.category || r.product.brand) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
|
|
889
|
+
] })
|
|
890
|
+
]
|
|
891
|
+
},
|
|
892
|
+
r.id
|
|
893
|
+
);
|
|
894
|
+
})
|
|
895
|
+
] })
|
|
709
896
|
] })
|
|
710
897
|
] });
|
|
711
898
|
}
|
|
712
899
|
|
|
713
900
|
// src/components/Sparkle.tsx
|
|
714
|
-
var
|
|
901
|
+
var import_react9 = require("react");
|
|
715
902
|
var import_react_dom = require("react-dom");
|
|
903
|
+
|
|
904
|
+
// src/utils/markdown.tsx
|
|
716
905
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
717
|
-
var
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
906
|
+
var parseInline = (text, keyPrefix) => {
|
|
907
|
+
const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
|
|
908
|
+
const parts = text.split(tokenRegex);
|
|
909
|
+
return parts.map((part, index) => {
|
|
910
|
+
if (!part) return null;
|
|
911
|
+
const key = `${keyPrefix}-inline-${index}`;
|
|
912
|
+
if (part.startsWith("`") && part.endsWith("`")) {
|
|
913
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
|
|
914
|
+
}
|
|
915
|
+
if (part.startsWith("**") && part.endsWith("**")) {
|
|
916
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: parseInline(part.slice(2, -2), key) }, key);
|
|
917
|
+
}
|
|
918
|
+
const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
919
|
+
if (linkMatch) {
|
|
920
|
+
const url = linkMatch[2];
|
|
921
|
+
const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
|
|
922
|
+
if (isSafeUrl) {
|
|
923
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "hsk-markdown-link", children: parseInline(linkMatch[1], key) }, key);
|
|
924
|
+
}
|
|
925
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: parseInline(linkMatch[1], key) }, key);
|
|
926
|
+
}
|
|
927
|
+
return part;
|
|
928
|
+
});
|
|
929
|
+
};
|
|
930
|
+
function renderMarkdown(content) {
|
|
931
|
+
const lines = content.split("\n");
|
|
932
|
+
const elements = [];
|
|
933
|
+
let i = 0;
|
|
934
|
+
while (i < lines.length) {
|
|
935
|
+
const line = lines[i];
|
|
936
|
+
const key = `md-line-${i}`;
|
|
937
|
+
if (!line.trim()) {
|
|
938
|
+
i++;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
|
|
942
|
+
if (headerMatch) {
|
|
943
|
+
const level = headerMatch[1].length;
|
|
944
|
+
const Tag = `h${level + 3}`;
|
|
945
|
+
elements.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
|
|
946
|
+
i++;
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
if (line.match(/^[-*]\s+/)) {
|
|
950
|
+
const listItems = [];
|
|
951
|
+
while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
|
|
952
|
+
const itemText = lines[i].replace(/^[-*]\s+/, "");
|
|
953
|
+
listItems.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
|
|
954
|
+
i++;
|
|
955
|
+
}
|
|
956
|
+
elements.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (line.trim().startsWith("|")) {
|
|
960
|
+
const tableRows = [];
|
|
961
|
+
let isHeader = true;
|
|
962
|
+
while (i < lines.length && lines[i].trim().startsWith("|")) {
|
|
963
|
+
const rowLine = lines[i].trim();
|
|
964
|
+
if (rowLine.match(/^\|[-:| ]+\|$/)) {
|
|
965
|
+
i++;
|
|
966
|
+
isHeader = false;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
|
|
970
|
+
const Tag = isHeader ? "th" : "td";
|
|
971
|
+
tableRows.push(
|
|
972
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tr", { children: cells.map((cell, cIdx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Tag, { children: parseInline(cell, `td-${i}-${cIdx}`) }, `td-${i}-${cIdx}`)) }, `tr-${i}`)
|
|
973
|
+
);
|
|
974
|
+
i++;
|
|
975
|
+
}
|
|
976
|
+
elements.push(
|
|
977
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-table-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("table", { className: "hsk-markdown-table", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: tableRows }) }) }, `table-wrapper-${key}`)
|
|
978
|
+
);
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
elements.push(
|
|
982
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
|
|
983
|
+
);
|
|
984
|
+
i++;
|
|
985
|
+
}
|
|
986
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: elements });
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/components/Sparkle.tsx
|
|
990
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
991
|
+
var SparkleIcon = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }) });
|
|
992
|
+
var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
993
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
994
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
995
|
+
] });
|
|
996
|
+
var ArrowUpIcon = () => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m5 12 7-7 7 7" }),
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 19V5" })
|
|
721
999
|
] });
|
|
1000
|
+
var getFriendlyError = (err) => {
|
|
1001
|
+
let str = "";
|
|
1002
|
+
if (typeof err === "string") str = err;
|
|
1003
|
+
else if (err && typeof err === "object" && err.message) str = err.message;
|
|
1004
|
+
else try {
|
|
1005
|
+
str = JSON.stringify(err);
|
|
1006
|
+
} catch (e) {
|
|
1007
|
+
str = String(err);
|
|
1008
|
+
}
|
|
1009
|
+
if (str.toLowerCase().includes("token limit")) {
|
|
1010
|
+
return "You've reached your usage limit. Please update your billing limits in your dashboard to continue.";
|
|
1011
|
+
}
|
|
1012
|
+
try {
|
|
1013
|
+
const parsed = JSON.parse(str);
|
|
1014
|
+
return parsed.error || parsed.message || str;
|
|
1015
|
+
} catch (e) {
|
|
1016
|
+
return str;
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
722
1019
|
function SparkleModal({
|
|
723
1020
|
productName,
|
|
724
1021
|
limit,
|
|
@@ -728,26 +1025,42 @@ function SparkleModal({
|
|
|
728
1025
|
onNavigate,
|
|
729
1026
|
onResult,
|
|
730
1027
|
theme,
|
|
731
|
-
classNames = {}
|
|
1028
|
+
classNames = {},
|
|
1029
|
+
product: initialProduct
|
|
732
1030
|
}) {
|
|
733
|
-
|
|
734
|
-
const
|
|
735
|
-
(0,
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1031
|
+
var _a, _b, _c;
|
|
1032
|
+
const client = useHuskelContext();
|
|
1033
|
+
const [fetchedProduct, setFetchedProduct] = (0, import_react9.useState)(null);
|
|
1034
|
+
const displayProduct = initialProduct || fetchedProduct;
|
|
1035
|
+
const { results, loading: searchLoading, search } = useSearch();
|
|
1036
|
+
const { messages, sources, loading: chatLoading, error: chatError, send } = useChat();
|
|
1037
|
+
const [chatInput, setChatInput] = (0, import_react9.useState)("");
|
|
1038
|
+
const chatBottomRef = (0, import_react9.useRef)(null);
|
|
1039
|
+
const chatTextareaRef = (0, import_react9.useRef)(null);
|
|
1040
|
+
(0, import_react9.useEffect)(() => {
|
|
1041
|
+
if (!initialProduct && !fetchedProduct) {
|
|
1042
|
+
client.api.searchVector(productName, 1).then((res) => {
|
|
1043
|
+
if (res.results && res.results.length > 0) {
|
|
1044
|
+
setFetchedProduct(res.results[0].product);
|
|
1045
|
+
}
|
|
1046
|
+
}).catch((err) => console.error("[Huskel] Failed to fetch product details", err));
|
|
739
1047
|
}
|
|
740
|
-
|
|
741
|
-
|
|
1048
|
+
search(productName, limit);
|
|
1049
|
+
}, [productName, initialProduct, fetchedProduct, client, limit, search]);
|
|
1050
|
+
(0, import_react9.useEffect)(() => {
|
|
742
1051
|
if (results.length > 0) onResult == null ? void 0 : onResult(results);
|
|
743
|
-
}, [results]);
|
|
744
|
-
(0,
|
|
1052
|
+
}, [results, onResult]);
|
|
1053
|
+
(0, import_react9.useEffect)(() => {
|
|
745
1054
|
const h = (e) => {
|
|
746
1055
|
if (e.key === "Escape") onClose();
|
|
747
1056
|
};
|
|
748
1057
|
document.addEventListener("keydown", h);
|
|
749
1058
|
return () => document.removeEventListener("keydown", h);
|
|
750
|
-
}, []);
|
|
1059
|
+
}, [onClose]);
|
|
1060
|
+
(0, import_react9.useEffect)(() => {
|
|
1061
|
+
var _a2;
|
|
1062
|
+
(_a2 = chatBottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
1063
|
+
}, [messages, chatLoading]);
|
|
751
1064
|
const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "16px";
|
|
752
1065
|
const bg = backdropColor != null ? backdropColor : void 0;
|
|
753
1066
|
const handleNav = (r) => {
|
|
@@ -757,83 +1070,209 @@ function SparkleModal({
|
|
|
757
1070
|
if (r.product.url) window.location.href = r.product.url;
|
|
758
1071
|
}
|
|
759
1072
|
};
|
|
760
|
-
const
|
|
761
|
-
|
|
1073
|
+
const handleSend = async (text) => {
|
|
1074
|
+
const q = (text != null ? text : chatInput).trim();
|
|
1075
|
+
if (!q || chatLoading) return;
|
|
1076
|
+
setChatInput("");
|
|
1077
|
+
if (chatTextareaRef.current) {
|
|
1078
|
+
chatTextareaRef.current.style.height = "auto";
|
|
1079
|
+
}
|
|
1080
|
+
if (messages.length === 0 && displayProduct) {
|
|
1081
|
+
const contextQuery = `[Context: Shopper is viewing "${displayProduct.name}". Price: ${displayProduct.price}. Description: ${displayProduct.description || ""}]
|
|
1082
|
+
|
|
1083
|
+
Question: ${q}`;
|
|
1084
|
+
await send(contextQuery, q);
|
|
1085
|
+
} else {
|
|
1086
|
+
await send(q);
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
const handleKeyDown = (e) => {
|
|
1090
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
1091
|
+
e.preventDefault();
|
|
1092
|
+
handleSend();
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
const handleInput = (e) => {
|
|
1096
|
+
setChatInput(e.target.value);
|
|
1097
|
+
const t = e.target;
|
|
1098
|
+
t.style.height = "auto";
|
|
1099
|
+
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1100
|
+
};
|
|
1101
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1102
|
+
const displayMessages = messages.length === 0 && displayProduct ? [
|
|
1103
|
+
{
|
|
1104
|
+
role: "assistant",
|
|
1105
|
+
content: `Hi! I can help you with **${displayProduct.name}**. Ask me about its specifications, features, compare it with other options, or find alternatives!`
|
|
1106
|
+
}
|
|
1107
|
+
] : messages;
|
|
1108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
762
1109
|
"div",
|
|
763
1110
|
{
|
|
764
|
-
className:
|
|
1111
|
+
className: cn("hsk-sp-backdrop", classNames.backdrop),
|
|
765
1112
|
onClick: onClose,
|
|
766
1113
|
style: __spreadValues({
|
|
767
1114
|
backdropFilter: `blur(${blurVal})`,
|
|
768
1115
|
WebkitBackdropFilter: `blur(${blurVal})`,
|
|
769
1116
|
background: bg != null ? bg : void 0
|
|
770
1117
|
}, customStyles),
|
|
771
|
-
children: /* @__PURE__ */ (0,
|
|
772
|
-
/* @__PURE__ */ (0,
|
|
773
|
-
/* @__PURE__ */ (0,
|
|
774
|
-
/* @__PURE__ */ (0,
|
|
775
|
-
/* @__PURE__ */ (0,
|
|
776
|
-
|
|
777
|
-
productName,
|
|
778
|
-
"\u201D"
|
|
779
|
-
] }),
|
|
780
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-header-sub", children: "AI vector similarity \xB7 instant results" })
|
|
1118
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("hsk-sp-card hsk-sp-fullscreen", classNames.card), onClick: (e) => e.stopPropagation(), children: [
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-header", children: [
|
|
1120
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-header-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon, {}) }),
|
|
1121
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-header-body", children: [
|
|
1122
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-header-title", children: (displayProduct == null ? void 0 : displayProduct.name) || productName }),
|
|
1123
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-header-sub", children: "Ask questions, compare specs, or check similar products" })
|
|
781
1124
|
] }),
|
|
782
|
-
/* @__PURE__ */ (0,
|
|
1125
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CloseIcon, {}) })
|
|
783
1126
|
] }),
|
|
784
|
-
|
|
785
|
-
/* @__PURE__ */ (0,
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1127
|
+
searchLoading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-bar" }),
|
|
1128
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-body", children: [
|
|
1129
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-details-pane", children: [
|
|
1130
|
+
displayProduct && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-product-profile-container", children: [
|
|
1131
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-product-profile", children: [
|
|
1132
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-details-imgwrap", children: ((_a = displayProduct.images) == null ? void 0 : _a[0]) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: displayProduct.images[0], alt: displayProduct.name }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
|
|
1133
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-details-meta", children: [
|
|
1134
|
+
displayProduct.brand && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-brand", children: displayProduct.brand }),
|
|
1135
|
+
displayProduct.category && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-cat", children: displayProduct.category }),
|
|
1136
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "hsk-sp-details-name", children: displayProduct.name }),
|
|
1137
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
|
|
1138
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-currency", children: (_b = displayProduct.currency) != null ? _b : "KES" }),
|
|
1139
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-price", children: parseFloat(((_c = displayProduct.price) == null ? void 0 : _c.replace(/[^0-9.]/g, "")) || "0").toLocaleString() }),
|
|
1140
|
+
displayProduct.originalPrice && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-original-price", children: parseFloat(displayProduct.originalPrice.replace(/[^0-9.]/g, "") || "0").toLocaleString() }),
|
|
1141
|
+
displayProduct.discount && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-item-discount", children: [
|
|
1142
|
+
"(",
|
|
1143
|
+
displayProduct.discount,
|
|
1144
|
+
")"
|
|
1145
|
+
] })
|
|
1146
|
+
] }),
|
|
1147
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-meta-badges", children: [
|
|
1148
|
+
displayProduct.rating && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-rating", children: [
|
|
1149
|
+
"\u2605 ",
|
|
1150
|
+
parseFloat(displayProduct.rating.toString()).toFixed(1),
|
|
1151
|
+
" ",
|
|
1152
|
+
displayProduct.reviewCount ? `(${displayProduct.reviewCount})` : ""
|
|
804
1153
|
] }),
|
|
805
|
-
/* @__PURE__ */ (0,
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
className: "hsk-sp-action hsk-sp-action-primary",
|
|
810
|
-
onClick: () => handleNav(r),
|
|
811
|
-
children: "View Product"
|
|
812
|
-
}
|
|
813
|
-
),
|
|
814
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
815
|
-
"button",
|
|
816
|
-
{
|
|
817
|
-
className: "hsk-sp-action hsk-sp-action-secondary",
|
|
818
|
-
onClick: () => onClose(),
|
|
819
|
-
children: "Add to Cart"
|
|
820
|
-
}
|
|
821
|
-
)
|
|
1154
|
+
displayProduct.availability && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `hsk-sp-meta-badge hsk-sp-meta-badge-avail ${displayProduct.availability.toLowerCase().includes("in") ? "in-stock" : "out-stock"}`, children: displayProduct.availability }),
|
|
1155
|
+
displayProduct.stock && !displayProduct.availability && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-stock", children: [
|
|
1156
|
+
"Stock: ",
|
|
1157
|
+
displayProduct.stock
|
|
822
1158
|
] })
|
|
823
1159
|
] })
|
|
824
|
-
]
|
|
825
|
-
},
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1160
|
+
] })
|
|
1161
|
+
] }),
|
|
1162
|
+
displayProduct.specs && Object.keys(displayProduct.specs).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-specs-horizontal", children: Object.entries(displayProduct.specs).map(([key, val]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-spec-item-horizontal", children: [
|
|
1163
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-spec-label-horizontal", children: [
|
|
1164
|
+
key,
|
|
1165
|
+
":"
|
|
1166
|
+
] }),
|
|
1167
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-spec-value-horizontal", title: val, children: val })
|
|
1168
|
+
] }, key)) }),
|
|
1169
|
+
displayProduct.description && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-details-desc", children: [
|
|
1170
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Description" }),
|
|
1171
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: displayProduct.description })
|
|
1172
|
+
] })
|
|
1173
|
+
] }),
|
|
1174
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-similar-section", children: [
|
|
1175
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "Similar Products" }),
|
|
1176
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-results", children: (() => {
|
|
1177
|
+
const similarProducts = results.filter(
|
|
1178
|
+
(r) => {
|
|
1179
|
+
var _a2;
|
|
1180
|
+
const isSameName = r.product.name.toLowerCase() === ((_a2 = displayProduct == null ? void 0 : displayProduct.name) == null ? void 0 : _a2.toLowerCase());
|
|
1181
|
+
const isSameSlug = r.product.slug && (displayProduct == null ? void 0 : displayProduct.slug) && r.product.slug.toLowerCase() === displayProduct.slug.toLowerCase();
|
|
1182
|
+
return !isSameName && !isSameSlug;
|
|
1183
|
+
}
|
|
1184
|
+
);
|
|
1185
|
+
if (!searchLoading && similarProducts.length === 0) {
|
|
1186
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-empty", children: "No similar products found." });
|
|
1187
|
+
}
|
|
1188
|
+
return similarProducts.map((r, i) => {
|
|
1189
|
+
var _a2, _b2, _c2;
|
|
1190
|
+
const price = parseFloat(((_a2 = r.product.price) == null ? void 0 : _a2.replace(/[^0-9.]/g, "")) || "0");
|
|
1191
|
+
const currency = (_b2 = r.product.currency) != null ? _b2 : "KES";
|
|
1192
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1193
|
+
"div",
|
|
1194
|
+
{
|
|
1195
|
+
className: cn("hsk-sp-item", classNames.item),
|
|
1196
|
+
style: { animationDelay: `${i * 55}ms` },
|
|
1197
|
+
children: [
|
|
1198
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-img-wrap", children: ((_c2 = r.product.images) == null ? void 0 : _c2[0]) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
|
|
1199
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-body", children: [
|
|
1200
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
1201
|
+
r.product.category && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-item-cat", children: r.product.category }),
|
|
1202
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-item-name", title: r.product.name, children: r.product.name })
|
|
1203
|
+
] }),
|
|
1204
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
|
|
1205
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-currency", children: currency }),
|
|
1206
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
|
|
1207
|
+
] }),
|
|
1208
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-actions", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1209
|
+
"button",
|
|
1210
|
+
{
|
|
1211
|
+
className: "hsk-sp-action hsk-sp-action-primary",
|
|
1212
|
+
onClick: () => handleNav(r),
|
|
1213
|
+
children: "View"
|
|
1214
|
+
}
|
|
1215
|
+
) })
|
|
1216
|
+
] })
|
|
1217
|
+
]
|
|
1218
|
+
},
|
|
1219
|
+
r.id
|
|
1220
|
+
);
|
|
1221
|
+
});
|
|
1222
|
+
})() })
|
|
1223
|
+
] })
|
|
834
1224
|
] }),
|
|
835
|
-
/* @__PURE__ */ (0,
|
|
836
|
-
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-chat-pane", children: [
|
|
1226
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-msgs", children: [
|
|
1227
|
+
displayMessages.map((msg, idx) => {
|
|
1228
|
+
const isUser = msg.role === "user";
|
|
1229
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-msg-group", children: isUser ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-user-msg", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-user-bubble", children: msg.content }) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
|
|
1230
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon, {}) }),
|
|
1231
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-ai-text", children: renderMarkdown(msg.content) }) })
|
|
1232
|
+
] }) }, idx);
|
|
1233
|
+
}),
|
|
1234
|
+
chatLoading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-typing-row", children: [
|
|
1235
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon, {}) }),
|
|
1236
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-typing", children: [
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-dot" }),
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-dot" }),
|
|
1239
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-dot" })
|
|
1240
|
+
] })
|
|
1241
|
+
] }),
|
|
1242
|
+
chatError && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-error", children: getFriendlyError(chatError) }),
|
|
1243
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: chatBottomRef, style: { height: 1 } })
|
|
1244
|
+
] }),
|
|
1245
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-input-wrap", children: [
|
|
1246
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-input-box", children: [
|
|
1247
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1248
|
+
"textarea",
|
|
1249
|
+
{
|
|
1250
|
+
ref: chatTextareaRef,
|
|
1251
|
+
className: "hsk-cb-textarea",
|
|
1252
|
+
value: chatInput,
|
|
1253
|
+
onChange: handleInput,
|
|
1254
|
+
onKeyDown: handleKeyDown,
|
|
1255
|
+
placeholder: "Ask about this product, specs, or comparison...",
|
|
1256
|
+
rows: 1,
|
|
1257
|
+
disabled: chatLoading
|
|
1258
|
+
}
|
|
1259
|
+
),
|
|
1260
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1261
|
+
"button",
|
|
1262
|
+
{
|
|
1263
|
+
className: "hsk-cb-send",
|
|
1264
|
+
onClick: () => handleSend(),
|
|
1265
|
+
disabled: !chatInput.trim() || chatLoading,
|
|
1266
|
+
"aria-label": "Send message",
|
|
1267
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ArrowUpIcon, {})
|
|
1268
|
+
}
|
|
1269
|
+
)
|
|
1270
|
+
] }),
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-hint", children: "Huskel AI \xB7 instant product knowledge" })
|
|
1272
|
+
] })
|
|
1273
|
+
] })
|
|
1274
|
+
] }),
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-footer", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-esc", children: "Esc to close" }) })
|
|
837
1276
|
] })
|
|
838
1277
|
}
|
|
839
1278
|
);
|
|
@@ -847,39 +1286,41 @@ function Sparkle({
|
|
|
847
1286
|
className,
|
|
848
1287
|
onNavigate,
|
|
849
1288
|
theme,
|
|
850
|
-
classNames = {}
|
|
1289
|
+
classNames = {},
|
|
1290
|
+
product
|
|
851
1291
|
}) {
|
|
852
|
-
const [open, setOpen] = (0,
|
|
853
|
-
const [mounted, setMounted] = (0,
|
|
854
|
-
(0,
|
|
1292
|
+
const [open, setOpen] = (0, import_react9.useState)(false);
|
|
1293
|
+
const [mounted, setMounted] = (0, import_react9.useState)(false);
|
|
1294
|
+
(0, import_react9.useEffect)(() => {
|
|
855
1295
|
setMounted(true);
|
|
856
1296
|
}, []);
|
|
857
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
858
|
-
return /* @__PURE__ */ (0,
|
|
859
|
-
/* @__PURE__ */ (0,
|
|
1297
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1298
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1299
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
860
1300
|
"button",
|
|
861
1301
|
{
|
|
862
|
-
className:
|
|
1302
|
+
className: cn("hsk-sp-btn", classNames.button, className),
|
|
863
1303
|
onClick: () => setOpen(true),
|
|
864
1304
|
style: customStyles,
|
|
865
1305
|
title: "Find similar products",
|
|
866
1306
|
"aria-label": "Find similar products",
|
|
867
|
-
children: /* @__PURE__ */ (0,
|
|
1307
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon, {})
|
|
868
1308
|
}
|
|
869
1309
|
),
|
|
870
1310
|
open && mounted && (0, import_react_dom.createPortal)(
|
|
871
|
-
/* @__PURE__ */ (0,
|
|
1311
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
872
1312
|
SparkleModal,
|
|
873
1313
|
{
|
|
874
1314
|
productName,
|
|
875
1315
|
limit,
|
|
1316
|
+
onResult,
|
|
876
1317
|
backdropColor,
|
|
877
1318
|
backdropBlur,
|
|
878
1319
|
onClose: () => setOpen(false),
|
|
879
|
-
onResult,
|
|
880
1320
|
onNavigate,
|
|
881
1321
|
theme,
|
|
882
|
-
classNames
|
|
1322
|
+
classNames,
|
|
1323
|
+
product
|
|
883
1324
|
}
|
|
884
1325
|
),
|
|
885
1326
|
document.body
|
|
@@ -888,97 +1329,10 @@ function Sparkle({
|
|
|
888
1329
|
}
|
|
889
1330
|
|
|
890
1331
|
// src/components/ChatWidget.tsx
|
|
891
|
-
var
|
|
892
|
-
|
|
893
|
-
// src/utils/markdown.tsx
|
|
894
|
-
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
895
|
-
var parseInline = (text, keyPrefix) => {
|
|
896
|
-
const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
|
|
897
|
-
const parts = text.split(tokenRegex);
|
|
898
|
-
return parts.map((part, index) => {
|
|
899
|
-
if (!part) return null;
|
|
900
|
-
const key = `${keyPrefix}-inline-${index}`;
|
|
901
|
-
if (part.startsWith("`") && part.endsWith("`")) {
|
|
902
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
|
|
903
|
-
}
|
|
904
|
-
if (part.startsWith("**") && part.endsWith("**")) {
|
|
905
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: parseInline(part.slice(2, -2), key) }, key);
|
|
906
|
-
}
|
|
907
|
-
const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
908
|
-
if (linkMatch) {
|
|
909
|
-
const url = linkMatch[2];
|
|
910
|
-
const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
|
|
911
|
-
if (isSafeUrl) {
|
|
912
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "hsk-markdown-link", children: parseInline(linkMatch[1], key) }, key);
|
|
913
|
-
}
|
|
914
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: parseInline(linkMatch[1], key) }, key);
|
|
915
|
-
}
|
|
916
|
-
return part;
|
|
917
|
-
});
|
|
918
|
-
};
|
|
919
|
-
function renderMarkdown(content) {
|
|
920
|
-
const lines = content.split("\n");
|
|
921
|
-
const elements = [];
|
|
922
|
-
let i = 0;
|
|
923
|
-
while (i < lines.length) {
|
|
924
|
-
const line = lines[i];
|
|
925
|
-
const key = `md-line-${i}`;
|
|
926
|
-
if (!line.trim()) {
|
|
927
|
-
i++;
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
|
|
931
|
-
if (headerMatch) {
|
|
932
|
-
const level = headerMatch[1].length;
|
|
933
|
-
const Tag = `h${level + 3}`;
|
|
934
|
-
elements.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
|
|
935
|
-
i++;
|
|
936
|
-
continue;
|
|
937
|
-
}
|
|
938
|
-
if (line.match(/^[-*]\s+/)) {
|
|
939
|
-
const listItems = [];
|
|
940
|
-
while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
|
|
941
|
-
const itemText = lines[i].replace(/^[-*]\s+/, "");
|
|
942
|
-
listItems.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
|
|
943
|
-
i++;
|
|
944
|
-
}
|
|
945
|
-
elements.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
|
|
946
|
-
continue;
|
|
947
|
-
}
|
|
948
|
-
if (line.trim().startsWith("|")) {
|
|
949
|
-
const tableRows = [];
|
|
950
|
-
let isHeader = true;
|
|
951
|
-
while (i < lines.length && lines[i].trim().startsWith("|")) {
|
|
952
|
-
const rowLine = lines[i].trim();
|
|
953
|
-
if (rowLine.match(/^\|[-:| ]+\|$/)) {
|
|
954
|
-
i++;
|
|
955
|
-
isHeader = false;
|
|
956
|
-
continue;
|
|
957
|
-
}
|
|
958
|
-
const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
|
|
959
|
-
const Tag = isHeader ? "th" : "td";
|
|
960
|
-
tableRows.push(
|
|
961
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tr", { children: cells.map((cell, cIdx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Tag, { children: parseInline(cell, `td-${i}-${cIdx}`) }, `td-${i}-${cIdx}`)) }, `tr-${i}`)
|
|
962
|
-
);
|
|
963
|
-
i++;
|
|
964
|
-
}
|
|
965
|
-
elements.push(
|
|
966
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-table-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("table", { className: "hsk-markdown-table", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: tableRows }) }) }, `table-wrapper-${key}`)
|
|
967
|
-
);
|
|
968
|
-
continue;
|
|
969
|
-
}
|
|
970
|
-
elements.push(
|
|
971
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
|
|
972
|
-
);
|
|
973
|
-
i++;
|
|
974
|
-
}
|
|
975
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: elements });
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// src/components/ChatWidget.tsx
|
|
1332
|
+
var import_react10 = require("react");
|
|
979
1333
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
980
1334
|
var SparkleIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }) });
|
|
981
|
-
var
|
|
1335
|
+
var ArrowUpIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
982
1336
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m5 12 7-7 7 7" }),
|
|
983
1337
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19V5" })
|
|
984
1338
|
] });
|
|
@@ -1008,10 +1362,10 @@ function ChatWidget({
|
|
|
1008
1362
|
onSelectSource
|
|
1009
1363
|
}) {
|
|
1010
1364
|
const { messages, sources, loading, error, send, reset } = useChat();
|
|
1011
|
-
const [input, setInput] = (0,
|
|
1012
|
-
const bottomRef = (0,
|
|
1013
|
-
const textareaRef = (0,
|
|
1014
|
-
(0,
|
|
1365
|
+
const [input, setInput] = (0, import_react10.useState)("");
|
|
1366
|
+
const bottomRef = (0, import_react10.useRef)(null);
|
|
1367
|
+
const textareaRef = (0, import_react10.useRef)(null);
|
|
1368
|
+
(0, import_react10.useEffect)(() => {
|
|
1015
1369
|
var _a;
|
|
1016
1370
|
(_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
1017
1371
|
}, [messages, loading]);
|
|
@@ -1034,14 +1388,14 @@ function ChatWidget({
|
|
|
1034
1388
|
t.style.height = "auto";
|
|
1035
1389
|
t.style.height = Math.min(t.scrollHeight, 120) + "px";
|
|
1036
1390
|
};
|
|
1037
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
1391
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1038
1392
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1039
1393
|
"div",
|
|
1040
1394
|
{
|
|
1041
|
-
className:
|
|
1395
|
+
className: cn("hsk-chat-widget", classNames.root, className),
|
|
1042
1396
|
style: customStyles,
|
|
1043
1397
|
children: [
|
|
1044
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className:
|
|
1398
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("hsk-chat-header", classNames.header), children: [
|
|
1045
1399
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-chat-header-icon", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon2, {}) }),
|
|
1046
1400
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-chat-title", children: title }),
|
|
1047
1401
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-chat-badge", children: "AI" }),
|
|
@@ -1054,8 +1408,8 @@ function ChatWidget({
|
|
|
1054
1408
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-chat-empty-suggestions", children: emptyStateSuggestions })
|
|
1055
1409
|
] }) : messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
1056
1410
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `hsk-msg-row ${msg.role}`, children: [
|
|
1057
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className:
|
|
1058
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className:
|
|
1411
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn("hsk-msg-avatar", msg.role === "assistant" ? "ai" : "user"), children: msg.role === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon2, {}) : "U" }),
|
|
1412
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn("hsk-msg-bubble", msg.role, classNames.messageBubble), children: renderMarkdown(msg.content) })
|
|
1059
1413
|
] }),
|
|
1060
1414
|
msg.role === "assistant" && idx === messages.length - 1 && sources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources", children: sources.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) })
|
|
1061
1415
|
] }, idx)),
|
|
@@ -1067,7 +1421,14 @@ function ChatWidget({
|
|
|
1067
1421
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-typing-dot" })
|
|
1068
1422
|
] })
|
|
1069
1423
|
] }),
|
|
1070
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-chat-error", children:
|
|
1424
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-chat-error", children: (() => {
|
|
1425
|
+
try {
|
|
1426
|
+
const parsed = JSON.parse(error);
|
|
1427
|
+
return parsed.error || parsed.message || error;
|
|
1428
|
+
} catch (e) {
|
|
1429
|
+
return error;
|
|
1430
|
+
}
|
|
1431
|
+
})() }),
|
|
1071
1432
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: bottomRef })
|
|
1072
1433
|
] }),
|
|
1073
1434
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-chat-input-area", children: [
|
|
@@ -1075,7 +1436,7 @@ function ChatWidget({
|
|
|
1075
1436
|
"textarea",
|
|
1076
1437
|
{
|
|
1077
1438
|
ref: textareaRef,
|
|
1078
|
-
className:
|
|
1439
|
+
className: cn("hsk-chat-input", classNames.input),
|
|
1079
1440
|
value: input,
|
|
1080
1441
|
onChange: handleInput,
|
|
1081
1442
|
onKeyDown: handleKey,
|
|
@@ -1091,7 +1452,7 @@ function ChatWidget({
|
|
|
1091
1452
|
onClick: handleSend,
|
|
1092
1453
|
disabled: !input.trim() || loading,
|
|
1093
1454
|
"aria-label": "Send message",
|
|
1094
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1455
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ArrowUpIcon2, {})
|
|
1095
1456
|
}
|
|
1096
1457
|
)
|
|
1097
1458
|
] })
|
|
@@ -1101,11 +1462,11 @@ function ChatWidget({
|
|
|
1101
1462
|
}
|
|
1102
1463
|
|
|
1103
1464
|
// src/components/AIChatButton.tsx
|
|
1104
|
-
var
|
|
1465
|
+
var import_react11 = require("react");
|
|
1105
1466
|
var import_react_dom2 = require("react-dom");
|
|
1106
1467
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1107
1468
|
var SparkleIcon3 = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }) });
|
|
1108
|
-
var
|
|
1469
|
+
var ArrowUpIcon3 = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1109
1470
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m5 12 7-7 7 7" }),
|
|
1110
1471
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 19V5" })
|
|
1111
1472
|
] });
|
|
@@ -1121,15 +1482,15 @@ var DEFAULT_CHIPS = [
|
|
|
1121
1482
|
"Best laptop for students"
|
|
1122
1483
|
];
|
|
1123
1484
|
function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
1124
|
-
const railRef = (0,
|
|
1125
|
-
const [showNext, setShowNext] = (0,
|
|
1126
|
-
const measure = (0,
|
|
1485
|
+
const railRef = (0, import_react11.useRef)(null);
|
|
1486
|
+
const [showNext, setShowNext] = (0, import_react11.useState)(false);
|
|
1487
|
+
const measure = (0, import_react11.useCallback)(() => {
|
|
1127
1488
|
const el = railRef.current;
|
|
1128
1489
|
if (!el) return;
|
|
1129
1490
|
const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
|
|
1130
1491
|
setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
|
|
1131
1492
|
}, []);
|
|
1132
|
-
(0,
|
|
1493
|
+
(0, import_react11.useEffect)(() => {
|
|
1133
1494
|
measure();
|
|
1134
1495
|
const el = railRef.current;
|
|
1135
1496
|
if (!el) return;
|
|
@@ -1182,7 +1543,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
|
1182
1543
|
] });
|
|
1183
1544
|
}
|
|
1184
1545
|
function ChatModal({
|
|
1185
|
-
title = "
|
|
1546
|
+
title = "Shopping Assistant",
|
|
1186
1547
|
placeholder = "Ask me anything \u2014 gifts, budget, use case\u2026",
|
|
1187
1548
|
backdropColor,
|
|
1188
1549
|
backdropBlur,
|
|
@@ -1195,15 +1556,15 @@ function ChatModal({
|
|
|
1195
1556
|
}) {
|
|
1196
1557
|
var _a, _b;
|
|
1197
1558
|
const { messages, sources, loading, error, send, reset } = useChat();
|
|
1198
|
-
const [input, setInput] = (0,
|
|
1199
|
-
const [selectedProduct, setSelectedProduct] = (0,
|
|
1200
|
-
const bottomRef = (0,
|
|
1201
|
-
const textareaRef = (0,
|
|
1202
|
-
(0,
|
|
1559
|
+
const [input, setInput] = (0, import_react11.useState)("");
|
|
1560
|
+
const [selectedProduct, setSelectedProduct] = (0, import_react11.useState)(null);
|
|
1561
|
+
const bottomRef = (0, import_react11.useRef)(null);
|
|
1562
|
+
const textareaRef = (0, import_react11.useRef)(null);
|
|
1563
|
+
(0, import_react11.useEffect)(() => {
|
|
1203
1564
|
var _a2;
|
|
1204
1565
|
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
1205
1566
|
}, [messages, loading, selectedProduct]);
|
|
1206
|
-
(0,
|
|
1567
|
+
(0, import_react11.useEffect)(() => {
|
|
1207
1568
|
const h = (e) => {
|
|
1208
1569
|
if (e.key === "Escape") onClose();
|
|
1209
1570
|
};
|
|
@@ -1240,24 +1601,21 @@ function ChatModal({
|
|
|
1240
1601
|
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1241
1602
|
};
|
|
1242
1603
|
const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "20px";
|
|
1243
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
1604
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1244
1605
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1245
1606
|
"div",
|
|
1246
1607
|
{
|
|
1247
|
-
className:
|
|
1608
|
+
className: cn("hsk-cb-overlay", classNames.overlay),
|
|
1248
1609
|
onClick: onClose,
|
|
1249
1610
|
style: __spreadValues(__spreadValues({
|
|
1250
1611
|
backdropFilter: `blur(${blurVal})`,
|
|
1251
1612
|
WebkitBackdropFilter: `blur(${blurVal})`
|
|
1252
1613
|
}, backdropColor ? { background: backdropColor } : {}), customStyles),
|
|
1253
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className:
|
|
1614
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cn("hsk-cb-panel", classNames.panel), onClick: (e) => e.stopPropagation(), children: [
|
|
1254
1615
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-topbar", children: [
|
|
1255
1616
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-topbar-left", children: [
|
|
1256
1617
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hsk-cb-topbar-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SparkleIcon3, {}) }),
|
|
1257
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.
|
|
1258
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-topbar-title", children: title }),
|
|
1259
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-topbar-sub", children: "Powered by Huskel AI \xB7 searches the whole catalogue" })
|
|
1260
|
-
] })
|
|
1618
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-topbar-title", children: title }) })
|
|
1261
1619
|
] }),
|
|
1262
1620
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-topbar-actions", children: [
|
|
1263
1621
|
messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "hsk-cb-topbar-btn", onClick: reset, children: "Clear chat" }),
|
|
@@ -1267,8 +1625,7 @@ function ChatModal({
|
|
|
1267
1625
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-msgs", children: [
|
|
1268
1626
|
messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-empty", children: [
|
|
1269
1627
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-empty-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SparkleIcon3, {}) }),
|
|
1270
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-empty-title", children: "
|
|
1271
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-empty-sub", children: "Ask about products, budgets, gift ideas, specs \u2014 I'll search the entire catalogue for you." }),
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-empty-title", children: "Find exactly what you need" }),
|
|
1272
1629
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-chips", children: chips.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1273
1630
|
"button",
|
|
1274
1631
|
{
|
|
@@ -1331,7 +1688,7 @@ function ChatModal({
|
|
|
1331
1688
|
"textarea",
|
|
1332
1689
|
{
|
|
1333
1690
|
ref: textareaRef,
|
|
1334
|
-
className:
|
|
1691
|
+
className: cn("hsk-cb-textarea", classNames.input),
|
|
1335
1692
|
value: input,
|
|
1336
1693
|
onChange: handleInput,
|
|
1337
1694
|
onKeyDown: handleKeyDown,
|
|
@@ -1344,11 +1701,11 @@ function ChatModal({
|
|
|
1344
1701
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1345
1702
|
"button",
|
|
1346
1703
|
{
|
|
1347
|
-
className:
|
|
1704
|
+
className: cn("hsk-cb-send", classNames.sendButton),
|
|
1348
1705
|
onClick: () => handleSend(),
|
|
1349
1706
|
disabled: !input.trim() || loading,
|
|
1350
1707
|
"aria-label": "Send message",
|
|
1351
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1708
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ArrowUpIcon3, {})
|
|
1352
1709
|
}
|
|
1353
1710
|
)
|
|
1354
1711
|
] }),
|
|
@@ -1371,17 +1728,17 @@ function AIChatButton({
|
|
|
1371
1728
|
theme,
|
|
1372
1729
|
classNames = {}
|
|
1373
1730
|
}) {
|
|
1374
|
-
const [open, setOpen] = (0,
|
|
1375
|
-
const [mounted, setMounted] = (0,
|
|
1376
|
-
(0,
|
|
1731
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
1732
|
+
const [mounted, setMounted] = (0, import_react11.useState)(false);
|
|
1733
|
+
(0, import_react11.useEffect)(() => {
|
|
1377
1734
|
setMounted(true);
|
|
1378
1735
|
}, []);
|
|
1379
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
1736
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1380
1737
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1381
1738
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1382
1739
|
"button",
|
|
1383
1740
|
{
|
|
1384
|
-
className:
|
|
1741
|
+
className: cn("hsk-cb-btn", classNames.button, className),
|
|
1385
1742
|
onClick: () => setOpen(true),
|
|
1386
1743
|
style: customStyles,
|
|
1387
1744
|
"aria-label": "Open AI chat",
|
|
@@ -1411,9 +1768,261 @@ function AIChatButton({
|
|
|
1411
1768
|
)
|
|
1412
1769
|
] });
|
|
1413
1770
|
}
|
|
1771
|
+
|
|
1772
|
+
// src/components/CartBadge.tsx
|
|
1773
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1774
|
+
function CartBadge({ className }) {
|
|
1775
|
+
const { cart } = useCart();
|
|
1776
|
+
if (!cart || cart.item_count === 0) return null;
|
|
1777
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: cn("hsk-cart-badge", className), children: cart.item_count });
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// src/components/CartDrawer.tsx
|
|
1781
|
+
var import_react13 = require("react");
|
|
1782
|
+
var import_react_dom4 = require("react-dom");
|
|
1783
|
+
|
|
1784
|
+
// src/components/CheckoutModal.tsx
|
|
1785
|
+
var import_react12 = require("react");
|
|
1786
|
+
var import_react_dom3 = require("react-dom");
|
|
1787
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1788
|
+
function CheckoutModal({
|
|
1789
|
+
onClose,
|
|
1790
|
+
theme,
|
|
1791
|
+
customStyles,
|
|
1792
|
+
hskThemeAttr
|
|
1793
|
+
}) {
|
|
1794
|
+
var _a, _b, _c, _d;
|
|
1795
|
+
const { cart, loading: cartLoading } = useCart();
|
|
1796
|
+
const client = useHuskelContext();
|
|
1797
|
+
const [config, setConfig] = (0, import_react12.useState)(null);
|
|
1798
|
+
const [loading, setLoading] = (0, import_react12.useState)(true);
|
|
1799
|
+
const [checkingOut, setCheckingOut] = (0, import_react12.useState)(false);
|
|
1800
|
+
const [paymentSuccess, setPaymentSuccess] = (0, import_react12.useState)(false);
|
|
1801
|
+
(0, import_react12.useEffect)(() => {
|
|
1802
|
+
client.api.getCheckoutConfig().then((res) => setConfig(res.payment_methods)).catch((e) => console.error("[Huskel] Failed to fetch checkout config", e)).finally(() => setLoading(false));
|
|
1803
|
+
}, [client]);
|
|
1804
|
+
const handlePay = async (method) => {
|
|
1805
|
+
setCheckingOut(true);
|
|
1806
|
+
setTimeout(async () => {
|
|
1807
|
+
try {
|
|
1808
|
+
const payload = await client.api.checkoutCart();
|
|
1809
|
+
if (client.onCheckout) {
|
|
1810
|
+
client.onCheckout(payload);
|
|
1811
|
+
}
|
|
1812
|
+
setPaymentSuccess(true);
|
|
1813
|
+
setTimeout(() => {
|
|
1814
|
+
onClose();
|
|
1815
|
+
}, 3e3);
|
|
1816
|
+
} catch (e) {
|
|
1817
|
+
console.error("[Huskel] Checkout failed", e);
|
|
1818
|
+
setCheckingOut(false);
|
|
1819
|
+
}
|
|
1820
|
+
}, 1500);
|
|
1821
|
+
};
|
|
1822
|
+
const hasPaymentMethods = config && Object.values(config).some((m) => m.enabled);
|
|
1823
|
+
return (0, import_react_dom3.createPortal)(
|
|
1824
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1825
|
+
"div",
|
|
1826
|
+
{
|
|
1827
|
+
className: "hsk-cart-backdrop",
|
|
1828
|
+
style: __spreadProps(__spreadValues({}, customStyles), { zIndex: 999999 }),
|
|
1829
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1830
|
+
onClick: onClose,
|
|
1831
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1832
|
+
"div",
|
|
1833
|
+
{
|
|
1834
|
+
className: "hsk-checkout-modal",
|
|
1835
|
+
style: customStyles,
|
|
1836
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1837
|
+
onClick: (e) => e.stopPropagation(),
|
|
1838
|
+
children: [
|
|
1839
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-header", children: [
|
|
1840
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { children: "Secure Checkout" }),
|
|
1841
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: onClose, className: "hsk-close-btn", children: "\xD7" })
|
|
1842
|
+
] }),
|
|
1843
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-checkout-content", children: paymentSuccess ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-success", children: [
|
|
1844
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "hsk-success-icon", children: [
|
|
1845
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
|
|
1846
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polyline", { points: "22 4 12 14.01 9 11.01" })
|
|
1847
|
+
] }),
|
|
1848
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: "Payment Successful!" }),
|
|
1849
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: "Thank you for your order." })
|
|
1850
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-split", children: [
|
|
1851
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-summary", children: [
|
|
1852
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: "Order Summary" }),
|
|
1853
|
+
cartLoading || !cart ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "hsk-cart-loading", children: "Loading order..." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
1854
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "hsk-checkout-items", children: cart.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { children: [
|
|
1855
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
1856
|
+
item.quantity,
|
|
1857
|
+
"x ",
|
|
1858
|
+
item.name
|
|
1859
|
+
] }),
|
|
1860
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
1861
|
+
item.currency,
|
|
1862
|
+
" ",
|
|
1863
|
+
(item.price_numeric * item.quantity).toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1864
|
+
] })
|
|
1865
|
+
] }, item.id)) }),
|
|
1866
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-total", children: [
|
|
1867
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Total" }),
|
|
1868
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
1869
|
+
cart.currency,
|
|
1870
|
+
" ",
|
|
1871
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1872
|
+
] })
|
|
1873
|
+
] })
|
|
1874
|
+
] })
|
|
1875
|
+
] }),
|
|
1876
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-payment", children: [
|
|
1877
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: "Payment Method" }),
|
|
1878
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "hsk-cart-loading", children: "Loading secure payment methods..." }) : !hasPaymentMethods ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "hsk-checkout-error", children: "No payment methods are currently available for this store." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-payment-options", children: [
|
|
1879
|
+
((_a = config == null ? void 0 : config.mpesa) == null ? void 0 : _a.enabled) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: () => handlePay("mpesa"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-mpesa", children: checkingOut ? "Processing..." : "Pay with M-Pesa" }),
|
|
1880
|
+
((_b = config == null ? void 0 : config.equity) == null ? void 0 : _b.enabled) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: () => handlePay("equity"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-equity", children: checkingOut ? "Processing..." : "Pay with Equity Bank" }),
|
|
1881
|
+
((_c = config == null ? void 0 : config.stripe) == null ? void 0 : _c.enabled) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: () => handlePay("stripe"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-stripe", children: checkingOut ? "Processing..." : "Pay with Card (Stripe)" }),
|
|
1882
|
+
((_d = config == null ? void 0 : config.paypal) == null ? void 0 : _d.enabled) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: () => handlePay("paypal"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-paypal", children: checkingOut ? "Processing..." : "Pay with PayPal" })
|
|
1883
|
+
] })
|
|
1884
|
+
] })
|
|
1885
|
+
] }) })
|
|
1886
|
+
]
|
|
1887
|
+
}
|
|
1888
|
+
)
|
|
1889
|
+
}
|
|
1890
|
+
),
|
|
1891
|
+
document.body
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// src/components/CartDrawer.tsx
|
|
1896
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1897
|
+
function CartDrawer({
|
|
1898
|
+
trigger,
|
|
1899
|
+
className,
|
|
1900
|
+
theme
|
|
1901
|
+
}) {
|
|
1902
|
+
const { cart, loading } = useCart();
|
|
1903
|
+
const [open, setOpen] = (0, import_react13.useState)(false);
|
|
1904
|
+
const [showCheckout, setShowCheckout] = (0, import_react13.useState)(false);
|
|
1905
|
+
const [mounted, setMounted] = (0, import_react13.useState)(false);
|
|
1906
|
+
const client = useHuskelContext();
|
|
1907
|
+
(0, import_react13.useEffect)(() => {
|
|
1908
|
+
setMounted(true);
|
|
1909
|
+
const handleTriggerCheckout = () => {
|
|
1910
|
+
setShowCheckout(true);
|
|
1911
|
+
setOpen(false);
|
|
1912
|
+
};
|
|
1913
|
+
window.addEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1914
|
+
return () => {
|
|
1915
|
+
window.removeEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1916
|
+
};
|
|
1917
|
+
}, []);
|
|
1918
|
+
(0, import_react13.useEffect)(() => {
|
|
1919
|
+
if (open) {
|
|
1920
|
+
document.body.style.overflow = "hidden";
|
|
1921
|
+
} else {
|
|
1922
|
+
document.body.style.overflow = "";
|
|
1923
|
+
}
|
|
1924
|
+
return () => {
|
|
1925
|
+
document.body.style.overflow = "";
|
|
1926
|
+
};
|
|
1927
|
+
}, [open]);
|
|
1928
|
+
const handleCheckout = async () => {
|
|
1929
|
+
if (!cart || cart.items.length === 0) return;
|
|
1930
|
+
setShowCheckout(true);
|
|
1931
|
+
};
|
|
1932
|
+
const isStringTheme = typeof theme === "string";
|
|
1933
|
+
const hskThemeAttr = isStringTheme ? theme : void 0;
|
|
1934
|
+
const customStyles = !isStringTheme && theme ? __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor, "--hsk-primary-color": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius }) : void 0;
|
|
1935
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
1936
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { onClick: () => setOpen(true), style: { display: "inline-block" }, children: trigger || /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1937
|
+
"button",
|
|
1938
|
+
{
|
|
1939
|
+
className: cn("hsk-cart-trigger", className),
|
|
1940
|
+
style: customStyles,
|
|
1941
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1942
|
+
"aria-label": "Open cart",
|
|
1943
|
+
children: [
|
|
1944
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1945
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "9", cy: "21", r: "1" }),
|
|
1946
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "20", cy: "21", r: "1" }),
|
|
1947
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" })
|
|
1948
|
+
] }),
|
|
1949
|
+
cart && cart.item_count > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hsk-cart-trigger-badge", children: cart.item_count }) : null
|
|
1950
|
+
]
|
|
1951
|
+
}
|
|
1952
|
+
) }),
|
|
1953
|
+
open && mounted && (0, import_react_dom4.createPortal)(
|
|
1954
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1955
|
+
"div",
|
|
1956
|
+
{
|
|
1957
|
+
className: "hsk-cart-backdrop",
|
|
1958
|
+
style: customStyles,
|
|
1959
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1960
|
+
onClick: () => setOpen(false),
|
|
1961
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1962
|
+
"div",
|
|
1963
|
+
{
|
|
1964
|
+
className: "hsk-cart-bottom-sheet",
|
|
1965
|
+
style: customStyles,
|
|
1966
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1967
|
+
onClick: (e) => e.stopPropagation(),
|
|
1968
|
+
children: [
|
|
1969
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hsk-cart-sheet-handle" }),
|
|
1970
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-sheet-header", children: [
|
|
1971
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { children: "Your Cart" }),
|
|
1972
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => setOpen(false), className: "hsk-close-btn", children: "\xD7" })
|
|
1973
|
+
] }),
|
|
1974
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hsk-cart-sheet-content", children: loading && !cart ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hsk-cart-loading", children: "Loading cart..." }) : !cart || cart.items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hsk-cart-empty", children: "Your cart is empty." }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { className: "hsk-cart-items", children: cart.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("li", { className: "hsk-cart-item", children: [
|
|
1975
|
+
item.image && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("img", { src: item.image, alt: item.name, className: "hsk-cart-item-img" }),
|
|
1976
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-item-info", children: [
|
|
1977
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hsk-cart-item-name", children: item.name }),
|
|
1978
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "hsk-cart-item-price", children: [
|
|
1979
|
+
item.currency,
|
|
1980
|
+
" ",
|
|
1981
|
+
item.price_numeric.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1982
|
+
] })
|
|
1983
|
+
] }),
|
|
1984
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-item-qty", children: [
|
|
1985
|
+
"x",
|
|
1986
|
+
item.quantity
|
|
1987
|
+
] })
|
|
1988
|
+
] }, item.id)) }) }),
|
|
1989
|
+
cart && cart.items.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-sheet-footer", children: [
|
|
1990
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-total", children: [
|
|
1991
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Total" }),
|
|
1992
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
|
|
1993
|
+
cart.currency,
|
|
1994
|
+
" ",
|
|
1995
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1996
|
+
] })
|
|
1997
|
+
] }),
|
|
1998
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: handleCheckout, className: "hsk-checkout-btn", children: "Checkout securely" })
|
|
1999
|
+
] })
|
|
2000
|
+
]
|
|
2001
|
+
}
|
|
2002
|
+
)
|
|
2003
|
+
}
|
|
2004
|
+
),
|
|
2005
|
+
document.body
|
|
2006
|
+
),
|
|
2007
|
+
showCheckout && mounted && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2008
|
+
CheckoutModal,
|
|
2009
|
+
{
|
|
2010
|
+
onClose: () => {
|
|
2011
|
+
setShowCheckout(false);
|
|
2012
|
+
setOpen(false);
|
|
2013
|
+
},
|
|
2014
|
+
theme: isStringTheme ? theme : void 0,
|
|
2015
|
+
customStyles,
|
|
2016
|
+
hskThemeAttr
|
|
2017
|
+
}
|
|
2018
|
+
)
|
|
2019
|
+
] });
|
|
2020
|
+
}
|
|
1414
2021
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1415
2022
|
0 && (module.exports = {
|
|
1416
2023
|
AIChatButton,
|
|
2024
|
+
CartBadge,
|
|
2025
|
+
CartDrawer,
|
|
1417
2026
|
ChatWidget,
|
|
1418
2027
|
HuskelAPI,
|
|
1419
2028
|
HuskelClient,
|
|
@@ -1422,6 +2031,7 @@ function AIChatButton({
|
|
|
1422
2031
|
Sparkle,
|
|
1423
2032
|
getHuskelClient,
|
|
1424
2033
|
initHuskel,
|
|
2034
|
+
useCart,
|
|
1425
2035
|
useChat,
|
|
1426
2036
|
useHuskel,
|
|
1427
2037
|
useIngest,
|