@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.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
- const err = { status: res.status, message: text };
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.shopperId,
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
- setError((_b = e.message) != null ? _b : "Search failed");
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
- setError((_c = e == null ? void 0 : e.message) != null ? _c : "Chat request failed");
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/components/SearchBar.tsx
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 = 80,
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, import_react7.useState)("");
628
- const [open, setOpen] = (0, import_react7.useState)(false);
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 timer = (0, import_react7.useRef)();
631
- const wrap = (0, import_react7.useRef)(null);
632
- (0, import_react7.useEffect)(() => {
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, import_react7.useEffect)(() => {
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: `hsk-sb-wrap ${classNames.root || ""} ${className || ""}`, ref: wrap, style: customStyles, children: [
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: `hsk-sb-input ${classNames.input || ""} ${inputClassName || ""}`,
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: `hsk-sb-drop ${classNames.dropdown || ""} ${dropdownClassName || ""}`, style: { position: "absolute" }, children: [
675
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
676
- results.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-empty", children: [
677
- "No results for \u201C",
678
- query,
679
- "\u201D"
680
- ] }),
681
- results.map((r, i) => {
682
- var _a;
683
- return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
684
- "div",
685
- {
686
- onClick: () => handleSelect(r),
687
- className: "hsk-sb-fade",
688
- style: { animationDelay: `${i * 18}ms` },
689
- children: renderResult(r)
690
- },
691
- r.id
692
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
693
- "div",
694
- {
695
- className: `hsk-sb-row hsk-sb-fade ${classNames.row || ""}`,
696
- style: { animationDelay: `${i * 18}ms` },
697
- onClick: () => handleSelect(r),
698
- children: [
699
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
700
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
701
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-title", children: r.product.name }),
702
- (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 })
703
- ] })
704
- ]
705
- },
706
- r.id
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 import_react8 = require("react");
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 SparkleIcon = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime3.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_runtime3.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" }) });
718
- var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
719
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
720
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
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
- const { results, loading, search } = useSearch();
734
- const initiated = (0, import_react8.useRef)(false);
735
- (0, import_react8.useEffect)(() => {
736
- if (!initiated.current) {
737
- initiated.current = true;
738
- search(productName, limit);
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
- (0, import_react8.useEffect)(() => {
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, import_react8.useEffect)(() => {
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 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 });
761
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
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: `hsk-sp-backdrop ${classNames.backdrop || ""}`,
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, import_jsx_runtime3.jsxs)("div", { className: `hsk-sp-card ${classNames.card || ""}`, onClick: (e) => e.stopPropagation(), children: [
772
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header", children: [
773
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-header-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, {}) }),
774
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header-body", children: [
775
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header-title", children: [
776
- "Similar to \u201C",
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, import_jsx_runtime3.jsx)("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}) })
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
- loading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-bar" }),
785
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-results", children: [
786
- !loading && results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-empty", children: "No similar products found." }),
787
- results.map((r, i) => {
788
- var _a, _b, _c;
789
- const price = parseFloat(((_a = r.product.price) == null ? void 0 : _a.replace(/[^0-9.]/g, "")) || "0");
790
- const currency = (_b = r.product.currency) != null ? _b : "KES";
791
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
792
- "div",
793
- {
794
- className: `hsk-sp-item ${classNames.item || ""}`,
795
- style: { animationDelay: `${i * 55}ms` },
796
- children: [
797
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-img-wrap", children: ((_c = r.product.images) == null ? void 0 : _c[0]) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
798
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-item-body", children: [
799
- r.product.category && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-item-cat", children: r.product.category }),
800
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-item-name", children: r.product.name }),
801
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
802
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-item-currency", children: currency }),
803
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
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, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-actions", children: [
806
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
807
- "button",
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
- r.id
827
- );
828
- })
829
- ] }),
830
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-footer", children: [
831
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "hsk-sp-badge", style: { display: "inline-flex", alignItems: "center", gap: "4px" }, children: [
832
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, {}),
833
- " Huskel AI"
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, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-esc", children: "Esc to close" })
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, import_react8.useState)(false);
853
- const [mounted, setMounted] = (0, import_react8.useState)(false);
854
- (0, import_react8.useEffect)(() => {
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, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
859
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
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: `hsk-sp-btn ${classNames.button || ""} ${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, import_jsx_runtime3.jsx)(SparkleIcon, {})
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, import_jsx_runtime3.jsx)(
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 import_react9 = require("react");
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 ArrowUpIcon = () => /* @__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: [
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, import_react9.useState)("");
1012
- const bottomRef = (0, import_react9.useRef)(null);
1013
- const textareaRef = (0, import_react9.useRef)(null);
1014
- (0, import_react9.useEffect)(() => {
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: `hsk-chat-widget ${classNames.root || ""} ${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: `hsk-chat-header ${classNames.header || ""}`, children: [
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: `hsk-msg-avatar ${msg.role === "assistant" ? "ai" : "user"}`, children: msg.role === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon2, {}) : "U" }),
1058
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: `hsk-msg-bubble ${msg.role} ${classNames.messageBubble || ""}`, children: renderMarkdown(msg.content) })
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: error }),
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: `hsk-chat-input ${classNames.input || ""}`,
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)(ArrowUpIcon, {})
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 import_react10 = require("react");
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 ArrowUpIcon2 = () => /* @__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: [
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, import_react10.useRef)(null);
1125
- const [showNext, setShowNext] = (0, import_react10.useState)(false);
1126
- const measure = (0, import_react10.useCallback)(() => {
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, import_react10.useEffect)(() => {
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 = "AI Shopping Assistant",
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, import_react10.useState)("");
1199
- const [selectedProduct, setSelectedProduct] = (0, import_react10.useState)(null);
1200
- const bottomRef = (0, import_react10.useRef)(null);
1201
- const textareaRef = (0, import_react10.useRef)(null);
1202
- (0, import_react10.useEffect)(() => {
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, import_react10.useEffect)(() => {
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: `hsk-cb-overlay ${classNames.overlay || ""}`,
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: `hsk-cb-panel ${classNames.panel || ""}`, onClick: (e) => e.stopPropagation(), children: [
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.jsxs)("div", { children: [
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: "What can I help you find?" }),
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: `hsk-cb-textarea ${classNames.input || ""}`,
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: `hsk-cb-send ${classNames.sendButton || ""}`,
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)(ArrowUpIcon2, {})
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, import_react10.useState)(false);
1375
- const [mounted, setMounted] = (0, import_react10.useState)(false);
1376
- (0, import_react10.useEffect)(() => {
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: `hsk-cb-btn ${classNames.button || ""} ${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,