@huskel/sdk 0.3.3 → 0.4.1

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
@@ -38,6 +38,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
38
38
  // src/index.ts
39
39
  var index_exports = {};
40
40
  __export(index_exports, {
41
+ AIChatButton: () => AIChatButton,
41
42
  ChatWidget: () => ChatWidget,
42
43
  HuskelAPI: () => HuskelAPI,
43
44
  HuskelClient: () => HuskelClient,
@@ -140,6 +141,10 @@ var HuskelAPI = class {
140
141
  async searchVector(query, limit = 10) {
141
142
  return this.post("/search/vector", { query, siteId: this.siteId, limit });
142
143
  }
144
+ // Autocomplete — pure in-memory Trie, <1ms, no Upstash call. Only true prefix matches.
145
+ async searchAutocomplete(query, limit = 8) {
146
+ return this.post("/search/autocomplete", { query, siteId: this.siteId, limit });
147
+ }
143
148
  // LLM chat — conversational search with history context.
144
149
  async chat(query, history = []) {
145
150
  log("info", "chat query", query);
@@ -455,29 +460,34 @@ function useSearch() {
455
460
  const [results, setResults] = (0, import_react3.useState)([]);
456
461
  const [loading, setLoading] = (0, import_react3.useState)(false);
457
462
  const [error, setError] = (0, import_react3.useState)(null);
458
- const abortRef = (0, import_react3.useRef)(null);
459
- const search = (0, import_react3.useCallback)(async (query, limit = 10) => {
460
- var _a, _b, _c;
463
+ const genRef = (0, import_react3.useRef)(0);
464
+ const search = (0, import_react3.useCallback)(async (query, limit = 8) => {
465
+ var _a, _b;
461
466
  if (!query.trim()) {
462
467
  setResults([]);
468
+ setLoading(false);
463
469
  return;
464
470
  }
465
- (_a = abortRef.current) == null ? void 0 : _a.abort();
466
- abortRef.current = new AbortController();
467
- setLoading(true);
471
+ const gen = ++genRef.current;
468
472
  setError(null);
469
473
  try {
470
- const res = await client.api.searchVector(query, limit);
471
- setResults((_b = res.results) != null ? _b : []);
474
+ const res = await client.api.searchAutocomplete(query, limit);
475
+ if (gen === genRef.current) {
476
+ setResults((_a = res.results) != null ? _a : []);
477
+ }
472
478
  } catch (e) {
473
- setError((_c = e.message) != null ? _c : "Search failed");
479
+ if (gen === genRef.current) {
480
+ setError((_b = e.message) != null ? _b : "Search failed");
481
+ }
474
482
  } finally {
475
- setLoading(false);
483
+ if (gen === genRef.current) setLoading(false);
476
484
  }
477
485
  }, [client]);
478
486
  const clear = (0, import_react3.useCallback)(() => {
487
+ genRef.current++;
479
488
  setResults([]);
480
489
  setError(null);
490
+ setLoading(false);
481
491
  }, []);
482
492
  return { results, loading, error, search, clear };
483
493
  }
@@ -575,27 +585,21 @@ function useChat() {
575
585
  // src/components/SearchBar.tsx
576
586
  var import_react7 = require("react");
577
587
  var import_jsx_runtime2 = require("react/jsx-runtime");
578
- var S = `
579
- .hsk-wrap{position:relative;width:100%;font-family:inherit}
580
- .hsk-input{width:100%;padding:10px 16px;font-size:15px;border:1.5px solid #e2e2e2;border-radius:8px;outline:none;box-sizing:border-box;background:#fff;transition:border-color .2s}
581
- .hsk-input:focus{border-color:#f47c3c}
582
- .hsk-drop{position:absolute;top:calc(100% + 6px);left:0;right:0;background:#fff;border:1px solid #e2e2e2;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.1);z-index:9999;max-height:360px;overflow-y:auto}
583
- .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}
584
- .hsk-item:hover{background:#faf5f1}
585
- .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}
586
- .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
587
- .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}
588
- .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}
589
- `;
588
+ 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: [
589
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
590
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "13", y1: "13", x2: "18", y2: "18" })
591
+ ] });
590
592
  function SearchBar({
591
- placeholder = "Search for what you want \u2014 how you want",
593
+ placeholder = "Search products\u2026",
592
594
  limit = 10,
593
- debounceMs = 300,
595
+ debounceMs = 80,
594
596
  onSelect,
595
597
  className,
596
598
  inputClassName,
597
599
  dropdownClassName,
598
- renderResult
600
+ renderResult,
601
+ theme,
602
+ classNames = {}
599
603
  }) {
600
604
  const [query, setQuery] = (0, import_react7.useState)("");
601
605
  const [open, setOpen] = (0, import_react7.useState)(false);
@@ -609,153 +613,290 @@ function SearchBar({
609
613
  setOpen(false);
610
614
  return;
611
615
  }
616
+ setOpen(true);
612
617
  timer.current = setTimeout(() => {
613
618
  search(query, limit);
614
- setOpen(true);
615
619
  }, debounceMs);
616
620
  return () => clearTimeout(timer.current);
617
- }, [query, search, clear, limit, debounceMs]);
621
+ }, [query]);
618
622
  (0, import_react7.useEffect)(() => {
619
- const handler = (e) => {
623
+ const h = (e) => {
620
624
  if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
621
625
  };
622
- document.addEventListener("mousedown", handler);
623
- return () => document.removeEventListener("mousedown", handler);
626
+ document.addEventListener("mousedown", h);
627
+ return () => document.removeEventListener("mousedown", h);
624
628
  }, []);
625
629
  const handleSelect = (r) => {
626
630
  setOpen(false);
627
631
  setQuery(r.product.name);
628
632
  onSelect == null ? void 0 : onSelect(r);
629
633
  };
630
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
631
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: S }),
632
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-wrap ${className != null ? className : ""}`, ref: wrap, children: [
633
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
634
- "input",
635
- {
636
- className: `hsk-input ${inputClassName != null ? inputClassName : ""}`,
637
- type: "text",
638
- value: query,
639
- placeholder,
640
- onChange: (e) => setQuery(e.target.value),
641
- onFocus: () => results.length && setOpen(true)
642
- }
643
- ),
644
- open && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-drop ${dropdownClassName != null ? dropdownClassName : ""}`, children: [
645
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-msg", children: "Searching\u2026" }),
646
- !loading && results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-msg", children: [
647
- 'No results for "',
648
- query,
649
- '"'
650
- ] }),
651
- results.map(
652
- (r) => {
653
- var _a, _b;
654
- return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { onClick: () => handleSelect(r), children: renderResult(r) }, r.id) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-item", onClick: () => handleSelect(r), children: [
655
- ((_a = r.product.images) == null ? void 0 : _a[0]) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: r.product.images[0], alt: r.product.name }),
656
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
657
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-item-name", children: r.product.name }),
658
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-item-price", children: [
659
- (_b = r.product.currency) != null ? _b : "KES",
660
- " ",
661
- r.product.price
662
- ] })
634
+ const showDrop = open && query.trim().length > 0;
635
+ 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 });
636
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-sb-wrap ${classNames.root || ""} ${className || ""}`, ref: wrap, style: customStyles, children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
639
+ "input",
640
+ {
641
+ className: `hsk-sb-input ${classNames.input || ""} ${inputClassName || ""}`,
642
+ type: "text",
643
+ value: query,
644
+ placeholder,
645
+ onChange: (e) => setQuery(e.target.value),
646
+ onFocus: () => results.length > 0 && query.trim() && setOpen(true),
647
+ autoComplete: "off",
648
+ spellCheck: false
649
+ }
650
+ ),
651
+ showDrop && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `hsk-sb-drop ${classNames.dropdown || ""} ${dropdownClassName || ""}`, style: { position: "absolute" }, children: [
652
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
653
+ results.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-empty", children: [
654
+ "No results for \u201C",
655
+ query,
656
+ "\u201D"
657
+ ] }),
658
+ results.map((r, i) => {
659
+ var _a;
660
+ return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
661
+ "div",
662
+ {
663
+ onClick: () => handleSelect(r),
664
+ className: "hsk-sb-fade",
665
+ style: { animationDelay: `${i * 18}ms` },
666
+ children: renderResult(r)
667
+ },
668
+ r.id
669
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
670
+ "div",
671
+ {
672
+ className: `hsk-sb-row hsk-sb-fade ${classNames.row || ""}`,
673
+ style: { animationDelay: `${i * 18}ms` },
674
+ onClick: () => handleSelect(r),
675
+ children: [
676
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
678
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-title", children: r.product.name }),
679
+ (r.product.category || r.product.brand) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
663
680
  ] })
664
- ] }, r.id);
665
- }
666
- )
667
- ] })
681
+ ]
682
+ },
683
+ r.id
684
+ );
685
+ })
668
686
  ] })
669
687
  ] });
670
688
  }
671
689
 
672
690
  // src/components/Sparkle.tsx
673
691
  var import_react8 = require("react");
692
+ var import_react_dom = require("react-dom");
674
693
  var import_jsx_runtime3 = require("react/jsx-runtime");
675
- var S2 = `
676
- .hsk-sparkle{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:12px;font-weight:600;background:#f47c3c;color:#fff;border:none;border-radius:20px;cursor:pointer;transition:opacity .2s,transform .15s}
677
- .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}
678
- .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}
679
- `;
680
- function Sparkle({ productName, limit = 5, onResult, className }) {
681
- const client = useHuskelContext();
682
- const [loading, setLoading] = (0, import_react8.useState)(false);
683
- const handleClick = async () => {
684
- setLoading(true);
685
- try {
686
- const res = await client.api.searchVector(productName, limit);
687
- onResult == null ? void 0 : onResult(res.results);
688
- } catch (e) {
689
- console.error("[Huskel Sparkle]", e);
690
- } finally {
691
- setLoading(false);
694
+ 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" }) });
695
+ 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: [
696
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
697
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
698
+ ] });
699
+ function SparkleModal({
700
+ productName,
701
+ limit,
702
+ backdropColor,
703
+ backdropBlur,
704
+ onClose,
705
+ onNavigate,
706
+ onResult,
707
+ theme,
708
+ classNames = {}
709
+ }) {
710
+ const { results, loading, search } = useSearch();
711
+ const initiated = (0, import_react8.useRef)(false);
712
+ (0, import_react8.useEffect)(() => {
713
+ if (!initiated.current) {
714
+ initiated.current = true;
715
+ search(productName, limit);
716
+ }
717
+ }, []);
718
+ (0, import_react8.useEffect)(() => {
719
+ if (results.length > 0) onResult == null ? void 0 : onResult(results);
720
+ }, [results]);
721
+ (0, import_react8.useEffect)(() => {
722
+ const h = (e) => {
723
+ if (e.key === "Escape") onClose();
724
+ };
725
+ document.addEventListener("keydown", h);
726
+ return () => document.removeEventListener("keydown", h);
727
+ }, []);
728
+ const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "16px";
729
+ const bg = backdropColor != null ? backdropColor : void 0;
730
+ const handleNav = (r) => {
731
+ const prevent = onNavigate == null ? void 0 : onNavigate(r);
732
+ if (prevent !== false) {
733
+ onClose();
734
+ if (r.product.url) window.location.href = r.product.url;
692
735
  }
693
736
  };
737
+ 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 });
738
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
739
+ "div",
740
+ {
741
+ className: `hsk-sp-backdrop ${classNames.backdrop || ""}`,
742
+ onClick: onClose,
743
+ style: __spreadValues({
744
+ backdropFilter: `blur(${blurVal})`,
745
+ WebkitBackdropFilter: `blur(${blurVal})`,
746
+ background: bg != null ? bg : void 0
747
+ }, customStyles),
748
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `hsk-sp-card ${classNames.card || ""}`, onClick: (e) => e.stopPropagation(), children: [
749
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header", children: [
750
+ /* @__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, {}) }),
751
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header-body", children: [
752
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-header-title", children: [
753
+ "Similar to \u201C",
754
+ productName,
755
+ "\u201D"
756
+ ] }),
757
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-header-sub", children: "AI vector similarity \xB7 instant results" })
758
+ ] }),
759
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}) })
760
+ ] }),
761
+ loading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-bar" }),
762
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-results", children: [
763
+ !loading && results.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-empty", children: "No similar products found." }),
764
+ results.map((r, i) => {
765
+ var _a, _b, _c;
766
+ const price = parseFloat(((_a = r.product.price) == null ? void 0 : _a.replace(/[^0-9.]/g, "")) || "0");
767
+ const currency = (_b = r.product.currency) != null ? _b : "KES";
768
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
769
+ "div",
770
+ {
771
+ className: `hsk-sp-item ${classNames.item || ""}`,
772
+ style: { animationDelay: `${i * 55}ms` },
773
+ children: [
774
+ /* @__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}" }) }),
775
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-item-body", children: [
776
+ r.product.category && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-item-cat", children: r.product.category }),
777
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hsk-sp-item-name", children: r.product.name }),
778
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
779
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-item-currency", children: currency }),
780
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
781
+ ] }),
782
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-actions", children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
784
+ "button",
785
+ {
786
+ className: "hsk-sp-action hsk-sp-action-primary",
787
+ onClick: () => handleNav(r),
788
+ children: "View Product"
789
+ }
790
+ ),
791
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
792
+ "button",
793
+ {
794
+ className: "hsk-sp-action hsk-sp-action-secondary",
795
+ onClick: () => onClose(),
796
+ children: "Add to Cart"
797
+ }
798
+ )
799
+ ] })
800
+ ] })
801
+ ]
802
+ },
803
+ r.id
804
+ );
805
+ })
806
+ ] }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hsk-sp-footer", children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "hsk-sp-badge", style: { display: "inline-flex", alignItems: "center", gap: "4px" }, children: [
809
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, {}),
810
+ " Huskel AI"
811
+ ] }),
812
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "hsk-sp-esc", children: "Esc to close" })
813
+ ] })
814
+ ] })
815
+ }
816
+ );
817
+ }
818
+ function Sparkle({
819
+ productName,
820
+ limit = 8,
821
+ onResult,
822
+ backdropColor,
823
+ backdropBlur,
824
+ className,
825
+ onNavigate,
826
+ theme,
827
+ classNames = {}
828
+ }) {
829
+ const [open, setOpen] = (0, import_react8.useState)(false);
830
+ const [mounted, setMounted] = (0, import_react8.useState)(false);
831
+ (0, import_react8.useEffect)(() => {
832
+ setMounted(true);
833
+ }, []);
834
+ 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 });
694
835
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
695
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: S2 }),
696
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { className: `hsk-sparkle ${className != null ? className : ""}`, onClick: handleClick, disabled: loading, children: [
697
- "\u2726 ",
698
- loading ? "Finding\u2026" : "Similar"
699
- ] })
836
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
837
+ "button",
838
+ {
839
+ className: `hsk-sp-btn ${classNames.button || ""} ${className || ""}`,
840
+ onClick: () => setOpen(true),
841
+ style: customStyles,
842
+ title: "Find similar products",
843
+ "aria-label": "Find similar products",
844
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SparkleIcon, {})
845
+ }
846
+ ),
847
+ open && mounted && (0, import_react_dom.createPortal)(
848
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
849
+ SparkleModal,
850
+ {
851
+ productName,
852
+ limit,
853
+ backdropColor,
854
+ backdropBlur,
855
+ onClose: () => setOpen(false),
856
+ onResult,
857
+ onNavigate,
858
+ theme,
859
+ classNames
860
+ }
861
+ ),
862
+ document.body
863
+ )
700
864
  ] });
701
865
  }
702
866
 
703
867
  // src/components/ChatWidget.tsx
704
868
  var import_react9 = require("react");
705
869
  var import_jsx_runtime4 = require("react/jsx-runtime");
706
- var S3 = `
707
- .hsk-chat-widget{display:flex;flex-direction:column;height:100%;min-height:320px;font-family:inherit;background:#0f0f10;border:1px solid #2a2a2d;border-radius:12px;overflow:hidden}
708
- .hsk-chat-header{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid #1e1e1f;background:#111112;flex-shrink:0}
709
- .hsk-chat-title{font-size:14px;font-weight:600;color:#f3f3f2}
710
- .hsk-chat-badge{font-size:10px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:#ff6a33;background:#ff6a3315;border:1px solid #ff6a3330;padding:2px 8px;border-radius:20px}
711
- .hsk-chat-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}
712
- .hsk-chat-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:8px;color:#555;font-size:13px;text-align:center;padding:24px}
713
- .hsk-chat-empty-icon{font-size:28px;margin-bottom:4px}
714
- .hsk-msg-row{display:flex;gap:8px;align-items:flex-start}
715
- .hsk-msg-row.user{flex-direction:row-reverse}
716
- .hsk-msg-avatar{width:28px;height:28px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700}
717
- .hsk-msg-avatar.ai{background:#ff6a3320;border:1px solid #ff6a3340;color:#ff6a33}
718
- .hsk-msg-avatar.user{background:#2a2a2d;color:#9a9aa1}
719
- .hsk-msg-bubble{max-width:78%;padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.6}
720
- .hsk-msg-bubble.ai{background:#171718;border:1px solid #2a2a2d;color:#e8e8e7;border-radius:4px 12px 12px 12px}
721
- .hsk-msg-bubble.user{background:#ff6a33;color:#fff;border-radius:12px 4px 12px 12px}
722
- .hsk-sources{margin-top:10px;display:flex;flex-direction:column;gap:6px}
723
- .hsk-source-card{display:flex;align-items:center;gap:10px;padding:8px 10px;background:#1a1a1b;border:1px solid #252527;border-radius:8px;cursor:pointer;transition:border-color 0.15s}
724
- .hsk-source-card:hover{border-color:#ff6a3360}
725
- .hsk-source-img{width:36px;height:36px;object-fit:cover;border-radius:4px;background:#fff}
726
- .hsk-source-name{font-size:12px;font-weight:500;color:#e8e8e7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
727
- .hsk-source-price{font-size:11px;color:#ff6a33;font-weight:700;margin-top:2px}
728
- .hsk-typing{display:flex;gap:4px;align-items:center;padding:10px 14px;background:#171718;border:1px solid #2a2a2d;border-radius:4px 12px 12px 12px;width:fit-content}
729
- .hsk-typing-dot{width:6px;height:6px;background:#ff6a33;border-radius:50%;animation:hsk-chat-bounce 1.2s infinite}
730
- .hsk-typing-dot:nth-child(2){animation-delay:0.2s}
731
- .hsk-typing-dot:nth-child(3){animation-delay:0.4s}
732
- @keyframes hsk-chat-bounce{0%,100%{opacity:0.3;transform:translateY(0)}50%{opacity:1;transform:translateY(-4px)}}
733
- .hsk-chat-input-area{display:flex;align-items:center;gap:8px;padding:12px 14px;border-top:1px solid #1e1e1f;background:#111112;flex-shrink:0}
734
- .hsk-chat-input{flex:1;background:#1a1a1b;border:1px solid #2a2a2d;border-radius:8px;padding:9px 14px;font-size:13px;color:#f3f3f2;outline:none;font-family:inherit;transition:border-color 0.2s;resize:none;min-height:38px;max-height:120px;line-height:1.5}
735
- .hsk-chat-input::placeholder{color:#555}
736
- .hsk-chat-input:focus{border-color:#ff6a33}
737
- .hsk-chat-send{width:34px;height:34px;border-radius:8px;background:#ff6a33;border:none;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:16px;transition:opacity 0.15s,transform 0.1s}
738
- .hsk-chat-send:hover{opacity:0.88}
739
- .hsk-chat-send:active{transform:scale(0.93)}
740
- .hsk-chat-send:disabled{opacity:0.4;cursor:not-allowed}
741
- .hsk-chat-reset{font-size:11px;color:#555;cursor:pointer;padding:0 4px;transition:color 0.15s;background:none;border:none;font-family:inherit}
742
- .hsk-chat-reset:hover{color:#ff6a33}
743
- `;
744
- function SourceCard({ source, onSelect }) {
870
+ var SparkleIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime4.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_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" }) });
871
+ 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: [
872
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m5 12 7-7 7 7" }),
873
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12 19V5" })
874
+ ] });
875
+ function SourceCard({ source, defaultCurrency, onSelect }) {
745
876
  var _a;
746
877
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-source-card", onClick: () => onSelect == null ? void 0 : onSelect(source), children: [
747
878
  source.image && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: source.image, alt: source.name, className: "hsk-source-img" }),
748
879
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
749
880
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-source-name", children: source.name }),
750
881
  source.price && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-source-price", children: [
751
- (_a = source.currency) != null ? _a : "KES",
882
+ (_a = source.currency) != null ? _a : defaultCurrency,
752
883
  " ",
753
884
  source.price
754
885
  ] })
755
886
  ] })
756
887
  ] });
757
888
  }
758
- function ChatWidget({ placeholder = "Ask about anything in our store\u2026", title = "AI Shopping Assistant", className, onSelectSource }) {
889
+ function ChatWidget({
890
+ title = "AI Shopping Assistant",
891
+ placeholder = "Ask about anything in our store\u2026",
892
+ emptyStateText = "Ask me anything about our products",
893
+ emptyStateSuggestions = '"Find me headphones under KSh 5,000" \xB7 "Gift ideas"',
894
+ defaultCurrency = "KES",
895
+ className,
896
+ theme,
897
+ classNames = {},
898
+ onSelectSource
899
+ }) {
759
900
  const { messages, sources, loading, error, send, reset } = useChat();
760
901
  const [input, setInput] = (0, import_react9.useState)("");
761
902
  const bottomRef = (0, import_react9.useRef)(null);
@@ -783,68 +924,386 @@ function ChatWidget({ placeholder = "Ask about anything in our store\u2026", tit
783
924
  t.style.height = "auto";
784
925
  t.style.height = Math.min(t.scrollHeight, 120) + "px";
785
926
  };
786
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
787
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: S3 }),
788
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `hsk-chat-widget ${className != null ? className : ""}`, children: [
789
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-header", children: [
790
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: 16, color: "#ff6a33" }, children: "\u2726" }),
791
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-chat-title", children: title }),
792
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-chat-badge", children: "AI" }),
793
- messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "hsk-chat-reset", onClick: reset, style: { marginLeft: "auto" }, children: "Clear" })
794
- ] }),
795
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-messages", children: [
796
- messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-empty", children: [
797
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-chat-empty-icon", children: "\u2726" }),
798
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "Ask me anything about our products" }),
799
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 12, color: "#444", marginTop: 4 }, children: '"Find me headphones under KSh 5,000" \xB7 "Gift ideas for a chef"' })
800
- ] }) : messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
801
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `hsk-msg-row ${msg.role}`, children: [
802
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `hsk-msg-avatar ${msg.role === "assistant" ? "ai" : "user"}`, children: msg.role === "assistant" ? "\u2726" : "\u2191" }),
803
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `hsk-msg-bubble ${msg.role === "assistant" ? "ai" : "user"}`, children: msg.content })
927
+ 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 });
928
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
929
+ "div",
930
+ {
931
+ className: `hsk-chat-widget ${classNames.root || ""} ${className || ""}`,
932
+ style: customStyles,
933
+ children: [
934
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `hsk-chat-header ${classNames.header || ""}`, children: [
935
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-chat-header-icon", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon2, {}) }),
936
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-chat-title", children: title }),
937
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "hsk-chat-badge", children: "AI" }),
938
+ messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "hsk-chat-reset", onClick: reset, style: { marginLeft: "auto" }, children: "Clear" })
939
+ ] }),
940
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-messages", children: [
941
+ messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-empty", children: [
942
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-chat-empty-icon", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon2, {}) }),
943
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: emptyStateText }),
944
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-chat-empty-suggestions", children: emptyStateSuggestions })
945
+ ] }) : messages.map((msg, idx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `hsk-msg-row ${msg.role}`, children: [
947
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `hsk-msg-avatar ${msg.role === "assistant" ? "ai" : "user"}`, children: msg.role === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon2, {}) : "U" }),
948
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `hsk-msg-bubble ${msg.role} ${classNames.messageBubble || ""}`, children: msg.content })
949
+ ] }),
950
+ msg.role === "assistant" && idx === messages.length - 1 && sources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sources-container", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sources", children: sources.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) })
951
+ ] }, idx)),
952
+ loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-msg-row", children: [
953
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-msg-avatar ai", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SparkleIcon2, {}) }),
954
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-typing", children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-typing-dot" }),
956
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-typing-dot" }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-typing-dot" })
958
+ ] })
959
+ ] }),
960
+ error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-chat-error", children: error }),
961
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: bottomRef })
962
+ ] }),
963
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-input-area", children: [
964
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
965
+ "textarea",
966
+ {
967
+ ref: textareaRef,
968
+ className: `hsk-chat-input ${classNames.input || ""}`,
969
+ value: input,
970
+ onChange: handleInput,
971
+ onKeyDown: handleKey,
972
+ placeholder,
973
+ rows: 1,
974
+ disabled: loading
975
+ }
976
+ ),
977
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
978
+ "button",
979
+ {
980
+ className: "hsk-chat-send",
981
+ onClick: handleSend,
982
+ disabled: !input.trim() || loading,
983
+ "aria-label": "Send message",
984
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ArrowUpIcon, {})
985
+ }
986
+ )
987
+ ] })
988
+ ]
989
+ }
990
+ );
991
+ }
992
+
993
+ // src/components/AIChatButton.tsx
994
+ var import_react10 = require("react");
995
+ var import_react_dom2 = require("react-dom");
996
+ var import_jsx_runtime5 = require("react/jsx-runtime");
997
+ var SparkleIcon3 = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime5.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_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" }) });
998
+ 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: [
999
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "m5 12 7-7 7 7" }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19V5" })
1001
+ ] });
1002
+ var CloseIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1003
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1004
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1005
+ ] });
1006
+ var ChevronRightIcon = () => /* @__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: "m9 18 6-6-6-6" }) });
1007
+ var DEFAULT_CHIPS = [
1008
+ "Cheapest smartphone",
1009
+ "Smart TV under KSh 20,000",
1010
+ "Noise-cancelling headphones",
1011
+ "Best laptop for students"
1012
+ ];
1013
+ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1014
+ const railRef = (0, import_react10.useRef)(null);
1015
+ const [showNext, setShowNext] = (0, import_react10.useState)(false);
1016
+ const measure = (0, import_react10.useCallback)(() => {
1017
+ const el = railRef.current;
1018
+ if (!el) return;
1019
+ const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
1020
+ setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
1021
+ }, []);
1022
+ (0, import_react10.useEffect)(() => {
1023
+ measure();
1024
+ const el = railRef.current;
1025
+ if (!el) return;
1026
+ const ro = new ResizeObserver(measure);
1027
+ ro.observe(el);
1028
+ el.addEventListener("scroll", measure, { passive: true });
1029
+ return () => {
1030
+ ro.disconnect();
1031
+ el.removeEventListener("scroll", measure);
1032
+ };
1033
+ }, [measure, sources]);
1034
+ const scrollNext = () => {
1035
+ var _a;
1036
+ (_a = railRef.current) == null ? void 0 : _a.scrollBy({ left: 170, behavior: "smooth" });
1037
+ };
1038
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-sources-wrap", children: [
1039
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-sources", ref: railRef, children: sources.map((src, si) => {
1040
+ var _a;
1041
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1042
+ "div",
1043
+ {
1044
+ className: "hsk-cb-source",
1045
+ style: { animationDelay: `${si * 50}ms` },
1046
+ onClick: () => onSelectSource == null ? void 0 : onSelectSource(src),
1047
+ children: [
1048
+ src.image ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-src-imgwrap", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: src.image, alt: src.name, loading: "lazy" }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-src-imgwrap-empty", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon3, {}) }),
1049
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-src-info", children: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-src-name", children: src.name }),
1051
+ src.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-src-price", children: [
1052
+ (_a = src.currency) != null ? _a : defaultCurrency,
1053
+ " ",
1054
+ parseFloat(src.price.replace(/[^0-9.]/g, "") || "0").toLocaleString()
1055
+ ] })
1056
+ ] })
1057
+ ]
1058
+ },
1059
+ si
1060
+ );
1061
+ }) }),
1062
+ showNext && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1063
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1064
+ "div",
1065
+ {
1066
+ className: "hsk-cb-sources-fade",
1067
+ style: { background: "linear-gradient(to right, transparent, var(--hsk-fade-bg, #0e0e0f))" }
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hsk-cb-sources-next", onClick: scrollNext, "aria-label": "See more", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChevronRightIcon, {}) })
1071
+ ] })
1072
+ ] });
1073
+ }
1074
+ function ChatModal({
1075
+ title = "AI Shopping Assistant",
1076
+ placeholder = "Ask me anything \u2014 gifts, budget, use case\u2026",
1077
+ backdropColor,
1078
+ backdropBlur,
1079
+ onClose,
1080
+ onSelectSource,
1081
+ defaultCurrency = "KES",
1082
+ chips = DEFAULT_CHIPS,
1083
+ theme,
1084
+ classNames = {}
1085
+ }) {
1086
+ var _a, _b;
1087
+ const { messages, sources, loading, error, send, reset } = useChat();
1088
+ const [input, setInput] = (0, import_react10.useState)("");
1089
+ const [selectedProduct, setSelectedProduct] = (0, import_react10.useState)(null);
1090
+ const bottomRef = (0, import_react10.useRef)(null);
1091
+ const textareaRef = (0, import_react10.useRef)(null);
1092
+ (0, import_react10.useEffect)(() => {
1093
+ var _a2;
1094
+ (_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
1095
+ }, [messages, loading, selectedProduct]);
1096
+ (0, import_react10.useEffect)(() => {
1097
+ const h = (e) => {
1098
+ if (e.key === "Escape") onClose();
1099
+ };
1100
+ document.addEventListener("keydown", h);
1101
+ return () => document.removeEventListener("keydown", h);
1102
+ }, []);
1103
+ const handleSourceClick = (src) => {
1104
+ var _a2;
1105
+ setSelectedProduct(src);
1106
+ onSelectSource == null ? void 0 : onSelectSource(src);
1107
+ const q = `Tell me more about the ${src.name}${src.price ? ` (${(_a2 = src.currency) != null ? _a2 : defaultCurrency} ${src.price})` : ""} \u2014 what are its key specs, who is it best for, and is it worth buying?`;
1108
+ send(q);
1109
+ };
1110
+ const handleSend = async (text) => {
1111
+ const q = (text != null ? text : input).trim();
1112
+ if (!q || loading) return;
1113
+ setSelectedProduct(null);
1114
+ setInput("");
1115
+ if (textareaRef.current) {
1116
+ textareaRef.current.style.height = "auto";
1117
+ }
1118
+ await send(q);
1119
+ };
1120
+ const handleKeyDown = (e) => {
1121
+ if (e.key === "Enter" && !e.shiftKey) {
1122
+ e.preventDefault();
1123
+ handleSend();
1124
+ }
1125
+ };
1126
+ const handleInput = (e) => {
1127
+ setInput(e.target.value);
1128
+ const t = e.target;
1129
+ t.style.height = "auto";
1130
+ t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
1131
+ };
1132
+ const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "20px";
1133
+ 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 });
1134
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1135
+ "div",
1136
+ {
1137
+ className: `hsk-cb-overlay ${classNames.overlay || ""}`,
1138
+ onClick: onClose,
1139
+ style: __spreadValues(__spreadValues({
1140
+ backdropFilter: `blur(${blurVal})`,
1141
+ WebkitBackdropFilter: `blur(${blurVal})`
1142
+ }, backdropColor ? { background: backdropColor } : {}), customStyles),
1143
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `hsk-cb-panel ${classNames.panel || ""}`, onClick: (e) => e.stopPropagation(), children: [
1144
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-topbar", children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-topbar-left", children: [
1146
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-cb-topbar-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon3, {}) }),
1147
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
1148
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-topbar-title", children: title }),
1149
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-topbar-sub", children: "Powered by Huskel AI \xB7 searches the whole catalogue" })
1150
+ ] })
804
1151
  ] }),
805
- msg.role === "assistant" && idx === messages.length - 1 && sources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { marginLeft: 36 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-sources", children: sources.map((src, si) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SourceCard, { source: src, onSelect: onSelectSource }, si)) }) })
806
- ] }, idx)),
807
- loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-msg-row", children: [
808
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-msg-avatar ai", children: "\u2726" }),
809
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-typing", children: [
810
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-typing-dot" }),
811
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-typing-dot" }),
812
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hsk-typing-dot" })
1152
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-topbar-actions", children: [
1153
+ messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hsk-cb-topbar-btn", onClick: reset, children: "Clear chat" }),
1154
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hsk-cb-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon2, {}) })
813
1155
  ] })
814
1156
  ] }),
815
- error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 12, color: "#ef4444", textAlign: "center", padding: 8 }, children: error }),
816
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: bottomRef })
817
- ] }),
818
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hsk-chat-input-area", children: [
819
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
820
- "textarea",
821
- {
822
- ref: textareaRef,
823
- className: "hsk-chat-input",
824
- value: input,
825
- onChange: handleInput,
826
- onKeyDown: handleKey,
827
- placeholder,
828
- rows: 1,
829
- disabled: loading
830
- }
831
- ),
832
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
833
- "button",
834
- {
835
- className: "hsk-chat-send",
836
- onClick: handleSend,
837
- disabled: !input.trim() || loading,
838
- "aria-label": "Send",
839
- children: "\u2191"
840
- }
841
- )
1157
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-msgs", children: [
1158
+ messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-empty", children: [
1159
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-empty-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon3, {}) }),
1160
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-empty-title", children: "What can I help you find?" }),
1161
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-empty-sub", children: "Ask about products, budgets, gift ideas, specs \u2014 I'll search the entire catalogue for you." }),
1162
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-chips", children: chips.map((chip) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1163
+ "button",
1164
+ {
1165
+ className: "hsk-cb-chip",
1166
+ onClick: () => handleSend(chip),
1167
+ children: chip
1168
+ },
1169
+ chip
1170
+ )) })
1171
+ ] }) : messages.map((msg, idx) => {
1172
+ const isLast = idx === messages.length - 1;
1173
+ const isUser = msg.role === "user";
1174
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-msg-group", children: isUser ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-user-msg", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-user-bubble", children: msg.content }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
1175
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon3, {}) }),
1176
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-ai-body", children: [
1177
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-ai-text", children: msg.content }),
1178
+ isLast && sources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1179
+ SourcesCarousel,
1180
+ {
1181
+ sources,
1182
+ defaultCurrency,
1183
+ onSelectSource: handleSourceClick
1184
+ }
1185
+ )
1186
+ ] })
1187
+ ] }) }, idx);
1188
+ }),
1189
+ selectedProduct && loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1190
+ "div",
1191
+ {
1192
+ className: "hsk-cb-selected-product",
1193
+ onClick: () => selectedProduct.url && window.open(selectedProduct.url, "_blank"),
1194
+ children: [
1195
+ selectedProduct.image && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { className: "hsk-cb-selected-img", src: selectedProduct.image, alt: selectedProduct.name }),
1196
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-selected-info", children: [
1197
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-selected-name", children: selectedProduct.name }),
1198
+ selectedProduct.price && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-selected-price", children: [
1199
+ (_a = selectedProduct.currency) != null ? _a : defaultCurrency,
1200
+ " ",
1201
+ parseFloat(((_b = selectedProduct.price) != null ? _b : "").replace(/[^0-9.]/g, "") || "0").toLocaleString()
1202
+ ] })
1203
+ ] })
1204
+ ]
1205
+ }
1206
+ ),
1207
+ loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-typing-row", children: [
1208
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon3, {}) }),
1209
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-typing", children: [
1210
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-dot" }),
1211
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-dot" }),
1212
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-dot" })
1213
+ ] })
1214
+ ] }),
1215
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-error", children: error }),
1216
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: bottomRef, style: { height: 1 } })
1217
+ ] }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-input-wrap", children: [
1219
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-cb-input-box", children: [
1220
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1221
+ "textarea",
1222
+ {
1223
+ ref: textareaRef,
1224
+ className: `hsk-cb-textarea ${classNames.input || ""}`,
1225
+ value: input,
1226
+ onChange: handleInput,
1227
+ onKeyDown: handleKeyDown,
1228
+ placeholder,
1229
+ rows: 1,
1230
+ disabled: loading,
1231
+ autoFocus: true
1232
+ }
1233
+ ),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1235
+ "button",
1236
+ {
1237
+ className: `hsk-cb-send ${classNames.sendButton || ""}`,
1238
+ onClick: () => handleSend(),
1239
+ disabled: !input.trim() || loading,
1240
+ "aria-label": "Send message",
1241
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ArrowUpIcon2, {})
1242
+ }
1243
+ )
1244
+ ] }),
1245
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-cb-hint", children: "Huskel AI \xB7 searches the whole catalogue in real time" })
1246
+ ] })
842
1247
  ] })
843
- ] })
1248
+ }
1249
+ );
1250
+ }
1251
+ function AIChatButton({
1252
+ label,
1253
+ title,
1254
+ placeholder,
1255
+ backdropColor,
1256
+ backdropBlur,
1257
+ className,
1258
+ onSelectSource,
1259
+ defaultCurrency,
1260
+ chips,
1261
+ theme,
1262
+ classNames = {}
1263
+ }) {
1264
+ const [open, setOpen] = (0, import_react10.useState)(false);
1265
+ const [mounted, setMounted] = (0, import_react10.useState)(false);
1266
+ (0, import_react10.useEffect)(() => {
1267
+ setMounted(true);
1268
+ }, []);
1269
+ 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 });
1270
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1271
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1272
+ "button",
1273
+ {
1274
+ className: `hsk-cb-btn ${classNames.button || ""} ${className || ""}`,
1275
+ onClick: () => setOpen(true),
1276
+ style: customStyles,
1277
+ "aria-label": "Open AI chat",
1278
+ children: [
1279
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "hsk-cb-btn-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SparkleIcon3, {}) }),
1280
+ label !== void 0 ? label : null
1281
+ ]
1282
+ }
1283
+ ),
1284
+ open && mounted && (0, import_react_dom2.createPortal)(
1285
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1286
+ ChatModal,
1287
+ {
1288
+ title,
1289
+ placeholder,
1290
+ backdropColor,
1291
+ backdropBlur,
1292
+ onClose: () => setOpen(false),
1293
+ onSelectSource,
1294
+ defaultCurrency,
1295
+ chips,
1296
+ theme,
1297
+ classNames
1298
+ }
1299
+ ),
1300
+ document.body
1301
+ )
844
1302
  ] });
845
1303
  }
846
1304
  // Annotate the CommonJS export names for ESM import in node:
847
1305
  0 && (module.exports = {
1306
+ AIChatButton,
848
1307
  ChatWidget,
849
1308
  HuskelAPI,
850
1309
  HuskelClient,