@huskel/sdk 0.4.2 → 0.4.3
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 +1008 -159
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +64 -27
- package/dist/index.d.ts +64 -27
- package/dist/index.js +798 -243
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +786 -234
- 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;
|
|
@@ -304,7 +362,7 @@ var _HuskelClient = class _HuskelClient {
|
|
|
304
362
|
this.shopperId = id;
|
|
305
363
|
}
|
|
306
364
|
getShopperId() {
|
|
307
|
-
return this.shopperId;
|
|
365
|
+
return this.shopperId || "guest_" + this.sessionId;
|
|
308
366
|
}
|
|
309
367
|
getSessionId() {
|
|
310
368
|
return this.sessionId;
|
|
@@ -477,7 +535,15 @@ function useSearch() {
|
|
|
477
535
|
}
|
|
478
536
|
} catch (e) {
|
|
479
537
|
if (gen === genRef.current) {
|
|
480
|
-
|
|
538
|
+
let msg = (_b = e == null ? void 0 : e.message) != null ? _b : "Search failed";
|
|
539
|
+
try {
|
|
540
|
+
const parsed = JSON.parse(msg);
|
|
541
|
+
if (parsed && parsed.error) {
|
|
542
|
+
msg = parsed.error;
|
|
543
|
+
}
|
|
544
|
+
} catch (e2) {
|
|
545
|
+
}
|
|
546
|
+
setError(msg);
|
|
481
547
|
}
|
|
482
548
|
} finally {
|
|
483
549
|
if (gen === genRef.current) setLoading(false);
|
|
@@ -552,13 +618,13 @@ function useChat() {
|
|
|
552
618
|
const [loading, setLoading] = (0, import_react6.useState)(false);
|
|
553
619
|
const [error, setError] = (0, import_react6.useState)(null);
|
|
554
620
|
const abortRef = (0, import_react6.useRef)(null);
|
|
555
|
-
const send = (0, import_react6.useCallback)(async (query) => {
|
|
556
|
-
var _a, _b, _c;
|
|
621
|
+
const send = (0, import_react6.useCallback)(async (query, displayQuery) => {
|
|
622
|
+
var _a, _b, _c, _d, _e;
|
|
557
623
|
if (!query.trim() || loading) return;
|
|
558
624
|
(_a = abortRef.current) == null ? void 0 : _a.abort();
|
|
559
625
|
abortRef.current = new AbortController();
|
|
560
626
|
const signal = abortRef.current.signal;
|
|
561
|
-
const userMsg = { role: "user", content: query };
|
|
627
|
+
const userMsg = { role: "user", content: displayQuery != null ? displayQuery : query };
|
|
562
628
|
setMessages((prev) => [...prev, userMsg]);
|
|
563
629
|
setLoading(true);
|
|
564
630
|
setError(null);
|
|
@@ -584,9 +650,30 @@ function useChat() {
|
|
|
584
650
|
}
|
|
585
651
|
if (signal.aborted) return;
|
|
586
652
|
setSources((_b = res.sources) != null ? _b : []);
|
|
653
|
+
if (((_c = res.action) == null ? void 0 : _c.type) === "add_to_cart" || res.checkout) {
|
|
654
|
+
if (typeof window !== "undefined") {
|
|
655
|
+
window.dispatchEvent(new CustomEvent("huskel:cart_updated", { detail: res.checkout }));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (((_d = res.action) == null ? void 0 : _d.type) === "checkout") {
|
|
659
|
+
if (typeof window !== "undefined") {
|
|
660
|
+
window.dispatchEvent(new CustomEvent("huskel:trigger_checkout", { detail: res.checkout }));
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (res.checkout && client.onCheckout) {
|
|
664
|
+
client.onCheckout(res.checkout);
|
|
665
|
+
}
|
|
587
666
|
} catch (e) {
|
|
588
667
|
if (signal.aborted) return;
|
|
589
|
-
|
|
668
|
+
let msg = (_e = e == null ? void 0 : e.message) != null ? _e : "Chat request failed";
|
|
669
|
+
try {
|
|
670
|
+
const parsed = JSON.parse(msg);
|
|
671
|
+
if (parsed && parsed.error) {
|
|
672
|
+
msg = parsed.error;
|
|
673
|
+
}
|
|
674
|
+
} catch (e2) {
|
|
675
|
+
}
|
|
676
|
+
setError(msg);
|
|
590
677
|
setMessages((prev) => prev.slice(0, -1));
|
|
591
678
|
} finally {
|
|
592
679
|
if (!signal.aborted) {
|
|
@@ -605,8 +692,53 @@ function useChat() {
|
|
|
605
692
|
return { messages, sources, loading, error, send, reset };
|
|
606
693
|
}
|
|
607
694
|
|
|
608
|
-
// src/
|
|
695
|
+
// src/hooks/useCart.ts
|
|
609
696
|
var import_react7 = require("react");
|
|
697
|
+
function useCart() {
|
|
698
|
+
const client = useHuskelContext();
|
|
699
|
+
const [cart, setCart] = (0, import_react7.useState)(null);
|
|
700
|
+
const [loading, setLoading] = (0, import_react7.useState)(false);
|
|
701
|
+
const shopperId = client.getShopperId();
|
|
702
|
+
const fetchCart = (0, import_react7.useCallback)(async () => {
|
|
703
|
+
if (!shopperId) return;
|
|
704
|
+
setLoading(true);
|
|
705
|
+
try {
|
|
706
|
+
const res = await client.api.getCart();
|
|
707
|
+
setCart(res);
|
|
708
|
+
} catch (e) {
|
|
709
|
+
console.error("[Huskel] Failed to fetch cart", e);
|
|
710
|
+
} finally {
|
|
711
|
+
setLoading(false);
|
|
712
|
+
}
|
|
713
|
+
}, [client, shopperId]);
|
|
714
|
+
(0, import_react7.useEffect)(() => {
|
|
715
|
+
fetchCart();
|
|
716
|
+
const handleCartUpdate = (e) => {
|
|
717
|
+
if (e.detail) {
|
|
718
|
+
setCart(e.detail);
|
|
719
|
+
} else {
|
|
720
|
+
fetchCart();
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
if (typeof window !== "undefined") {
|
|
724
|
+
window.addEventListener("huskel:cart_updated", handleCartUpdate);
|
|
725
|
+
return () => window.removeEventListener("huskel:cart_updated", handleCartUpdate);
|
|
726
|
+
}
|
|
727
|
+
}, [fetchCart, shopperId]);
|
|
728
|
+
return { cart, loading, fetchCart };
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/components/SearchBar.tsx
|
|
732
|
+
var import_react8 = require("react");
|
|
733
|
+
|
|
734
|
+
// src/utils/cn.ts
|
|
735
|
+
var import_clsx = require("clsx");
|
|
736
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
737
|
+
function cn(...inputs) {
|
|
738
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/components/SearchBar.tsx
|
|
610
742
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
611
743
|
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
744
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
|
|
@@ -624,12 +756,12 @@ function SearchBar({
|
|
|
624
756
|
theme,
|
|
625
757
|
classNames = {}
|
|
626
758
|
}) {
|
|
627
|
-
const [query, setQuery] = (0,
|
|
628
|
-
const [open, setOpen] = (0,
|
|
759
|
+
const [query, setQuery] = (0, import_react8.useState)("");
|
|
760
|
+
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
629
761
|
const { results, loading, search, clear } = useSearch();
|
|
630
|
-
const timer = (0,
|
|
631
|
-
const wrap = (0,
|
|
632
|
-
(0,
|
|
762
|
+
const timer = (0, import_react8.useRef)();
|
|
763
|
+
const wrap = (0, import_react8.useRef)(null);
|
|
764
|
+
(0, import_react8.useEffect)(() => {
|
|
633
765
|
clearTimeout(timer.current);
|
|
634
766
|
if (!query.trim()) {
|
|
635
767
|
clear();
|
|
@@ -642,7 +774,7 @@ function SearchBar({
|
|
|
642
774
|
}, debounceMs);
|
|
643
775
|
return () => clearTimeout(timer.current);
|
|
644
776
|
}, [query]);
|
|
645
|
-
(0,
|
|
777
|
+
(0, import_react8.useEffect)(() => {
|
|
646
778
|
const h = (e) => {
|
|
647
779
|
if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
|
|
648
780
|
};
|
|
@@ -655,13 +787,13 @@ function SearchBar({
|
|
|
655
787
|
onSelect == null ? void 0 : onSelect(r);
|
|
656
788
|
};
|
|
657
789
|
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:
|
|
790
|
+
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 });
|
|
791
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("hsk-sb-wrap", classNames.root, className), ref: wrap, style: customStyles, children: [
|
|
660
792
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
|
|
661
793
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
662
794
|
"input",
|
|
663
795
|
{
|
|
664
|
-
className:
|
|
796
|
+
className: cn("hsk-sb-input", classNames.input, inputClassName),
|
|
665
797
|
type: "text",
|
|
666
798
|
value: query,
|
|
667
799
|
placeholder,
|
|
@@ -671,7 +803,7 @@ function SearchBar({
|
|
|
671
803
|
spellCheck: false
|
|
672
804
|
}
|
|
673
805
|
),
|
|
674
|
-
showDrop && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className:
|
|
806
|
+
showDrop && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
|
|
675
807
|
loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
|
|
676
808
|
results.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-empty", children: [
|
|
677
809
|
"No results for \u201C",
|
|
@@ -692,7 +824,7 @@ function SearchBar({
|
|
|
692
824
|
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
693
825
|
"div",
|
|
694
826
|
{
|
|
695
|
-
className:
|
|
827
|
+
className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
|
|
696
828
|
style: { animationDelay: `${i * 18}ms` },
|
|
697
829
|
onClick: () => handleSelect(r),
|
|
698
830
|
children: [
|
|
@@ -711,14 +843,124 @@ function SearchBar({
|
|
|
711
843
|
}
|
|
712
844
|
|
|
713
845
|
// src/components/Sparkle.tsx
|
|
714
|
-
var
|
|
846
|
+
var import_react9 = require("react");
|
|
715
847
|
var import_react_dom = require("react-dom");
|
|
848
|
+
|
|
849
|
+
// src/utils/markdown.tsx
|
|
716
850
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
717
|
-
var
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
851
|
+
var parseInline = (text, keyPrefix) => {
|
|
852
|
+
const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
|
|
853
|
+
const parts = text.split(tokenRegex);
|
|
854
|
+
return parts.map((part, index) => {
|
|
855
|
+
if (!part) return null;
|
|
856
|
+
const key = `${keyPrefix}-inline-${index}`;
|
|
857
|
+
if (part.startsWith("`") && part.endsWith("`")) {
|
|
858
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
|
|
859
|
+
}
|
|
860
|
+
if (part.startsWith("**") && part.endsWith("**")) {
|
|
861
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: parseInline(part.slice(2, -2), key) }, key);
|
|
862
|
+
}
|
|
863
|
+
const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
864
|
+
if (linkMatch) {
|
|
865
|
+
const url = linkMatch[2];
|
|
866
|
+
const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
|
|
867
|
+
if (isSafeUrl) {
|
|
868
|
+
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);
|
|
869
|
+
}
|
|
870
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: parseInline(linkMatch[1], key) }, key);
|
|
871
|
+
}
|
|
872
|
+
return part;
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
function renderMarkdown(content) {
|
|
876
|
+
const lines = content.split("\n");
|
|
877
|
+
const elements = [];
|
|
878
|
+
let i = 0;
|
|
879
|
+
while (i < lines.length) {
|
|
880
|
+
const line = lines[i];
|
|
881
|
+
const key = `md-line-${i}`;
|
|
882
|
+
if (!line.trim()) {
|
|
883
|
+
i++;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
|
|
887
|
+
if (headerMatch) {
|
|
888
|
+
const level = headerMatch[1].length;
|
|
889
|
+
const Tag = `h${level + 3}`;
|
|
890
|
+
elements.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
|
|
891
|
+
i++;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (line.match(/^[-*]\s+/)) {
|
|
895
|
+
const listItems = [];
|
|
896
|
+
while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
|
|
897
|
+
const itemText = lines[i].replace(/^[-*]\s+/, "");
|
|
898
|
+
listItems.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
|
|
899
|
+
i++;
|
|
900
|
+
}
|
|
901
|
+
elements.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (line.trim().startsWith("|")) {
|
|
905
|
+
const tableRows = [];
|
|
906
|
+
let isHeader = true;
|
|
907
|
+
while (i < lines.length && lines[i].trim().startsWith("|")) {
|
|
908
|
+
const rowLine = lines[i].trim();
|
|
909
|
+
if (rowLine.match(/^\|[-:| ]+\|$/)) {
|
|
910
|
+
i++;
|
|
911
|
+
isHeader = false;
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
|
|
915
|
+
const Tag = isHeader ? "th" : "td";
|
|
916
|
+
tableRows.push(
|
|
917
|
+
/* @__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}`)
|
|
918
|
+
);
|
|
919
|
+
i++;
|
|
920
|
+
}
|
|
921
|
+
elements.push(
|
|
922
|
+
/* @__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}`)
|
|
923
|
+
);
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
elements.push(
|
|
927
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
|
|
928
|
+
);
|
|
929
|
+
i++;
|
|
930
|
+
}
|
|
931
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: elements });
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/components/Sparkle.tsx
|
|
935
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
936
|
+
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" }) });
|
|
937
|
+
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: [
|
|
938
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
939
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
721
940
|
] });
|
|
941
|
+
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: [
|
|
942
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m5 12 7-7 7 7" }),
|
|
943
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 19V5" })
|
|
944
|
+
] });
|
|
945
|
+
var getFriendlyError = (err) => {
|
|
946
|
+
let str = "";
|
|
947
|
+
if (typeof err === "string") str = err;
|
|
948
|
+
else if (err && typeof err === "object" && err.message) str = err.message;
|
|
949
|
+
else try {
|
|
950
|
+
str = JSON.stringify(err);
|
|
951
|
+
} catch (e) {
|
|
952
|
+
str = String(err);
|
|
953
|
+
}
|
|
954
|
+
if (str.toLowerCase().includes("token limit")) {
|
|
955
|
+
return "You've reached your usage limit. Please update your billing limits in your dashboard to continue.";
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
const parsed = JSON.parse(str);
|
|
959
|
+
return parsed.error || parsed.message || str;
|
|
960
|
+
} catch (e) {
|
|
961
|
+
return str;
|
|
962
|
+
}
|
|
963
|
+
};
|
|
722
964
|
function SparkleModal({
|
|
723
965
|
productName,
|
|
724
966
|
limit,
|
|
@@ -728,26 +970,42 @@ function SparkleModal({
|
|
|
728
970
|
onNavigate,
|
|
729
971
|
onResult,
|
|
730
972
|
theme,
|
|
731
|
-
classNames = {}
|
|
973
|
+
classNames = {},
|
|
974
|
+
product: initialProduct
|
|
732
975
|
}) {
|
|
733
|
-
|
|
734
|
-
const
|
|
735
|
-
(0,
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
976
|
+
var _a, _b, _c;
|
|
977
|
+
const client = useHuskelContext();
|
|
978
|
+
const [fetchedProduct, setFetchedProduct] = (0, import_react9.useState)(null);
|
|
979
|
+
const displayProduct = initialProduct || fetchedProduct;
|
|
980
|
+
const { results, loading: searchLoading, search } = useSearch();
|
|
981
|
+
const { messages, sources, loading: chatLoading, error: chatError, send } = useChat();
|
|
982
|
+
const [chatInput, setChatInput] = (0, import_react9.useState)("");
|
|
983
|
+
const chatBottomRef = (0, import_react9.useRef)(null);
|
|
984
|
+
const chatTextareaRef = (0, import_react9.useRef)(null);
|
|
985
|
+
(0, import_react9.useEffect)(() => {
|
|
986
|
+
if (!initialProduct && !fetchedProduct) {
|
|
987
|
+
client.api.searchVector(productName, 1).then((res) => {
|
|
988
|
+
if (res.results && res.results.length > 0) {
|
|
989
|
+
setFetchedProduct(res.results[0].product);
|
|
990
|
+
}
|
|
991
|
+
}).catch((err) => console.error("[Huskel] Failed to fetch product details", err));
|
|
739
992
|
}
|
|
740
|
-
|
|
741
|
-
|
|
993
|
+
search(productName, limit);
|
|
994
|
+
}, [productName, initialProduct, fetchedProduct, client, limit, search]);
|
|
995
|
+
(0, import_react9.useEffect)(() => {
|
|
742
996
|
if (results.length > 0) onResult == null ? void 0 : onResult(results);
|
|
743
|
-
}, [results]);
|
|
744
|
-
(0,
|
|
997
|
+
}, [results, onResult]);
|
|
998
|
+
(0, import_react9.useEffect)(() => {
|
|
745
999
|
const h = (e) => {
|
|
746
1000
|
if (e.key === "Escape") onClose();
|
|
747
1001
|
};
|
|
748
1002
|
document.addEventListener("keydown", h);
|
|
749
1003
|
return () => document.removeEventListener("keydown", h);
|
|
750
|
-
}, []);
|
|
1004
|
+
}, [onClose]);
|
|
1005
|
+
(0, import_react9.useEffect)(() => {
|
|
1006
|
+
var _a2;
|
|
1007
|
+
(_a2 = chatBottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
1008
|
+
}, [messages, chatLoading]);
|
|
751
1009
|
const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "16px";
|
|
752
1010
|
const bg = backdropColor != null ? backdropColor : void 0;
|
|
753
1011
|
const handleNav = (r) => {
|
|
@@ -757,83 +1015,209 @@ function SparkleModal({
|
|
|
757
1015
|
if (r.product.url) window.location.href = r.product.url;
|
|
758
1016
|
}
|
|
759
1017
|
};
|
|
760
|
-
const
|
|
761
|
-
|
|
1018
|
+
const handleSend = async (text) => {
|
|
1019
|
+
const q = (text != null ? text : chatInput).trim();
|
|
1020
|
+
if (!q || chatLoading) return;
|
|
1021
|
+
setChatInput("");
|
|
1022
|
+
if (chatTextareaRef.current) {
|
|
1023
|
+
chatTextareaRef.current.style.height = "auto";
|
|
1024
|
+
}
|
|
1025
|
+
if (messages.length === 0 && displayProduct) {
|
|
1026
|
+
const contextQuery = `[Context: Shopper is viewing "${displayProduct.name}". Price: ${displayProduct.price}. Description: ${displayProduct.description || ""}]
|
|
1027
|
+
|
|
1028
|
+
Question: ${q}`;
|
|
1029
|
+
await send(contextQuery, q);
|
|
1030
|
+
} else {
|
|
1031
|
+
await send(q);
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
const handleKeyDown = (e) => {
|
|
1035
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
1036
|
+
e.preventDefault();
|
|
1037
|
+
handleSend();
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
const handleInput = (e) => {
|
|
1041
|
+
setChatInput(e.target.value);
|
|
1042
|
+
const t = e.target;
|
|
1043
|
+
t.style.height = "auto";
|
|
1044
|
+
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1045
|
+
};
|
|
1046
|
+
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 });
|
|
1047
|
+
const displayMessages = messages.length === 0 && displayProduct ? [
|
|
1048
|
+
{
|
|
1049
|
+
role: "assistant",
|
|
1050
|
+
content: `Hi! I can help you with **${displayProduct.name}**. Ask me about its specifications, features, compare it with other options, or find alternatives!`
|
|
1051
|
+
}
|
|
1052
|
+
] : messages;
|
|
1053
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
762
1054
|
"div",
|
|
763
1055
|
{
|
|
764
|
-
className:
|
|
1056
|
+
className: cn("hsk-sp-backdrop", classNames.backdrop),
|
|
765
1057
|
onClick: onClose,
|
|
766
1058
|
style: __spreadValues({
|
|
767
1059
|
backdropFilter: `blur(${blurVal})`,
|
|
768
1060
|
WebkitBackdropFilter: `blur(${blurVal})`,
|
|
769
1061
|
background: bg != null ? bg : void 0
|
|
770
1062
|
}, 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" })
|
|
1063
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("hsk-sp-card hsk-sp-fullscreen", classNames.card), onClick: (e) => e.stopPropagation(), children: [
|
|
1064
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-header", children: [
|
|
1065
|
+
/* @__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, {}) }),
|
|
1066
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-header-body", children: [
|
|
1067
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-header-title", children: (displayProduct == null ? void 0 : displayProduct.name) || productName }),
|
|
1068
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-header-sub", children: "Ask questions, compare specs, or check similar products" })
|
|
781
1069
|
] }),
|
|
782
|
-
/* @__PURE__ */ (0,
|
|
1070
|
+
/* @__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
1071
|
] }),
|
|
784
|
-
|
|
785
|
-
/* @__PURE__ */ (0,
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1072
|
+
searchLoading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-bar" }),
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-body", children: [
|
|
1074
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-details-pane", children: [
|
|
1075
|
+
displayProduct && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-product-profile-container", children: [
|
|
1076
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-product-profile", children: [
|
|
1077
|
+
/* @__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}" }) }),
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-details-meta", children: [
|
|
1079
|
+
displayProduct.brand && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-brand", children: displayProduct.brand }),
|
|
1080
|
+
displayProduct.category && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-cat", children: displayProduct.category }),
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "hsk-sp-details-name", children: displayProduct.name }),
|
|
1082
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
|
|
1083
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-currency", children: (_b = displayProduct.currency) != null ? _b : "KES" }),
|
|
1084
|
+
/* @__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() }),
|
|
1085
|
+
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() }),
|
|
1086
|
+
displayProduct.discount && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-item-discount", children: [
|
|
1087
|
+
"(",
|
|
1088
|
+
displayProduct.discount,
|
|
1089
|
+
")"
|
|
1090
|
+
] })
|
|
1091
|
+
] }),
|
|
1092
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-meta-badges", children: [
|
|
1093
|
+
displayProduct.rating && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-rating", children: [
|
|
1094
|
+
"\u2605 ",
|
|
1095
|
+
parseFloat(displayProduct.rating.toString()).toFixed(1),
|
|
1096
|
+
" ",
|
|
1097
|
+
displayProduct.reviewCount ? `(${displayProduct.reviewCount})` : ""
|
|
804
1098
|
] }),
|
|
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
|
-
)
|
|
1099
|
+
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 }),
|
|
1100
|
+
displayProduct.stock && !displayProduct.availability && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-stock", children: [
|
|
1101
|
+
"Stock: ",
|
|
1102
|
+
displayProduct.stock
|
|
822
1103
|
] })
|
|
823
1104
|
] })
|
|
824
|
-
]
|
|
825
|
-
},
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1105
|
+
] })
|
|
1106
|
+
] }),
|
|
1107
|
+
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: [
|
|
1108
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "hsk-sp-spec-label-horizontal", children: [
|
|
1109
|
+
key,
|
|
1110
|
+
":"
|
|
1111
|
+
] }),
|
|
1112
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-spec-value-horizontal", title: val, children: val })
|
|
1113
|
+
] }, key)) }),
|
|
1114
|
+
displayProduct.description && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-details-desc", children: [
|
|
1115
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Description" }),
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: displayProduct.description })
|
|
1117
|
+
] })
|
|
1118
|
+
] }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-similar-section", children: [
|
|
1120
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "Similar Products" }),
|
|
1121
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-results", children: (() => {
|
|
1122
|
+
const similarProducts = results.filter(
|
|
1123
|
+
(r) => {
|
|
1124
|
+
var _a2;
|
|
1125
|
+
const isSameName = r.product.name.toLowerCase() === ((_a2 = displayProduct == null ? void 0 : displayProduct.name) == null ? void 0 : _a2.toLowerCase());
|
|
1126
|
+
const isSameSlug = r.product.slug && (displayProduct == null ? void 0 : displayProduct.slug) && r.product.slug.toLowerCase() === displayProduct.slug.toLowerCase();
|
|
1127
|
+
return !isSameName && !isSameSlug;
|
|
1128
|
+
}
|
|
1129
|
+
);
|
|
1130
|
+
if (!searchLoading && similarProducts.length === 0) {
|
|
1131
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-empty", children: "No similar products found." });
|
|
1132
|
+
}
|
|
1133
|
+
return similarProducts.map((r, i) => {
|
|
1134
|
+
var _a2, _b2, _c2;
|
|
1135
|
+
const price = parseFloat(((_a2 = r.product.price) == null ? void 0 : _a2.replace(/[^0-9.]/g, "")) || "0");
|
|
1136
|
+
const currency = (_b2 = r.product.currency) != null ? _b2 : "KES";
|
|
1137
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1138
|
+
"div",
|
|
1139
|
+
{
|
|
1140
|
+
className: cn("hsk-sp-item", classNames.item),
|
|
1141
|
+
style: { animationDelay: `${i * 55}ms` },
|
|
1142
|
+
children: [
|
|
1143
|
+
/* @__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}" }) }),
|
|
1144
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-body", children: [
|
|
1145
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
1146
|
+
r.product.category && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-item-cat", children: r.product.category }),
|
|
1147
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-item-name", title: r.product.name, children: r.product.name })
|
|
1148
|
+
] }),
|
|
1149
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
|
|
1150
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-currency", children: currency }),
|
|
1151
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
|
|
1152
|
+
] }),
|
|
1153
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sp-actions", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1154
|
+
"button",
|
|
1155
|
+
{
|
|
1156
|
+
className: "hsk-sp-action hsk-sp-action-primary",
|
|
1157
|
+
onClick: () => handleNav(r),
|
|
1158
|
+
children: "View"
|
|
1159
|
+
}
|
|
1160
|
+
) })
|
|
1161
|
+
] })
|
|
1162
|
+
]
|
|
1163
|
+
},
|
|
1164
|
+
r.id
|
|
1165
|
+
);
|
|
1166
|
+
});
|
|
1167
|
+
})() })
|
|
1168
|
+
] })
|
|
834
1169
|
] }),
|
|
835
|
-
/* @__PURE__ */ (0,
|
|
836
|
-
|
|
1170
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-sp-chat-pane", children: [
|
|
1171
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-msgs", children: [
|
|
1172
|
+
displayMessages.map((msg, idx) => {
|
|
1173
|
+
const isUser = msg.role === "user";
|
|
1174
|
+
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: [
|
|
1175
|
+
/* @__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, {}) }),
|
|
1176
|
+
/* @__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) }) })
|
|
1177
|
+
] }) }, idx);
|
|
1178
|
+
}),
|
|
1179
|
+
chatLoading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-typing-row", children: [
|
|
1180
|
+
/* @__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, {}) }),
|
|
1181
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-typing", children: [
|
|
1182
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-dot" }),
|
|
1183
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-dot" }),
|
|
1184
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-dot" })
|
|
1185
|
+
] })
|
|
1186
|
+
] }),
|
|
1187
|
+
chatError && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-error", children: getFriendlyError(chatError) }),
|
|
1188
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: chatBottomRef, style: { height: 1 } })
|
|
1189
|
+
] }),
|
|
1190
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-input-wrap", children: [
|
|
1191
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-cb-input-box", children: [
|
|
1192
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1193
|
+
"textarea",
|
|
1194
|
+
{
|
|
1195
|
+
ref: chatTextareaRef,
|
|
1196
|
+
className: "hsk-cb-textarea",
|
|
1197
|
+
value: chatInput,
|
|
1198
|
+
onChange: handleInput,
|
|
1199
|
+
onKeyDown: handleKeyDown,
|
|
1200
|
+
placeholder: "Ask about this product, specs, or comparison...",
|
|
1201
|
+
rows: 1,
|
|
1202
|
+
disabled: chatLoading
|
|
1203
|
+
}
|
|
1204
|
+
),
|
|
1205
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1206
|
+
"button",
|
|
1207
|
+
{
|
|
1208
|
+
className: "hsk-cb-send",
|
|
1209
|
+
onClick: () => handleSend(),
|
|
1210
|
+
disabled: !chatInput.trim() || chatLoading,
|
|
1211
|
+
"aria-label": "Send message",
|
|
1212
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ArrowUpIcon, {})
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
] }),
|
|
1216
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-cb-hint", children: "Huskel AI \xB7 instant product knowledge" })
|
|
1217
|
+
] })
|
|
1218
|
+
] })
|
|
1219
|
+
] }),
|
|
1220
|
+
/* @__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
1221
|
] })
|
|
838
1222
|
}
|
|
839
1223
|
);
|
|
@@ -847,39 +1231,41 @@ function Sparkle({
|
|
|
847
1231
|
className,
|
|
848
1232
|
onNavigate,
|
|
849
1233
|
theme,
|
|
850
|
-
classNames = {}
|
|
1234
|
+
classNames = {},
|
|
1235
|
+
product
|
|
851
1236
|
}) {
|
|
852
|
-
const [open, setOpen] = (0,
|
|
853
|
-
const [mounted, setMounted] = (0,
|
|
854
|
-
(0,
|
|
1237
|
+
const [open, setOpen] = (0, import_react9.useState)(false);
|
|
1238
|
+
const [mounted, setMounted] = (0, import_react9.useState)(false);
|
|
1239
|
+
(0, import_react9.useEffect)(() => {
|
|
855
1240
|
setMounted(true);
|
|
856
1241
|
}, []);
|
|
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,
|
|
1242
|
+
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 });
|
|
1243
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1244
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
860
1245
|
"button",
|
|
861
1246
|
{
|
|
862
|
-
className:
|
|
1247
|
+
className: cn("hsk-sp-btn", classNames.button, className),
|
|
863
1248
|
onClick: () => setOpen(true),
|
|
864
1249
|
style: customStyles,
|
|
865
1250
|
title: "Find similar products",
|
|
866
1251
|
"aria-label": "Find similar products",
|
|
867
|
-
children: /* @__PURE__ */ (0,
|
|
1252
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon, {})
|
|
868
1253
|
}
|
|
869
1254
|
),
|
|
870
1255
|
open && mounted && (0, import_react_dom.createPortal)(
|
|
871
|
-
/* @__PURE__ */ (0,
|
|
1256
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
872
1257
|
SparkleModal,
|
|
873
1258
|
{
|
|
874
1259
|
productName,
|
|
875
1260
|
limit,
|
|
1261
|
+
onResult,
|
|
876
1262
|
backdropColor,
|
|
877
1263
|
backdropBlur,
|
|
878
1264
|
onClose: () => setOpen(false),
|
|
879
|
-
onResult,
|
|
880
1265
|
onNavigate,
|
|
881
1266
|
theme,
|
|
882
|
-
classNames
|
|
1267
|
+
classNames,
|
|
1268
|
+
product
|
|
883
1269
|
}
|
|
884
1270
|
),
|
|
885
1271
|
document.body
|
|
@@ -888,97 +1274,10 @@ function Sparkle({
|
|
|
888
1274
|
}
|
|
889
1275
|
|
|
890
1276
|
// 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
|
|
1277
|
+
var import_react10 = require("react");
|
|
979
1278
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
980
1279
|
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
|
|
1280
|
+
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
1281
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m5 12 7-7 7 7" }),
|
|
983
1282
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19V5" })
|
|
984
1283
|
] });
|
|
@@ -1008,10 +1307,10 @@ function ChatWidget({
|
|
|
1008
1307
|
onSelectSource
|
|
1009
1308
|
}) {
|
|
1010
1309
|
const { messages, sources, loading, error, send, reset } = useChat();
|
|
1011
|
-
const [input, setInput] = (0,
|
|
1012
|
-
const bottomRef = (0,
|
|
1013
|
-
const textareaRef = (0,
|
|
1014
|
-
(0,
|
|
1310
|
+
const [input, setInput] = (0, import_react10.useState)("");
|
|
1311
|
+
const bottomRef = (0, import_react10.useRef)(null);
|
|
1312
|
+
const textareaRef = (0, import_react10.useRef)(null);
|
|
1313
|
+
(0, import_react10.useEffect)(() => {
|
|
1015
1314
|
var _a;
|
|
1016
1315
|
(_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
1017
1316
|
}, [messages, loading]);
|
|
@@ -1034,14 +1333,14 @@ function ChatWidget({
|
|
|
1034
1333
|
t.style.height = "auto";
|
|
1035
1334
|
t.style.height = Math.min(t.scrollHeight, 120) + "px";
|
|
1036
1335
|
};
|
|
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 });
|
|
1336
|
+
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
1337
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1039
1338
|
"div",
|
|
1040
1339
|
{
|
|
1041
|
-
className:
|
|
1340
|
+
className: cn("hsk-chat-widget", classNames.root, className),
|
|
1042
1341
|
style: customStyles,
|
|
1043
1342
|
children: [
|
|
1044
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className:
|
|
1343
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("hsk-chat-header", classNames.header), children: [
|
|
1045
1344
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-chat-header-icon", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon2, {}) }),
|
|
1046
1345
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-chat-title", children: title }),
|
|
1047
1346
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-chat-badge", children: "AI" }),
|
|
@@ -1054,8 +1353,8 @@ function ChatWidget({
|
|
|
1054
1353
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-chat-empty-suggestions", children: emptyStateSuggestions })
|
|
1055
1354
|
] }) : messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
1056
1355
|
/* @__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:
|
|
1356
|
+
/* @__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" }),
|
|
1357
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn("hsk-msg-bubble", msg.role, classNames.messageBubble), children: renderMarkdown(msg.content) })
|
|
1059
1358
|
] }),
|
|
1060
1359
|
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
1360
|
] }, idx)),
|
|
@@ -1067,7 +1366,14 @@ function ChatWidget({
|
|
|
1067
1366
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-typing-dot" })
|
|
1068
1367
|
] })
|
|
1069
1368
|
] }),
|
|
1070
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-chat-error", children:
|
|
1369
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-chat-error", children: (() => {
|
|
1370
|
+
try {
|
|
1371
|
+
const parsed = JSON.parse(error);
|
|
1372
|
+
return parsed.error || parsed.message || error;
|
|
1373
|
+
} catch (e) {
|
|
1374
|
+
return error;
|
|
1375
|
+
}
|
|
1376
|
+
})() }),
|
|
1071
1377
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: bottomRef })
|
|
1072
1378
|
] }),
|
|
1073
1379
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-chat-input-area", children: [
|
|
@@ -1075,7 +1381,7 @@ function ChatWidget({
|
|
|
1075
1381
|
"textarea",
|
|
1076
1382
|
{
|
|
1077
1383
|
ref: textareaRef,
|
|
1078
|
-
className:
|
|
1384
|
+
className: cn("hsk-chat-input", classNames.input),
|
|
1079
1385
|
value: input,
|
|
1080
1386
|
onChange: handleInput,
|
|
1081
1387
|
onKeyDown: handleKey,
|
|
@@ -1091,7 +1397,7 @@ function ChatWidget({
|
|
|
1091
1397
|
onClick: handleSend,
|
|
1092
1398
|
disabled: !input.trim() || loading,
|
|
1093
1399
|
"aria-label": "Send message",
|
|
1094
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1400
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ArrowUpIcon2, {})
|
|
1095
1401
|
}
|
|
1096
1402
|
)
|
|
1097
1403
|
] })
|
|
@@ -1101,11 +1407,11 @@ function ChatWidget({
|
|
|
1101
1407
|
}
|
|
1102
1408
|
|
|
1103
1409
|
// src/components/AIChatButton.tsx
|
|
1104
|
-
var
|
|
1410
|
+
var import_react11 = require("react");
|
|
1105
1411
|
var import_react_dom2 = require("react-dom");
|
|
1106
1412
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1107
1413
|
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
|
|
1414
|
+
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
1415
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m5 12 7-7 7 7" }),
|
|
1110
1416
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 19V5" })
|
|
1111
1417
|
] });
|
|
@@ -1121,15 +1427,15 @@ var DEFAULT_CHIPS = [
|
|
|
1121
1427
|
"Best laptop for students"
|
|
1122
1428
|
];
|
|
1123
1429
|
function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
1124
|
-
const railRef = (0,
|
|
1125
|
-
const [showNext, setShowNext] = (0,
|
|
1126
|
-
const measure = (0,
|
|
1430
|
+
const railRef = (0, import_react11.useRef)(null);
|
|
1431
|
+
const [showNext, setShowNext] = (0, import_react11.useState)(false);
|
|
1432
|
+
const measure = (0, import_react11.useCallback)(() => {
|
|
1127
1433
|
const el = railRef.current;
|
|
1128
1434
|
if (!el) return;
|
|
1129
1435
|
const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
|
|
1130
1436
|
setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
|
|
1131
1437
|
}, []);
|
|
1132
|
-
(0,
|
|
1438
|
+
(0, import_react11.useEffect)(() => {
|
|
1133
1439
|
measure();
|
|
1134
1440
|
const el = railRef.current;
|
|
1135
1441
|
if (!el) return;
|
|
@@ -1182,7 +1488,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
|
1182
1488
|
] });
|
|
1183
1489
|
}
|
|
1184
1490
|
function ChatModal({
|
|
1185
|
-
title = "
|
|
1491
|
+
title = "Shopping Assistant",
|
|
1186
1492
|
placeholder = "Ask me anything \u2014 gifts, budget, use case\u2026",
|
|
1187
1493
|
backdropColor,
|
|
1188
1494
|
backdropBlur,
|
|
@@ -1195,15 +1501,15 @@ function ChatModal({
|
|
|
1195
1501
|
}) {
|
|
1196
1502
|
var _a, _b;
|
|
1197
1503
|
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,
|
|
1504
|
+
const [input, setInput] = (0, import_react11.useState)("");
|
|
1505
|
+
const [selectedProduct, setSelectedProduct] = (0, import_react11.useState)(null);
|
|
1506
|
+
const bottomRef = (0, import_react11.useRef)(null);
|
|
1507
|
+
const textareaRef = (0, import_react11.useRef)(null);
|
|
1508
|
+
(0, import_react11.useEffect)(() => {
|
|
1203
1509
|
var _a2;
|
|
1204
1510
|
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
1205
1511
|
}, [messages, loading, selectedProduct]);
|
|
1206
|
-
(0,
|
|
1512
|
+
(0, import_react11.useEffect)(() => {
|
|
1207
1513
|
const h = (e) => {
|
|
1208
1514
|
if (e.key === "Escape") onClose();
|
|
1209
1515
|
};
|
|
@@ -1240,24 +1546,21 @@ function ChatModal({
|
|
|
1240
1546
|
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1241
1547
|
};
|
|
1242
1548
|
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 });
|
|
1549
|
+
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
1550
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1245
1551
|
"div",
|
|
1246
1552
|
{
|
|
1247
|
-
className:
|
|
1553
|
+
className: cn("hsk-cb-overlay", classNames.overlay),
|
|
1248
1554
|
onClick: onClose,
|
|
1249
1555
|
style: __spreadValues(__spreadValues({
|
|
1250
1556
|
backdropFilter: `blur(${blurVal})`,
|
|
1251
1557
|
WebkitBackdropFilter: `blur(${blurVal})`
|
|
1252
1558
|
}, backdropColor ? { background: backdropColor } : {}), customStyles),
|
|
1253
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className:
|
|
1559
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cn("hsk-cb-panel", classNames.panel), onClick: (e) => e.stopPropagation(), children: [
|
|
1254
1560
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-topbar", children: [
|
|
1255
1561
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-topbar-left", children: [
|
|
1256
1562
|
/* @__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
|
-
] })
|
|
1563
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-topbar-title", children: title }) })
|
|
1261
1564
|
] }),
|
|
1262
1565
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-topbar-actions", children: [
|
|
1263
1566
|
messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "hsk-cb-topbar-btn", onClick: reset, children: "Clear chat" }),
|
|
@@ -1267,8 +1570,7 @@ function ChatModal({
|
|
|
1267
1570
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-msgs", children: [
|
|
1268
1571
|
messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-empty", children: [
|
|
1269
1572
|
/* @__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." }),
|
|
1573
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-empty-title", children: "Find exactly what you need" }),
|
|
1272
1574
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-chips", children: chips.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1273
1575
|
"button",
|
|
1274
1576
|
{
|
|
@@ -1331,7 +1633,7 @@ function ChatModal({
|
|
|
1331
1633
|
"textarea",
|
|
1332
1634
|
{
|
|
1333
1635
|
ref: textareaRef,
|
|
1334
|
-
className:
|
|
1636
|
+
className: cn("hsk-cb-textarea", classNames.input),
|
|
1335
1637
|
value: input,
|
|
1336
1638
|
onChange: handleInput,
|
|
1337
1639
|
onKeyDown: handleKeyDown,
|
|
@@ -1344,11 +1646,11 @@ function ChatModal({
|
|
|
1344
1646
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1345
1647
|
"button",
|
|
1346
1648
|
{
|
|
1347
|
-
className:
|
|
1649
|
+
className: cn("hsk-cb-send", classNames.sendButton),
|
|
1348
1650
|
onClick: () => handleSend(),
|
|
1349
1651
|
disabled: !input.trim() || loading,
|
|
1350
1652
|
"aria-label": "Send message",
|
|
1351
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1653
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ArrowUpIcon3, {})
|
|
1352
1654
|
}
|
|
1353
1655
|
)
|
|
1354
1656
|
] }),
|
|
@@ -1371,17 +1673,17 @@ function AIChatButton({
|
|
|
1371
1673
|
theme,
|
|
1372
1674
|
classNames = {}
|
|
1373
1675
|
}) {
|
|
1374
|
-
const [open, setOpen] = (0,
|
|
1375
|
-
const [mounted, setMounted] = (0,
|
|
1376
|
-
(0,
|
|
1676
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
1677
|
+
const [mounted, setMounted] = (0, import_react11.useState)(false);
|
|
1678
|
+
(0, import_react11.useEffect)(() => {
|
|
1377
1679
|
setMounted(true);
|
|
1378
1680
|
}, []);
|
|
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 });
|
|
1681
|
+
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
1682
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1381
1683
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1382
1684
|
"button",
|
|
1383
1685
|
{
|
|
1384
|
-
className:
|
|
1686
|
+
className: cn("hsk-cb-btn", classNames.button, className),
|
|
1385
1687
|
onClick: () => setOpen(true),
|
|
1386
1688
|
style: customStyles,
|
|
1387
1689
|
"aria-label": "Open AI chat",
|
|
@@ -1411,9 +1713,261 @@ function AIChatButton({
|
|
|
1411
1713
|
)
|
|
1412
1714
|
] });
|
|
1413
1715
|
}
|
|
1716
|
+
|
|
1717
|
+
// src/components/CartBadge.tsx
|
|
1718
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1719
|
+
function CartBadge({ className }) {
|
|
1720
|
+
const { cart } = useCart();
|
|
1721
|
+
if (!cart || cart.item_count === 0) return null;
|
|
1722
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: cn("hsk-cart-badge", className), children: cart.item_count });
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// src/components/CartDrawer.tsx
|
|
1726
|
+
var import_react13 = require("react");
|
|
1727
|
+
var import_react_dom4 = require("react-dom");
|
|
1728
|
+
|
|
1729
|
+
// src/components/CheckoutModal.tsx
|
|
1730
|
+
var import_react12 = require("react");
|
|
1731
|
+
var import_react_dom3 = require("react-dom");
|
|
1732
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1733
|
+
function CheckoutModal({
|
|
1734
|
+
onClose,
|
|
1735
|
+
theme,
|
|
1736
|
+
customStyles,
|
|
1737
|
+
hskThemeAttr
|
|
1738
|
+
}) {
|
|
1739
|
+
var _a, _b, _c, _d;
|
|
1740
|
+
const { cart, loading: cartLoading } = useCart();
|
|
1741
|
+
const client = useHuskelContext();
|
|
1742
|
+
const [config, setConfig] = (0, import_react12.useState)(null);
|
|
1743
|
+
const [loading, setLoading] = (0, import_react12.useState)(true);
|
|
1744
|
+
const [checkingOut, setCheckingOut] = (0, import_react12.useState)(false);
|
|
1745
|
+
const [paymentSuccess, setPaymentSuccess] = (0, import_react12.useState)(false);
|
|
1746
|
+
(0, import_react12.useEffect)(() => {
|
|
1747
|
+
client.api.getCheckoutConfig().then((res) => setConfig(res.payment_methods)).catch((e) => console.error("[Huskel] Failed to fetch checkout config", e)).finally(() => setLoading(false));
|
|
1748
|
+
}, [client]);
|
|
1749
|
+
const handlePay = async (method) => {
|
|
1750
|
+
setCheckingOut(true);
|
|
1751
|
+
setTimeout(async () => {
|
|
1752
|
+
try {
|
|
1753
|
+
const payload = await client.api.checkoutCart();
|
|
1754
|
+
if (client.onCheckout) {
|
|
1755
|
+
client.onCheckout(payload);
|
|
1756
|
+
}
|
|
1757
|
+
setPaymentSuccess(true);
|
|
1758
|
+
setTimeout(() => {
|
|
1759
|
+
onClose();
|
|
1760
|
+
}, 3e3);
|
|
1761
|
+
} catch (e) {
|
|
1762
|
+
console.error("[Huskel] Checkout failed", e);
|
|
1763
|
+
setCheckingOut(false);
|
|
1764
|
+
}
|
|
1765
|
+
}, 1500);
|
|
1766
|
+
};
|
|
1767
|
+
const hasPaymentMethods = config && Object.values(config).some((m) => m.enabled);
|
|
1768
|
+
return (0, import_react_dom3.createPortal)(
|
|
1769
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1770
|
+
"div",
|
|
1771
|
+
{
|
|
1772
|
+
className: "hsk-cart-backdrop",
|
|
1773
|
+
style: __spreadProps(__spreadValues({}, customStyles), { zIndex: 999999 }),
|
|
1774
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1775
|
+
onClick: onClose,
|
|
1776
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1777
|
+
"div",
|
|
1778
|
+
{
|
|
1779
|
+
className: "hsk-checkout-modal",
|
|
1780
|
+
style: customStyles,
|
|
1781
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1782
|
+
onClick: (e) => e.stopPropagation(),
|
|
1783
|
+
children: [
|
|
1784
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-header", children: [
|
|
1785
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { children: "Secure Checkout" }),
|
|
1786
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: onClose, className: "hsk-close-btn", children: "\xD7" })
|
|
1787
|
+
] }),
|
|
1788
|
+
/* @__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: [
|
|
1789
|
+
/* @__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: [
|
|
1790
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
|
|
1791
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polyline", { points: "22 4 12 14.01 9 11.01" })
|
|
1792
|
+
] }),
|
|
1793
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: "Payment Successful!" }),
|
|
1794
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: "Thank you for your order." })
|
|
1795
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-split", children: [
|
|
1796
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-summary", children: [
|
|
1797
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: "Order Summary" }),
|
|
1798
|
+
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: [
|
|
1799
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "hsk-checkout-items", children: cart.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { children: [
|
|
1800
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
1801
|
+
item.quantity,
|
|
1802
|
+
"x ",
|
|
1803
|
+
item.name
|
|
1804
|
+
] }),
|
|
1805
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
1806
|
+
item.currency,
|
|
1807
|
+
" ",
|
|
1808
|
+
(item.price_numeric * item.quantity).toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1809
|
+
] })
|
|
1810
|
+
] }, item.id)) }),
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-total", children: [
|
|
1812
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Total" }),
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
|
|
1814
|
+
cart.currency,
|
|
1815
|
+
" ",
|
|
1816
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1817
|
+
] })
|
|
1818
|
+
] })
|
|
1819
|
+
] })
|
|
1820
|
+
] }),
|
|
1821
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-checkout-payment", children: [
|
|
1822
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: "Payment Method" }),
|
|
1823
|
+
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: [
|
|
1824
|
+
((_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" }),
|
|
1825
|
+
((_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" }),
|
|
1826
|
+
((_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)" }),
|
|
1827
|
+
((_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" })
|
|
1828
|
+
] })
|
|
1829
|
+
] })
|
|
1830
|
+
] }) })
|
|
1831
|
+
]
|
|
1832
|
+
}
|
|
1833
|
+
)
|
|
1834
|
+
}
|
|
1835
|
+
),
|
|
1836
|
+
document.body
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// src/components/CartDrawer.tsx
|
|
1841
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1842
|
+
function CartDrawer({
|
|
1843
|
+
trigger,
|
|
1844
|
+
className,
|
|
1845
|
+
theme
|
|
1846
|
+
}) {
|
|
1847
|
+
const { cart, loading } = useCart();
|
|
1848
|
+
const [open, setOpen] = (0, import_react13.useState)(false);
|
|
1849
|
+
const [showCheckout, setShowCheckout] = (0, import_react13.useState)(false);
|
|
1850
|
+
const [mounted, setMounted] = (0, import_react13.useState)(false);
|
|
1851
|
+
const client = useHuskelContext();
|
|
1852
|
+
(0, import_react13.useEffect)(() => {
|
|
1853
|
+
setMounted(true);
|
|
1854
|
+
const handleTriggerCheckout = () => {
|
|
1855
|
+
setShowCheckout(true);
|
|
1856
|
+
setOpen(false);
|
|
1857
|
+
};
|
|
1858
|
+
window.addEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1859
|
+
return () => {
|
|
1860
|
+
window.removeEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1861
|
+
};
|
|
1862
|
+
}, []);
|
|
1863
|
+
(0, import_react13.useEffect)(() => {
|
|
1864
|
+
if (open) {
|
|
1865
|
+
document.body.style.overflow = "hidden";
|
|
1866
|
+
} else {
|
|
1867
|
+
document.body.style.overflow = "";
|
|
1868
|
+
}
|
|
1869
|
+
return () => {
|
|
1870
|
+
document.body.style.overflow = "";
|
|
1871
|
+
};
|
|
1872
|
+
}, [open]);
|
|
1873
|
+
const handleCheckout = async () => {
|
|
1874
|
+
if (!cart || cart.items.length === 0) return;
|
|
1875
|
+
setShowCheckout(true);
|
|
1876
|
+
};
|
|
1877
|
+
const isStringTheme = typeof theme === "string";
|
|
1878
|
+
const hskThemeAttr = isStringTheme ? theme : void 0;
|
|
1879
|
+
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;
|
|
1880
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
1881
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { onClick: () => setOpen(true), style: { display: "inline-block" }, children: trigger || /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1882
|
+
"button",
|
|
1883
|
+
{
|
|
1884
|
+
className: cn("hsk-cart-trigger", className),
|
|
1885
|
+
style: customStyles,
|
|
1886
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1887
|
+
"aria-label": "Open cart",
|
|
1888
|
+
children: [
|
|
1889
|
+
/* @__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: [
|
|
1890
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "9", cy: "21", r: "1" }),
|
|
1891
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "20", cy: "21", r: "1" }),
|
|
1892
|
+
/* @__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" })
|
|
1893
|
+
] }),
|
|
1894
|
+
cart && cart.item_count > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hsk-cart-trigger-badge", children: cart.item_count }) : null
|
|
1895
|
+
]
|
|
1896
|
+
}
|
|
1897
|
+
) }),
|
|
1898
|
+
open && mounted && (0, import_react_dom4.createPortal)(
|
|
1899
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1900
|
+
"div",
|
|
1901
|
+
{
|
|
1902
|
+
className: "hsk-cart-backdrop",
|
|
1903
|
+
style: customStyles,
|
|
1904
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1905
|
+
onClick: () => setOpen(false),
|
|
1906
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1907
|
+
"div",
|
|
1908
|
+
{
|
|
1909
|
+
className: "hsk-cart-bottom-sheet",
|
|
1910
|
+
style: customStyles,
|
|
1911
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1912
|
+
onClick: (e) => e.stopPropagation(),
|
|
1913
|
+
children: [
|
|
1914
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hsk-cart-sheet-handle" }),
|
|
1915
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-sheet-header", children: [
|
|
1916
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { children: "Your Cart" }),
|
|
1917
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => setOpen(false), className: "hsk-close-btn", children: "\xD7" })
|
|
1918
|
+
] }),
|
|
1919
|
+
/* @__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: [
|
|
1920
|
+
item.image && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("img", { src: item.image, alt: item.name, className: "hsk-cart-item-img" }),
|
|
1921
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-item-info", children: [
|
|
1922
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "hsk-cart-item-name", children: item.name }),
|
|
1923
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "hsk-cart-item-price", children: [
|
|
1924
|
+
item.currency,
|
|
1925
|
+
" ",
|
|
1926
|
+
item.price_numeric.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1927
|
+
] })
|
|
1928
|
+
] }),
|
|
1929
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-item-qty", children: [
|
|
1930
|
+
"x",
|
|
1931
|
+
item.quantity
|
|
1932
|
+
] })
|
|
1933
|
+
] }, item.id)) }) }),
|
|
1934
|
+
cart && cart.items.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-sheet-footer", children: [
|
|
1935
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hsk-cart-total", children: [
|
|
1936
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: "Total" }),
|
|
1937
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
|
|
1938
|
+
cart.currency,
|
|
1939
|
+
" ",
|
|
1940
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1941
|
+
] })
|
|
1942
|
+
] }),
|
|
1943
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: handleCheckout, className: "hsk-checkout-btn", children: "Checkout securely" })
|
|
1944
|
+
] })
|
|
1945
|
+
]
|
|
1946
|
+
}
|
|
1947
|
+
)
|
|
1948
|
+
}
|
|
1949
|
+
),
|
|
1950
|
+
document.body
|
|
1951
|
+
),
|
|
1952
|
+
showCheckout && mounted && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1953
|
+
CheckoutModal,
|
|
1954
|
+
{
|
|
1955
|
+
onClose: () => {
|
|
1956
|
+
setShowCheckout(false);
|
|
1957
|
+
setOpen(false);
|
|
1958
|
+
},
|
|
1959
|
+
theme: isStringTheme ? theme : void 0,
|
|
1960
|
+
customStyles,
|
|
1961
|
+
hskThemeAttr
|
|
1962
|
+
}
|
|
1963
|
+
)
|
|
1964
|
+
] });
|
|
1965
|
+
}
|
|
1414
1966
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1415
1967
|
0 && (module.exports = {
|
|
1416
1968
|
AIChatButton,
|
|
1969
|
+
CartBadge,
|
|
1970
|
+
CartDrawer,
|
|
1417
1971
|
ChatWidget,
|
|
1418
1972
|
HuskelAPI,
|
|
1419
1973
|
HuskelClient,
|
|
@@ -1422,6 +1976,7 @@ function AIChatButton({
|
|
|
1422
1976
|
Sparkle,
|
|
1423
1977
|
getHuskelClient,
|
|
1424
1978
|
initHuskel,
|
|
1979
|
+
useCart,
|
|
1425
1980
|
useChat,
|
|
1426
1981
|
useHuskel,
|
|
1427
1982
|
useIngest,
|