@huskel/sdk 0.2.2 → 0.3.0
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 +228 -121
- package/dist/index.d.mts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +47 -9
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +45 -5
- package/dist/index.mjs.map +1 -0
- package/package.json +31 -32
package/README.md
CHANGED
|
@@ -1,121 +1,228 @@
|
|
|
1
|
-
# @huskel/sdk
|
|
2
|
-
|
|
3
|
-
AI-powered vector search
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @huskel/sdk
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
1
|
+
# @huskel/sdk
|
|
2
|
+
|
|
3
|
+
AI-powered vector search for any storefront. Your customers browse → products index automatically. Zero scraping, zero manual uploads.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @huskel/sdk
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @huskel/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## How it works
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Customer visits /products/nike-air-max
|
|
19
|
+
↓
|
|
20
|
+
usePageIngest() fires silently in the browser
|
|
21
|
+
↓
|
|
22
|
+
POST /ingest → Go backend → Upstash vector DB
|
|
23
|
+
↓
|
|
24
|
+
Product is now searchable via <SearchBar />
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
No cron jobs. No scraping. No manual uploads.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
### Environment variables
|
|
34
|
+
|
|
35
|
+
Add these to `.env` or `.env.local` in your project:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
NEXT_PUBLIC_HUSKEL_SITE_ID=your-site-id
|
|
39
|
+
NEXT_PUBLIC_HUSKEL_API_URL=https://your-huskel-backend.com
|
|
40
|
+
NEXT_PUBLIC_HUSKEL_API_TOKEN=sk_live_...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The SDK picks these up automatically — no need to pass them manually.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Next.js (App Router)
|
|
48
|
+
|
|
49
|
+
Next.js App Router uses **Server Components** by default. The SDK is client-only, so follow this pattern:
|
|
50
|
+
|
|
51
|
+
### 1. Create a client provider wrapper
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// app/components/HuskelClientProvider.tsx
|
|
55
|
+
'use client';
|
|
56
|
+
|
|
57
|
+
import { HuskelProvider } from '@huskel/sdk';
|
|
58
|
+
|
|
59
|
+
export default function HuskelClientProvider({
|
|
60
|
+
children,
|
|
61
|
+
}: {
|
|
62
|
+
children: React.ReactNode;
|
|
63
|
+
}) {
|
|
64
|
+
return <HuskelProvider>{children}</HuskelProvider>;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Add it to your root layout
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// app/layout.tsx ← this is a Server Component, no 'use client' needed
|
|
72
|
+
import HuskelClientProvider from './components/HuskelClientProvider';
|
|
73
|
+
|
|
74
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
75
|
+
return (
|
|
76
|
+
<html lang="en">
|
|
77
|
+
<body>
|
|
78
|
+
<HuskelClientProvider>
|
|
79
|
+
{children}
|
|
80
|
+
</HuskelClientProvider>
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Auto-ingest on product pages
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// app/products/[slug]/page.tsx
|
|
91
|
+
import { getProduct } from '@/lib/db'; // your own data fetching
|
|
92
|
+
|
|
93
|
+
// ProductView is a Client Component — handles ingestion
|
|
94
|
+
'use client';
|
|
95
|
+
import { usePageIngest } from '@huskel/sdk';
|
|
96
|
+
|
|
97
|
+
export function ProductView({ product }) {
|
|
98
|
+
// One line — fires automatically when the customer's browser loads the page
|
|
99
|
+
usePageIngest({
|
|
100
|
+
name: product.title,
|
|
101
|
+
price: product.price,
|
|
102
|
+
url: window.location.href,
|
|
103
|
+
images: [product.thumbnail],
|
|
104
|
+
category: product.category,
|
|
105
|
+
description: product.description,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return <div>{/* your product UI */}</div>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> **Why a separate client component?**
|
|
113
|
+
> `usePageIngest` uses `useEffect` which runs only in the browser. Server Components can't call hooks. The wrapper pattern keeps your data-fetching in Server Components (fast, cached) while the SDK fires client-side.
|
|
114
|
+
|
|
115
|
+
### 4. Add the search bar
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// app/components/Header.tsx
|
|
119
|
+
'use client';
|
|
120
|
+
import { SearchBar } from '@huskel/sdk';
|
|
121
|
+
import { useRouter } from 'next/navigation';
|
|
122
|
+
|
|
123
|
+
export function Header() {
|
|
124
|
+
const router = useRouter();
|
|
125
|
+
return (
|
|
126
|
+
<SearchBar
|
|
127
|
+
placeholder="Search products..."
|
|
128
|
+
onSelect={(result) => router.push(result.product.url)}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## React (CRA / Vite)
|
|
137
|
+
|
|
138
|
+
With a standard SPA, everything is already a client component. Much simpler:
|
|
139
|
+
|
|
140
|
+
### 1. Wrap your app
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
// src/main.tsx or src/App.tsx
|
|
144
|
+
import { HuskelProvider } from '@huskel/sdk';
|
|
145
|
+
|
|
146
|
+
function App() {
|
|
147
|
+
return (
|
|
148
|
+
<HuskelProvider>
|
|
149
|
+
<Router>
|
|
150
|
+
<Routes />
|
|
151
|
+
</Router>
|
|
152
|
+
</HuskelProvider>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 2. Ingest on product pages
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// src/pages/ProductPage.tsx
|
|
161
|
+
import { usePageIngest } from '@huskel/sdk';
|
|
162
|
+
|
|
163
|
+
export function ProductPage({ product }) {
|
|
164
|
+
usePageIngest({
|
|
165
|
+
name: product.title,
|
|
166
|
+
price: product.price,
|
|
167
|
+
url: window.location.href,
|
|
168
|
+
images: [product.thumbnail],
|
|
169
|
+
category: product.category,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return <div>{/* your product UI */}</div>;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 3. Add search
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { SearchBar } from '@huskel/sdk';
|
|
180
|
+
|
|
181
|
+
<SearchBar onSelect={(result) => navigate(result.product.url)} />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Batch ingest (listing pages)
|
|
187
|
+
|
|
188
|
+
When rendering a grid of products, ingest them all at once:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
'use client';
|
|
192
|
+
import { useIngest } from '@huskel/sdk';
|
|
193
|
+
import { useEffect } from 'react';
|
|
194
|
+
|
|
195
|
+
export function ProductGrid({ products }) {
|
|
196
|
+
const { ingestBatch } = useIngest();
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
ingestBatch(
|
|
200
|
+
products.map((p) => ({
|
|
201
|
+
name: p.title,
|
|
202
|
+
price: p.price,
|
|
203
|
+
url: `/products/${p.slug}`,
|
|
204
|
+
images: [p.thumbnail],
|
|
205
|
+
category: p.category,
|
|
206
|
+
currency: 'KES',
|
|
207
|
+
}))
|
|
208
|
+
);
|
|
209
|
+
}, [products]);
|
|
210
|
+
|
|
211
|
+
return <ul>{/* render cards */}</ul>;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## API Reference
|
|
218
|
+
|
|
219
|
+
| Export | Type | Description |
|
|
220
|
+
|--------|------|-------------|
|
|
221
|
+
| `HuskelProvider` | Component | Wraps your app. Reads env vars automatically. |
|
|
222
|
+
| `usePageIngest(product)` | Hook | Ingest one product. Call on any product detail page. |
|
|
223
|
+
| `useIngest()` | Hook | Returns `{ ingest, ingestBatch }` for manual control. |
|
|
224
|
+
| `useSearch()` | Hook | Returns `{ search, results, loading }` for headless search. |
|
|
225
|
+
| `SearchBar` | Component | Plug-and-play autocomplete search UI. |
|
|
226
|
+
| `Sparkle` | Component | "Similar products" button powered by vector similarity. |
|
|
227
|
+
| `getHuskelClient()` | Function | Get the singleton client instance imperatively. |
|
|
228
|
+
| `initHuskel(config)` | Function | Initialize manually (non-React environments). |
|
package/dist/index.d.mts
CHANGED
|
@@ -127,6 +127,28 @@ interface UseIngestReturn {
|
|
|
127
127
|
}
|
|
128
128
|
declare function useIngest(): UseIngestReturn;
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* usePageIngest — drop this into any product page component.
|
|
132
|
+
* The moment a customer's browser renders the page, the product is
|
|
133
|
+
* automatically captured and queued for ingestion into the vector index.
|
|
134
|
+
*
|
|
135
|
+
* No configuration needed beyond <HuskelProvider> in your layout.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* // Product detail page — Next.js or React
|
|
139
|
+
* export function ProductPage({ product }) {
|
|
140
|
+
* usePageIngest({
|
|
141
|
+
* name: product.title,
|
|
142
|
+
* price: product.price,
|
|
143
|
+
* url: window.location.href,
|
|
144
|
+
* images: [product.thumbnail],
|
|
145
|
+
* category: product.category,
|
|
146
|
+
* });
|
|
147
|
+
* return <div>...</div>;
|
|
148
|
+
* }
|
|
149
|
+
*/
|
|
150
|
+
declare function usePageIngest(product: RawProductInput | null | undefined): void;
|
|
151
|
+
|
|
130
152
|
interface SearchBarProps {
|
|
131
153
|
placeholder?: string;
|
|
132
154
|
limit?: number;
|
|
@@ -152,4 +174,4 @@ interface HuskelProviderProps extends HuskelConfig {
|
|
|
152
174
|
}
|
|
153
175
|
declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
|
|
154
176
|
|
|
155
|
-
export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, HuskelProvider, type IngestResponse, type Product, type RawProductInput, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, useSearch };
|
|
177
|
+
export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, HuskelProvider, type IngestResponse, type Product, type RawProductInput, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, usePageIngest, useSearch };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,6 +127,28 @@ interface UseIngestReturn {
|
|
|
127
127
|
}
|
|
128
128
|
declare function useIngest(): UseIngestReturn;
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* usePageIngest — drop this into any product page component.
|
|
132
|
+
* The moment a customer's browser renders the page, the product is
|
|
133
|
+
* automatically captured and queued for ingestion into the vector index.
|
|
134
|
+
*
|
|
135
|
+
* No configuration needed beyond <HuskelProvider> in your layout.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* // Product detail page — Next.js or React
|
|
139
|
+
* export function ProductPage({ product }) {
|
|
140
|
+
* usePageIngest({
|
|
141
|
+
* name: product.title,
|
|
142
|
+
* price: product.price,
|
|
143
|
+
* url: window.location.href,
|
|
144
|
+
* images: [product.thumbnail],
|
|
145
|
+
* category: product.category,
|
|
146
|
+
* });
|
|
147
|
+
* return <div>...</div>;
|
|
148
|
+
* }
|
|
149
|
+
*/
|
|
150
|
+
declare function usePageIngest(product: RawProductInput | null | undefined): void;
|
|
151
|
+
|
|
130
152
|
interface SearchBarProps {
|
|
131
153
|
placeholder?: string;
|
|
132
154
|
limit?: number;
|
|
@@ -152,4 +174,4 @@ interface HuskelProviderProps extends HuskelConfig {
|
|
|
152
174
|
}
|
|
153
175
|
declare function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps): react_jsx_runtime.JSX.Element;
|
|
154
176
|
|
|
155
|
-
export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, HuskelProvider, type IngestResponse, type Product, type RawProductInput, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, useSearch };
|
|
177
|
+
export { HuskelAPI, HuskelClient, type HuskelConfig, type HuskelError, HuskelProvider, type IngestResponse, type Product, type RawProductInput, SearchBar, type SearchRequest, type SearchResponse, type SearchResult, Sparkle, getHuskelClient, initHuskel, useHuskel, useIngest, usePageIngest, useSearch };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
"use strict";
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
3
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
9
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
11
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
+
var __spreadValues = (a, b) => {
|
|
13
|
+
for (var prop in b || (b = {}))
|
|
14
|
+
if (__hasOwnProp.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
if (__getOwnPropSymbols)
|
|
17
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
18
|
+
if (__propIsEnum.call(b, prop))
|
|
19
|
+
__defNormalProp(a, prop, b[prop]);
|
|
20
|
+
}
|
|
21
|
+
return a;
|
|
22
|
+
};
|
|
23
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
6
24
|
var __export = (target, all) => {
|
|
7
25
|
for (var name in all)
|
|
8
26
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -29,6 +47,7 @@ __export(index_exports, {
|
|
|
29
47
|
initHuskel: () => initHuskel,
|
|
30
48
|
useHuskel: () => useHuskel,
|
|
31
49
|
useIngest: () => useIngest,
|
|
50
|
+
usePageIngest: () => usePageIngest,
|
|
32
51
|
useSearch: () => useSearch
|
|
33
52
|
});
|
|
34
53
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -396,8 +415,25 @@ function useIngest() {
|
|
|
396
415
|
return { ingest, ingestBatch, loading, error };
|
|
397
416
|
}
|
|
398
417
|
|
|
399
|
-
// src/
|
|
418
|
+
// src/hooks/usePageIngest.ts
|
|
400
419
|
var import_react5 = require("react");
|
|
420
|
+
function usePageIngest(product) {
|
|
421
|
+
var _a;
|
|
422
|
+
const ingestedRef = (0, import_react5.useRef)(null);
|
|
423
|
+
(0, import_react5.useEffect)(() => {
|
|
424
|
+
if (!product) return;
|
|
425
|
+
const url = product.url || (typeof window !== "undefined" ? window.location.href : "");
|
|
426
|
+
if (ingestedRef.current === url) return;
|
|
427
|
+
ingestedRef.current = url;
|
|
428
|
+
try {
|
|
429
|
+
getHuskelClient().queueIngest(__spreadProps(__spreadValues({}, product), { url }));
|
|
430
|
+
} catch (e) {
|
|
431
|
+
}
|
|
432
|
+
}, [(_a = product == null ? void 0 : product.url) != null ? _a : product == null ? void 0 : product.name]);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/components/SearchBar.tsx
|
|
436
|
+
var import_react6 = require("react");
|
|
401
437
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
402
438
|
var S = `
|
|
403
439
|
.hsk-wrap{position:relative;width:100%;font-family:inherit}
|
|
@@ -421,12 +457,12 @@ function SearchBar({
|
|
|
421
457
|
dropdownClassName,
|
|
422
458
|
renderResult
|
|
423
459
|
}) {
|
|
424
|
-
const [query, setQuery] = (0,
|
|
425
|
-
const [open, setOpen] = (0,
|
|
460
|
+
const [query, setQuery] = (0, import_react6.useState)("");
|
|
461
|
+
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
426
462
|
const { results, loading, search, clear } = useSearch();
|
|
427
|
-
const timer = (0,
|
|
428
|
-
const wrap = (0,
|
|
429
|
-
(0,
|
|
463
|
+
const timer = (0, import_react6.useRef)();
|
|
464
|
+
const wrap = (0, import_react6.useRef)(null);
|
|
465
|
+
(0, import_react6.useEffect)(() => {
|
|
430
466
|
clearTimeout(timer.current);
|
|
431
467
|
if (!query.trim()) {
|
|
432
468
|
clear();
|
|
@@ -439,7 +475,7 @@ function SearchBar({
|
|
|
439
475
|
}, debounceMs);
|
|
440
476
|
return () => clearTimeout(timer.current);
|
|
441
477
|
}, [query, search, clear, limit, debounceMs]);
|
|
442
|
-
(0,
|
|
478
|
+
(0, import_react6.useEffect)(() => {
|
|
443
479
|
const handler = (e) => {
|
|
444
480
|
if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
|
|
445
481
|
};
|
|
@@ -494,7 +530,7 @@ function SearchBar({
|
|
|
494
530
|
}
|
|
495
531
|
|
|
496
532
|
// src/components/Sparkle.tsx
|
|
497
|
-
var
|
|
533
|
+
var import_react7 = require("react");
|
|
498
534
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
499
535
|
var S2 = `
|
|
500
536
|
.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}
|
|
@@ -503,7 +539,7 @@ var S2 = `
|
|
|
503
539
|
`;
|
|
504
540
|
function Sparkle({ productName, limit = 5, onResult, className }) {
|
|
505
541
|
const client = useHuskelContext();
|
|
506
|
-
const [loading, setLoading] = (0,
|
|
542
|
+
const [loading, setLoading] = (0, import_react7.useState)(false);
|
|
507
543
|
const handleClick = async () => {
|
|
508
544
|
setLoading(true);
|
|
509
545
|
try {
|
|
@@ -534,5 +570,7 @@ function Sparkle({ productName, limit = 5, onResult, className }) {
|
|
|
534
570
|
initHuskel,
|
|
535
571
|
useHuskel,
|
|
536
572
|
useIngest,
|
|
573
|
+
usePageIngest,
|
|
537
574
|
useSearch
|
|
538
575
|
});
|
|
576
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api.ts","../src/client.ts","../src/hooks/useHuskel.ts","../src/hooks/useSearch.ts","../src/components/HuskelProvider.tsx","../src/hooks/useIngest.ts","../src/hooks/usePageIngest.ts","../src/components/SearchBar.tsx","../src/components/Sparkle.tsx"],"sourcesContent":["export { initHuskel, getHuskelClient, HuskelClient } from './client';\r\nexport { HuskelAPI } from './api';\r\nexport { useHuskel } from './hooks/useHuskel';\r\nexport { useSearch } from './hooks/useSearch';\r\nexport { useIngest } from './hooks/useIngest';\r\nexport { usePageIngest } from './hooks/usePageIngest';\r\nexport { SearchBar } from './components/SearchBar';\r\nexport { Sparkle } from './components/Sparkle';\r\nexport { HuskelProvider } from './components/HuskelProvider';\r\nexport type {\r\n Product,\r\n RawProductInput,\r\n HuskelConfig,\r\n SearchRequest,\r\n SearchResult,\r\n SearchResponse,\r\n IngestResponse,\r\n HuskelError,\r\n} from './types';\r\n","import { Product, SearchResponse, IngestResponse, HuskelError } from './types';\r\n\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAYS = [500, 1000, 2000]; // ms\r\n\r\nfunction log(level: 'info' | 'warn' | 'error', msg: string, data?: unknown) {\r\n const prefix = '[Huskel]';\r\n if (level === 'error') console.error(prefix, msg, data ?? '');\r\n else if (level === 'warn') console.warn(prefix, msg, data ?? '');\r\n else console.log(prefix, msg, data ?? '');\r\n}\r\n\r\nasync function sleep(ms: number) {\r\n return new Promise(r => setTimeout(r, ms));\r\n}\r\n\r\nexport class HuskelAPI {\r\n constructor(\r\n private apiUrl: string,\r\n private siteId: string,\r\n private apiToken: string\r\n ) {}\r\n\r\n private async post<T>(path: string, body: unknown, attempt = 0): Promise<T> {\r\n const url = `${this.apiUrl}${path}`;\r\n\r\n try {\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-Huskel-Token': this.apiToken,\r\n 'X-Huskel-Site': this.siteId,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const text = await res.text();\r\n const err: HuskelError = { status: res.status, message: text };\r\n\r\n // Don't retry 4xx — developer errors\r\n if (res.status >= 400 && res.status < 500) {\r\n log('error', `${path} failed [${res.status}]`, text);\r\n throw err;\r\n }\r\n\r\n // Retry 5xx\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n\r\n log('error', `${path} failed after ${MAX_RETRIES} attempts`, err);\r\n throw err;\r\n }\r\n\r\n return res.json();\r\n } catch (e) {\r\n // Network error (offline, DNS, etc.)\r\n if ((e as HuskelError).status === undefined) {\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n log('error', `${path} unreachable after ${MAX_RETRIES} attempts`);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n async ingest(product: Product): Promise<IngestResponse> {\r\n log('info', 'ingesting product', product.name);\r\n return this.post('/ingest', { siteId: this.siteId, product });\r\n }\r\n\r\n async ingestBatch(products: Product[]): Promise<IngestResponse> {\r\n log('info', `ingesting batch of ${products.length} products`);\r\n return this.post('/ingest/batch', { siteId: this.siteId, products });\r\n }\r\n\r\n async search(query: string, limit = 10): Promise<SearchResponse> {\r\n log('info', 'search query', query);\r\n return this.post('/search', { query, siteId: this.siteId, limit });\r\n }\r\n}\r\n","import { HuskelConfig, Product, RawProductInput } from './types';\r\nimport { HuskelAPI } from './api';\r\n\r\nfunction getEnvVar(key: string): string | undefined {\r\n if (typeof globalThis !== 'undefined') {\r\n const g = globalThis as any;\r\n if (g.process && g.process.env) {\r\n return g.process.env[key];\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction mapRawProduct(input: RawProductInput): Product | null {\r\n const name = input.name || input.title || input.productName || '';\r\n \r\n let price = '';\r\n let priceNumeric: number | undefined = undefined;\r\n\r\n if (input.price !== undefined) {\r\n if (typeof input.price === 'number') {\r\n priceNumeric = input.price;\r\n price = String(input.price);\r\n } else {\r\n price = input.price;\r\n const num = parseFloat(input.price.replace(/[^0-9.]/g, ''));\r\n priceNumeric = isNaN(num) ? undefined : num;\r\n }\r\n }\r\n if (input.priceNumeric !== undefined) {\r\n priceNumeric = input.priceNumeric;\r\n }\r\n\r\n let url = input.url || '';\r\n if (!url && typeof window !== 'undefined') {\r\n url = window.location.href;\r\n }\r\n\r\n let slug = input.slug || input.id || input.productId || '';\r\n if (!slug && url) {\r\n slug = url.split('/').filter(Boolean).pop() || '';\r\n }\r\n if (!slug && name) {\r\n slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\r\n }\r\n\r\n let images: string[] = [];\r\n if (input.images) {\r\n images = input.images;\r\n } else if (input.image) {\r\n images = [input.image];\r\n } else if (input.thumbnail) {\r\n images = [input.thumbnail];\r\n }\r\n\r\n if (!name) {\r\n console.warn('[Huskel] Validation warning: Product name/title is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!price) {\r\n console.warn('[Huskel] Validation warning: Product price is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!url) {\r\n console.warn('[Huskel] Validation warning: Product URL is missing. Skipping:', input);\r\n return null;\r\n }\r\n\r\n return {\r\n name,\r\n price,\r\n url,\r\n brand: input.brand,\r\n description: input.description,\r\n originalPrice: input.originalPrice,\r\n discount: input.discount,\r\n currency: input.currency ?? 'KES',\r\n stock: input.stock,\r\n availability: input.availability,\r\n rating: input.rating,\r\n reviewCount: input.reviewCount,\r\n category: input.category,\r\n subCategory: input.subCategory,\r\n tags: input.tags,\r\n images: images.length > 0 ? images : undefined,\r\n specs: input.specs,\r\n priceNumeric,\r\n slug,\r\n };\r\n}\r\n\r\nexport class HuskelClient {\r\n readonly api: HuskelAPI;\r\n private ingestQueue: Product[] = [];\r\n private ingestTimer: ReturnType<typeof setTimeout> | null = null;\r\n private ingestedUrls = new Set<string>();\r\n private onlineHandler: (() => void) | null = null;\r\n\r\n constructor(config: HuskelConfig) {\r\n const siteId = config.siteId || getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID') || '';\r\n const apiUrl = config.apiUrl || getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL') || '';\r\n const apiToken = config.apiToken || getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN') || '';\r\n\r\n // Runtime validation — fail loudly so misconfiguration is never silent\r\n if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId=\"...\"> or NEXT_PUBLIC_HUSKEL_SITE_ID.');\r\n if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl=\"...\"> or NEXT_PUBLIC_HUSKEL_API_URL.');\r\n if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken=\"...\"> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');\r\n\r\n this.api = new HuskelAPI(apiUrl, siteId, apiToken);\r\n instance = this;\r\n\r\n if (typeof window !== 'undefined') {\r\n this.onlineHandler = () => {\r\n console.log('[Huskel] Connectivity restored, flushing queued ingestions.');\r\n this.flushQueue();\r\n };\r\n window.addEventListener('online', this.onlineHandler);\r\n }\r\n }\r\n\r\n destroy() {\r\n if (typeof window !== 'undefined' && this.onlineHandler) {\r\n window.removeEventListener('online', this.onlineHandler);\r\n this.onlineHandler = null;\r\n }\r\n if (this.ingestTimer) {\r\n clearTimeout(this.ingestTimer);\r\n this.ingestTimer = null;\r\n }\r\n if (instance === this) instance = null;\r\n }\r\n\r\n async queueIngest(rawProduct: RawProductInput): Promise<void> {\r\n const product = mapRawProduct(rawProduct);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n\r\n this.ingestQueue.push(product);\r\n this.scheduleFlush();\r\n }\r\n\r\n async queueIngestBatch(rawProducts: RawProductInput[]): Promise<void> {\r\n rawProducts.forEach(p => {\r\n const product = mapRawProduct(p);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n this.ingestQueue.push(product);\r\n });\r\n\r\n if (this.ingestQueue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.ingestTimer) return;\r\n this.ingestTimer = setTimeout(() => {\r\n this.flushQueue();\r\n }, 300);\r\n }\r\n\r\n private async flushQueue() {\r\n this.ingestTimer = null;\r\n if (this.ingestQueue.length === 0) return;\r\n\r\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\r\n console.warn('[Huskel] Browser offline. Postponing ingestion.');\r\n return;\r\n }\r\n\r\n const batch = [...this.ingestQueue];\r\n this.ingestQueue = [];\r\n\r\n try {\r\n await this.api.ingestBatch(batch);\r\n } catch (e: any) {\r\n if (e.status && e.status >= 400 && e.status < 500) {\r\n console.error('[Huskel] Ingestion discarded due to client error:', e.message);\r\n return;\r\n }\r\n\r\n // Re-queue and schedule another flush so items are not stuck forever\r\n console.warn('[Huskel] Ingestion failed. Re-queuing to retry.', e);\r\n this.ingestQueue = [...batch, ...this.ingestQueue];\r\n this.scheduleFlush();\r\n }\r\n }\r\n}\r\n\r\nlet instance: HuskelClient | null = null;\r\n\r\nexport function initHuskel(config: HuskelConfig): HuskelClient {\r\n instance = new HuskelClient(config);\r\n return instance;\r\n}\r\n\r\nexport function getHuskelClient(): HuskelClient {\r\n if (!instance) {\r\n const siteId = getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID');\r\n const apiUrl = getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL');\r\n const apiToken = getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN');\r\n\r\n if (siteId && apiUrl && apiToken) {\r\n instance = new HuskelClient({ siteId, apiUrl, apiToken });\r\n } else {\r\n throw new Error('[Huskel] Call initHuskel() or set NEXT_PUBLIC_HUSKEL_* environment variables before using the client.');\r\n }\r\n }\r\n return instance;\r\n}\r\n","import { useRef } from 'react';\r\nimport { HuskelConfig } from '../types';\r\nimport { HuskelClient, initHuskel } from '../client';\r\n\r\n/**\r\n * @deprecated Use <HuskelProvider> instead to avoid SSR issues.\r\n */\r\nexport function useHuskel(config: HuskelConfig): HuskelClient {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n console.warn('[Huskel] useHuskel() is deprecated. Please wrap your application in <HuskelProvider> instead.');\r\n clientRef.current = initHuskel(config);\r\n }\r\n\r\n return clientRef.current;\r\n}\r\n","import { useState, useCallback, useRef } from 'react';\r\nimport { SearchResult } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseSearchReturn {\r\n results: SearchResult[];\r\n loading: boolean;\r\n error: string | null;\r\n search: (query: string, limit?: number) => Promise<void>;\r\n clear: () => void;\r\n}\r\n\r\nexport function useSearch(): UseSearchReturn {\r\n const client = useHuskelContext();\r\n const [results, setResults] = useState<SearchResult[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const abortRef = useRef<AbortController | null>(null);\r\n\r\n const search = useCallback(async (query: string, limit = 10) => {\r\n if (!query.trim()) { setResults([]); return; }\r\n abortRef.current?.abort();\r\n abortRef.current = new AbortController();\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n const res = await client.api.search(query, limit);\r\n setResults(res.results ?? []);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Search failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const clear = useCallback(() => { setResults([]); setError(null); }, []);\r\n\r\n return { results, loading, error, search, clear };\r\n}\r\n","'use client';\r\n\r\nimport React, { createContext, useContext, useEffect, useRef } from 'react';\r\nimport { HuskelClient, getHuskelClient } from '../client';\r\nimport { HuskelConfig } from '../types';\r\n\r\nexport const HuskelContext = createContext<HuskelClient | null>(null);\r\n\r\ninterface HuskelProviderProps extends HuskelConfig {\r\n children: React.ReactNode;\r\n}\r\n\r\nexport function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps) {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken });\r\n }\r\n\r\n // Clean up the online listener and timers when the provider unmounts\r\n // (prevents leaks during hot module reload and React StrictMode double-mount)\r\n useEffect(() => {\r\n return () => {\r\n clientRef.current?.destroy();\r\n };\r\n }, []);\r\n\r\n return (\r\n <HuskelContext.Provider value={clientRef.current}>\r\n {children}\r\n </HuskelContext.Provider>\r\n );\r\n}\r\n\r\nexport function useHuskelContext(): HuskelClient {\r\n const context = useContext(HuskelContext);\r\n if (!context) {\r\n return getHuskelClient();\r\n }\r\n return context;\r\n}\r\n\r\n","import { useCallback, useState } from 'react';\r\nimport { RawProductInput } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseIngestReturn {\r\n ingest: (product: RawProductInput) => Promise<void>;\r\n ingestBatch: (products: RawProductInput[]) => Promise<void>;\r\n loading: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport function useIngest(): UseIngestReturn {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const ingest = useCallback(async (product: RawProductInput) => {\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngest(product);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const ingestBatch = useCallback(async (products: RawProductInput[]) => {\r\n if (!products.length) return;\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngestBatch(products);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Batch ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n return { ingest, ingestBatch, loading, error };\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { RawProductInput } from '../types';\nimport { getHuskelClient } from '../client';\n\n/**\n * usePageIngest — drop this into any product page component.\n * The moment a customer's browser renders the page, the product is\n * automatically captured and queued for ingestion into the vector index.\n *\n * No configuration needed beyond <HuskelProvider> in your layout.\n *\n * @example\n * // Product detail page — Next.js or React\n * export function ProductPage({ product }) {\n * usePageIngest({\n * name: product.title,\n * price: product.price,\n * url: window.location.href,\n * images: [product.thumbnail],\n * category: product.category,\n * });\n * return <div>...</div>;\n * }\n */\nexport function usePageIngest(product: RawProductInput | null | undefined): void {\n // Use url as the stable key — avoids re-ingesting on unrelated re-renders\n const ingestedRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!product) return;\n\n // Resolve URL — falls back to window.location if not provided\n const url =\n product.url ||\n (typeof window !== 'undefined' ? window.location.href : '');\n\n // Guard: only ingest once per URL per component lifecycle\n if (ingestedRef.current === url) return;\n ingestedRef.current = url;\n\n try {\n getHuskelClient().queueIngest({ ...product, url });\n } catch {\n // Client not initialised — silently skip\n }\n }, [product?.url ?? product?.name]);\n}\n","import React, { useState, useEffect, useRef } from 'react';\r\nimport { useSearch } from '../hooks/useSearch';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SearchBarProps {\r\n placeholder?: string;\r\n limit?: number;\r\n debounceMs?: number;\r\n onSelect?: (result: SearchResult) => void;\r\n className?: string;\r\n inputClassName?: string;\r\n dropdownClassName?: string;\r\n renderResult?: (result: SearchResult) => React.ReactNode;\r\n}\r\n\r\nconst S = `\r\n .hsk-wrap{position:relative;width:100%;font-family:inherit}\r\n .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}\r\n .hsk-input:focus{border-color:#f47c3c}\r\n .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}\r\n .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}\r\n .hsk-item:hover{background:#faf5f1}\r\n .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}\r\n .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}\r\n .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}\r\n`;\r\n\r\nexport function SearchBar({\r\n placeholder = 'Search for what you want — how you want',\r\n limit = 10,\r\n debounceMs = 300,\r\n onSelect,\r\n className,\r\n inputClassName,\r\n dropdownClassName,\r\n renderResult,\r\n}: SearchBarProps) {\r\n const [query, setQuery] = useState('');\r\n const [open, setOpen] = useState(false);\r\n const { results, loading, search, clear } = useSearch();\r\n const timer = useRef<ReturnType<typeof setTimeout>>();\r\n const wrap = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n clearTimeout(timer.current);\r\n if (!query.trim()) { clear(); setOpen(false); return; }\r\n timer.current = setTimeout(() => { search(query, limit); setOpen(true); }, debounceMs);\r\n return () => clearTimeout(timer.current);\r\n }, [query, search, clear, limit, debounceMs]);\r\n\r\n useEffect(() => {\r\n const handler = (e: MouseEvent) => {\r\n if (wrap.current && !wrap.current.contains(e.target as Node)) setOpen(false);\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, []);\r\n\r\n const handleSelect = (r: SearchResult) => {\r\n setOpen(false);\r\n setQuery(r.product.name);\r\n onSelect?.(r);\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <div className={`hsk-wrap ${className ?? ''}`} ref={wrap}>\r\n <input\r\n className={`hsk-input ${inputClassName ?? ''}`}\r\n type=\"text\"\r\n value={query}\r\n placeholder={placeholder}\r\n onChange={e => setQuery(e.target.value)}\r\n onFocus={() => results.length && setOpen(true)}\r\n />\r\n {open && (\r\n <div className={`hsk-drop ${dropdownClassName ?? ''}`}>\r\n {loading && <div className=\"hsk-msg\">Searching…</div>}\r\n {!loading && results.length === 0 && <div className=\"hsk-msg\">No results for \"{query}\"</div>}\r\n {results.map(r =>\r\n renderResult ? (\r\n <div key={r.id} onClick={() => handleSelect(r)}>{renderResult(r)}</div>\r\n ) : (\r\n <div key={r.id} className=\"hsk-item\" onClick={() => handleSelect(r)}>\r\n {r.product.images?.[0] && <img src={r.product.images[0]} alt={r.product.name} />}\r\n <div>\r\n <div className=\"hsk-item-name\">{r.product.name}</div>\r\n <div className=\"hsk-item-price\">{r.product.currency ?? 'KES'} {r.product.price}</div>\r\n </div>\r\n </div>\r\n )\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { useHuskelContext } from './HuskelProvider';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SparkleProps {\r\n productName: string;\r\n limit?: number;\r\n onResult?: (results: SearchResult[]) => void;\r\n className?: string;\r\n}\r\n\r\nconst S = `\r\n .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}\r\n .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}\r\n .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}\r\n`;\r\n\r\nexport function Sparkle({ productName, limit = 5, onResult, className }: SparkleProps) {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n setLoading(true);\r\n try {\r\n const res = await client.api.search(productName, limit);\r\n onResult?.(res.results);\r\n } catch (e) {\r\n console.error('[Huskel Sparkle]', e);\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <button className={`hsk-sparkle ${className ?? ''}`} onClick={handleClick} disabled={loading}>\r\n ✦ {loading ? 'Finding…' : 'Similar'}\r\n </button>\r\n </>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","S"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __spreadValues = (a, b) => {
|
|
10
|
+
for (var prop in b || (b = {}))
|
|
11
|
+
if (__hasOwnProp.call(b, prop))
|
|
12
|
+
__defNormalProp(a, prop, b[prop]);
|
|
13
|
+
if (__getOwnPropSymbols)
|
|
14
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
15
|
+
if (__propIsEnum.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
}
|
|
18
|
+
return a;
|
|
19
|
+
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
+
|
|
1
22
|
// src/api.ts
|
|
2
23
|
var MAX_RETRIES = 3;
|
|
3
24
|
var RETRY_DELAYS = [500, 1e3, 2e3];
|
|
@@ -361,8 +382,25 @@ function useIngest() {
|
|
|
361
382
|
return { ingest, ingestBatch, loading, error };
|
|
362
383
|
}
|
|
363
384
|
|
|
385
|
+
// src/hooks/usePageIngest.ts
|
|
386
|
+
import { useEffect as useEffect2, useRef as useRef4 } from "react";
|
|
387
|
+
function usePageIngest(product) {
|
|
388
|
+
var _a;
|
|
389
|
+
const ingestedRef = useRef4(null);
|
|
390
|
+
useEffect2(() => {
|
|
391
|
+
if (!product) return;
|
|
392
|
+
const url = product.url || (typeof window !== "undefined" ? window.location.href : "");
|
|
393
|
+
if (ingestedRef.current === url) return;
|
|
394
|
+
ingestedRef.current = url;
|
|
395
|
+
try {
|
|
396
|
+
getHuskelClient().queueIngest(__spreadProps(__spreadValues({}, product), { url }));
|
|
397
|
+
} catch (e) {
|
|
398
|
+
}
|
|
399
|
+
}, [(_a = product == null ? void 0 : product.url) != null ? _a : product == null ? void 0 : product.name]);
|
|
400
|
+
}
|
|
401
|
+
|
|
364
402
|
// src/components/SearchBar.tsx
|
|
365
|
-
import { useState as useState3, useEffect as
|
|
403
|
+
import { useState as useState3, useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
366
404
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
367
405
|
var S = `
|
|
368
406
|
.hsk-wrap{position:relative;width:100%;font-family:inherit}
|
|
@@ -389,9 +427,9 @@ function SearchBar({
|
|
|
389
427
|
const [query, setQuery] = useState3("");
|
|
390
428
|
const [open, setOpen] = useState3(false);
|
|
391
429
|
const { results, loading, search, clear } = useSearch();
|
|
392
|
-
const timer =
|
|
393
|
-
const wrap =
|
|
394
|
-
|
|
430
|
+
const timer = useRef5();
|
|
431
|
+
const wrap = useRef5(null);
|
|
432
|
+
useEffect3(() => {
|
|
395
433
|
clearTimeout(timer.current);
|
|
396
434
|
if (!query.trim()) {
|
|
397
435
|
clear();
|
|
@@ -404,7 +442,7 @@ function SearchBar({
|
|
|
404
442
|
}, debounceMs);
|
|
405
443
|
return () => clearTimeout(timer.current);
|
|
406
444
|
}, [query, search, clear, limit, debounceMs]);
|
|
407
|
-
|
|
445
|
+
useEffect3(() => {
|
|
408
446
|
const handler = (e) => {
|
|
409
447
|
if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
|
|
410
448
|
};
|
|
@@ -498,5 +536,7 @@ export {
|
|
|
498
536
|
initHuskel,
|
|
499
537
|
useHuskel,
|
|
500
538
|
useIngest,
|
|
539
|
+
usePageIngest,
|
|
501
540
|
useSearch
|
|
502
541
|
};
|
|
542
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api.ts","../src/client.ts","../src/hooks/useHuskel.ts","../src/hooks/useSearch.ts","../src/components/HuskelProvider.tsx","../src/hooks/useIngest.ts","../src/hooks/usePageIngest.ts","../src/components/SearchBar.tsx","../src/components/Sparkle.tsx"],"sourcesContent":["import { Product, SearchResponse, IngestResponse, HuskelError } from './types';\r\n\r\nconst MAX_RETRIES = 3;\r\nconst RETRY_DELAYS = [500, 1000, 2000]; // ms\r\n\r\nfunction log(level: 'info' | 'warn' | 'error', msg: string, data?: unknown) {\r\n const prefix = '[Huskel]';\r\n if (level === 'error') console.error(prefix, msg, data ?? '');\r\n else if (level === 'warn') console.warn(prefix, msg, data ?? '');\r\n else console.log(prefix, msg, data ?? '');\r\n}\r\n\r\nasync function sleep(ms: number) {\r\n return new Promise(r => setTimeout(r, ms));\r\n}\r\n\r\nexport class HuskelAPI {\r\n constructor(\r\n private apiUrl: string,\r\n private siteId: string,\r\n private apiToken: string\r\n ) {}\r\n\r\n private async post<T>(path: string, body: unknown, attempt = 0): Promise<T> {\r\n const url = `${this.apiUrl}${path}`;\r\n\r\n try {\r\n const res = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-Huskel-Token': this.apiToken,\r\n 'X-Huskel-Site': this.siteId,\r\n },\r\n body: JSON.stringify(body),\r\n });\r\n\r\n if (!res.ok) {\r\n const text = await res.text();\r\n const err: HuskelError = { status: res.status, message: text };\r\n\r\n // Don't retry 4xx — developer errors\r\n if (res.status >= 400 && res.status < 500) {\r\n log('error', `${path} failed [${res.status}]`, text);\r\n throw err;\r\n }\r\n\r\n // Retry 5xx\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n\r\n log('error', `${path} failed after ${MAX_RETRIES} attempts`, err);\r\n throw err;\r\n }\r\n\r\n return res.json();\r\n } catch (e) {\r\n // Network error (offline, DNS, etc.)\r\n if ((e as HuskelError).status === undefined) {\r\n if (attempt < MAX_RETRIES - 1) {\r\n log('warn', `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);\r\n await sleep(RETRY_DELAYS[attempt]);\r\n return this.post(path, body, attempt + 1);\r\n }\r\n log('error', `${path} unreachable after ${MAX_RETRIES} attempts`);\r\n }\r\n throw e;\r\n }\r\n }\r\n\r\n async ingest(product: Product): Promise<IngestResponse> {\r\n log('info', 'ingesting product', product.name);\r\n return this.post('/ingest', { siteId: this.siteId, product });\r\n }\r\n\r\n async ingestBatch(products: Product[]): Promise<IngestResponse> {\r\n log('info', `ingesting batch of ${products.length} products`);\r\n return this.post('/ingest/batch', { siteId: this.siteId, products });\r\n }\r\n\r\n async search(query: string, limit = 10): Promise<SearchResponse> {\r\n log('info', 'search query', query);\r\n return this.post('/search', { query, siteId: this.siteId, limit });\r\n }\r\n}\r\n","import { HuskelConfig, Product, RawProductInput } from './types';\r\nimport { HuskelAPI } from './api';\r\n\r\nfunction getEnvVar(key: string): string | undefined {\r\n if (typeof globalThis !== 'undefined') {\r\n const g = globalThis as any;\r\n if (g.process && g.process.env) {\r\n return g.process.env[key];\r\n }\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction mapRawProduct(input: RawProductInput): Product | null {\r\n const name = input.name || input.title || input.productName || '';\r\n \r\n let price = '';\r\n let priceNumeric: number | undefined = undefined;\r\n\r\n if (input.price !== undefined) {\r\n if (typeof input.price === 'number') {\r\n priceNumeric = input.price;\r\n price = String(input.price);\r\n } else {\r\n price = input.price;\r\n const num = parseFloat(input.price.replace(/[^0-9.]/g, ''));\r\n priceNumeric = isNaN(num) ? undefined : num;\r\n }\r\n }\r\n if (input.priceNumeric !== undefined) {\r\n priceNumeric = input.priceNumeric;\r\n }\r\n\r\n let url = input.url || '';\r\n if (!url && typeof window !== 'undefined') {\r\n url = window.location.href;\r\n }\r\n\r\n let slug = input.slug || input.id || input.productId || '';\r\n if (!slug && url) {\r\n slug = url.split('/').filter(Boolean).pop() || '';\r\n }\r\n if (!slug && name) {\r\n slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');\r\n }\r\n\r\n let images: string[] = [];\r\n if (input.images) {\r\n images = input.images;\r\n } else if (input.image) {\r\n images = [input.image];\r\n } else if (input.thumbnail) {\r\n images = [input.thumbnail];\r\n }\r\n\r\n if (!name) {\r\n console.warn('[Huskel] Validation warning: Product name/title is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!price) {\r\n console.warn('[Huskel] Validation warning: Product price is missing. Skipping:', input);\r\n return null;\r\n }\r\n if (!url) {\r\n console.warn('[Huskel] Validation warning: Product URL is missing. Skipping:', input);\r\n return null;\r\n }\r\n\r\n return {\r\n name,\r\n price,\r\n url,\r\n brand: input.brand,\r\n description: input.description,\r\n originalPrice: input.originalPrice,\r\n discount: input.discount,\r\n currency: input.currency ?? 'KES',\r\n stock: input.stock,\r\n availability: input.availability,\r\n rating: input.rating,\r\n reviewCount: input.reviewCount,\r\n category: input.category,\r\n subCategory: input.subCategory,\r\n tags: input.tags,\r\n images: images.length > 0 ? images : undefined,\r\n specs: input.specs,\r\n priceNumeric,\r\n slug,\r\n };\r\n}\r\n\r\nexport class HuskelClient {\r\n readonly api: HuskelAPI;\r\n private ingestQueue: Product[] = [];\r\n private ingestTimer: ReturnType<typeof setTimeout> | null = null;\r\n private ingestedUrls = new Set<string>();\r\n private onlineHandler: (() => void) | null = null;\r\n\r\n constructor(config: HuskelConfig) {\r\n const siteId = config.siteId || getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID') || '';\r\n const apiUrl = config.apiUrl || getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL') || '';\r\n const apiToken = config.apiToken || getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN') || '';\r\n\r\n // Runtime validation — fail loudly so misconfiguration is never silent\r\n if (!siteId) console.error('[Huskel] Missing siteId. Set it via <HuskelProvider siteId=\"...\"> or NEXT_PUBLIC_HUSKEL_SITE_ID.');\r\n if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl=\"...\"> or NEXT_PUBLIC_HUSKEL_API_URL.');\r\n if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken=\"...\"> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');\r\n\r\n this.api = new HuskelAPI(apiUrl, siteId, apiToken);\r\n instance = this;\r\n\r\n if (typeof window !== 'undefined') {\r\n this.onlineHandler = () => {\r\n console.log('[Huskel] Connectivity restored, flushing queued ingestions.');\r\n this.flushQueue();\r\n };\r\n window.addEventListener('online', this.onlineHandler);\r\n }\r\n }\r\n\r\n destroy() {\r\n if (typeof window !== 'undefined' && this.onlineHandler) {\r\n window.removeEventListener('online', this.onlineHandler);\r\n this.onlineHandler = null;\r\n }\r\n if (this.ingestTimer) {\r\n clearTimeout(this.ingestTimer);\r\n this.ingestTimer = null;\r\n }\r\n if (instance === this) instance = null;\r\n }\r\n\r\n async queueIngest(rawProduct: RawProductInput): Promise<void> {\r\n const product = mapRawProduct(rawProduct);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n\r\n this.ingestQueue.push(product);\r\n this.scheduleFlush();\r\n }\r\n\r\n async queueIngestBatch(rawProducts: RawProductInput[]): Promise<void> {\r\n rawProducts.forEach(p => {\r\n const product = mapRawProduct(p);\r\n if (!product) return;\r\n\r\n if (this.ingestedUrls.has(product.url)) {\r\n return;\r\n }\r\n this.ingestedUrls.add(product.url);\r\n this.ingestQueue.push(product);\r\n });\r\n\r\n if (this.ingestQueue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.ingestTimer) return;\r\n this.ingestTimer = setTimeout(() => {\r\n this.flushQueue();\r\n }, 300);\r\n }\r\n\r\n private async flushQueue() {\r\n this.ingestTimer = null;\r\n if (this.ingestQueue.length === 0) return;\r\n\r\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\r\n console.warn('[Huskel] Browser offline. Postponing ingestion.');\r\n return;\r\n }\r\n\r\n const batch = [...this.ingestQueue];\r\n this.ingestQueue = [];\r\n\r\n try {\r\n await this.api.ingestBatch(batch);\r\n } catch (e: any) {\r\n if (e.status && e.status >= 400 && e.status < 500) {\r\n console.error('[Huskel] Ingestion discarded due to client error:', e.message);\r\n return;\r\n }\r\n\r\n // Re-queue and schedule another flush so items are not stuck forever\r\n console.warn('[Huskel] Ingestion failed. Re-queuing to retry.', e);\r\n this.ingestQueue = [...batch, ...this.ingestQueue];\r\n this.scheduleFlush();\r\n }\r\n }\r\n}\r\n\r\nlet instance: HuskelClient | null = null;\r\n\r\nexport function initHuskel(config: HuskelConfig): HuskelClient {\r\n instance = new HuskelClient(config);\r\n return instance;\r\n}\r\n\r\nexport function getHuskelClient(): HuskelClient {\r\n if (!instance) {\r\n const siteId = getEnvVar('NEXT_PUBLIC_HUSKEL_SITE_ID');\r\n const apiUrl = getEnvVar('NEXT_PUBLIC_HUSKEL_API_URL');\r\n const apiToken = getEnvVar('NEXT_PUBLIC_HUSKEL_API_TOKEN');\r\n\r\n if (siteId && apiUrl && apiToken) {\r\n instance = new HuskelClient({ siteId, apiUrl, apiToken });\r\n } else {\r\n throw new Error('[Huskel] Call initHuskel() or set NEXT_PUBLIC_HUSKEL_* environment variables before using the client.');\r\n }\r\n }\r\n return instance;\r\n}\r\n","import { useRef } from 'react';\r\nimport { HuskelConfig } from '../types';\r\nimport { HuskelClient, initHuskel } from '../client';\r\n\r\n/**\r\n * @deprecated Use <HuskelProvider> instead to avoid SSR issues.\r\n */\r\nexport function useHuskel(config: HuskelConfig): HuskelClient {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n console.warn('[Huskel] useHuskel() is deprecated. Please wrap your application in <HuskelProvider> instead.');\r\n clientRef.current = initHuskel(config);\r\n }\r\n\r\n return clientRef.current;\r\n}\r\n","import { useState, useCallback, useRef } from 'react';\r\nimport { SearchResult } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseSearchReturn {\r\n results: SearchResult[];\r\n loading: boolean;\r\n error: string | null;\r\n search: (query: string, limit?: number) => Promise<void>;\r\n clear: () => void;\r\n}\r\n\r\nexport function useSearch(): UseSearchReturn {\r\n const client = useHuskelContext();\r\n const [results, setResults] = useState<SearchResult[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const abortRef = useRef<AbortController | null>(null);\r\n\r\n const search = useCallback(async (query: string, limit = 10) => {\r\n if (!query.trim()) { setResults([]); return; }\r\n abortRef.current?.abort();\r\n abortRef.current = new AbortController();\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n const res = await client.api.search(query, limit);\r\n setResults(res.results ?? []);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Search failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const clear = useCallback(() => { setResults([]); setError(null); }, []);\r\n\r\n return { results, loading, error, search, clear };\r\n}\r\n","'use client';\r\n\r\nimport React, { createContext, useContext, useEffect, useRef } from 'react';\r\nimport { HuskelClient, getHuskelClient } from '../client';\r\nimport { HuskelConfig } from '../types';\r\n\r\nexport const HuskelContext = createContext<HuskelClient | null>(null);\r\n\r\ninterface HuskelProviderProps extends HuskelConfig {\r\n children: React.ReactNode;\r\n}\r\n\r\nexport function HuskelProvider({ siteId, apiUrl, apiToken, children }: HuskelProviderProps) {\r\n const clientRef = useRef<HuskelClient | null>(null);\r\n\r\n if (!clientRef.current) {\r\n clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken });\r\n }\r\n\r\n // Clean up the online listener and timers when the provider unmounts\r\n // (prevents leaks during hot module reload and React StrictMode double-mount)\r\n useEffect(() => {\r\n return () => {\r\n clientRef.current?.destroy();\r\n };\r\n }, []);\r\n\r\n return (\r\n <HuskelContext.Provider value={clientRef.current}>\r\n {children}\r\n </HuskelContext.Provider>\r\n );\r\n}\r\n\r\nexport function useHuskelContext(): HuskelClient {\r\n const context = useContext(HuskelContext);\r\n if (!context) {\r\n return getHuskelClient();\r\n }\r\n return context;\r\n}\r\n\r\n","import { useCallback, useState } from 'react';\r\nimport { RawProductInput } from '../types';\r\nimport { useHuskelContext } from '../components/HuskelProvider';\r\n\r\ninterface UseIngestReturn {\r\n ingest: (product: RawProductInput) => Promise<void>;\r\n ingestBatch: (products: RawProductInput[]) => Promise<void>;\r\n loading: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport function useIngest(): UseIngestReturn {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const ingest = useCallback(async (product: RawProductInput) => {\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngest(product);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n const ingestBatch = useCallback(async (products: RawProductInput[]) => {\r\n if (!products.length) return;\r\n setLoading(true);\r\n setError(null);\r\n try {\r\n await client.queueIngestBatch(products);\r\n } catch (e: unknown) {\r\n setError((e as Error).message ?? 'Batch ingest failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [client]);\r\n\r\n return { ingest, ingestBatch, loading, error };\r\n}\r\n","import { useEffect, useRef } from 'react';\nimport { RawProductInput } from '../types';\nimport { getHuskelClient } from '../client';\n\n/**\n * usePageIngest — drop this into any product page component.\n * The moment a customer's browser renders the page, the product is\n * automatically captured and queued for ingestion into the vector index.\n *\n * No configuration needed beyond <HuskelProvider> in your layout.\n *\n * @example\n * // Product detail page — Next.js or React\n * export function ProductPage({ product }) {\n * usePageIngest({\n * name: product.title,\n * price: product.price,\n * url: window.location.href,\n * images: [product.thumbnail],\n * category: product.category,\n * });\n * return <div>...</div>;\n * }\n */\nexport function usePageIngest(product: RawProductInput | null | undefined): void {\n // Use url as the stable key — avoids re-ingesting on unrelated re-renders\n const ingestedRef = useRef<string | null>(null);\n\n useEffect(() => {\n if (!product) return;\n\n // Resolve URL — falls back to window.location if not provided\n const url =\n product.url ||\n (typeof window !== 'undefined' ? window.location.href : '');\n\n // Guard: only ingest once per URL per component lifecycle\n if (ingestedRef.current === url) return;\n ingestedRef.current = url;\n\n try {\n getHuskelClient().queueIngest({ ...product, url });\n } catch {\n // Client not initialised — silently skip\n }\n }, [product?.url ?? product?.name]);\n}\n","import React, { useState, useEffect, useRef } from 'react';\r\nimport { useSearch } from '../hooks/useSearch';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SearchBarProps {\r\n placeholder?: string;\r\n limit?: number;\r\n debounceMs?: number;\r\n onSelect?: (result: SearchResult) => void;\r\n className?: string;\r\n inputClassName?: string;\r\n dropdownClassName?: string;\r\n renderResult?: (result: SearchResult) => React.ReactNode;\r\n}\r\n\r\nconst S = `\r\n .hsk-wrap{position:relative;width:100%;font-family:inherit}\r\n .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}\r\n .hsk-input:focus{border-color:#f47c3c}\r\n .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}\r\n .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}\r\n .hsk-item:hover{background:#faf5f1}\r\n .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}\r\n .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}\r\n .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}\r\n`;\r\n\r\nexport function SearchBar({\r\n placeholder = 'Search for what you want — how you want',\r\n limit = 10,\r\n debounceMs = 300,\r\n onSelect,\r\n className,\r\n inputClassName,\r\n dropdownClassName,\r\n renderResult,\r\n}: SearchBarProps) {\r\n const [query, setQuery] = useState('');\r\n const [open, setOpen] = useState(false);\r\n const { results, loading, search, clear } = useSearch();\r\n const timer = useRef<ReturnType<typeof setTimeout>>();\r\n const wrap = useRef<HTMLDivElement>(null);\r\n\r\n useEffect(() => {\r\n clearTimeout(timer.current);\r\n if (!query.trim()) { clear(); setOpen(false); return; }\r\n timer.current = setTimeout(() => { search(query, limit); setOpen(true); }, debounceMs);\r\n return () => clearTimeout(timer.current);\r\n }, [query, search, clear, limit, debounceMs]);\r\n\r\n useEffect(() => {\r\n const handler = (e: MouseEvent) => {\r\n if (wrap.current && !wrap.current.contains(e.target as Node)) setOpen(false);\r\n };\r\n document.addEventListener('mousedown', handler);\r\n return () => document.removeEventListener('mousedown', handler);\r\n }, []);\r\n\r\n const handleSelect = (r: SearchResult) => {\r\n setOpen(false);\r\n setQuery(r.product.name);\r\n onSelect?.(r);\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <div className={`hsk-wrap ${className ?? ''}`} ref={wrap}>\r\n <input\r\n className={`hsk-input ${inputClassName ?? ''}`}\r\n type=\"text\"\r\n value={query}\r\n placeholder={placeholder}\r\n onChange={e => setQuery(e.target.value)}\r\n onFocus={() => results.length && setOpen(true)}\r\n />\r\n {open && (\r\n <div className={`hsk-drop ${dropdownClassName ?? ''}`}>\r\n {loading && <div className=\"hsk-msg\">Searching…</div>}\r\n {!loading && results.length === 0 && <div className=\"hsk-msg\">No results for \"{query}\"</div>}\r\n {results.map(r =>\r\n renderResult ? (\r\n <div key={r.id} onClick={() => handleSelect(r)}>{renderResult(r)}</div>\r\n ) : (\r\n <div key={r.id} className=\"hsk-item\" onClick={() => handleSelect(r)}>\r\n {r.product.images?.[0] && <img src={r.product.images[0]} alt={r.product.name} />}\r\n <div>\r\n <div className=\"hsk-item-name\">{r.product.name}</div>\r\n <div className=\"hsk-item-price\">{r.product.currency ?? 'KES'} {r.product.price}</div>\r\n </div>\r\n </div>\r\n )\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n </>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { useHuskelContext } from './HuskelProvider';\r\nimport { SearchResult } from '../types';\r\n\r\ninterface SparkleProps {\r\n productName: string;\r\n limit?: number;\r\n onResult?: (results: SearchResult[]) => void;\r\n className?: string;\r\n}\r\n\r\nconst S = `\r\n .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}\r\n .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}\r\n .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}\r\n`;\r\n\r\nexport function Sparkle({ productName, limit = 5, onResult, className }: SparkleProps) {\r\n const client = useHuskelContext();\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n setLoading(true);\r\n try {\r\n const res = await client.api.search(productName, limit);\r\n onResult?.(res.results);\r\n } catch (e) {\r\n console.error('[Huskel Sparkle]', e);\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <>\r\n <style>{S}</style>\r\n <button className={`hsk-sparkle ${className ?? ''}`} onClick={handleClick} disabled={loading}>\r\n ✦ {loading ? 'Finding…' : 'Similar'}\r\n </button>\r\n </>\r\n );\r\n}\r\n"],"mappings":"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","names":["useRef","useRef","useRef","useRef","useCallback","useState","useState","useCallback","useEffect","useRef","useRef","useEffect","useState","useEffect","useRef","jsx","useState","useRef","useEffect","useState","Fragment","jsx","jsxs","S","useState"]}
|
package/package.json
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@huskel/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Huskel AI-powered search SDK for SPAs",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.esm.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
"react-dom": {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@huskel/sdk",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Huskel AI-powered search SDK for SPAs",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"dev": "tsup --watch"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"react": ">=17",
|
|
15
|
+
"react-dom": ">=17"
|
|
16
|
+
},
|
|
17
|
+
"peerDependenciesMeta": {
|
|
18
|
+
"react": { "optional": true },
|
|
19
|
+
"react-dom": { "optional": true }
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^18.0.0",
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"typescript": "^5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"pnpm": {
|
|
27
|
+
"onlyBuiltDependencies": [
|
|
28
|
+
"esbuild"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|