@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.mjs CHANGED
@@ -63,7 +63,15 @@ var HuskelAPI = class {
63
63
  });
64
64
  if (!res.ok) {
65
65
  const text = await res.text();
66
- const err = { status: res.status, message: text };
66
+ let message = text;
67
+ try {
68
+ const parsed = JSON.parse(text);
69
+ if (parsed && typeof parsed.error === "string") {
70
+ message = parsed.error;
71
+ }
72
+ } catch (e) {
73
+ }
74
+ const err = { status: res.status, message };
67
75
  if (res.status >= 400 && res.status < 500) {
68
76
  log("error", `${path} failed [${res.status}]`, text);
69
77
  throw err;
@@ -114,6 +122,52 @@ var HuskelAPI = class {
114
122
  log("info", "chat query", query);
115
123
  return this.post("/chat", { query, siteId: this.siteId, history });
116
124
  }
125
+ // --- Cart System ---
126
+ buildHeaders() {
127
+ var _a, _b;
128
+ const headers = {
129
+ "Content-Type": "application/json",
130
+ "X-Huskel-Token": this.apiToken,
131
+ "X-Huskel-Site": this.siteId
132
+ };
133
+ const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
134
+ if (shopperId) headers["X-Huskel-Shopper-Id"] = shopperId;
135
+ const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
136
+ if (sessionId) headers["X-Huskel-Session-Id"] = sessionId;
137
+ return headers;
138
+ }
139
+ async getCart() {
140
+ const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
141
+ headers: this.buildHeaders()
142
+ });
143
+ if (!res.ok) throw new Error("Failed to fetch cart");
144
+ return res.json();
145
+ }
146
+ async clearCart() {
147
+ const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
148
+ method: "DELETE",
149
+ headers: this.buildHeaders()
150
+ });
151
+ if (!res.ok) throw new Error("Failed to clear cart");
152
+ return res.json();
153
+ }
154
+ async checkoutCart() {
155
+ const res = await fetch(`${this.apiUrl}/cart/checkout`, {
156
+ method: "POST",
157
+ headers: this.buildHeaders(),
158
+ body: JSON.stringify({ siteId: this.siteId })
159
+ });
160
+ if (!res.ok) throw new Error("Failed to checkout cart");
161
+ return res.json();
162
+ }
163
+ async getCheckoutConfig() {
164
+ const res = await fetch(`${this.apiUrl}/checkout/config?site_id=${this.siteId}`, {
165
+ method: "GET",
166
+ headers: this.buildHeaders()
167
+ });
168
+ if (!res.ok) throw new Error("Failed to fetch checkout config");
169
+ return res.json();
170
+ }
117
171
  };
118
172
 
119
173
  // src/client.ts
@@ -221,13 +275,14 @@ var _HuskelClient = class _HuskelClient {
221
275
  if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl="..."> or NEXT_PUBLIC_HUSKEL_API_URL.');
222
276
  if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken="..."> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');
223
277
  this.shopperId = config.shopperId;
278
+ this.onCheckout = config.onCheckout;
224
279
  this.initSession();
225
280
  this.loadIngestedCache();
226
281
  this.api = new HuskelAPI(
227
282
  apiUrl,
228
283
  siteId,
229
284
  apiToken,
230
- () => this.shopperId,
285
+ () => this.getShopperId(),
231
286
  () => this.sessionId
232
287
  );
233
288
  instance = this;
@@ -268,7 +323,7 @@ var _HuskelClient = class _HuskelClient {
268
323
  this.shopperId = id;
269
324
  }
270
325
  getShopperId() {
271
- return this.shopperId;
326
+ return this.shopperId || "guest_" + this.sessionId;
272
327
  }
273
328
  getSessionId() {
274
329
  return this.sessionId;
@@ -441,7 +496,15 @@ function useSearch() {
441
496
  }
442
497
  } catch (e) {
443
498
  if (gen === genRef.current) {
444
- setError((_b = e.message) != null ? _b : "Search failed");
499
+ let msg = (_b = e == null ? void 0 : e.message) != null ? _b : "Search failed";
500
+ try {
501
+ const parsed = JSON.parse(msg);
502
+ if (parsed && parsed.error) {
503
+ msg = parsed.error;
504
+ }
505
+ } catch (e2) {
506
+ }
507
+ setError(msg);
445
508
  }
446
509
  } finally {
447
510
  if (gen === genRef.current) setLoading(false);
@@ -516,13 +579,13 @@ function useChat() {
516
579
  const [loading, setLoading] = useState3(false);
517
580
  const [error, setError] = useState3(null);
518
581
  const abortRef = useRef5(null);
519
- const send = useCallback3(async (query) => {
520
- var _a, _b, _c;
582
+ const send = useCallback3(async (query, displayQuery) => {
583
+ var _a, _b, _c, _d, _e;
521
584
  if (!query.trim() || loading) return;
522
585
  (_a = abortRef.current) == null ? void 0 : _a.abort();
523
586
  abortRef.current = new AbortController();
524
587
  const signal = abortRef.current.signal;
525
- const userMsg = { role: "user", content: query };
588
+ const userMsg = { role: "user", content: displayQuery != null ? displayQuery : query };
526
589
  setMessages((prev) => [...prev, userMsg]);
527
590
  setLoading(true);
528
591
  setError(null);
@@ -548,9 +611,30 @@ function useChat() {
548
611
  }
549
612
  if (signal.aborted) return;
550
613
  setSources((_b = res.sources) != null ? _b : []);
614
+ if (((_c = res.action) == null ? void 0 : _c.type) === "add_to_cart" || res.checkout) {
615
+ if (typeof window !== "undefined") {
616
+ window.dispatchEvent(new CustomEvent("huskel:cart_updated", { detail: res.checkout }));
617
+ }
618
+ }
619
+ if (((_d = res.action) == null ? void 0 : _d.type) === "checkout") {
620
+ if (typeof window !== "undefined") {
621
+ window.dispatchEvent(new CustomEvent("huskel:trigger_checkout", { detail: res.checkout }));
622
+ }
623
+ }
624
+ if (res.checkout && client.onCheckout) {
625
+ client.onCheckout(res.checkout);
626
+ }
551
627
  } catch (e) {
552
628
  if (signal.aborted) return;
553
- setError((_c = e == null ? void 0 : e.message) != null ? _c : "Chat request failed");
629
+ let msg = (_e = e == null ? void 0 : e.message) != null ? _e : "Chat request failed";
630
+ try {
631
+ const parsed = JSON.parse(msg);
632
+ if (parsed && parsed.error) {
633
+ msg = parsed.error;
634
+ }
635
+ } catch (e2) {
636
+ }
637
+ setError(msg);
554
638
  setMessages((prev) => prev.slice(0, -1));
555
639
  } finally {
556
640
  if (!signal.aborted) {
@@ -569,8 +653,53 @@ function useChat() {
569
653
  return { messages, sources, loading, error, send, reset };
570
654
  }
571
655
 
656
+ // src/hooks/useCart.ts
657
+ import { useState as useState4, useEffect as useEffect3, useCallback as useCallback4 } from "react";
658
+ function useCart() {
659
+ const client = useHuskelContext();
660
+ const [cart, setCart] = useState4(null);
661
+ const [loading, setLoading] = useState4(false);
662
+ const shopperId = client.getShopperId();
663
+ const fetchCart = useCallback4(async () => {
664
+ if (!shopperId) return;
665
+ setLoading(true);
666
+ try {
667
+ const res = await client.api.getCart();
668
+ setCart(res);
669
+ } catch (e) {
670
+ console.error("[Huskel] Failed to fetch cart", e);
671
+ } finally {
672
+ setLoading(false);
673
+ }
674
+ }, [client, shopperId]);
675
+ useEffect3(() => {
676
+ fetchCart();
677
+ const handleCartUpdate = (e) => {
678
+ if (e.detail) {
679
+ setCart(e.detail);
680
+ } else {
681
+ fetchCart();
682
+ }
683
+ };
684
+ if (typeof window !== "undefined") {
685
+ window.addEventListener("huskel:cart_updated", handleCartUpdate);
686
+ return () => window.removeEventListener("huskel:cart_updated", handleCartUpdate);
687
+ }
688
+ }, [fetchCart, shopperId]);
689
+ return { cart, loading, fetchCart };
690
+ }
691
+
692
+ // src/components/SearchBar.tsx
693
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef6 } from "react";
694
+
695
+ // src/utils/cn.ts
696
+ import { clsx } from "clsx";
697
+ import { twMerge } from "tailwind-merge";
698
+ function cn(...inputs) {
699
+ return twMerge(clsx(inputs));
700
+ }
701
+
572
702
  // src/components/SearchBar.tsx
573
- import { useState as useState4, useEffect as useEffect3, useRef as useRef6 } from "react";
574
703
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
575
704
  var SearchIcon = () => /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
576
705
  /* @__PURE__ */ jsx2("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
@@ -588,12 +717,12 @@ function SearchBar({
588
717
  theme,
589
718
  classNames = {}
590
719
  }) {
591
- const [query, setQuery] = useState4("");
592
- const [open, setOpen] = useState4(false);
720
+ const [query, setQuery] = useState5("");
721
+ const [open, setOpen] = useState5(false);
593
722
  const { results, loading, search, clear } = useSearch();
594
723
  const timer = useRef6();
595
724
  const wrap = useRef6(null);
596
- useEffect3(() => {
725
+ useEffect4(() => {
597
726
  clearTimeout(timer.current);
598
727
  if (!query.trim()) {
599
728
  clear();
@@ -606,7 +735,7 @@ function SearchBar({
606
735
  }, debounceMs);
607
736
  return () => clearTimeout(timer.current);
608
737
  }, [query]);
609
- useEffect3(() => {
738
+ useEffect4(() => {
610
739
  const h = (e) => {
611
740
  if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
612
741
  };
@@ -619,13 +748,13 @@ function SearchBar({
619
748
  onSelect == null ? void 0 : onSelect(r);
620
749
  };
621
750
  const showDrop = open && query.trim().length > 0;
622
- 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 });
623
- return /* @__PURE__ */ jsxs("div", { className: `hsk-sb-wrap ${classNames.root || ""} ${className || ""}`, ref: wrap, style: customStyles, children: [
751
+ 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 });
752
+ return /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-wrap", classNames.root, className), ref: wrap, style: customStyles, children: [
624
753
  /* @__PURE__ */ jsx2("span", { className: "hsk-sb-icon", children: /* @__PURE__ */ jsx2(SearchIcon, {}) }),
625
754
  /* @__PURE__ */ jsx2(
626
755
  "input",
627
756
  {
628
- className: `hsk-sb-input ${classNames.input || ""} ${inputClassName || ""}`,
757
+ className: cn("hsk-sb-input", classNames.input, inputClassName),
629
758
  type: "text",
630
759
  value: query,
631
760
  placeholder,
@@ -635,7 +764,7 @@ function SearchBar({
635
764
  spellCheck: false
636
765
  }
637
766
  ),
638
- showDrop && /* @__PURE__ */ jsxs("div", { className: `hsk-sb-drop ${classNames.dropdown || ""} ${dropdownClassName || ""}`, style: { position: "absolute" }, children: [
767
+ showDrop && /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
639
768
  loading && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-loading-bar" }),
640
769
  results.length === 0 && !loading && /* @__PURE__ */ jsxs("div", { className: "hsk-sb-empty", children: [
641
770
  "No results for \u201C",
@@ -656,7 +785,7 @@ function SearchBar({
656
785
  ) : /* @__PURE__ */ jsxs(
657
786
  "div",
658
787
  {
659
- className: `hsk-sb-row hsk-sb-fade ${classNames.row || ""}`,
788
+ className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
660
789
  style: { animationDelay: `${i * 18}ms` },
661
790
  onClick: () => handleSelect(r),
662
791
  children: [
@@ -675,14 +804,124 @@ function SearchBar({
675
804
  }
676
805
 
677
806
  // src/components/Sparkle.tsx
678
- import { useState as useState5, useEffect as useEffect4, useRef as useRef7 } from "react";
807
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef7 } from "react";
679
808
  import { createPortal } from "react-dom";
680
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
681
- var SparkleIcon = ({ className }) => /* @__PURE__ */ jsx3("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("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" }) });
809
+
810
+ // src/utils/markdown.tsx
811
+ import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
812
+ var parseInline = (text, keyPrefix) => {
813
+ const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
814
+ const parts = text.split(tokenRegex);
815
+ return parts.map((part, index) => {
816
+ if (!part) return null;
817
+ const key = `${keyPrefix}-inline-${index}`;
818
+ if (part.startsWith("`") && part.endsWith("`")) {
819
+ return /* @__PURE__ */ jsx3("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
820
+ }
821
+ if (part.startsWith("**") && part.endsWith("**")) {
822
+ return /* @__PURE__ */ jsx3("strong", { children: parseInline(part.slice(2, -2), key) }, key);
823
+ }
824
+ const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
825
+ if (linkMatch) {
826
+ const url = linkMatch[2];
827
+ const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
828
+ if (isSafeUrl) {
829
+ return /* @__PURE__ */ jsx3("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "hsk-markdown-link", children: parseInline(linkMatch[1], key) }, key);
830
+ }
831
+ return /* @__PURE__ */ jsx3("span", { children: parseInline(linkMatch[1], key) }, key);
832
+ }
833
+ return part;
834
+ });
835
+ };
836
+ function renderMarkdown(content) {
837
+ const lines = content.split("\n");
838
+ const elements = [];
839
+ let i = 0;
840
+ while (i < lines.length) {
841
+ const line = lines[i];
842
+ const key = `md-line-${i}`;
843
+ if (!line.trim()) {
844
+ i++;
845
+ continue;
846
+ }
847
+ const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
848
+ if (headerMatch) {
849
+ const level = headerMatch[1].length;
850
+ const Tag = `h${level + 3}`;
851
+ elements.push(/* @__PURE__ */ jsx3(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
852
+ i++;
853
+ continue;
854
+ }
855
+ if (line.match(/^[-*]\s+/)) {
856
+ const listItems = [];
857
+ while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
858
+ const itemText = lines[i].replace(/^[-*]\s+/, "");
859
+ listItems.push(/* @__PURE__ */ jsx3("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
860
+ i++;
861
+ }
862
+ elements.push(/* @__PURE__ */ jsx3("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
863
+ continue;
864
+ }
865
+ if (line.trim().startsWith("|")) {
866
+ const tableRows = [];
867
+ let isHeader = true;
868
+ while (i < lines.length && lines[i].trim().startsWith("|")) {
869
+ const rowLine = lines[i].trim();
870
+ if (rowLine.match(/^\|[-:| ]+\|$/)) {
871
+ i++;
872
+ isHeader = false;
873
+ continue;
874
+ }
875
+ const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
876
+ const Tag = isHeader ? "th" : "td";
877
+ tableRows.push(
878
+ /* @__PURE__ */ jsx3("tr", { children: cells.map((cell, cIdx) => /* @__PURE__ */ jsx3(Tag, { children: parseInline(cell, `td-${i}-${cIdx}`) }, `td-${i}-${cIdx}`)) }, `tr-${i}`)
879
+ );
880
+ i++;
881
+ }
882
+ elements.push(
883
+ /* @__PURE__ */ jsx3("div", { className: "hsk-table-wrapper", children: /* @__PURE__ */ jsx3("table", { className: "hsk-markdown-table", children: /* @__PURE__ */ jsx3("tbody", { children: tableRows }) }) }, `table-wrapper-${key}`)
884
+ );
885
+ continue;
886
+ }
887
+ elements.push(
888
+ /* @__PURE__ */ jsx3("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
889
+ );
890
+ i++;
891
+ }
892
+ return /* @__PURE__ */ jsx3(Fragment, { children: elements });
893
+ }
894
+
895
+ // src/components/Sparkle.tsx
896
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
897
+ var SparkleIcon = ({ className }) => /* @__PURE__ */ jsx4("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("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" }) });
682
898
  var CloseIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
683
- /* @__PURE__ */ jsx3("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
684
- /* @__PURE__ */ jsx3("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
899
+ /* @__PURE__ */ jsx4("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
900
+ /* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
901
+ ] });
902
+ var ArrowUpIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
903
+ /* @__PURE__ */ jsx4("path", { d: "m5 12 7-7 7 7" }),
904
+ /* @__PURE__ */ jsx4("path", { d: "M12 19V5" })
685
905
  ] });
906
+ var getFriendlyError = (err) => {
907
+ let str = "";
908
+ if (typeof err === "string") str = err;
909
+ else if (err && typeof err === "object" && err.message) str = err.message;
910
+ else try {
911
+ str = JSON.stringify(err);
912
+ } catch (e) {
913
+ str = String(err);
914
+ }
915
+ if (str.toLowerCase().includes("token limit")) {
916
+ return "You've reached your usage limit. Please update your billing limits in your dashboard to continue.";
917
+ }
918
+ try {
919
+ const parsed = JSON.parse(str);
920
+ return parsed.error || parsed.message || str;
921
+ } catch (e) {
922
+ return str;
923
+ }
924
+ };
686
925
  function SparkleModal({
687
926
  productName,
688
927
  limit,
@@ -692,26 +931,42 @@ function SparkleModal({
692
931
  onNavigate,
693
932
  onResult,
694
933
  theme,
695
- classNames = {}
934
+ classNames = {},
935
+ product: initialProduct
696
936
  }) {
697
- const { results, loading, search } = useSearch();
698
- const initiated = useRef7(false);
699
- useEffect4(() => {
700
- if (!initiated.current) {
701
- initiated.current = true;
702
- search(productName, limit);
937
+ var _a, _b, _c;
938
+ const client = useHuskelContext();
939
+ const [fetchedProduct, setFetchedProduct] = useState6(null);
940
+ const displayProduct = initialProduct || fetchedProduct;
941
+ const { results, loading: searchLoading, search } = useSearch();
942
+ const { messages, sources, loading: chatLoading, error: chatError, send } = useChat();
943
+ const [chatInput, setChatInput] = useState6("");
944
+ const chatBottomRef = useRef7(null);
945
+ const chatTextareaRef = useRef7(null);
946
+ useEffect5(() => {
947
+ if (!initialProduct && !fetchedProduct) {
948
+ client.api.searchVector(productName, 1).then((res) => {
949
+ if (res.results && res.results.length > 0) {
950
+ setFetchedProduct(res.results[0].product);
951
+ }
952
+ }).catch((err) => console.error("[Huskel] Failed to fetch product details", err));
703
953
  }
704
- }, []);
705
- useEffect4(() => {
954
+ search(productName, limit);
955
+ }, [productName, initialProduct, fetchedProduct, client, limit, search]);
956
+ useEffect5(() => {
706
957
  if (results.length > 0) onResult == null ? void 0 : onResult(results);
707
- }, [results]);
708
- useEffect4(() => {
958
+ }, [results, onResult]);
959
+ useEffect5(() => {
709
960
  const h = (e) => {
710
961
  if (e.key === "Escape") onClose();
711
962
  };
712
963
  document.addEventListener("keydown", h);
713
964
  return () => document.removeEventListener("keydown", h);
714
- }, []);
965
+ }, [onClose]);
966
+ useEffect5(() => {
967
+ var _a2;
968
+ (_a2 = chatBottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
969
+ }, [messages, chatLoading]);
715
970
  const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "16px";
716
971
  const bg = backdropColor != null ? backdropColor : void 0;
717
972
  const handleNav = (r) => {
@@ -721,83 +976,209 @@ function SparkleModal({
721
976
  if (r.product.url) window.location.href = r.product.url;
722
977
  }
723
978
  };
724
- 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 });
725
- return /* @__PURE__ */ jsx3(
979
+ const handleSend = async (text) => {
980
+ const q = (text != null ? text : chatInput).trim();
981
+ if (!q || chatLoading) return;
982
+ setChatInput("");
983
+ if (chatTextareaRef.current) {
984
+ chatTextareaRef.current.style.height = "auto";
985
+ }
986
+ if (messages.length === 0 && displayProduct) {
987
+ const contextQuery = `[Context: Shopper is viewing "${displayProduct.name}". Price: ${displayProduct.price}. Description: ${displayProduct.description || ""}]
988
+
989
+ Question: ${q}`;
990
+ await send(contextQuery, q);
991
+ } else {
992
+ await send(q);
993
+ }
994
+ };
995
+ const handleKeyDown = (e) => {
996
+ if (e.key === "Enter" && !e.shiftKey) {
997
+ e.preventDefault();
998
+ handleSend();
999
+ }
1000
+ };
1001
+ const handleInput = (e) => {
1002
+ setChatInput(e.target.value);
1003
+ const t = e.target;
1004
+ t.style.height = "auto";
1005
+ t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
1006
+ };
1007
+ 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 });
1008
+ const displayMessages = messages.length === 0 && displayProduct ? [
1009
+ {
1010
+ role: "assistant",
1011
+ content: `Hi! I can help you with **${displayProduct.name}**. Ask me about its specifications, features, compare it with other options, or find alternatives!`
1012
+ }
1013
+ ] : messages;
1014
+ return /* @__PURE__ */ jsx4(
726
1015
  "div",
727
1016
  {
728
- className: `hsk-sp-backdrop ${classNames.backdrop || ""}`,
1017
+ className: cn("hsk-sp-backdrop", classNames.backdrop),
729
1018
  onClick: onClose,
730
1019
  style: __spreadValues({
731
1020
  backdropFilter: `blur(${blurVal})`,
732
1021
  WebkitBackdropFilter: `blur(${blurVal})`,
733
1022
  background: bg != null ? bg : void 0
734
1023
  }, customStyles),
735
- children: /* @__PURE__ */ jsxs2("div", { className: `hsk-sp-card ${classNames.card || ""}`, onClick: (e) => e.stopPropagation(), children: [
1024
+ children: /* @__PURE__ */ jsxs2("div", { className: cn("hsk-sp-card hsk-sp-fullscreen", classNames.card), onClick: (e) => e.stopPropagation(), children: [
736
1025
  /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-header", children: [
737
- /* @__PURE__ */ jsx3("span", { className: "hsk-sp-header-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx3(SparkleIcon, {}) }),
1026
+ /* @__PURE__ */ jsx4("span", { className: "hsk-sp-header-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
738
1027
  /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-header-body", children: [
739
- /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-header-title", children: [
740
- "Similar to \u201C",
741
- productName,
742
- "\u201D"
743
- ] }),
744
- /* @__PURE__ */ jsx3("div", { className: "hsk-sp-header-sub", children: "AI vector similarity \xB7 instant results" })
1028
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-header-title", children: (displayProduct == null ? void 0 : displayProduct.name) || productName }),
1029
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-header-sub", children: "Ask questions, compare specs, or check similar products" })
745
1030
  ] }),
746
- /* @__PURE__ */ jsx3("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx3(CloseIcon, {}) })
1031
+ /* @__PURE__ */ jsx4("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx4(CloseIcon, {}) })
747
1032
  ] }),
748
- loading && /* @__PURE__ */ jsx3("div", { className: "hsk-sp-bar" }),
749
- /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-results", children: [
750
- !loading && results.length === 0 && /* @__PURE__ */ jsx3("div", { className: "hsk-sp-empty", children: "No similar products found." }),
751
- results.map((r, i) => {
752
- var _a, _b, _c;
753
- const price = parseFloat(((_a = r.product.price) == null ? void 0 : _a.replace(/[^0-9.]/g, "")) || "0");
754
- const currency = (_b = r.product.currency) != null ? _b : "KES";
755
- return /* @__PURE__ */ jsxs2(
756
- "div",
757
- {
758
- className: `hsk-sp-item ${classNames.item || ""}`,
759
- style: { animationDelay: `${i * 55}ms` },
760
- children: [
761
- /* @__PURE__ */ jsx3("div", { className: "hsk-sp-img-wrap", children: ((_c = r.product.images) == null ? void 0 : _c[0]) ? /* @__PURE__ */ jsx3("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ jsx3("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
762
- /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-body", children: [
763
- r.product.category && /* @__PURE__ */ jsx3("div", { className: "hsk-sp-item-cat", children: r.product.category }),
764
- /* @__PURE__ */ jsx3("div", { className: "hsk-sp-item-name", children: r.product.name }),
765
- /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-price-row", children: [
766
- /* @__PURE__ */ jsx3("span", { className: "hsk-sp-item-currency", children: currency }),
767
- /* @__PURE__ */ jsx3("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
1033
+ searchLoading && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-bar" }),
1034
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-body", children: [
1035
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-pane", children: [
1036
+ displayProduct && /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-product-profile-container", children: [
1037
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-product-profile", children: [
1038
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-details-imgwrap", children: ((_a = displayProduct.images) == null ? void 0 : _a[0]) ? /* @__PURE__ */ jsx4("img", { src: displayProduct.images[0], alt: displayProduct.name }) : /* @__PURE__ */ jsx4("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
1039
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-meta", children: [
1040
+ displayProduct.brand && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-brand", children: displayProduct.brand }),
1041
+ displayProduct.category && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-cat", children: displayProduct.category }),
1042
+ /* @__PURE__ */ jsx4("h2", { className: "hsk-sp-details-name", children: displayProduct.name }),
1043
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-price-row", children: [
1044
+ /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-currency", children: (_b = displayProduct.currency) != null ? _b : "KES" }),
1045
+ /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-price", children: parseFloat(((_c = displayProduct.price) == null ? void 0 : _c.replace(/[^0-9.]/g, "")) || "0").toLocaleString() }),
1046
+ displayProduct.originalPrice && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-original-price", children: parseFloat(displayProduct.originalPrice.replace(/[^0-9.]/g, "") || "0").toLocaleString() }),
1047
+ displayProduct.discount && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-item-discount", children: [
1048
+ "(",
1049
+ displayProduct.discount,
1050
+ ")"
1051
+ ] })
1052
+ ] }),
1053
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-meta-badges", children: [
1054
+ displayProduct.rating && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-rating", children: [
1055
+ "\u2605 ",
1056
+ parseFloat(displayProduct.rating.toString()).toFixed(1),
1057
+ " ",
1058
+ displayProduct.reviewCount ? `(${displayProduct.reviewCount})` : ""
768
1059
  ] }),
769
- /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-actions", children: [
770
- /* @__PURE__ */ jsx3(
771
- "button",
772
- {
773
- className: "hsk-sp-action hsk-sp-action-primary",
774
- onClick: () => handleNav(r),
775
- children: "View Product"
776
- }
777
- ),
778
- /* @__PURE__ */ jsx3(
779
- "button",
780
- {
781
- className: "hsk-sp-action hsk-sp-action-secondary",
782
- onClick: () => onClose(),
783
- children: "Add to Cart"
784
- }
785
- )
1060
+ displayProduct.availability && /* @__PURE__ */ jsx4("span", { className: `hsk-sp-meta-badge hsk-sp-meta-badge-avail ${displayProduct.availability.toLowerCase().includes("in") ? "in-stock" : "out-stock"}`, children: displayProduct.availability }),
1061
+ displayProduct.stock && !displayProduct.availability && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-stock", children: [
1062
+ "Stock: ",
1063
+ displayProduct.stock
786
1064
  ] })
787
1065
  ] })
788
- ]
789
- },
790
- r.id
791
- );
792
- })
793
- ] }),
794
- /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-footer", children: [
795
- /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-badge", style: { display: "inline-flex", alignItems: "center", gap: "4px" }, children: [
796
- /* @__PURE__ */ jsx3(SparkleIcon, {}),
797
- " Huskel AI"
1066
+ ] })
1067
+ ] }),
1068
+ displayProduct.specs && Object.keys(displayProduct.specs).length > 0 && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-specs-horizontal", children: Object.entries(displayProduct.specs).map(([key, val]) => /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-spec-item-horizontal", children: [
1069
+ /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-spec-label-horizontal", children: [
1070
+ key,
1071
+ ":"
1072
+ ] }),
1073
+ /* @__PURE__ */ jsx4("span", { className: "hsk-sp-spec-value-horizontal", title: val, children: val })
1074
+ ] }, key)) }),
1075
+ displayProduct.description && /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-desc", children: [
1076
+ /* @__PURE__ */ jsx4("h4", { children: "Description" }),
1077
+ /* @__PURE__ */ jsx4("p", { children: displayProduct.description })
1078
+ ] })
1079
+ ] }),
1080
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-similar-section", children: [
1081
+ /* @__PURE__ */ jsx4("h3", { children: "Similar Products" }),
1082
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-results", children: (() => {
1083
+ const similarProducts = results.filter(
1084
+ (r) => {
1085
+ var _a2;
1086
+ const isSameName = r.product.name.toLowerCase() === ((_a2 = displayProduct == null ? void 0 : displayProduct.name) == null ? void 0 : _a2.toLowerCase());
1087
+ const isSameSlug = r.product.slug && (displayProduct == null ? void 0 : displayProduct.slug) && r.product.slug.toLowerCase() === displayProduct.slug.toLowerCase();
1088
+ return !isSameName && !isSameSlug;
1089
+ }
1090
+ );
1091
+ if (!searchLoading && similarProducts.length === 0) {
1092
+ return /* @__PURE__ */ jsx4("div", { className: "hsk-sp-empty", children: "No similar products found." });
1093
+ }
1094
+ return similarProducts.map((r, i) => {
1095
+ var _a2, _b2, _c2;
1096
+ const price = parseFloat(((_a2 = r.product.price) == null ? void 0 : _a2.replace(/[^0-9.]/g, "")) || "0");
1097
+ const currency = (_b2 = r.product.currency) != null ? _b2 : "KES";
1098
+ return /* @__PURE__ */ jsxs2(
1099
+ "div",
1100
+ {
1101
+ className: cn("hsk-sp-item", classNames.item),
1102
+ style: { animationDelay: `${i * 55}ms` },
1103
+ children: [
1104
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-img-wrap", children: ((_c2 = r.product.images) == null ? void 0 : _c2[0]) ? /* @__PURE__ */ jsx4("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ jsx4("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
1105
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-body", children: [
1106
+ /* @__PURE__ */ jsxs2("div", { children: [
1107
+ r.product.category && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-item-cat", children: r.product.category }),
1108
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-item-name", title: r.product.name, children: r.product.name })
1109
+ ] }),
1110
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-price-row", children: [
1111
+ /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-currency", children: currency }),
1112
+ /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
1113
+ ] }),
1114
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-actions", children: /* @__PURE__ */ jsx4(
1115
+ "button",
1116
+ {
1117
+ className: "hsk-sp-action hsk-sp-action-primary",
1118
+ onClick: () => handleNav(r),
1119
+ children: "View"
1120
+ }
1121
+ ) })
1122
+ ] })
1123
+ ]
1124
+ },
1125
+ r.id
1126
+ );
1127
+ });
1128
+ })() })
1129
+ ] })
798
1130
  ] }),
799
- /* @__PURE__ */ jsx3("span", { className: "hsk-sp-esc", children: "Esc to close" })
800
- ] })
1131
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-chat-pane", children: [
1132
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-msgs", children: [
1133
+ displayMessages.map((msg, idx) => {
1134
+ const isUser = msg.role === "user";
1135
+ return /* @__PURE__ */ jsx4("div", { className: "hsk-cb-msg-group", children: isUser ? /* @__PURE__ */ jsx4("div", { className: "hsk-cb-user-msg", children: /* @__PURE__ */ jsx4("div", { className: "hsk-cb-user-bubble", children: msg.content }) }) : /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-ai-msg", children: [
1136
+ /* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
1137
+ /* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-text", children: renderMarkdown(msg.content) }) })
1138
+ ] }) }, idx);
1139
+ }),
1140
+ chatLoading && /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-typing-row", children: [
1141
+ /* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
1142
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-typing", children: [
1143
+ /* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" }),
1144
+ /* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" }),
1145
+ /* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" })
1146
+ ] })
1147
+ ] }),
1148
+ chatError && /* @__PURE__ */ jsx4("div", { className: "hsk-cb-error", children: getFriendlyError(chatError) }),
1149
+ /* @__PURE__ */ jsx4("div", { ref: chatBottomRef, style: { height: 1 } })
1150
+ ] }),
1151
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-input-wrap", children: [
1152
+ /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-input-box", children: [
1153
+ /* @__PURE__ */ jsx4(
1154
+ "textarea",
1155
+ {
1156
+ ref: chatTextareaRef,
1157
+ className: "hsk-cb-textarea",
1158
+ value: chatInput,
1159
+ onChange: handleInput,
1160
+ onKeyDown: handleKeyDown,
1161
+ placeholder: "Ask about this product, specs, or comparison...",
1162
+ rows: 1,
1163
+ disabled: chatLoading
1164
+ }
1165
+ ),
1166
+ /* @__PURE__ */ jsx4(
1167
+ "button",
1168
+ {
1169
+ className: "hsk-cb-send",
1170
+ onClick: () => handleSend(),
1171
+ disabled: !chatInput.trim() || chatLoading,
1172
+ "aria-label": "Send message",
1173
+ children: /* @__PURE__ */ jsx4(ArrowUpIcon, {})
1174
+ }
1175
+ )
1176
+ ] }),
1177
+ /* @__PURE__ */ jsx4("div", { className: "hsk-cb-hint", children: "Huskel AI \xB7 instant product knowledge" })
1178
+ ] })
1179
+ ] })
1180
+ ] }),
1181
+ /* @__PURE__ */ jsx4("div", { className: "hsk-sp-footer", children: /* @__PURE__ */ jsx4("span", { className: "hsk-sp-esc", children: "Esc to close" }) })
801
1182
  ] })
802
1183
  }
803
1184
  );
@@ -811,39 +1192,41 @@ function Sparkle({
811
1192
  className,
812
1193
  onNavigate,
813
1194
  theme,
814
- classNames = {}
1195
+ classNames = {},
1196
+ product
815
1197
  }) {
816
- const [open, setOpen] = useState5(false);
817
- const [mounted, setMounted] = useState5(false);
818
- useEffect4(() => {
1198
+ const [open, setOpen] = useState6(false);
1199
+ const [mounted, setMounted] = useState6(false);
1200
+ useEffect5(() => {
819
1201
  setMounted(true);
820
1202
  }, []);
821
- 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 });
822
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
823
- /* @__PURE__ */ jsx3(
1203
+ 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 });
1204
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1205
+ /* @__PURE__ */ jsx4(
824
1206
  "button",
825
1207
  {
826
- className: `hsk-sp-btn ${classNames.button || ""} ${className || ""}`,
1208
+ className: cn("hsk-sp-btn", classNames.button, className),
827
1209
  onClick: () => setOpen(true),
828
1210
  style: customStyles,
829
1211
  title: "Find similar products",
830
1212
  "aria-label": "Find similar products",
831
- children: /* @__PURE__ */ jsx3(SparkleIcon, {})
1213
+ children: /* @__PURE__ */ jsx4(SparkleIcon, {})
832
1214
  }
833
1215
  ),
834
1216
  open && mounted && createPortal(
835
- /* @__PURE__ */ jsx3(
1217
+ /* @__PURE__ */ jsx4(
836
1218
  SparkleModal,
837
1219
  {
838
1220
  productName,
839
1221
  limit,
1222
+ onResult,
840
1223
  backdropColor,
841
1224
  backdropBlur,
842
1225
  onClose: () => setOpen(false),
843
- onResult,
844
1226
  onNavigate,
845
1227
  theme,
846
- classNames
1228
+ classNames,
1229
+ product
847
1230
  }
848
1231
  ),
849
1232
  document.body
@@ -852,97 +1235,10 @@ function Sparkle({
852
1235
  }
853
1236
 
854
1237
  // src/components/ChatWidget.tsx
855
- import { useState as useState6, useRef as useRef8, useEffect as useEffect5 } from "react";
856
-
857
- // src/utils/markdown.tsx
858
- import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
859
- var parseInline = (text, keyPrefix) => {
860
- const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
861
- const parts = text.split(tokenRegex);
862
- return parts.map((part, index) => {
863
- if (!part) return null;
864
- const key = `${keyPrefix}-inline-${index}`;
865
- if (part.startsWith("`") && part.endsWith("`")) {
866
- return /* @__PURE__ */ jsx4("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
867
- }
868
- if (part.startsWith("**") && part.endsWith("**")) {
869
- return /* @__PURE__ */ jsx4("strong", { children: parseInline(part.slice(2, -2), key) }, key);
870
- }
871
- const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
872
- if (linkMatch) {
873
- const url = linkMatch[2];
874
- const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
875
- if (isSafeUrl) {
876
- return /* @__PURE__ */ jsx4("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "hsk-markdown-link", children: parseInline(linkMatch[1], key) }, key);
877
- }
878
- return /* @__PURE__ */ jsx4("span", { children: parseInline(linkMatch[1], key) }, key);
879
- }
880
- return part;
881
- });
882
- };
883
- function renderMarkdown(content) {
884
- const lines = content.split("\n");
885
- const elements = [];
886
- let i = 0;
887
- while (i < lines.length) {
888
- const line = lines[i];
889
- const key = `md-line-${i}`;
890
- if (!line.trim()) {
891
- i++;
892
- continue;
893
- }
894
- const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
895
- if (headerMatch) {
896
- const level = headerMatch[1].length;
897
- const Tag = `h${level + 3}`;
898
- elements.push(/* @__PURE__ */ jsx4(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
899
- i++;
900
- continue;
901
- }
902
- if (line.match(/^[-*]\s+/)) {
903
- const listItems = [];
904
- while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
905
- const itemText = lines[i].replace(/^[-*]\s+/, "");
906
- listItems.push(/* @__PURE__ */ jsx4("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
907
- i++;
908
- }
909
- elements.push(/* @__PURE__ */ jsx4("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
910
- continue;
911
- }
912
- if (line.trim().startsWith("|")) {
913
- const tableRows = [];
914
- let isHeader = true;
915
- while (i < lines.length && lines[i].trim().startsWith("|")) {
916
- const rowLine = lines[i].trim();
917
- if (rowLine.match(/^\|[-:| ]+\|$/)) {
918
- i++;
919
- isHeader = false;
920
- continue;
921
- }
922
- const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
923
- const Tag = isHeader ? "th" : "td";
924
- tableRows.push(
925
- /* @__PURE__ */ jsx4("tr", { children: cells.map((cell, cIdx) => /* @__PURE__ */ jsx4(Tag, { children: parseInline(cell, `td-${i}-${cIdx}`) }, `td-${i}-${cIdx}`)) }, `tr-${i}`)
926
- );
927
- i++;
928
- }
929
- elements.push(
930
- /* @__PURE__ */ jsx4("div", { className: "hsk-table-wrapper", children: /* @__PURE__ */ jsx4("table", { className: "hsk-markdown-table", children: /* @__PURE__ */ jsx4("tbody", { children: tableRows }) }) }, `table-wrapper-${key}`)
931
- );
932
- continue;
933
- }
934
- elements.push(
935
- /* @__PURE__ */ jsx4("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
936
- );
937
- i++;
938
- }
939
- return /* @__PURE__ */ jsx4(Fragment2, { children: elements });
940
- }
941
-
942
- // src/components/ChatWidget.tsx
1238
+ import { useState as useState7, useRef as useRef8, useEffect as useEffect6 } from "react";
943
1239
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
944
1240
  var SparkleIcon2 = () => /* @__PURE__ */ jsx5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx5("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" }) });
945
- var ArrowUpIcon = () => /* @__PURE__ */ jsxs3("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1241
+ var ArrowUpIcon2 = () => /* @__PURE__ */ jsxs3("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
946
1242
  /* @__PURE__ */ jsx5("path", { d: "m5 12 7-7 7 7" }),
947
1243
  /* @__PURE__ */ jsx5("path", { d: "M12 19V5" })
948
1244
  ] });
@@ -972,10 +1268,10 @@ function ChatWidget({
972
1268
  onSelectSource
973
1269
  }) {
974
1270
  const { messages, sources, loading, error, send, reset } = useChat();
975
- const [input, setInput] = useState6("");
1271
+ const [input, setInput] = useState7("");
976
1272
  const bottomRef = useRef8(null);
977
1273
  const textareaRef = useRef8(null);
978
- useEffect5(() => {
1274
+ useEffect6(() => {
979
1275
  var _a;
980
1276
  (_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
981
1277
  }, [messages, loading]);
@@ -998,14 +1294,14 @@ function ChatWidget({
998
1294
  t.style.height = "auto";
999
1295
  t.style.height = Math.min(t.scrollHeight, 120) + "px";
1000
1296
  };
1001
- 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 });
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 });
1002
1298
  return /* @__PURE__ */ jsxs3(
1003
1299
  "div",
1004
1300
  {
1005
- className: `hsk-chat-widget ${classNames.root || ""} ${className || ""}`,
1301
+ className: cn("hsk-chat-widget", classNames.root, className),
1006
1302
  style: customStyles,
1007
1303
  children: [
1008
- /* @__PURE__ */ jsxs3("div", { className: `hsk-chat-header ${classNames.header || ""}`, children: [
1304
+ /* @__PURE__ */ jsxs3("div", { className: cn("hsk-chat-header", classNames.header), children: [
1009
1305
  /* @__PURE__ */ jsx5("span", { className: "hsk-chat-header-icon", children: /* @__PURE__ */ jsx5(SparkleIcon2, {}) }),
1010
1306
  /* @__PURE__ */ jsx5("span", { className: "hsk-chat-title", children: title }),
1011
1307
  /* @__PURE__ */ jsx5("span", { className: "hsk-chat-badge", children: "AI" }),
@@ -1018,8 +1314,8 @@ function ChatWidget({
1018
1314
  /* @__PURE__ */ jsx5("div", { className: "hsk-chat-empty-suggestions", children: emptyStateSuggestions })
1019
1315
  ] }) : messages.map((msg, idx) => /* @__PURE__ */ jsxs3("div", { children: [
1020
1316
  /* @__PURE__ */ jsxs3("div", { className: `hsk-msg-row ${msg.role}`, children: [
1021
- /* @__PURE__ */ jsx5("div", { className: `hsk-msg-avatar ${msg.role === "assistant" ? "ai" : "user"}`, children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(SparkleIcon2, {}) : "U" }),
1022
- /* @__PURE__ */ jsx5("div", { className: `hsk-msg-bubble ${msg.role} ${classNames.messageBubble || ""}`, children: renderMarkdown(msg.content) })
1317
+ /* @__PURE__ */ jsx5("div", { className: cn("hsk-msg-avatar", msg.role === "assistant" ? "ai" : "user"), children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(SparkleIcon2, {}) : "U" }),
1318
+ /* @__PURE__ */ jsx5("div", { className: cn("hsk-msg-bubble", msg.role, classNames.messageBubble), children: renderMarkdown(msg.content) })
1023
1319
  ] }),
1024
1320
  msg.role === "assistant" && idx === messages.length - 1 && sources.length > 0 && /* @__PURE__ */ jsx5("div", { className: "hsk-sources-container", children: /* @__PURE__ */ jsx5("div", { className: "hsk-sources", children: sources.map((src, si) => /* @__PURE__ */ jsx5(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) })
1025
1321
  ] }, idx)),
@@ -1031,7 +1327,14 @@ function ChatWidget({
1031
1327
  /* @__PURE__ */ jsx5("div", { className: "hsk-typing-dot" })
1032
1328
  ] })
1033
1329
  ] }),
1034
- error && /* @__PURE__ */ jsx5("div", { className: "hsk-chat-error", children: error }),
1330
+ error && /* @__PURE__ */ jsx5("div", { className: "hsk-chat-error", children: (() => {
1331
+ try {
1332
+ const parsed = JSON.parse(error);
1333
+ return parsed.error || parsed.message || error;
1334
+ } catch (e) {
1335
+ return error;
1336
+ }
1337
+ })() }),
1035
1338
  /* @__PURE__ */ jsx5("div", { ref: bottomRef })
1036
1339
  ] }),
1037
1340
  /* @__PURE__ */ jsxs3("div", { className: "hsk-chat-input-area", children: [
@@ -1039,7 +1342,7 @@ function ChatWidget({
1039
1342
  "textarea",
1040
1343
  {
1041
1344
  ref: textareaRef,
1042
- className: `hsk-chat-input ${classNames.input || ""}`,
1345
+ className: cn("hsk-chat-input", classNames.input),
1043
1346
  value: input,
1044
1347
  onChange: handleInput,
1045
1348
  onKeyDown: handleKey,
@@ -1055,7 +1358,7 @@ function ChatWidget({
1055
1358
  onClick: handleSend,
1056
1359
  disabled: !input.trim() || loading,
1057
1360
  "aria-label": "Send message",
1058
- children: /* @__PURE__ */ jsx5(ArrowUpIcon, {})
1361
+ children: /* @__PURE__ */ jsx5(ArrowUpIcon2, {})
1059
1362
  }
1060
1363
  )
1061
1364
  ] })
@@ -1065,11 +1368,11 @@ function ChatWidget({
1065
1368
  }
1066
1369
 
1067
1370
  // src/components/AIChatButton.tsx
1068
- import { useState as useState7, useEffect as useEffect6, useRef as useRef9, useCallback as useCallback4 } from "react";
1371
+ import { useState as useState8, useEffect as useEffect7, useRef as useRef9, useCallback as useCallback5 } from "react";
1069
1372
  import { createPortal as createPortal2 } from "react-dom";
1070
1373
  import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1071
1374
  var SparkleIcon3 = ({ className }) => /* @__PURE__ */ jsx6("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6("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" }) });
1072
- var ArrowUpIcon2 = () => /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1375
+ var ArrowUpIcon3 = () => /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1073
1376
  /* @__PURE__ */ jsx6("path", { d: "m5 12 7-7 7 7" }),
1074
1377
  /* @__PURE__ */ jsx6("path", { d: "M12 19V5" })
1075
1378
  ] });
@@ -1086,14 +1389,14 @@ var DEFAULT_CHIPS = [
1086
1389
  ];
1087
1390
  function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1088
1391
  const railRef = useRef9(null);
1089
- const [showNext, setShowNext] = useState7(false);
1090
- const measure = useCallback4(() => {
1392
+ const [showNext, setShowNext] = useState8(false);
1393
+ const measure = useCallback5(() => {
1091
1394
  const el = railRef.current;
1092
1395
  if (!el) return;
1093
1396
  const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
1094
1397
  setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
1095
1398
  }, []);
1096
- useEffect6(() => {
1399
+ useEffect7(() => {
1097
1400
  measure();
1098
1401
  const el = railRef.current;
1099
1402
  if (!el) return;
@@ -1146,7 +1449,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1146
1449
  ] });
1147
1450
  }
1148
1451
  function ChatModal({
1149
- title = "AI Shopping Assistant",
1452
+ title = "Shopping Assistant",
1150
1453
  placeholder = "Ask me anything \u2014 gifts, budget, use case\u2026",
1151
1454
  backdropColor,
1152
1455
  backdropBlur,
@@ -1159,15 +1462,15 @@ function ChatModal({
1159
1462
  }) {
1160
1463
  var _a, _b;
1161
1464
  const { messages, sources, loading, error, send, reset } = useChat();
1162
- const [input, setInput] = useState7("");
1163
- const [selectedProduct, setSelectedProduct] = useState7(null);
1465
+ const [input, setInput] = useState8("");
1466
+ const [selectedProduct, setSelectedProduct] = useState8(null);
1164
1467
  const bottomRef = useRef9(null);
1165
1468
  const textareaRef = useRef9(null);
1166
- useEffect6(() => {
1469
+ useEffect7(() => {
1167
1470
  var _a2;
1168
1471
  (_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
1169
1472
  }, [messages, loading, selectedProduct]);
1170
- useEffect6(() => {
1473
+ useEffect7(() => {
1171
1474
  const h = (e) => {
1172
1475
  if (e.key === "Escape") onClose();
1173
1476
  };
@@ -1204,24 +1507,21 @@ function ChatModal({
1204
1507
  t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
1205
1508
  };
1206
1509
  const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "20px";
1207
- 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 });
1510
+ 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 });
1208
1511
  return /* @__PURE__ */ jsx6(
1209
1512
  "div",
1210
1513
  {
1211
- className: `hsk-cb-overlay ${classNames.overlay || ""}`,
1514
+ className: cn("hsk-cb-overlay", classNames.overlay),
1212
1515
  onClick: onClose,
1213
1516
  style: __spreadValues(__spreadValues({
1214
1517
  backdropFilter: `blur(${blurVal})`,
1215
1518
  WebkitBackdropFilter: `blur(${blurVal})`
1216
1519
  }, backdropColor ? { background: backdropColor } : {}), customStyles),
1217
- children: /* @__PURE__ */ jsxs4("div", { className: `hsk-cb-panel ${classNames.panel || ""}`, onClick: (e) => e.stopPropagation(), children: [
1520
+ children: /* @__PURE__ */ jsxs4("div", { className: cn("hsk-cb-panel", classNames.panel), onClick: (e) => e.stopPropagation(), children: [
1218
1521
  /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar", children: [
1219
1522
  /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar-left", children: [
1220
1523
  /* @__PURE__ */ jsx6("span", { className: "hsk-cb-topbar-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx6(SparkleIcon3, {}) }),
1221
- /* @__PURE__ */ jsxs4("div", { children: [
1222
- /* @__PURE__ */ jsx6("div", { className: "hsk-cb-topbar-title", children: title }),
1223
- /* @__PURE__ */ jsx6("div", { className: "hsk-cb-topbar-sub", children: "Powered by Huskel AI \xB7 searches the whole catalogue" })
1224
- ] })
1524
+ /* @__PURE__ */ jsx6("div", { children: /* @__PURE__ */ jsx6("div", { className: "hsk-cb-topbar-title", children: title }) })
1225
1525
  ] }),
1226
1526
  /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar-actions", children: [
1227
1527
  messages.length > 0 && /* @__PURE__ */ jsx6("button", { className: "hsk-cb-topbar-btn", onClick: reset, children: "Clear chat" }),
@@ -1231,8 +1531,7 @@ function ChatModal({
1231
1531
  /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-msgs", children: [
1232
1532
  messages.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-empty", children: [
1233
1533
  /* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx6(SparkleIcon3, {}) }),
1234
- /* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-title", children: "What can I help you find?" }),
1235
- /* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-sub", children: "Ask about products, budgets, gift ideas, specs \u2014 I'll search the entire catalogue for you." }),
1534
+ /* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-title", children: "Find exactly what you need" }),
1236
1535
  /* @__PURE__ */ jsx6("div", { className: "hsk-cb-chips", children: chips.map((chip) => /* @__PURE__ */ jsx6(
1237
1536
  "button",
1238
1537
  {
@@ -1295,7 +1594,7 @@ function ChatModal({
1295
1594
  "textarea",
1296
1595
  {
1297
1596
  ref: textareaRef,
1298
- className: `hsk-cb-textarea ${classNames.input || ""}`,
1597
+ className: cn("hsk-cb-textarea", classNames.input),
1299
1598
  value: input,
1300
1599
  onChange: handleInput,
1301
1600
  onKeyDown: handleKeyDown,
@@ -1308,11 +1607,11 @@ function ChatModal({
1308
1607
  /* @__PURE__ */ jsx6(
1309
1608
  "button",
1310
1609
  {
1311
- className: `hsk-cb-send ${classNames.sendButton || ""}`,
1610
+ className: cn("hsk-cb-send", classNames.sendButton),
1312
1611
  onClick: () => handleSend(),
1313
1612
  disabled: !input.trim() || loading,
1314
1613
  "aria-label": "Send message",
1315
- children: /* @__PURE__ */ jsx6(ArrowUpIcon2, {})
1614
+ children: /* @__PURE__ */ jsx6(ArrowUpIcon3, {})
1316
1615
  }
1317
1616
  )
1318
1617
  ] }),
@@ -1335,17 +1634,17 @@ function AIChatButton({
1335
1634
  theme,
1336
1635
  classNames = {}
1337
1636
  }) {
1338
- const [open, setOpen] = useState7(false);
1339
- const [mounted, setMounted] = useState7(false);
1340
- useEffect6(() => {
1637
+ const [open, setOpen] = useState8(false);
1638
+ const [mounted, setMounted] = useState8(false);
1639
+ useEffect7(() => {
1341
1640
  setMounted(true);
1342
1641
  }, []);
1343
- 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 });
1642
+ 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 });
1344
1643
  return /* @__PURE__ */ jsxs4(Fragment3, { children: [
1345
1644
  /* @__PURE__ */ jsxs4(
1346
1645
  "button",
1347
1646
  {
1348
- className: `hsk-cb-btn ${classNames.button || ""} ${className || ""}`,
1647
+ className: cn("hsk-cb-btn", classNames.button, className),
1349
1648
  onClick: () => setOpen(true),
1350
1649
  style: customStyles,
1351
1650
  "aria-label": "Open AI chat",
@@ -1375,8 +1674,260 @@ function AIChatButton({
1375
1674
  )
1376
1675
  ] });
1377
1676
  }
1677
+
1678
+ // src/components/CartBadge.tsx
1679
+ import { jsx as jsx7 } from "react/jsx-runtime";
1680
+ function CartBadge({ className }) {
1681
+ const { cart } = useCart();
1682
+ if (!cart || cart.item_count === 0) return null;
1683
+ return /* @__PURE__ */ jsx7("span", { className: cn("hsk-cart-badge", className), children: cart.item_count });
1684
+ }
1685
+
1686
+ // src/components/CartDrawer.tsx
1687
+ import { useState as useState10, useEffect as useEffect9 } from "react";
1688
+ import { createPortal as createPortal4 } from "react-dom";
1689
+
1690
+ // src/components/CheckoutModal.tsx
1691
+ import { useState as useState9, useEffect as useEffect8 } from "react";
1692
+ import { createPortal as createPortal3 } from "react-dom";
1693
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1694
+ function CheckoutModal({
1695
+ onClose,
1696
+ theme,
1697
+ customStyles,
1698
+ hskThemeAttr
1699
+ }) {
1700
+ var _a, _b, _c, _d;
1701
+ const { cart, loading: cartLoading } = useCart();
1702
+ const client = useHuskelContext();
1703
+ const [config, setConfig] = useState9(null);
1704
+ const [loading, setLoading] = useState9(true);
1705
+ const [checkingOut, setCheckingOut] = useState9(false);
1706
+ const [paymentSuccess, setPaymentSuccess] = useState9(false);
1707
+ useEffect8(() => {
1708
+ client.api.getCheckoutConfig().then((res) => setConfig(res.payment_methods)).catch((e) => console.error("[Huskel] Failed to fetch checkout config", e)).finally(() => setLoading(false));
1709
+ }, [client]);
1710
+ const handlePay = async (method) => {
1711
+ setCheckingOut(true);
1712
+ setTimeout(async () => {
1713
+ try {
1714
+ const payload = await client.api.checkoutCart();
1715
+ if (client.onCheckout) {
1716
+ client.onCheckout(payload);
1717
+ }
1718
+ setPaymentSuccess(true);
1719
+ setTimeout(() => {
1720
+ onClose();
1721
+ }, 3e3);
1722
+ } catch (e) {
1723
+ console.error("[Huskel] Checkout failed", e);
1724
+ setCheckingOut(false);
1725
+ }
1726
+ }, 1500);
1727
+ };
1728
+ const hasPaymentMethods = config && Object.values(config).some((m) => m.enabled);
1729
+ return createPortal3(
1730
+ /* @__PURE__ */ jsx8(
1731
+ "div",
1732
+ {
1733
+ className: "hsk-cart-backdrop",
1734
+ style: __spreadProps(__spreadValues({}, customStyles), { zIndex: 999999 }),
1735
+ "data-hsk-theme": hskThemeAttr,
1736
+ onClick: onClose,
1737
+ children: /* @__PURE__ */ jsxs5(
1738
+ "div",
1739
+ {
1740
+ className: "hsk-checkout-modal",
1741
+ style: customStyles,
1742
+ "data-hsk-theme": hskThemeAttr,
1743
+ onClick: (e) => e.stopPropagation(),
1744
+ children: [
1745
+ /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-header", children: [
1746
+ /* @__PURE__ */ jsx8("h2", { children: "Secure Checkout" }),
1747
+ /* @__PURE__ */ jsx8("button", { onClick: onClose, className: "hsk-close-btn", children: "\xD7" })
1748
+ ] }),
1749
+ /* @__PURE__ */ jsx8("div", { className: "hsk-checkout-content", children: paymentSuccess ? /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-success", children: [
1750
+ /* @__PURE__ */ jsxs5("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "hsk-success-icon", children: [
1751
+ /* @__PURE__ */ jsx8("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
1752
+ /* @__PURE__ */ jsx8("polyline", { points: "22 4 12 14.01 9 11.01" })
1753
+ ] }),
1754
+ /* @__PURE__ */ jsx8("h3", { children: "Payment Successful!" }),
1755
+ /* @__PURE__ */ jsx8("p", { children: "Thank you for your order." })
1756
+ ] }) : /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-split", children: [
1757
+ /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-summary", children: [
1758
+ /* @__PURE__ */ jsx8("h3", { children: "Order Summary" }),
1759
+ cartLoading || !cart ? /* @__PURE__ */ jsx8("p", { className: "hsk-cart-loading", children: "Loading order..." }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
1760
+ /* @__PURE__ */ jsx8("ul", { className: "hsk-checkout-items", children: cart.items.map((item) => /* @__PURE__ */ jsxs5("li", { children: [
1761
+ /* @__PURE__ */ jsxs5("span", { children: [
1762
+ item.quantity,
1763
+ "x ",
1764
+ item.name
1765
+ ] }),
1766
+ /* @__PURE__ */ jsxs5("span", { children: [
1767
+ item.currency,
1768
+ " ",
1769
+ (item.price_numeric * item.quantity).toLocaleString(void 0, { minimumFractionDigits: 2 })
1770
+ ] })
1771
+ ] }, item.id)) }),
1772
+ /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-total", children: [
1773
+ /* @__PURE__ */ jsx8("span", { children: "Total" }),
1774
+ /* @__PURE__ */ jsxs5("span", { children: [
1775
+ cart.currency,
1776
+ " ",
1777
+ cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
1778
+ ] })
1779
+ ] })
1780
+ ] })
1781
+ ] }),
1782
+ /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-payment", children: [
1783
+ /* @__PURE__ */ jsx8("h3", { children: "Payment Method" }),
1784
+ loading ? /* @__PURE__ */ jsx8("p", { className: "hsk-cart-loading", children: "Loading secure payment methods..." }) : !hasPaymentMethods ? /* @__PURE__ */ jsx8("p", { className: "hsk-checkout-error", children: "No payment methods are currently available for this store." }) : /* @__PURE__ */ jsxs5("div", { className: "hsk-payment-options", children: [
1785
+ ((_a = config == null ? void 0 : config.mpesa) == null ? void 0 : _a.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("mpesa"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-mpesa", children: checkingOut ? "Processing..." : "Pay with M-Pesa" }),
1786
+ ((_b = config == null ? void 0 : config.equity) == null ? void 0 : _b.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("equity"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-equity", children: checkingOut ? "Processing..." : "Pay with Equity Bank" }),
1787
+ ((_c = config == null ? void 0 : config.stripe) == null ? void 0 : _c.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("stripe"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-stripe", children: checkingOut ? "Processing..." : "Pay with Card (Stripe)" }),
1788
+ ((_d = config == null ? void 0 : config.paypal) == null ? void 0 : _d.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("paypal"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-paypal", children: checkingOut ? "Processing..." : "Pay with PayPal" })
1789
+ ] })
1790
+ ] })
1791
+ ] }) })
1792
+ ]
1793
+ }
1794
+ )
1795
+ }
1796
+ ),
1797
+ document.body
1798
+ );
1799
+ }
1800
+
1801
+ // src/components/CartDrawer.tsx
1802
+ import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1803
+ function CartDrawer({
1804
+ trigger,
1805
+ className,
1806
+ theme
1807
+ }) {
1808
+ const { cart, loading } = useCart();
1809
+ const [open, setOpen] = useState10(false);
1810
+ const [showCheckout, setShowCheckout] = useState10(false);
1811
+ const [mounted, setMounted] = useState10(false);
1812
+ const client = useHuskelContext();
1813
+ useEffect9(() => {
1814
+ setMounted(true);
1815
+ const handleTriggerCheckout = () => {
1816
+ setShowCheckout(true);
1817
+ setOpen(false);
1818
+ };
1819
+ window.addEventListener("huskel:trigger_checkout", handleTriggerCheckout);
1820
+ return () => {
1821
+ window.removeEventListener("huskel:trigger_checkout", handleTriggerCheckout);
1822
+ };
1823
+ }, []);
1824
+ useEffect9(() => {
1825
+ if (open) {
1826
+ document.body.style.overflow = "hidden";
1827
+ } else {
1828
+ document.body.style.overflow = "";
1829
+ }
1830
+ return () => {
1831
+ document.body.style.overflow = "";
1832
+ };
1833
+ }, [open]);
1834
+ const handleCheckout = async () => {
1835
+ if (!cart || cart.items.length === 0) return;
1836
+ setShowCheckout(true);
1837
+ };
1838
+ const isStringTheme = typeof theme === "string";
1839
+ const hskThemeAttr = isStringTheme ? theme : void 0;
1840
+ 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;
1841
+ return /* @__PURE__ */ jsxs6(Fragment5, { children: [
1842
+ /* @__PURE__ */ jsx9("div", { onClick: () => setOpen(true), style: { display: "inline-block" }, children: trigger || /* @__PURE__ */ jsxs6(
1843
+ "button",
1844
+ {
1845
+ className: cn("hsk-cart-trigger", className),
1846
+ style: customStyles,
1847
+ "data-hsk-theme": hskThemeAttr,
1848
+ "aria-label": "Open cart",
1849
+ children: [
1850
+ /* @__PURE__ */ jsxs6("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1851
+ /* @__PURE__ */ jsx9("circle", { cx: "9", cy: "21", r: "1" }),
1852
+ /* @__PURE__ */ jsx9("circle", { cx: "20", cy: "21", r: "1" }),
1853
+ /* @__PURE__ */ jsx9("path", { d: "M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" })
1854
+ ] }),
1855
+ cart && cart.item_count > 0 ? /* @__PURE__ */ jsx9("span", { className: "hsk-cart-trigger-badge", children: cart.item_count }) : null
1856
+ ]
1857
+ }
1858
+ ) }),
1859
+ open && mounted && createPortal4(
1860
+ /* @__PURE__ */ jsx9(
1861
+ "div",
1862
+ {
1863
+ className: "hsk-cart-backdrop",
1864
+ style: customStyles,
1865
+ "data-hsk-theme": hskThemeAttr,
1866
+ onClick: () => setOpen(false),
1867
+ children: /* @__PURE__ */ jsxs6(
1868
+ "div",
1869
+ {
1870
+ className: "hsk-cart-bottom-sheet",
1871
+ style: customStyles,
1872
+ "data-hsk-theme": hskThemeAttr,
1873
+ onClick: (e) => e.stopPropagation(),
1874
+ children: [
1875
+ /* @__PURE__ */ jsx9("div", { className: "hsk-cart-sheet-handle" }),
1876
+ /* @__PURE__ */ jsxs6("div", { className: "hsk-cart-sheet-header", children: [
1877
+ /* @__PURE__ */ jsx9("h2", { children: "Your Cart" }),
1878
+ /* @__PURE__ */ jsx9("button", { onClick: () => setOpen(false), className: "hsk-close-btn", children: "\xD7" })
1879
+ ] }),
1880
+ /* @__PURE__ */ jsx9("div", { className: "hsk-cart-sheet-content", children: loading && !cart ? /* @__PURE__ */ jsx9("div", { className: "hsk-cart-loading", children: "Loading cart..." }) : !cart || cart.items.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "hsk-cart-empty", children: "Your cart is empty." }) : /* @__PURE__ */ jsx9("ul", { className: "hsk-cart-items", children: cart.items.map((item) => /* @__PURE__ */ jsxs6("li", { className: "hsk-cart-item", children: [
1881
+ item.image && /* @__PURE__ */ jsx9("img", { src: item.image, alt: item.name, className: "hsk-cart-item-img" }),
1882
+ /* @__PURE__ */ jsxs6("div", { className: "hsk-cart-item-info", children: [
1883
+ /* @__PURE__ */ jsx9("span", { className: "hsk-cart-item-name", children: item.name }),
1884
+ /* @__PURE__ */ jsxs6("span", { className: "hsk-cart-item-price", children: [
1885
+ item.currency,
1886
+ " ",
1887
+ item.price_numeric.toLocaleString(void 0, { minimumFractionDigits: 2 })
1888
+ ] })
1889
+ ] }),
1890
+ /* @__PURE__ */ jsxs6("div", { className: "hsk-cart-item-qty", children: [
1891
+ "x",
1892
+ item.quantity
1893
+ ] })
1894
+ ] }, item.id)) }) }),
1895
+ cart && cart.items.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "hsk-cart-sheet-footer", children: [
1896
+ /* @__PURE__ */ jsxs6("div", { className: "hsk-cart-total", children: [
1897
+ /* @__PURE__ */ jsx9("span", { children: "Total" }),
1898
+ /* @__PURE__ */ jsxs6("span", { children: [
1899
+ cart.currency,
1900
+ " ",
1901
+ cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
1902
+ ] })
1903
+ ] }),
1904
+ /* @__PURE__ */ jsx9("button", { onClick: handleCheckout, className: "hsk-checkout-btn", children: "Checkout securely" })
1905
+ ] })
1906
+ ]
1907
+ }
1908
+ )
1909
+ }
1910
+ ),
1911
+ document.body
1912
+ ),
1913
+ showCheckout && mounted && /* @__PURE__ */ jsx9(
1914
+ CheckoutModal,
1915
+ {
1916
+ onClose: () => {
1917
+ setShowCheckout(false);
1918
+ setOpen(false);
1919
+ },
1920
+ theme: isStringTheme ? theme : void 0,
1921
+ customStyles,
1922
+ hskThemeAttr
1923
+ }
1924
+ )
1925
+ ] });
1926
+ }
1378
1927
  export {
1379
1928
  AIChatButton,
1929
+ CartBadge,
1930
+ CartDrawer,
1380
1931
  ChatWidget,
1381
1932
  HuskelAPI,
1382
1933
  HuskelClient,
@@ -1385,6 +1936,7 @@ export {
1385
1936
  Sparkle,
1386
1937
  getHuskelClient,
1387
1938
  initHuskel,
1939
+ useCart,
1388
1940
  useChat,
1389
1941
  useHuskel,
1390
1942
  useIngest,