@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.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;
@@ -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
- setError((_b = e.message) != null ? _b : "Search failed");
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
- setError((_c = e == null ? void 0 : e.message) != null ? _c : "Chat request failed");
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/components/SearchBar.tsx
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, import_react7.useState)("");
628
- const [open, setOpen] = (0, import_react7.useState)(false);
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, import_react7.useRef)();
631
- const wrap = (0, import_react7.useRef)(null);
632
- (0, import_react7.useEffect)(() => {
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, import_react7.useEffect)(() => {
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: `hsk-sb-wrap ${classNames.root || ""} ${className || ""}`, ref: wrap, style: customStyles, children: [
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: `hsk-sb-input ${classNames.input || ""} ${inputClassName || ""}`,
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: `hsk-sb-drop ${classNames.dropdown || ""} ${dropdownClassName || ""}`, style: { position: "absolute" }, children: [
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: `hsk-sb-row hsk-sb-fade ${classNames.row || ""}`,
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 import_react8 = require("react");
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 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" })
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
- 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);
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
- (0, import_react8.useEffect)(() => {
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, import_react8.useEffect)(() => {
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 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)(
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: `hsk-sp-backdrop ${classNames.backdrop || ""}`,
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, 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" })
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, import_jsx_runtime3.jsx)("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}) })
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
- 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() })
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, 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
- )
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
- 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"
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, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-esc", children: "Esc to close" })
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, import_react8.useState)(false);
853
- const [mounted, setMounted] = (0, import_react8.useState)(false);
854
- (0, import_react8.useEffect)(() => {
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, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
859
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
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: `hsk-sp-btn ${classNames.button || ""} ${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, import_jsx_runtime3.jsx)(SparkleIcon, {})
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, import_jsx_runtime3.jsx)(
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 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
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 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: [
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, import_react9.useState)("");
1012
- const bottomRef = (0, import_react9.useRef)(null);
1013
- const textareaRef = (0, import_react9.useRef)(null);
1014
- (0, import_react9.useEffect)(() => {
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: `hsk-chat-widget ${classNames.root || ""} ${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: `hsk-chat-header ${classNames.header || ""}`, children: [
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: `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) })
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: error }),
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: `hsk-chat-input ${classNames.input || ""}`,
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)(ArrowUpIcon, {})
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 import_react10 = require("react");
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 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: [
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, import_react10.useRef)(null);
1125
- const [showNext, setShowNext] = (0, import_react10.useState)(false);
1126
- const measure = (0, import_react10.useCallback)(() => {
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, import_react10.useEffect)(() => {
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 = "AI Shopping Assistant",
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, 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)(() => {
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, import_react10.useEffect)(() => {
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: `hsk-cb-overlay ${classNames.overlay || ""}`,
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: `hsk-cb-panel ${classNames.panel || ""}`, onClick: (e) => e.stopPropagation(), children: [
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.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
- ] })
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: "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." }),
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: `hsk-cb-textarea ${classNames.input || ""}`,
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: `hsk-cb-send ${classNames.sendButton || ""}`,
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)(ArrowUpIcon2, {})
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, import_react10.useState)(false);
1375
- const [mounted, setMounted] = (0, import_react10.useState)(false);
1376
- (0, import_react10.useEffect)(() => {
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: `hsk-cb-btn ${classNames.button || ""} ${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,