@akropolys/kiku 1.5.17 → 1.5.19

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/README.md CHANGED
@@ -1,30 +1,16 @@
1
1
  # @akropolys/kiku
2
2
 
3
- > Plug-and-play React AI chat, search, and commerce UI components.
3
+ [![npm](https://img.shields.io/npm/v/@akropolys/kiku?color=orange)](https://www.npmjs.com/package/@akropolys/kiku) [![License](https://img.shields.io/npm/l/@akropolys/kiku)](https://github.com/akropolys/sdk/blob/main/LICENSE) [![TypeScript](https://img.shields.io/badge/types-included-blue)](https://www.typescriptlang.org/)
4
4
 
5
- Kiku is the ready-made UI layer for Akropolys. Drop in a floating chat button, a search bar with AI-powered results, a product comparison matrix, or a full checkout modal — all styled, animated, and wired to the Akropolys backend out of the box.
6
-
7
- If you want full control over the UI, use [`@akropolys/sdk`](https://www.npmjs.com/package/@akropolys/sdk) directly.
5
+ **[Docs](https://akropolys.io/docs)**  •  **[GitHub](https://github.com/akropolys/sdk)**
8
6
 
9
7
  ---
10
8
 
11
- ## Install
12
-
13
- ```bash
14
- npm install @akropolys/kiku @akropolys/sdk
15
- # or
16
- pnpm add @akropolys/kiku @akropolys/sdk
17
- ```
18
-
19
- **Import the stylesheet once in your app entry point:**
9
+ ## What is `@akropolys/kiku`?
20
10
 
21
- ```ts
22
- import '@akropolys/kiku/styles.css';
23
- ```
24
-
25
- ---
11
+ Ready-made React UI components for the Akropolys intent platform. Drop in a floating chat assistant or AI search bar and your catalog is immediately searchable in natural language — no configuration required.
26
12
 
27
- ## Quick start
13
+ For full UI control, use [`@akropolys/sdk`](https://www.npmjs.com/package/@akropolys/sdk) directly.
28
14
 
29
15
  ```tsx
30
16
  import { AkropolysProvider } from '@akropolys/sdk';
@@ -34,169 +20,41 @@ import '@akropolys/kiku/styles.css';
34
20
  export default function App() {
35
21
  return (
36
22
  <AkropolysProvider
37
- siteId="your-site-id"
38
- apiUrl="https://api.akropolys.io"
39
- apiToken="your-api-token"
23
+ siteId={process.env.NEXT_PUBLIC_AKROPOLYS_SITE_ID}
24
+ apiUrl={process.env.NEXT_PUBLIC_AKROPOLYS_API_URL}
25
+ apiToken={process.env.NEXT_PUBLIC_AKROPOLYS_API_TOKEN}
40
26
  >
41
- {/* Floating chat button — renders the full AI shopping assistant */}
42
- <KikuButton label="Chat with Kiku" />
27
+ <KikuButton />
43
28
  </AkropolysProvider>
44
29
  );
45
30
  }
46
31
  ```
47
32
 
48
- ---
49
-
50
33
  ## Components
51
34
 
52
- ### `<KikuButton />` Floating AI chat assistant
53
-
54
- ```tsx
55
- import { KikuButton } from '@akropolys/kiku';
56
-
57
- <KikuButton
58
- label="Ask Kiku"
59
- title="Shopping Assistant"
60
- placeholder="What are you looking for?"
61
- theme="dark" // 'light' | 'dark' | AkropolysTheme
62
- chips={['Best phones', 'Laptops under KSh 50K']}
63
- onSelectSource={(src) => console.log(src)}
64
- />
65
- ```
66
-
67
- ---
68
-
69
- ### `<KikuChat />` — Inline embedded chat widget
70
-
71
- ```tsx
72
- import { KikuChat } from '@akropolys/kiku';
73
-
74
- <KikuChat
75
- title="AI Shopping Assistant"
76
- placeholder="Ask about anything in our store…"
77
- defaultCurrency="KES"
78
- />
79
- ```
80
-
81
- ---
82
-
83
- ### `<SearchBar />` — Debounced AI search with dropdown
84
-
85
- ```tsx
86
- import { SearchBar } from '@akropolys/kiku';
87
-
88
- <SearchBar
89
- placeholder="Search products…"
90
- limit={8}
91
- debounceMs={300}
92
- onSelect={(result) => {
93
- window.location.href = result.product.url;
94
- }}
95
- />
96
- ```
97
-
98
- ---
99
-
100
- ### `<Sparkle />` — Per-product AI assistant button
101
-
102
- ```tsx
103
- import { Sparkle } from '@akropolys/kiku';
104
-
105
- // Place next to any product on a listing or detail page
106
- <Sparkle productName="Samsung Galaxy S24" />
107
- ```
108
-
109
- ---
110
-
111
- ### `<ComparisonMatrix />` — Side-by-side product comparison table
112
-
113
- ```tsx
114
- import { ComparisonMatrix } from '@akropolys/kiku';
115
-
116
- // sources come from useKiku() or useSearch()
117
- <ComparisonMatrix sources={sources} defaultCurrency="KES" />
118
- ```
119
-
120
- ---
121
-
122
- ### `<CartBadge />` — Cart item count badge
123
-
124
- ```tsx
125
- import { CartBadge } from '@akropolys/kiku';
126
-
127
- <CartBadge className="my-badge" />
128
- ```
129
-
130
- ---
131
-
132
- ### `<CartDrawer />` — Slide-out cart drawer
133
-
134
- ```tsx
135
- import { CartDrawer } from '@akropolys/kiku';
136
-
137
- <CartDrawer trigger={<button>🛒 Cart</button>} theme="dark" />
138
- ```
35
+ | Component | Description |
36
+ |---|---|
37
+ | `<KikuButton />` | Floating AI chat assistant |
38
+ | `<KikuChat />` | Inline embedded chat widget |
39
+ | `<SearchBar />` | AI search with dropdown results |
40
+ | `<Sparkle />` | Per-product assistant button |
41
+ | `<ComparisonMatrix />` | Side-by-side product comparison |
42
+ | `<CartBadge />` | Cart item count |
43
+ | `<CartDrawer />` | Slide-out cart |
44
+ | `<CheckoutModal />` | Checkout modal |
139
45
 
140
- ---
141
-
142
- ### `<CheckoutModal />` — Full checkout modal (M-Pesa + extensible)
143
-
144
- ```tsx
145
- import { CheckoutModal } from '@akropolys/kiku';
146
-
147
- <CheckoutModal onClose={() => setOpen(false)} theme="dark" />
148
- ```
149
-
150
- ---
151
-
152
- ## Theming
153
-
154
- All components accept a `theme` prop. Pass `'light'` | `'dark'` for the built-in modes, or pass an `AkropolysTheme` object for full control:
155
-
156
- ```tsx
157
- <KikuButton
158
- theme={{
159
- primaryColor: '#6d28d9',
160
- backgroundColor: '#0f0f0f',
161
- textColor: '#f5f5f5',
162
- fontFamily: 'Inter, sans-serif',
163
- borderRadius: '12px',
164
- }}
165
- />
166
- ```
167
-
168
- Alternatively, override CSS variables globally in your stylesheet:
169
-
170
- ```css
171
- :root {
172
- --hsk-primary: #6d28d9;
173
- --hsk-bg: #0f0f0f;
174
- --hsk-text: #f5f5f5;
175
- --hsk-font: 'Inter', sans-serif;
176
- --hsk-border-radius: 12px;
177
- }
178
- ```
179
-
180
- ---
181
-
182
- ## Using the headless SDK instead
183
-
184
- If you want to build your own UI from scratch, install only the core:
46
+ ## Installation
185
47
 
186
48
  ```bash
187
- npm install @akropolys/sdk
49
+ npm install @akropolys/kiku @akropolys/sdk
188
50
  ```
189
51
 
190
- ```tsx
191
- import { useKiku } from '@akropolys/sdk';
192
-
193
- const { send, messages, loading, streaming } = useKiku({
194
- onToken: (token) => appendToMyUI(token),
195
- onMeta: (meta) => showProducts(meta.sources),
196
- onDone: (full) => saveToDatabase(full),
197
- });
52
+ ```ts
53
+ import '@akropolys/kiku/styles.css';
198
54
  ```
199
55
 
56
+ [Read the full docs →](https://akropolys.io/docs)
57
+
200
58
  ---
201
59
 
202
60
  ## License
package/dist/index.d.mts CHANGED
@@ -78,11 +78,11 @@ interface SparkleProps {
78
78
  onResult?: (results: SearchResult[]) => void;
79
79
  /** Override the backdrop colour (any CSS colour/gradient) */
80
80
  backdropColor?: string;
81
- /** Override backdrop blur e.g. "8px" or 8 */
81
+ /** Override backdrop blur — e.g. "8px" or 8 */
82
82
  backdropBlur?: string | number;
83
83
  /** Extra classes on the trigger button */
84
84
  className?: string;
85
- /** Called when user clicks a result return false to prevent default navigation */
85
+ /** Called when user clicks a result — return false to prevent default navigation */
86
86
  onNavigate?: (result: SearchResult) => boolean | void;
87
87
  theme?: AkropolysTheme;
88
88
  classNames?: {
package/dist/index.d.ts CHANGED
@@ -78,11 +78,11 @@ interface SparkleProps {
78
78
  onResult?: (results: SearchResult[]) => void;
79
79
  /** Override the backdrop colour (any CSS colour/gradient) */
80
80
  backdropColor?: string;
81
- /** Override backdrop blur e.g. "8px" or 8 */
81
+ /** Override backdrop blur — e.g. "8px" or 8 */
82
82
  backdropBlur?: string | number;
83
83
  /** Extra classes on the trigger button */
84
84
  className?: string;
85
- /** Called when user clicks a result return false to prevent default navigation */
85
+ /** Called when user clicks a result — return false to prevent default navigation */
86
86
  onNavigate?: (result: SearchResult) => boolean | void;
87
87
  theme?: AkropolysTheme;
88
88
  classNames?: {
package/dist/index.js CHANGED
@@ -106,7 +106,7 @@ function SearchBar({
106
106
  }
107
107
  ignoreNextQueryChange.current = true;
108
108
  setOpen(false);
109
- setQuery(r.product.name ?? "");
109
+ setQuery(r.entity.title ?? r.entity.name ?? "");
110
110
  onSelect?.(r);
111
111
  };
112
112
  const handleCommitSearch = () => {
@@ -186,8 +186,8 @@ function SearchBar({
186
186
  children: [
187
187
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchIcon, {}) }),
188
188
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hsk-sb-row-body", children: [
189
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hsk-sb-row-title", children: r.product.name }),
190
- (r.product.category || r.product.brand) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hsk-sb-row-sub", children: r.product.category ?? r.product.brand })
189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hsk-sb-row-title", children: r.entity.title ?? r.entity.name }),
190
+ (r.entity.category || r.entity.brand) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hsk-sb-row-sub", children: r.entity.category ?? r.entity.brand })
191
191
  ] })
192
192
  ]
193
193
  },
@@ -688,7 +688,7 @@ I couldn't find any matching products in the store.`;
688
688
  }) }) });
689
689
  }
690
690
  const featured = sources.filter((src) => src.id && referencedIds.includes(src.id));
691
- const general = featured.length > 0 ? [] : sources.filter((src) => !src.id || !referencedIds.includes(src.id));
691
+ const general = referencedIds.length > 0 ? [] : sources.filter((src) => !src.id || !referencedIds.includes(src.id));
692
692
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-sources-container", children: [
693
693
  featured.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hsk-sources-group", style: { marginBottom: "10px" }, children: [
694
694
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hsk-sources-group-title", children: "\u2B50 Featured in response" }),
@@ -1360,7 +1360,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource, referencedI
1360
1360
  railRef.current?.scrollBy({ left: 170, behavior: "smooth" });
1361
1361
  };
1362
1362
  const referenced = sources.filter((s) => s.id && referencedIds.includes(s.id));
1363
- const display = referenced.length > 0 ? referenced : sources;
1363
+ const display = referencedIds.length > 0 ? referenced : sources;
1364
1364
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-sources-wrap", children: [
1365
1365
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-sources", ref: railRef, children: display.map((src, si) => {
1366
1366
  const isReferenced = !!(src.id && referencedIds.includes(src.id));
@@ -1786,30 +1786,14 @@ function ChatModal({
1786
1786
  } : void 0;
1787
1787
  const handlePhoneSubmit = async () => {
1788
1788
  if (!phoneInput.trim()) return;
1789
- try {
1790
- if (typeof window !== "undefined") {
1791
- localStorage.setItem("akropolys_user_phone", phoneInput.trim());
1792
- }
1793
- const isHistoryIntent = lastIntent === "capture" || lastIntent === "delete" || lastIntent === "view_history";
1794
- if (isHistoryIntent) {
1795
- const { phone, word } = parseMemoryKey(phoneInput);
1796
- if (!phone) return;
1797
- client.setShopperIdentity(phone, word);
1798
- setMemoryKey(phone + word);
1799
- setPaymentPhase("idle");
1800
- const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1801
- if (lastUserMsg) {
1802
- await send(lastUserMsg.content);
1803
- }
1804
- return;
1805
- }
1806
- const res = await client.api.initiatePayment(phoneInput.trim());
1807
- setMerchantRef(res.merchantReference);
1808
- setPaymentPhase("awaiting");
1809
- } catch (e) {
1810
- console.error("[Akropolys] initiatePayment error", e);
1811
- setPaymentPhase("failed");
1812
- }
1789
+ const { phone, word } = parseMemoryKey(phoneInput);
1790
+ if (!phone) return;
1791
+ if (typeof window !== "undefined") localStorage.setItem("akropolys_user_phone", phone);
1792
+ client.setShopperIdentity(phone, word);
1793
+ setMemoryKey(phone + word);
1794
+ setPaymentPhase("idle");
1795
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
1796
+ if (lastUserMsg) await send(lastUserMsg.content);
1813
1797
  };
1814
1798
  const msgsContainerRef = (0, import_react5.useRef)(null);
1815
1799
  (0, import_react5.useEffect)(() => {
@@ -2098,49 +2082,25 @@ function ChatModal({
2098
2082
  ] })
2099
2083
  ] }),
2100
2084
  error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-error", children: getFriendlyError(error) }),
2101
- !replayMessages && paymentPhase === "prompt_phone" && (() => {
2102
- const isHistoryIntent = lastIntent === "capture" || lastIntent === "delete" || lastIntent === "view_history";
2103
- if (isHistoryIntent) {
2104
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2105
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
2106
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-phone-form", children: [
2107
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "hsk-cb-phone-label", children: "Your phone number \u2014 or paste your memory key" }),
2108
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2109
- "input",
2110
- {
2111
- type: "text",
2112
- className: "hsk-cb-phone-input",
2113
- placeholder: "0712345678 or 0712345678flyingthunder",
2114
- value: phoneInput,
2115
- onChange: (e) => setPhoneInput(e.target.value.replace(/[^0-9a-zA-Z]/g, "")),
2116
- onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit(),
2117
- autoFocus: true
2118
- }
2119
- ),
2120
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "hsk-cb-phone-submit", onClick: handlePhoneSubmit, children: "Save & sync my items" })
2121
- ] }) }) })
2122
- ] });
2123
- }
2124
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2125
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
2126
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-phone-form", children: [
2127
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "hsk-cb-phone-label", children: "Enter your M-Pesa number to pay" }),
2128
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2129
- "input",
2130
- {
2131
- type: "tel",
2132
- className: "hsk-cb-phone-input",
2133
- placeholder: "0712 345 678",
2134
- value: phoneInput,
2135
- onChange: (e) => setPhoneInput(e.target.value.replace(/\D/g, "")),
2136
- onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit(),
2137
- autoFocus: true
2138
- }
2139
- ),
2140
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "hsk-cb-phone-submit", onClick: handlePhoneSubmit, children: "Send STK push" })
2141
- ] }) }) })
2142
- ] });
2143
- })(),
2085
+ !replayMessages && paymentPhase === "prompt_phone" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2086
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
2087
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-phone-form", children: [
2088
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "hsk-cb-phone-label", children: "Your phone number \u2014 or paste your memory key" }),
2089
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2090
+ "input",
2091
+ {
2092
+ type: "text",
2093
+ className: "hsk-cb-phone-input",
2094
+ placeholder: "0712345678 or 0712345678flyingthunder",
2095
+ value: phoneInput,
2096
+ onChange: (e) => setPhoneInput(e.target.value.replace(/[^0-9a-zA-Z]/g, "")),
2097
+ onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit(),
2098
+ autoFocus: true
2099
+ }
2100
+ ),
2101
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "hsk-cb-phone-submit", onClick: handlePhoneSubmit, children: "Save & sync my items" })
2102
+ ] }) }) })
2103
+ ] }),
2144
2104
  !replayMessages && memoryKey && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hsk-cb-ai-msg", children: [
2145
2105
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SparkleIcon2, {}) }),
2146
2106
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hsk-cb-ai-text", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { padding: "10px 12px", border: "1px solid var(--hsk-border, #e5e5e5)", borderRadius: 8 }, children: [
@@ -2515,7 +2475,7 @@ function SparkleModal({
2515
2475
  if (!initialProduct && !fetchedProduct) {
2516
2476
  client.api.searchVector(productName, 1).then((res) => {
2517
2477
  if (res.results && res.results.length > 0) {
2518
- setFetchedProduct(res.results[0].product);
2478
+ setFetchedProduct(res.results[0].entity);
2519
2479
  }
2520
2480
  }).catch((err) => console.error("[Akropolys] Failed to fetch product details", err));
2521
2481
  }
@@ -2548,7 +2508,7 @@ function SparkleModal({
2548
2508
  const prevent = onNavigate?.(r);
2549
2509
  if (prevent !== false) {
2550
2510
  onClose();
2551
- if (r.product.url) window.location.href = r.product.url;
2511
+ if (r.entity.url) window.location.href = r.entity.url;
2552
2512
  }
2553
2513
  };
2554
2514
  const handleSend = async (text) => {
@@ -2635,7 +2595,7 @@ Question: ${q}`;
2635
2595
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-cb-ai-text", children: renderMarkdown(msg.content) }),
2636
2596
  idx === 0 && displayProduct && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-attachment-deck", children: [
2637
2597
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-main-card", children: [
2638
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-main-card-img", children: displayProduct.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: displayProduct.images[0], alt: displayProduct.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "\u{1F6CD}" }) }),
2598
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-main-card-img", children: displayProduct.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: displayProduct.images[0], alt: displayProduct.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "\xF0\u0178\u203A\x8D" }) }),
2639
2599
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-main-card-info", children: [
2640
2600
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-main-card-brand", children: displayProduct.brand || displayProduct.category || "Product" }),
2641
2601
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-main-card-name", children: displayProduct.name }),
@@ -2658,8 +2618,8 @@ Question: ${q}`;
2658
2618
  (() => {
2659
2619
  const similarProducts = results.filter(
2660
2620
  (r) => {
2661
- const isSameName = !!(r.product.name && displayProduct?.name && r.product.name.toLowerCase() === displayProduct.name.toLowerCase());
2662
- const isSameSlug = r.product.slug && displayProduct?.slug && r.product.slug.toLowerCase() === displayProduct.slug.toLowerCase();
2621
+ const isSameName = !!(r.entity.name && displayProduct?.name && r.entity.name.toLowerCase() === displayProduct.name.toLowerCase());
2622
+ const isSameSlug = r.entity.slug && displayProduct?.slug && r.entity.slug.toLowerCase() === displayProduct.slug.toLowerCase();
2663
2623
  return !isSameName && !isSameSlug;
2664
2624
  }
2665
2625
  );
@@ -2667,17 +2627,17 @@ Question: ${q}`;
2667
2627
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-similar-carousel-inline", children: [
2668
2628
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-title", children: "Similar Products" }),
2669
2629
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-list", children: similarProducts.map((r) => {
2670
- const price = parseFloat(r.product.price?.replace(/[^0-9.]/g, "") || "0");
2671
- const currency = r.product.currency ?? "KES";
2630
+ const price = parseFloat(r.entity.price?.replace(/[^0-9.]/g, "") || "0");
2631
+ const currency = r.entity.currency ?? "KES";
2672
2632
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2673
2633
  "div",
2674
2634
  {
2675
2635
  className: "hsk-sp-mobile-similar-carousel-item",
2676
2636
  onClick: () => handleNav(r),
2677
2637
  children: [
2678
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-img", children: r.product.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "\u{1F6CD}" }) }),
2638
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-img", children: r.entity.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: r.entity.images[0], alt: r.entity.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "\xF0\u0178\u203A\x8D" }) }),
2679
2639
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-similar-carousel-meta", children: [
2680
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-name", title: r.product.name, children: r.product.name }),
2640
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-name", title: r.entity.name, children: r.entity.name }),
2681
2641
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-similar-carousel-price", children: [
2682
2642
  currency,
2683
2643
  " ",
@@ -2781,7 +2741,7 @@ Question: ${q}`;
2781
2741
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-details-pane", children: [
2782
2742
  displayProduct && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-product-profile-container", children: [
2783
2743
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-product-profile", children: [
2784
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-details-imgwrap", children: displayProduct.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: displayProduct.images[0], alt: displayProduct.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
2744
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-details-imgwrap", children: displayProduct.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: displayProduct.images[0], alt: displayProduct.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\xF0\u0178\u203A\x8D" }) }),
2785
2745
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-details-meta", children: [
2786
2746
  displayProduct.brand && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-item-brand", children: displayProduct.brand }),
2787
2747
  displayProduct.category && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-item-cat", children: displayProduct.category }),
@@ -2798,7 +2758,7 @@ Question: ${q}`;
2798
2758
  ] }),
2799
2759
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-item-meta-badges", children: [
2800
2760
  displayProduct.rating && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-rating", children: [
2801
- "\u2605 ",
2761
+ "\xE2\u02DC\u2026 ",
2802
2762
  parseFloat(displayProduct.rating.toString()).toFixed(1),
2803
2763
  " ",
2804
2764
  displayProduct.reviewCount ? `(${displayProduct.reviewCount})` : ""
@@ -2828,8 +2788,8 @@ Question: ${q}`;
2828
2788
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-results", children: (() => {
2829
2789
  const similarProducts = results.filter(
2830
2790
  (r) => {
2831
- const isSameName = !!(r.product.name && displayProduct?.name && r.product.name.toLowerCase() === displayProduct.name.toLowerCase());
2832
- const isSameSlug = r.product.slug && displayProduct?.slug && r.product.slug.toLowerCase() === displayProduct.slug.toLowerCase();
2791
+ const isSameName = !!(r.entity.name && displayProduct?.name && r.entity.name.toLowerCase() === displayProduct.name.toLowerCase());
2792
+ const isSameSlug = r.entity.slug && displayProduct?.slug && r.entity.slug.toLowerCase() === displayProduct.slug.toLowerCase();
2833
2793
  return !isSameName && !isSameSlug;
2834
2794
  }
2835
2795
  );
@@ -2837,8 +2797,8 @@ Question: ${q}`;
2837
2797
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-empty", children: "No similar products found." });
2838
2798
  }
2839
2799
  return similarProducts.map((r, i) => {
2840
- const price = parseFloat(r.product.price?.replace(/[^0-9.]/g, "") || "0");
2841
- const currency = r.product.currency ?? "KES";
2800
+ const price = parseFloat(r.entity.price?.replace(/[^0-9.]/g, "") || "0");
2801
+ const currency = r.entity.currency ?? "KES";
2842
2802
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2843
2803
  "div",
2844
2804
  {
@@ -2846,11 +2806,11 @@ Question: ${q}`;
2846
2806
  style: { animationDelay: `${i * 55}ms`, cursor: "pointer" },
2847
2807
  onClick: () => handleNav(r),
2848
2808
  children: [
2849
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-img-wrap", children: r.product.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
2809
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-img-wrap", children: r.entity.images?.[0] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: r.entity.images[0], alt: r.entity.name }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-img-placeholder", children: "\xF0\u0178\u203A\x8D" }) }),
2850
2810
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-item-body", children: [
2851
2811
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
2852
- r.product.category && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-item-cat", children: r.product.category }),
2853
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-item-name", title: r.product.name, children: r.product.name })
2812
+ r.entity.category && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-item-cat", children: r.entity.category }),
2813
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-item-name", title: r.entity.name, children: r.entity.name })
2854
2814
  ] }),
2855
2815
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
2856
2816
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-item-currency", children: currency }),