@akropolys/kiku 1.5.18 → 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 +25 -167
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -23
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +0 -4
- package/dist/styles.css.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,30 +1,16 @@
|
|
|
1
1
|
# @akropolys/kiku
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@akropolys/kiku) [](https://github.com/akropolys/sdk/blob/main/LICENSE) [](https://www.typescriptlang.org/)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
38
|
-
apiUrl=
|
|
39
|
-
apiToken=
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
```
|
|
191
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
190
|
-
(r.
|
|
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 =
|
|
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 =
|
|
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));
|
|
@@ -2475,7 +2475,7 @@ function SparkleModal({
|
|
|
2475
2475
|
if (!initialProduct && !fetchedProduct) {
|
|
2476
2476
|
client.api.searchVector(productName, 1).then((res) => {
|
|
2477
2477
|
if (res.results && res.results.length > 0) {
|
|
2478
|
-
setFetchedProduct(res.results[0].
|
|
2478
|
+
setFetchedProduct(res.results[0].entity);
|
|
2479
2479
|
}
|
|
2480
2480
|
}).catch((err) => console.error("[Akropolys] Failed to fetch product details", err));
|
|
2481
2481
|
}
|
|
@@ -2508,7 +2508,7 @@ function SparkleModal({
|
|
|
2508
2508
|
const prevent = onNavigate?.(r);
|
|
2509
2509
|
if (prevent !== false) {
|
|
2510
2510
|
onClose();
|
|
2511
|
-
if (r.
|
|
2511
|
+
if (r.entity.url) window.location.href = r.entity.url;
|
|
2512
2512
|
}
|
|
2513
2513
|
};
|
|
2514
2514
|
const handleSend = async (text) => {
|
|
@@ -2595,7 +2595,7 @@ Question: ${q}`;
|
|
|
2595
2595
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-cb-ai-text", children: renderMarkdown(msg.content) }),
|
|
2596
2596
|
idx === 0 && displayProduct && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-attachment-deck", children: [
|
|
2597
2597
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-main-card", children: [
|
|
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: "\
|
|
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" }) }),
|
|
2599
2599
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-main-card-info", children: [
|
|
2600
2600
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-main-card-brand", children: displayProduct.brand || displayProduct.category || "Product" }),
|
|
2601
2601
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-main-card-name", children: displayProduct.name }),
|
|
@@ -2618,8 +2618,8 @@ Question: ${q}`;
|
|
|
2618
2618
|
(() => {
|
|
2619
2619
|
const similarProducts = results.filter(
|
|
2620
2620
|
(r) => {
|
|
2621
|
-
const isSameName = !!(r.
|
|
2622
|
-
const isSameSlug = r.
|
|
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();
|
|
2623
2623
|
return !isSameName && !isSameSlug;
|
|
2624
2624
|
}
|
|
2625
2625
|
);
|
|
@@ -2627,17 +2627,17 @@ Question: ${q}`;
|
|
|
2627
2627
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-similar-carousel-inline", children: [
|
|
2628
2628
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-title", children: "Similar Products" }),
|
|
2629
2629
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-list", children: similarProducts.map((r) => {
|
|
2630
|
-
const price = parseFloat(r.
|
|
2631
|
-
const currency = r.
|
|
2630
|
+
const price = parseFloat(r.entity.price?.replace(/[^0-9.]/g, "") || "0");
|
|
2631
|
+
const currency = r.entity.currency ?? "KES";
|
|
2632
2632
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2633
2633
|
"div",
|
|
2634
2634
|
{
|
|
2635
2635
|
className: "hsk-sp-mobile-similar-carousel-item",
|
|
2636
2636
|
onClick: () => handleNav(r),
|
|
2637
2637
|
children: [
|
|
2638
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-img", children: r.
|
|
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" }) }),
|
|
2639
2639
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-similar-carousel-meta", children: [
|
|
2640
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-name", title: r.
|
|
2640
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-mobile-similar-carousel-name", title: r.entity.name, children: r.entity.name }),
|
|
2641
2641
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-mobile-similar-carousel-price", children: [
|
|
2642
2642
|
currency,
|
|
2643
2643
|
" ",
|
|
@@ -2741,7 +2741,7 @@ Question: ${q}`;
|
|
|
2741
2741
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-details-pane", children: [
|
|
2742
2742
|
displayProduct && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-product-profile-container", children: [
|
|
2743
2743
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-product-profile", children: [
|
|
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: "\
|
|
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" }) }),
|
|
2745
2745
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-details-meta", children: [
|
|
2746
2746
|
displayProduct.brand && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-item-brand", children: displayProduct.brand }),
|
|
2747
2747
|
displayProduct.category && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-item-cat", children: displayProduct.category }),
|
|
@@ -2758,7 +2758,7 @@ Question: ${q}`;
|
|
|
2758
2758
|
] }),
|
|
2759
2759
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-item-meta-badges", children: [
|
|
2760
2760
|
displayProduct.rating && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-rating", children: [
|
|
2761
|
-
"\
|
|
2761
|
+
"\xE2\u02DC\u2026 ",
|
|
2762
2762
|
parseFloat(displayProduct.rating.toString()).toFixed(1),
|
|
2763
2763
|
" ",
|
|
2764
2764
|
displayProduct.reviewCount ? `(${displayProduct.reviewCount})` : ""
|
|
@@ -2788,8 +2788,8 @@ Question: ${q}`;
|
|
|
2788
2788
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-results", children: (() => {
|
|
2789
2789
|
const similarProducts = results.filter(
|
|
2790
2790
|
(r) => {
|
|
2791
|
-
const isSameName = !!(r.
|
|
2792
|
-
const isSameSlug = r.
|
|
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();
|
|
2793
2793
|
return !isSameName && !isSameSlug;
|
|
2794
2794
|
}
|
|
2795
2795
|
);
|
|
@@ -2797,8 +2797,8 @@ Question: ${q}`;
|
|
|
2797
2797
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-empty", children: "No similar products found." });
|
|
2798
2798
|
}
|
|
2799
2799
|
return similarProducts.map((r, i) => {
|
|
2800
|
-
const price = parseFloat(r.
|
|
2801
|
-
const currency = r.
|
|
2800
|
+
const price = parseFloat(r.entity.price?.replace(/[^0-9.]/g, "") || "0");
|
|
2801
|
+
const currency = r.entity.currency ?? "KES";
|
|
2802
2802
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2803
2803
|
"div",
|
|
2804
2804
|
{
|
|
@@ -2806,11 +2806,11 @@ Question: ${q}`;
|
|
|
2806
2806
|
style: { animationDelay: `${i * 55}ms`, cursor: "pointer" },
|
|
2807
2807
|
onClick: () => handleNav(r),
|
|
2808
2808
|
children: [
|
|
2809
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-img-wrap", children: r.
|
|
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" }) }),
|
|
2810
2810
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-item-body", children: [
|
|
2811
2811
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
|
|
2812
|
-
r.
|
|
2813
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hsk-sp-item-name", title: r.
|
|
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 })
|
|
2814
2814
|
] }),
|
|
2815
2815
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hsk-sp-item-price-row", children: [
|
|
2816
2816
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hsk-sp-item-currency", children: currency }),
|