@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 +25 -167
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +50 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -90
- 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));
|
|
@@ -1786,30 +1786,14 @@ function ChatModal({
|
|
|
1786
1786
|
} : void 0;
|
|
1787
1787
|
const handlePhoneSubmit = async () => {
|
|
1788
1788
|
if (!phoneInput.trim()) return;
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
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].
|
|
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.
|
|
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: "\
|
|
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.
|
|
2662
|
-
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();
|
|
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.
|
|
2671
|
-
const currency = r.
|
|
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.
|
|
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.
|
|
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: "\
|
|
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
|
-
"\
|
|
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.
|
|
2832
|
-
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();
|
|
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.
|
|
2841
|
-
const currency = r.
|
|
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.
|
|
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.
|
|
2853
|
-
/* @__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 })
|
|
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 }),
|