@huskel/sdk 0.1.0 → 0.2.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/dist/index.mjs CHANGED
@@ -1,180 +1,79 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
- var __hasOwnProp = Object.prototype.hasOwnProperty;
4
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
- var __spreadValues = (a, b) => {
7
- for (var prop in b || (b = {}))
8
- if (__hasOwnProp.call(b, prop))
9
- __defNormalProp(a, prop, b[prop]);
10
- if (__getOwnPropSymbols)
11
- for (var prop of __getOwnPropSymbols(b)) {
12
- if (__propIsEnum.call(b, prop))
13
- __defNormalProp(a, prop, b[prop]);
14
- }
15
- return a;
16
- };
17
-
18
1
  // src/api.ts
2
+ var MAX_RETRIES = 3;
3
+ var RETRY_DELAYS = [500, 1e3, 2e3];
4
+ function log(level, msg, data) {
5
+ const prefix = "[Huskel]";
6
+ if (level === "error") console.error(prefix, msg, data != null ? data : "");
7
+ else if (level === "warn") console.warn(prefix, msg, data != null ? data : "");
8
+ else console.log(prefix, msg, data != null ? data : "");
9
+ }
10
+ async function sleep(ms) {
11
+ return new Promise((r) => setTimeout(r, ms));
12
+ }
19
13
  var HuskelAPI = class {
20
- constructor(apiUrl, siteId) {
14
+ constructor(apiUrl, siteId, apiToken) {
21
15
  this.apiUrl = apiUrl;
22
16
  this.siteId = siteId;
17
+ this.apiToken = apiToken;
23
18
  }
24
- async post(path, body) {
25
- const res = await fetch(`${this.apiUrl}${path}`, {
26
- method: "POST",
27
- headers: { "Content-Type": "application/json" },
28
- body: JSON.stringify(body)
29
- });
30
- if (!res.ok) {
31
- const err = await res.text();
32
- throw new Error(`Huskel API ${path} [${res.status}]: ${err}`);
19
+ async post(path, body, attempt = 0) {
20
+ const url = `${this.apiUrl}${path}`;
21
+ try {
22
+ const res = await fetch(url, {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ "X-Huskel-Token": this.apiToken,
27
+ "X-Huskel-Site": this.siteId
28
+ },
29
+ body: JSON.stringify(body)
30
+ });
31
+ if (!res.ok) {
32
+ const text = await res.text();
33
+ const err = { status: res.status, message: text };
34
+ if (res.status >= 400 && res.status < 500) {
35
+ log("error", `${path} failed [${res.status}]`, text);
36
+ throw err;
37
+ }
38
+ if (attempt < MAX_RETRIES - 1) {
39
+ log("warn", `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);
40
+ await sleep(RETRY_DELAYS[attempt]);
41
+ return this.post(path, body, attempt + 1);
42
+ }
43
+ log("error", `${path} failed after ${MAX_RETRIES} attempts`, err);
44
+ throw err;
45
+ }
46
+ return res.json();
47
+ } catch (e) {
48
+ if (e.status === void 0) {
49
+ if (attempt < MAX_RETRIES - 1) {
50
+ log("warn", `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);
51
+ await sleep(RETRY_DELAYS[attempt]);
52
+ return this.post(path, body, attempt + 1);
53
+ }
54
+ log("error", `${path} unreachable after ${MAX_RETRIES} attempts`);
55
+ }
56
+ throw e;
33
57
  }
34
- return res.json();
35
58
  }
36
59
  async ingest(product) {
60
+ log("info", "ingesting product", product.name);
37
61
  return this.post("/ingest", { siteId: this.siteId, product });
38
62
  }
39
63
  async ingestBatch(products) {
64
+ log("info", `ingesting batch of ${products.length} products`);
40
65
  return this.post("/ingest/batch", { siteId: this.siteId, products });
41
66
  }
42
67
  async search(query, limit = 10) {
68
+ log("info", "search query", query);
43
69
  return this.post("/search", { query, siteId: this.siteId, limit });
44
70
  }
45
- async pushConfig(config) {
46
- return this.post("/sites/config", __spreadValues({ siteId: this.siteId }, config));
47
- }
48
- };
49
-
50
- // src/extractor.ts
51
- function getText(el, selector) {
52
- var _a, _b;
53
- if (!selector) return "";
54
- const found = el.querySelector(selector);
55
- return (_b = (_a = found == null ? void 0 : found.textContent) == null ? void 0 : _a.trim()) != null ? _b : "";
56
- }
57
- function getAttr(el, selector, attr) {
58
- var _a, _b;
59
- if (!selector) return "";
60
- const found = el.querySelector(selector);
61
- return (_b = (_a = found == null ? void 0 : found.getAttribute(attr)) == null ? void 0 : _a.trim()) != null ? _b : "";
62
- }
63
- function getAll(el, selector) {
64
- if (!selector) return [];
65
- return Array.from(el.querySelectorAll(selector)).map((n) => {
66
- var _a;
67
- return n.getAttribute("src") || n.getAttribute("href") || ((_a = n.textContent) == null ? void 0 : _a.trim()) || "";
68
- }).filter(Boolean);
69
- }
70
- function parsePrice(raw) {
71
- const num = parseFloat(raw.replace(/[^0-9.]/g, ""));
72
- return isNaN(num) ? void 0 : num;
73
- }
74
- function extractProducts(config) {
75
- const containers = document.querySelectorAll(config.selectorContainer);
76
- if (!containers.length) return [];
77
- const products = [];
78
- containers.forEach((el) => {
79
- var _a;
80
- const name = getText(el, config.selectorName);
81
- const price = getText(el, config.selectorPrice);
82
- const rawUrl = getAttr(el, config.selectorUrl, "href") || el.href || window.location.href;
83
- const url = rawUrl.startsWith("http") ? rawUrl : `${window.location.origin}${rawUrl}`;
84
- if (!name || !price || !url) return;
85
- const product = {
86
- name,
87
- price,
88
- url,
89
- brand: getText(el, config.selectorBrand) || void 0,
90
- description: getText(el, config.selectorDescription) || void 0,
91
- originalPrice: getText(el, config.selectorOriginalPrice) || void 0,
92
- discount: getText(el, config.selectorDiscount) || void 0,
93
- currency: (_a = config.currency) != null ? _a : "KES",
94
- availability: getText(el, config.selectorAvailability) || void 0,
95
- rating: getText(el, config.selectorRating) || void 0,
96
- category: getText(el, config.selectorCategory) || void 0,
97
- images: config.selectorImage ? getAll(el, config.selectorImage) : void 0,
98
- priceNumeric: parsePrice(price),
99
- slug: url.split("/").filter(Boolean).pop()
100
- };
101
- products.push(product);
102
- });
103
- return products;
104
- }
105
-
106
- // src/observer.ts
107
- var RouteObserver = class {
108
- constructor() {
109
- this.callbacks = [];
110
- this.current = window.location.href;
111
- this.patchHistory();
112
- window.addEventListener("popstate", () => this.notify());
113
- }
114
- patchHistory() {
115
- const notify = () => this.notify();
116
- const wrap = (original) => function(...args) {
117
- original.apply(this, args);
118
- notify();
119
- };
120
- history.pushState = wrap(history.pushState);
121
- history.replaceState = wrap(history.replaceState);
122
- }
123
- notify() {
124
- const next = window.location.href;
125
- if (next !== this.current) {
126
- this.current = next;
127
- this.callbacks.forEach((cb) => cb(next));
128
- }
129
- }
130
- onChange(cb) {
131
- this.callbacks.push(cb);
132
- return () => {
133
- this.callbacks = this.callbacks.filter((fn) => fn !== cb);
134
- };
135
- }
136
- destroy() {
137
- this.callbacks = [];
138
- }
139
71
  };
140
72
 
141
73
  // src/client.ts
142
74
  var HuskelClient = class {
143
75
  constructor(config) {
144
- this.config = config;
145
- this.api = new HuskelAPI(config.apiUrl, config.siteId);
146
- }
147
- /** Push selectors config to backend once (idempotent) */
148
- async configure() {
149
- await this.api.pushConfig(this.config.selectors);
150
- }
151
- /** Scrape current DOM and ingest all products on the page */
152
- async ingestCurrentPage() {
153
- const products = extractProducts(__spreadValues({
154
- siteId: this.config.siteId
155
- }, this.config.selectors));
156
- if (products.length === 0) return [];
157
- await this.api.ingestBatch(products);
158
- return products;
159
- }
160
- /** Start watching route changes and auto-ingesting */
161
- start() {
162
- if (!this.config.autoIngest) return;
163
- this.observer = new RouteObserver();
164
- this.unsubscribe = this.observer.onChange(() => {
165
- var _a;
166
- clearTimeout(this.debounceTimer);
167
- this.debounceTimer = setTimeout(() => {
168
- this.ingestCurrentPage().catch(console.error);
169
- }, (_a = this.config.debounceMs) != null ? _a : 600);
170
- });
171
- this.ingestCurrentPage().catch(console.error);
172
- }
173
- stop() {
174
- var _a, _b;
175
- (_a = this.unsubscribe) == null ? void 0 : _a.call(this);
176
- (_b = this.observer) == null ? void 0 : _b.destroy();
177
- clearTimeout(this.debounceTimer);
76
+ this.api = new HuskelAPI(config.apiUrl, config.siteId, config.apiToken);
178
77
  }
179
78
  };
180
79
  var instance = null;
@@ -187,15 +86,25 @@ function getHuskelClient() {
187
86
  return instance;
188
87
  }
189
88
 
89
+ // src/hooks/useHuskel.ts
90
+ import { useRef } from "react";
91
+ function useHuskel(config) {
92
+ const clientRef = useRef(null);
93
+ if (!clientRef.current) {
94
+ clientRef.current = initHuskel(config);
95
+ }
96
+ return clientRef.current;
97
+ }
98
+
190
99
  // src/hooks/useSearch.ts
191
- import { useState, useCallback, useRef } from "react";
100
+ import { useState, useCallback, useRef as useRef2 } from "react";
192
101
  function useSearch() {
193
102
  const [results, setResults] = useState([]);
194
103
  const [loading, setLoading] = useState(false);
195
104
  const [error, setError] = useState(null);
196
- const abortRef = useRef(null);
105
+ const abortRef = useRef2(null);
197
106
  const search = useCallback(async (query, limit = 10) => {
198
- var _a, _b;
107
+ var _a, _b, _c;
199
108
  if (!query.trim()) {
200
109
  setResults([]);
201
110
  return;
@@ -205,13 +114,10 @@ function useSearch() {
205
114
  setLoading(true);
206
115
  setError(null);
207
116
  try {
208
- const client = getHuskelClient();
209
- const res = await client.api.search(query, limit);
117
+ const res = await getHuskelClient().api.search(query, limit);
210
118
  setResults((_b = res.results) != null ? _b : []);
211
119
  } catch (e) {
212
- if (e.name !== "AbortError") {
213
- setError(e.message);
214
- }
120
+ setError((_c = e.message) != null ? _c : "Search failed");
215
121
  } finally {
216
122
  setLoading(false);
217
123
  }
@@ -223,51 +129,53 @@ function useSearch() {
223
129
  return { results, loading, error, search, clear };
224
130
  }
225
131
 
226
- // src/hooks/useHuskel.ts
227
- import { useEffect, useRef as useRef2 } from "react";
228
- function useHuskel(options) {
229
- const clientRef = useRef2(null);
230
- useEffect(() => {
132
+ // src/hooks/useIngest.ts
133
+ import { useCallback as useCallback2, useState as useState2 } from "react";
134
+ function useIngest() {
135
+ const [loading, setLoading] = useState2(false);
136
+ const [error, setError] = useState2(null);
137
+ const ingest = useCallback2(async (product) => {
231
138
  var _a;
232
- const client = initHuskel(options);
233
- clientRef.current = client;
234
- client.configure().then(() => {
235
- client.start();
236
- }).catch((_a = options.onError) != null ? _a : console.error);
237
- return () => client.stop();
139
+ setLoading(true);
140
+ setError(null);
141
+ try {
142
+ await getHuskelClient().api.ingest(product);
143
+ } catch (e) {
144
+ setError((_a = e.message) != null ? _a : "Ingest failed");
145
+ } finally {
146
+ setLoading(false);
147
+ }
238
148
  }, []);
239
- return clientRef.current;
149
+ const ingestBatch = useCallback2(async (products) => {
150
+ var _a;
151
+ if (!products.length) return;
152
+ setLoading(true);
153
+ setError(null);
154
+ try {
155
+ await getHuskelClient().api.ingestBatch(products);
156
+ } catch (e) {
157
+ setError((_a = e.message) != null ? _a : "Batch ingest failed");
158
+ } finally {
159
+ setLoading(false);
160
+ }
161
+ }, []);
162
+ return { ingest, ingestBatch, loading, error };
240
163
  }
241
164
 
242
165
  // src/components/SearchBar.tsx
243
- import { useState as useState2, useEffect as useEffect2, useRef as useRef3 } from "react";
166
+ import { useState as useState3, useEffect as useEffect2, useRef as useRef3 } from "react";
244
167
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
245
- var DEFAULT_STYLES = `
246
- .huskel-search-wrap { position: relative; width: 100%; font-family: inherit; }
247
- .huskel-search-input {
248
- width: 100%; padding: 10px 16px; font-size: 15px;
249
- border: 1.5px solid #e2e2e2; border-radius: 8px;
250
- outline: none; box-sizing: border-box; background: #fff;
251
- transition: border-color 0.2s;
252
- }
253
- .huskel-search-input:focus { border-color: #f47c3c; }
254
- .huskel-search-dropdown {
255
- position: absolute; top: calc(100% + 6px); left: 0; right: 0;
256
- background: #fff; border: 1px solid #e2e2e2; border-radius: 8px;
257
- box-shadow: 0 8px 24px rgba(0,0,0,0.10); z-index: 9999;
258
- max-height: 360px; overflow-y: auto;
259
- }
260
- .huskel-search-item {
261
- display: flex; align-items: center; gap: 12px;
262
- padding: 10px 14px; cursor: pointer; transition: background 0.15s;
263
- }
264
- .huskel-search-item:hover { background: #faf5f1; }
265
- .huskel-search-item img { width: 40px; height: 40px; object-fit: cover; border-radius: 4px; }
266
- .huskel-search-item-info { flex: 1; min-width: 0; }
267
- .huskel-search-item-name { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
268
- .huskel-search-item-price { font-size: 13px; color: #f47c3c; margin-top: 2px; }
269
- .huskel-search-empty { padding: 16px; text-align: center; color: #888; font-size: 14px; }
270
- .huskel-search-loading { padding: 16px; text-align: center; color: #aaa; font-size: 13px; }
168
+ var S = `
169
+ .hsk-wrap{position:relative;width:100%;font-family:inherit}
170
+ .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}
171
+ .hsk-input:focus{border-color:#f47c3c}
172
+ .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}
173
+ .hsk-item{display:flex;align-items:center;gap:12px;padding:10px 14px;cursor:pointer;transition:background .15s}
174
+ .hsk-item:hover{background:#faf5f1}
175
+ .hsk-item img{width:40px;height:40px;object-fit:cover;border-radius:4px}
176
+ .hsk-item-name{font-size:14px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
177
+ .hsk-item-price{font-size:13px;color:#f47c3c;margin-top:2px}
178
+ .hsk-msg{padding:16px;text-align:center;font-size:14px;color:#888}
271
179
  `;
272
180
  function SearchBar({
273
181
  placeholder = "Search for what you want \u2014 how you want",
@@ -275,31 +183,31 @@ function SearchBar({
275
183
  debounceMs = 300,
276
184
  onSelect,
277
185
  className,
186
+ inputClassName,
187
+ dropdownClassName,
278
188
  renderResult
279
189
  }) {
280
- const [query, setQuery] = useState2("");
281
- const [open, setOpen] = useState2(false);
190
+ const [query, setQuery] = useState3("");
191
+ const [open, setOpen] = useState3(false);
282
192
  const { results, loading, search, clear } = useSearch();
283
- const timerRef = useRef3();
284
- const wrapRef = useRef3(null);
193
+ const timer = useRef3();
194
+ const wrap = useRef3(null);
285
195
  useEffect2(() => {
286
- clearTimeout(timerRef.current);
196
+ clearTimeout(timer.current);
287
197
  if (!query.trim()) {
288
198
  clear();
289
199
  setOpen(false);
290
200
  return;
291
201
  }
292
- timerRef.current = setTimeout(() => {
202
+ timer.current = setTimeout(() => {
293
203
  search(query, limit);
294
204
  setOpen(true);
295
205
  }, debounceMs);
296
- return () => clearTimeout(timerRef.current);
206
+ return () => clearTimeout(timer.current);
297
207
  }, [query]);
298
208
  useEffect2(() => {
299
209
  const handler = (e) => {
300
- if (wrapRef.current && !wrapRef.current.contains(e.target)) {
301
- setOpen(false);
302
- }
210
+ if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
303
211
  };
304
212
  document.addEventListener("mousedown", handler);
305
213
  return () => document.removeEventListener("mousedown", handler);
@@ -310,12 +218,12 @@ function SearchBar({
310
218
  onSelect == null ? void 0 : onSelect(r);
311
219
  };
312
220
  return /* @__PURE__ */ jsxs(Fragment, { children: [
313
- /* @__PURE__ */ jsx("style", { children: DEFAULT_STYLES }),
314
- /* @__PURE__ */ jsxs("div", { className: `huskel-search-wrap ${className != null ? className : ""}`, ref: wrapRef, children: [
221
+ /* @__PURE__ */ jsx("style", { children: S }),
222
+ /* @__PURE__ */ jsxs("div", { className: `hsk-wrap ${className != null ? className : ""}`, ref: wrap, children: [
315
223
  /* @__PURE__ */ jsx(
316
224
  "input",
317
225
  {
318
- className: "huskel-search-input",
226
+ className: `hsk-input ${inputClassName != null ? inputClassName : ""}`,
319
227
  type: "text",
320
228
  value: query,
321
229
  placeholder,
@@ -323,9 +231,9 @@ function SearchBar({
323
231
  onFocus: () => results.length && setOpen(true)
324
232
  }
325
233
  ),
326
- open && /* @__PURE__ */ jsxs("div", { className: "huskel-search-dropdown", children: [
327
- loading && /* @__PURE__ */ jsx("div", { className: "huskel-search-loading", children: "Searching\u2026" }),
328
- !loading && results.length === 0 && /* @__PURE__ */ jsxs("div", { className: "huskel-search-empty", children: [
234
+ open && /* @__PURE__ */ jsxs("div", { className: `hsk-drop ${dropdownClassName != null ? dropdownClassName : ""}`, children: [
235
+ loading && /* @__PURE__ */ jsx("div", { className: "hsk-msg", children: "Searching\u2026" }),
236
+ !loading && results.length === 0 && /* @__PURE__ */ jsxs("div", { className: "hsk-msg", children: [
329
237
  'No results for "',
330
238
  query,
331
239
  '"'
@@ -333,11 +241,11 @@ function SearchBar({
333
241
  results.map(
334
242
  (r) => {
335
243
  var _a, _b;
336
- return renderResult ? /* @__PURE__ */ jsx("div", { onClick: () => handleSelect(r), children: renderResult(r) }, r.id) : /* @__PURE__ */ jsxs("div", { className: "huskel-search-item", onClick: () => handleSelect(r), children: [
244
+ return renderResult ? /* @__PURE__ */ jsx("div", { onClick: () => handleSelect(r), children: renderResult(r) }, r.id) : /* @__PURE__ */ jsxs("div", { className: "hsk-item", onClick: () => handleSelect(r), children: [
337
245
  ((_a = r.product.images) == null ? void 0 : _a[0]) && /* @__PURE__ */ jsx("img", { src: r.product.images[0], alt: r.product.name }),
338
- /* @__PURE__ */ jsxs("div", { className: "huskel-search-item-info", children: [
339
- /* @__PURE__ */ jsx("div", { className: "huskel-search-item-name", children: r.product.name }),
340
- /* @__PURE__ */ jsxs("div", { className: "huskel-search-item-price", children: [
246
+ /* @__PURE__ */ jsxs("div", { children: [
247
+ /* @__PURE__ */ jsx("div", { className: "hsk-item-name", children: r.product.name }),
248
+ /* @__PURE__ */ jsxs("div", { className: "hsk-item-price", children: [
341
249
  (_b = r.product.currency) != null ? _b : "KES",
342
250
  " ",
343
251
  r.product.price
@@ -352,27 +260,19 @@ function SearchBar({
352
260
  }
353
261
 
354
262
  // src/components/Sparkle.tsx
355
- import { useState as useState3 } from "react";
263
+ import { useState as useState4 } from "react";
356
264
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
357
- var STYLES = `
358
- .huskel-sparkle-btn {
359
- display: inline-flex; align-items: center; gap: 5px;
360
- padding: 4px 10px; font-size: 12px; font-weight: 600;
361
- background: #f47c3c; color: #fff; border: none;
362
- border-radius: 20px; cursor: pointer; letter-spacing: 0.02em;
363
- transition: opacity 0.2s, transform 0.15s;
364
- }
365
- .huskel-sparkle-btn:hover { opacity: 0.88; transform: scale(1.04); }
366
- .huskel-sparkle-btn:disabled { opacity: 0.5; cursor: not-allowed; }
367
- .huskel-sparkle-icon { font-size: 13px; }
265
+ var S2 = `
266
+ .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}
267
+ .hsk-sparkle:hover{opacity:.88;transform:scale(1.04)}
268
+ .hsk-sparkle:disabled{opacity:.5;cursor:not-allowed}
368
269
  `;
369
- function Sparkle({ productName, className, onResult }) {
370
- const [loading, setLoading] = useState3(false);
270
+ function Sparkle({ productName, limit = 5, onResult, className }) {
271
+ const [loading, setLoading] = useState4(false);
371
272
  const handleClick = async () => {
372
273
  setLoading(true);
373
274
  try {
374
- const client = getHuskelClient();
375
- const res = await client.api.search(productName, 5);
275
+ const res = await getHuskelClient().api.search(productName, limit);
376
276
  onResult == null ? void 0 : onResult(res.results);
377
277
  } catch (e) {
378
278
  console.error("[Huskel Sparkle]", e);
@@ -381,20 +281,11 @@ function Sparkle({ productName, className, onResult }) {
381
281
  }
382
282
  };
383
283
  return /* @__PURE__ */ jsxs2(Fragment2, { children: [
384
- /* @__PURE__ */ jsx2("style", { children: STYLES }),
385
- /* @__PURE__ */ jsxs2(
386
- "button",
387
- {
388
- className: `huskel-sparkle-btn ${className != null ? className : ""}`,
389
- onClick: handleClick,
390
- disabled: loading,
391
- title: "Find similar with AI",
392
- children: [
393
- /* @__PURE__ */ jsx2("span", { className: "huskel-sparkle-icon", children: "\u2726" }),
394
- loading ? "Finding\u2026" : "Similar"
395
- ]
396
- }
397
- )
284
+ /* @__PURE__ */ jsx2("style", { children: S2 }),
285
+ /* @__PURE__ */ jsxs2("button", { className: `hsk-sparkle ${className != null ? className : ""}`, onClick: handleClick, disabled: loading, children: [
286
+ "\u2726 ",
287
+ loading ? "Finding\u2026" : "Similar"
288
+ ] })
398
289
  ] });
399
290
  }
400
291
  export {
@@ -405,5 +296,6 @@ export {
405
296
  getHuskelClient,
406
297
  initHuskel,
407
298
  useHuskel,
299
+ useIngest,
408
300
  useSearch
409
301
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huskel/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Huskel AI-powered search SDK for SPAs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",