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