@akropolys/sdk 1.0.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 +130 -0
- package/dist/chunk-BDDPLGUO.mjs +1164 -0
- package/dist/chunk-BDDPLGUO.mjs.map +1 -0
- package/dist/chunk-FBM65EZM.mjs +1127 -0
- package/dist/chunk-FBM65EZM.mjs.map +1 -0
- package/dist/chunk-NWQKZTKU.mjs +1127 -0
- package/dist/chunk-NWQKZTKU.mjs.map +1 -0
- package/dist/commerce.d.mts +2 -0
- package/dist/commerce.d.ts +2 -0
- package/dist/commerce.js +1171 -0
- package/dist/commerce.js.map +1 -0
- package/dist/commerce.mjs +41 -0
- package/dist/commerce.mjs.map +1 -0
- package/dist/index.d.mts +377 -0
- package/dist/index.d.ts +377 -0
- package/dist/index.js +1168 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +38 -0
- package/dist/index.mjs.map +1 -0
- package/dist/property.d.mts +2 -0
- package/dist/property.d.ts +2 -0
- package/dist/property.js +1171 -0
- package/dist/property.js.map +1 -0
- package/dist/property.mjs +41 -0
- package/dist/property.mjs.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,1164 @@
|
|
|
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
|
+
|
|
22
|
+
// src/api.ts
|
|
23
|
+
var MAX_RETRIES = 3;
|
|
24
|
+
var RETRY_DELAYS = [500, 1e3, 2e3];
|
|
25
|
+
function log(level, msg, data) {
|
|
26
|
+
const prefix = "[Akropolys]";
|
|
27
|
+
if (level === "error") console.error(prefix, msg, data != null ? data : "");
|
|
28
|
+
else if (level === "warn") console.warn(prefix, msg, data != null ? data : "");
|
|
29
|
+
else console.log(prefix, msg, data != null ? data : "");
|
|
30
|
+
}
|
|
31
|
+
async function sleep(ms) {
|
|
32
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
33
|
+
}
|
|
34
|
+
var AkropolysAPI = class {
|
|
35
|
+
constructor(apiUrl, siteId, apiToken, getShopperId, getSessionId, vertical) {
|
|
36
|
+
this.apiUrl = apiUrl;
|
|
37
|
+
this.siteId = siteId;
|
|
38
|
+
this.apiToken = apiToken;
|
|
39
|
+
this.getShopperId = getShopperId;
|
|
40
|
+
this.getSessionId = getSessionId;
|
|
41
|
+
this.vertical = vertical;
|
|
42
|
+
}
|
|
43
|
+
async post(path, body, attempt = 0) {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
const url = `${this.apiUrl}${path}`;
|
|
46
|
+
try {
|
|
47
|
+
const headers = {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
"X-Akropolys-Token": this.apiToken,
|
|
50
|
+
"X-Akropolys-Site": this.siteId
|
|
51
|
+
};
|
|
52
|
+
const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
|
|
53
|
+
if (shopperId) {
|
|
54
|
+
headers["X-Akropolys-Shopper-Id"] = shopperId;
|
|
55
|
+
}
|
|
56
|
+
const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
|
|
57
|
+
if (sessionId) {
|
|
58
|
+
headers["X-Akropolys-Session-Id"] = sessionId;
|
|
59
|
+
}
|
|
60
|
+
if (typeof window !== "undefined") {
|
|
61
|
+
const phone = localStorage.getItem("akropolys_user_phone");
|
|
62
|
+
if (phone) {
|
|
63
|
+
headers["X-Akropolys-Shopper-Phone"] = phone;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const res = await fetch(url, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers,
|
|
69
|
+
body: JSON.stringify(body)
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
let message = text;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(text);
|
|
76
|
+
if (parsed && typeof parsed.error === "string") {
|
|
77
|
+
message = parsed.error;
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {
|
|
80
|
+
}
|
|
81
|
+
const err = { status: res.status, message };
|
|
82
|
+
if (res.status >= 400 && res.status < 500) {
|
|
83
|
+
log("error", `${path} failed [${res.status}]`, text);
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
87
|
+
log("warn", `${path} [${res.status}] retrying (${attempt + 1}/${MAX_RETRIES})...`);
|
|
88
|
+
await sleep(RETRY_DELAYS[attempt]);
|
|
89
|
+
return this.post(path, body, attempt + 1);
|
|
90
|
+
}
|
|
91
|
+
log("error", `${path} failed after ${MAX_RETRIES} attempts`, err);
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
return res.json();
|
|
95
|
+
} catch (e) {
|
|
96
|
+
if (e.status === void 0) {
|
|
97
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
98
|
+
log("warn", `${path} network error, retrying (${attempt + 1}/${MAX_RETRIES})...`);
|
|
99
|
+
await sleep(RETRY_DELAYS[attempt]);
|
|
100
|
+
return this.post(path, body, attempt + 1);
|
|
101
|
+
}
|
|
102
|
+
log("error", `${path} unreachable after ${MAX_RETRIES} attempts`);
|
|
103
|
+
}
|
|
104
|
+
throw e;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async ingest(product) {
|
|
108
|
+
log("info", "ingesting product", product.name);
|
|
109
|
+
return this.post("/ingest", { siteId: this.siteId, product });
|
|
110
|
+
}
|
|
111
|
+
async ingestBatch(products) {
|
|
112
|
+
log("info", `ingesting batch of ${products.length} products`);
|
|
113
|
+
return this.post("/ingest/batch", { siteId: this.siteId, products });
|
|
114
|
+
}
|
|
115
|
+
async search(query, limit = 10) {
|
|
116
|
+
log("info", "search query", query);
|
|
117
|
+
return this.post("/search", { query, siteId: this.siteId, limit });
|
|
118
|
+
}
|
|
119
|
+
// Pure vector search — no LLM, instant results.
|
|
120
|
+
async searchVector(query, limit = 10) {
|
|
121
|
+
return this.post("/search/vector", { query, siteId: this.siteId, limit });
|
|
122
|
+
}
|
|
123
|
+
// Autocomplete — pure in-memory Trie, <1ms, no Upstash call. Only true prefix matches.
|
|
124
|
+
async searchAutocomplete(query, limit = 8) {
|
|
125
|
+
return this.post("/search/autocomplete", { query, siteId: this.siteId, limit });
|
|
126
|
+
}
|
|
127
|
+
// LLM chat — conversational search with history context.
|
|
128
|
+
async chat(query, history = []) {
|
|
129
|
+
log("info", "chat query", query);
|
|
130
|
+
const path = !this.vertical || this.vertical === "commerce" ? "/chat" : `/chat/${this.vertical}`;
|
|
131
|
+
return this.post(path, { query, siteId: this.siteId, history });
|
|
132
|
+
}
|
|
133
|
+
// Streaming variant — returns the raw fetch Response.
|
|
134
|
+
// The caller reads body as a ReadableStream of SSE frames.
|
|
135
|
+
async chatStream(query, history = [], signal) {
|
|
136
|
+
var _a, _b;
|
|
137
|
+
log("info", "chatStream query", query);
|
|
138
|
+
const headers = {
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
"X-Akropolys-Token": this.apiToken,
|
|
141
|
+
"X-Akropolys-Site": this.siteId
|
|
142
|
+
};
|
|
143
|
+
const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
|
|
144
|
+
if (shopperId) headers["X-Akropolys-Shopper-Id"] = shopperId;
|
|
145
|
+
const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
|
|
146
|
+
if (sessionId) headers["X-Akropolys-Session-Id"] = sessionId;
|
|
147
|
+
if (typeof window !== "undefined") {
|
|
148
|
+
const phone = localStorage.getItem("akropolys_user_phone");
|
|
149
|
+
if (phone) headers["X-Akropolys-Shopper-Phone"] = phone;
|
|
150
|
+
}
|
|
151
|
+
const path = !this.vertical || this.vertical === "commerce" ? "/chat/stream" : `/chat/stream/${this.vertical}`;
|
|
152
|
+
const res = await fetch(`${this.apiUrl}${path}`, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers,
|
|
155
|
+
body: JSON.stringify({ query, siteId: this.siteId, history }),
|
|
156
|
+
signal
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok || !res.body) {
|
|
159
|
+
throw new Error(`Stream request failed: ${res.status}`);
|
|
160
|
+
}
|
|
161
|
+
return res;
|
|
162
|
+
}
|
|
163
|
+
// --- Cart System ---
|
|
164
|
+
buildHeaders() {
|
|
165
|
+
var _a, _b;
|
|
166
|
+
const headers = {
|
|
167
|
+
"Content-Type": "application/json",
|
|
168
|
+
"X-Akropolys-Token": this.apiToken,
|
|
169
|
+
"X-Akropolys-Site": this.siteId
|
|
170
|
+
};
|
|
171
|
+
const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
|
|
172
|
+
if (shopperId) headers["X-Akropolys-Shopper-Id"] = shopperId;
|
|
173
|
+
const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
|
|
174
|
+
if (sessionId) headers["X-Akropolys-Session-Id"] = sessionId;
|
|
175
|
+
if (typeof window !== "undefined") {
|
|
176
|
+
const phone = localStorage.getItem("akropolys_user_phone");
|
|
177
|
+
if (phone) {
|
|
178
|
+
headers["X-Akropolys-Shopper-Phone"] = phone;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return headers;
|
|
182
|
+
}
|
|
183
|
+
async getCart() {
|
|
184
|
+
const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
|
|
185
|
+
headers: this.buildHeaders()
|
|
186
|
+
});
|
|
187
|
+
if (!res.ok) throw new Error("Failed to fetch cart");
|
|
188
|
+
return res.json();
|
|
189
|
+
}
|
|
190
|
+
async clearCart() {
|
|
191
|
+
const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
|
|
192
|
+
method: "DELETE",
|
|
193
|
+
headers: this.buildHeaders()
|
|
194
|
+
});
|
|
195
|
+
if (!res.ok) throw new Error("Failed to clear cart");
|
|
196
|
+
return res.json();
|
|
197
|
+
}
|
|
198
|
+
async checkoutCart() {
|
|
199
|
+
const res = await fetch(`${this.apiUrl}/cart/checkout`, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: this.buildHeaders(),
|
|
202
|
+
body: JSON.stringify({ siteId: this.siteId })
|
|
203
|
+
});
|
|
204
|
+
if (!res.ok) throw new Error("Failed to checkout cart");
|
|
205
|
+
return res.json();
|
|
206
|
+
}
|
|
207
|
+
async getCheckoutConfig() {
|
|
208
|
+
const res = await fetch(`${this.apiUrl}/checkout/config?site_id=${this.siteId}`, {
|
|
209
|
+
method: "GET",
|
|
210
|
+
headers: this.buildHeaders()
|
|
211
|
+
});
|
|
212
|
+
if (!res.ok) throw new Error("Failed to fetch checkout config");
|
|
213
|
+
return res.json();
|
|
214
|
+
}
|
|
215
|
+
async initiatePayment(phoneNumber, email, firstName, lastName) {
|
|
216
|
+
const res = await fetch(`${this.apiUrl}/payment/initiate`, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: this.buildHeaders(),
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
siteId: this.siteId,
|
|
221
|
+
phoneNumber,
|
|
222
|
+
email,
|
|
223
|
+
firstName,
|
|
224
|
+
lastName
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
const errText = await res.text();
|
|
229
|
+
throw new Error("Failed to initiate payment: " + errText);
|
|
230
|
+
}
|
|
231
|
+
return res.json();
|
|
232
|
+
}
|
|
233
|
+
async getPaymentStatus(ref) {
|
|
234
|
+
const res = await fetch(`${this.apiUrl}/payment/status?ref=${ref}`, {
|
|
235
|
+
method: "GET",
|
|
236
|
+
headers: this.buildHeaders()
|
|
237
|
+
});
|
|
238
|
+
if (!res.ok) throw new Error("Failed to get payment status");
|
|
239
|
+
return res.json();
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/stream.ts
|
|
244
|
+
function parseSSEChunk(raw) {
|
|
245
|
+
const frames = [];
|
|
246
|
+
const blocks = raw.split(/\n\n+/);
|
|
247
|
+
for (const block of blocks) {
|
|
248
|
+
if (!block.trim()) continue;
|
|
249
|
+
let event = "";
|
|
250
|
+
let data = "";
|
|
251
|
+
for (const line of block.split("\n")) {
|
|
252
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
253
|
+
else if (line.startsWith("data:")) data = line.slice(5);
|
|
254
|
+
}
|
|
255
|
+
if (data !== "") frames.push({ event, data });
|
|
256
|
+
}
|
|
257
|
+
return frames;
|
|
258
|
+
}
|
|
259
|
+
var KikuStream = class {
|
|
260
|
+
constructor(responsePromise, abortController) {
|
|
261
|
+
this.listeners = {};
|
|
262
|
+
this.aborted = false;
|
|
263
|
+
this.responsePromise = responsePromise;
|
|
264
|
+
this.abortController = abortController;
|
|
265
|
+
this.startReading();
|
|
266
|
+
}
|
|
267
|
+
on(event, callback) {
|
|
268
|
+
if (!this.listeners[event]) {
|
|
269
|
+
this.listeners[event] = [];
|
|
270
|
+
}
|
|
271
|
+
this.listeners[event].push(callback);
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
off(event, callback) {
|
|
275
|
+
if (!this.listeners[event]) return this;
|
|
276
|
+
this.listeners[event] = this.listeners[event].filter((cb) => cb !== callback);
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
destroy() {
|
|
280
|
+
this.aborted = true;
|
|
281
|
+
this.abortController.abort();
|
|
282
|
+
}
|
|
283
|
+
emit(event, ...args) {
|
|
284
|
+
const list = this.listeners[event];
|
|
285
|
+
if (!list) return;
|
|
286
|
+
for (const cb of list) {
|
|
287
|
+
try {
|
|
288
|
+
cb(...args);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error(`[Akropolys] Error in KikuStream event listener for "${event}":`, err);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async startReading() {
|
|
295
|
+
var _a, _b;
|
|
296
|
+
try {
|
|
297
|
+
const response = await this.responsePromise;
|
|
298
|
+
if (this.aborted) return;
|
|
299
|
+
const reader = (_a = response.body) == null ? void 0 : _a.getReader();
|
|
300
|
+
if (!reader) {
|
|
301
|
+
throw new Error("Response body is not readable");
|
|
302
|
+
}
|
|
303
|
+
const decoder = new TextDecoder();
|
|
304
|
+
let buffer = "";
|
|
305
|
+
let accumulatedMessage = "";
|
|
306
|
+
while (true) {
|
|
307
|
+
const { done, value } = await reader.read();
|
|
308
|
+
if (done || this.aborted) break;
|
|
309
|
+
buffer += decoder.decode(value, { stream: true });
|
|
310
|
+
const lastBoundary = buffer.lastIndexOf("\n\n");
|
|
311
|
+
if (lastBoundary === -1) continue;
|
|
312
|
+
const complete = buffer.slice(0, lastBoundary + 2);
|
|
313
|
+
buffer = buffer.slice(lastBoundary + 2);
|
|
314
|
+
const frames = parseSSEChunk(complete);
|
|
315
|
+
for (const { event, data } of frames) {
|
|
316
|
+
if (this.aborted) return;
|
|
317
|
+
if (event === "meta") {
|
|
318
|
+
try {
|
|
319
|
+
const meta = JSON.parse(data);
|
|
320
|
+
this.emit("meta", meta);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
}
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (event === "done") {
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
if (event === "error") {
|
|
329
|
+
let msg = "Stream error";
|
|
330
|
+
try {
|
|
331
|
+
msg = (_b = JSON.parse(data).error) != null ? _b : msg;
|
|
332
|
+
} catch (e) {
|
|
333
|
+
msg = data;
|
|
334
|
+
}
|
|
335
|
+
throw new Error(msg);
|
|
336
|
+
}
|
|
337
|
+
const token = data.replace(/\\n/g, "\n");
|
|
338
|
+
accumulatedMessage += token;
|
|
339
|
+
this.emit("token", token);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (!this.aborted) {
|
|
343
|
+
this.emit("done", accumulatedMessage);
|
|
344
|
+
}
|
|
345
|
+
} catch (err) {
|
|
346
|
+
if (!this.aborted) {
|
|
347
|
+
this.emit("error", err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// src/client.ts
|
|
354
|
+
var import_meta = {};
|
|
355
|
+
var defaultVertical = "commerce";
|
|
356
|
+
function setSDKDefaultVertical(v) {
|
|
357
|
+
defaultVertical = v;
|
|
358
|
+
}
|
|
359
|
+
function getEnvVar(key) {
|
|
360
|
+
if (key === "NEXT_PUBLIC_AKROPOLYS_SITE_ID") {
|
|
361
|
+
try {
|
|
362
|
+
return process.env.NEXT_PUBLIC_AKROPOLYS_SITE_ID;
|
|
363
|
+
} catch (e) {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (key === "NEXT_PUBLIC_AKROPOLYS_API_URL") {
|
|
367
|
+
try {
|
|
368
|
+
return process.env.NEXT_PUBLIC_AKROPOLYS_API_URL;
|
|
369
|
+
} catch (e) {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (key === "NEXT_PUBLIC_AKROPOLYS_API_TOKEN") {
|
|
373
|
+
try {
|
|
374
|
+
return process.env.NEXT_PUBLIC_AKROPOLYS_API_TOKEN;
|
|
375
|
+
} catch (e) {
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const metaEnv = import_meta.env;
|
|
380
|
+
if (metaEnv) {
|
|
381
|
+
if (key === "NEXT_PUBLIC_AKROPOLYS_SITE_ID") return metaEnv.NEXT_PUBLIC_AKROPOLYS_SITE_ID || metaEnv.VITE_AKROPOLYS_SITE_ID;
|
|
382
|
+
if (key === "NEXT_PUBLIC_AKROPOLYS_API_URL") return metaEnv.NEXT_PUBLIC_AKROPOLYS_API_URL || metaEnv.VITE_AKROPOLYS_API_URL;
|
|
383
|
+
if (key === "NEXT_PUBLIC_AKROPOLYS_API_TOKEN") return metaEnv.NEXT_PUBLIC_AKROPOLYS_API_TOKEN || metaEnv.VITE_AKROPOLYS_API_TOKEN;
|
|
384
|
+
}
|
|
385
|
+
} catch (e) {
|
|
386
|
+
}
|
|
387
|
+
if (typeof globalThis !== "undefined") {
|
|
388
|
+
const g = globalThis;
|
|
389
|
+
if (g.process && g.process.env) {
|
|
390
|
+
return g.process.env[key];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return void 0;
|
|
394
|
+
}
|
|
395
|
+
function mapRawProduct(input) {
|
|
396
|
+
var _a;
|
|
397
|
+
const name = input.name || input.title || input.productName || "";
|
|
398
|
+
let price = "";
|
|
399
|
+
let priceNumeric = void 0;
|
|
400
|
+
if (input.price !== void 0) {
|
|
401
|
+
if (typeof input.price === "number") {
|
|
402
|
+
priceNumeric = input.price;
|
|
403
|
+
price = String(input.price);
|
|
404
|
+
} else {
|
|
405
|
+
price = input.price;
|
|
406
|
+
const num = parseFloat(input.price.replace(/[^0-9.]/g, ""));
|
|
407
|
+
priceNumeric = isNaN(num) ? void 0 : num;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (input.priceNumeric !== void 0) {
|
|
411
|
+
priceNumeric = input.priceNumeric;
|
|
412
|
+
}
|
|
413
|
+
let url = input.url || "";
|
|
414
|
+
if (!url && typeof window !== "undefined") {
|
|
415
|
+
url = window.location.href;
|
|
416
|
+
}
|
|
417
|
+
let slug = input.slug || input.id || input.productId || "";
|
|
418
|
+
if (!slug && url) {
|
|
419
|
+
slug = url.split("/").filter(Boolean).pop() || "";
|
|
420
|
+
}
|
|
421
|
+
if (!slug && name) {
|
|
422
|
+
slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
423
|
+
}
|
|
424
|
+
let images = [];
|
|
425
|
+
if (input.images) {
|
|
426
|
+
images = input.images;
|
|
427
|
+
} else if (input.image) {
|
|
428
|
+
images = [input.image];
|
|
429
|
+
} else if (input.listing_agent_photo) {
|
|
430
|
+
images = [input.listing_agent_photo];
|
|
431
|
+
} else if (input.thumbnail) {
|
|
432
|
+
images = [input.thumbnail];
|
|
433
|
+
}
|
|
434
|
+
if (!name) {
|
|
435
|
+
console.warn("[Akropolys] Validation warning: Product name/title is missing. Skipping:", input);
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
if (!price) {
|
|
439
|
+
console.warn("[Akropolys] Validation warning: Product price is missing. Skipping:", input);
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
if (!url) {
|
|
443
|
+
console.warn("[Akropolys] Validation warning: Product URL is missing. Skipping:", input);
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
const coreKeys = /* @__PURE__ */ new Set([
|
|
447
|
+
"name",
|
|
448
|
+
"title",
|
|
449
|
+
"productName",
|
|
450
|
+
"price",
|
|
451
|
+
"priceNumeric",
|
|
452
|
+
"url",
|
|
453
|
+
"image",
|
|
454
|
+
"thumbnail",
|
|
455
|
+
"images",
|
|
456
|
+
"slug",
|
|
457
|
+
"id",
|
|
458
|
+
"productId",
|
|
459
|
+
"brand",
|
|
460
|
+
"description",
|
|
461
|
+
"originalPrice",
|
|
462
|
+
"discount",
|
|
463
|
+
"currency",
|
|
464
|
+
"stock",
|
|
465
|
+
"availability",
|
|
466
|
+
"rating",
|
|
467
|
+
"reviewCount",
|
|
468
|
+
"category",
|
|
469
|
+
"subCategory",
|
|
470
|
+
"tags",
|
|
471
|
+
"specs",
|
|
472
|
+
"metadata"
|
|
473
|
+
]);
|
|
474
|
+
const metadata = __spreadValues({}, input.metadata);
|
|
475
|
+
for (const [key, value] of Object.entries(input)) {
|
|
476
|
+
if (!coreKeys.has(key) && value !== void 0) {
|
|
477
|
+
metadata[key] = value;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
name,
|
|
482
|
+
price,
|
|
483
|
+
url,
|
|
484
|
+
brand: input.brand,
|
|
485
|
+
description: input.description,
|
|
486
|
+
originalPrice: input.originalPrice,
|
|
487
|
+
discount: input.discount,
|
|
488
|
+
currency: (_a = input.currency) != null ? _a : "KES",
|
|
489
|
+
stock: input.stock,
|
|
490
|
+
availability: input.availability,
|
|
491
|
+
rating: input.rating,
|
|
492
|
+
reviewCount: input.reviewCount,
|
|
493
|
+
category: input.category,
|
|
494
|
+
subCategory: input.subCategory,
|
|
495
|
+
tags: input.tags,
|
|
496
|
+
images: images.length > 0 ? images : void 0,
|
|
497
|
+
specs: input.specs,
|
|
498
|
+
priceNumeric,
|
|
499
|
+
slug,
|
|
500
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function generateUUID() {
|
|
504
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
505
|
+
return crypto.randomUUID();
|
|
506
|
+
}
|
|
507
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
508
|
+
const r = Math.random() * 16 | 0;
|
|
509
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
510
|
+
return v.toString(16);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
var _AkropolysClient = class _AkropolysClient {
|
|
514
|
+
constructor(config) {
|
|
515
|
+
this.ingestQueue = [];
|
|
516
|
+
this.ingestTimer = null;
|
|
517
|
+
this.ingestedUrls = /* @__PURE__ */ new Set();
|
|
518
|
+
this.onlineHandler = null;
|
|
519
|
+
this.sessionId = "";
|
|
520
|
+
const siteId = config.siteId || getEnvVar("NEXT_PUBLIC_AKROPOLYS_SITE_ID") || "";
|
|
521
|
+
const apiUrl = config.apiUrl || getEnvVar("NEXT_PUBLIC_AKROPOLYS_API_URL") || "";
|
|
522
|
+
const apiToken = config.apiToken || getEnvVar("NEXT_PUBLIC_AKROPOLYS_API_TOKEN") || "";
|
|
523
|
+
if (!siteId) console.error('[Akropolys] Missing siteId. Set it via <AkropolysProvider siteId="..."> or NEXT_PUBLIC_AKROPOLYS_SITE_ID.');
|
|
524
|
+
if (!apiUrl) console.error('[Akropolys] Missing apiUrl. Set it via <AkropolysProvider apiUrl="..."> or NEXT_PUBLIC_AKROPOLYS_API_URL.');
|
|
525
|
+
if (!apiToken) console.error('[Akropolys] Missing apiToken. Set it via <AkropolysProvider apiToken="..."> or NEXT_PUBLIC_AKROPOLYS_API_TOKEN.');
|
|
526
|
+
this.shopperId = config.shopperId;
|
|
527
|
+
this.authLoading = config.authLoading;
|
|
528
|
+
this.onCheckout = config.onCheckout;
|
|
529
|
+
this.onError = config.onError;
|
|
530
|
+
this.vertical = config.vertical || defaultVertical;
|
|
531
|
+
this.initSession();
|
|
532
|
+
this.loadIngestedCache();
|
|
533
|
+
this.api = new AkropolysAPI(
|
|
534
|
+
apiUrl,
|
|
535
|
+
siteId,
|
|
536
|
+
apiToken,
|
|
537
|
+
() => this.getShopperId(),
|
|
538
|
+
() => this.sessionId,
|
|
539
|
+
this.vertical
|
|
540
|
+
);
|
|
541
|
+
instance = this;
|
|
542
|
+
if (typeof window !== "undefined") {
|
|
543
|
+
this.onlineHandler = () => {
|
|
544
|
+
console.log("[Akropolys] Connectivity restored, flushing queued ingestions.");
|
|
545
|
+
this.flushQueue();
|
|
546
|
+
};
|
|
547
|
+
window.addEventListener("online", this.onlineHandler);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// 24h
|
|
551
|
+
loadIngestedCache() {
|
|
552
|
+
if (typeof window === "undefined") return;
|
|
553
|
+
try {
|
|
554
|
+
const raw = localStorage.getItem(_AkropolysClient.INGEST_CACHE_KEY);
|
|
555
|
+
if (!raw) return;
|
|
556
|
+
const { ts, urls } = JSON.parse(raw);
|
|
557
|
+
if (Date.now() - ts > _AkropolysClient.INGEST_CACHE_TTL) {
|
|
558
|
+
localStorage.removeItem(_AkropolysClient.INGEST_CACHE_KEY);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
this.ingestedUrls = new Set(urls);
|
|
562
|
+
} catch (e) {
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
saveIngestedCache() {
|
|
566
|
+
if (typeof window === "undefined") return;
|
|
567
|
+
try {
|
|
568
|
+
localStorage.setItem(
|
|
569
|
+
_AkropolysClient.INGEST_CACHE_KEY,
|
|
570
|
+
JSON.stringify({ ts: Date.now(), urls: [...this.ingestedUrls] })
|
|
571
|
+
);
|
|
572
|
+
} catch (e) {
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
chat(query, history = []) {
|
|
576
|
+
const abortController = new AbortController();
|
|
577
|
+
const responsePromise = this.api.chatStream(query, history, abortController.signal);
|
|
578
|
+
return new KikuStream(responsePromise, abortController);
|
|
579
|
+
}
|
|
580
|
+
reRegister() {
|
|
581
|
+
instance = this;
|
|
582
|
+
}
|
|
583
|
+
setShopperId(id) {
|
|
584
|
+
this.shopperId = id;
|
|
585
|
+
if (!this.authLoading) {
|
|
586
|
+
this.flushQueue();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
setAuthLoading(loading) {
|
|
590
|
+
const wasLoading = this.authLoading;
|
|
591
|
+
this.authLoading = loading;
|
|
592
|
+
if (wasLoading && !loading) {
|
|
593
|
+
this.flushQueue();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
getShopperId() {
|
|
597
|
+
return this.shopperId || "guest_" + this.sessionId;
|
|
598
|
+
}
|
|
599
|
+
getSessionId() {
|
|
600
|
+
return this.sessionId;
|
|
601
|
+
}
|
|
602
|
+
initSession() {
|
|
603
|
+
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
604
|
+
try {
|
|
605
|
+
let sid = window.sessionStorage.getItem("akropolys_session_id");
|
|
606
|
+
if (!sid) {
|
|
607
|
+
sid = generateUUID();
|
|
608
|
+
window.sessionStorage.setItem("akropolys_session_id", sid);
|
|
609
|
+
}
|
|
610
|
+
this.sessionId = sid;
|
|
611
|
+
return;
|
|
612
|
+
} catch (e) {
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
this.sessionId = generateUUID();
|
|
616
|
+
}
|
|
617
|
+
destroy() {
|
|
618
|
+
if (typeof window !== "undefined" && this.onlineHandler) {
|
|
619
|
+
window.removeEventListener("online", this.onlineHandler);
|
|
620
|
+
this.onlineHandler = null;
|
|
621
|
+
}
|
|
622
|
+
if (this.ingestTimer) {
|
|
623
|
+
clearTimeout(this.ingestTimer);
|
|
624
|
+
this.ingestTimer = null;
|
|
625
|
+
}
|
|
626
|
+
if (instance === this) instance = null;
|
|
627
|
+
}
|
|
628
|
+
async queueIngest(rawProduct) {
|
|
629
|
+
const product = mapRawProduct(rawProduct);
|
|
630
|
+
if (!product) return;
|
|
631
|
+
if (this.ingestedUrls.has(product.url)) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
this.ingestedUrls.add(product.url);
|
|
635
|
+
this.saveIngestedCache();
|
|
636
|
+
this.ingestQueue.push(product);
|
|
637
|
+
this.scheduleFlush();
|
|
638
|
+
}
|
|
639
|
+
async queueIngestBatch(rawProducts) {
|
|
640
|
+
rawProducts.forEach((p) => {
|
|
641
|
+
const product = mapRawProduct(p);
|
|
642
|
+
if (!product) return;
|
|
643
|
+
if (this.ingestedUrls.has(product.url)) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
this.ingestedUrls.add(product.url);
|
|
647
|
+
this.ingestQueue.push(product);
|
|
648
|
+
});
|
|
649
|
+
if (this.ingestQueue.length > 0) {
|
|
650
|
+
this.saveIngestedCache();
|
|
651
|
+
this.scheduleFlush();
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
scheduleFlush() {
|
|
655
|
+
if (this.ingestTimer) return;
|
|
656
|
+
this.ingestTimer = setTimeout(() => {
|
|
657
|
+
this.flushQueue();
|
|
658
|
+
}, 300);
|
|
659
|
+
}
|
|
660
|
+
async flushQueue() {
|
|
661
|
+
this.ingestTimer = null;
|
|
662
|
+
if (this.ingestQueue.length === 0) return;
|
|
663
|
+
if (this.authLoading) {
|
|
664
|
+
console.log("[Akropolys] Authentication is loading. Deferring ingestion flush.");
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
668
|
+
console.warn("[Akropolys] Browser offline. Postponing ingestion.");
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const batch = [...this.ingestQueue];
|
|
672
|
+
this.ingestQueue = [];
|
|
673
|
+
try {
|
|
674
|
+
await this.api.ingestBatch(batch);
|
|
675
|
+
} catch (e) {
|
|
676
|
+
const akropolysError = {
|
|
677
|
+
status: e.status || 500,
|
|
678
|
+
message: e.message || "Unknown network error"
|
|
679
|
+
};
|
|
680
|
+
if (this.onError) {
|
|
681
|
+
try {
|
|
682
|
+
this.onError(akropolysError);
|
|
683
|
+
} catch (err) {
|
|
684
|
+
console.error("[Akropolys] Error inside onError callback:", err);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (e.status && e.status >= 400 && e.status < 500) {
|
|
688
|
+
console.error("[Akropolys] Ingestion discarded due to client error:", e.message);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
console.warn("[Akropolys] Ingestion failed. Re-queuing to retry.", e);
|
|
692
|
+
this.ingestQueue = [...batch, ...this.ingestQueue];
|
|
693
|
+
this.scheduleFlush();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
_AkropolysClient.INGEST_CACHE_KEY = "akropolys_ingested_v2";
|
|
698
|
+
_AkropolysClient.INGEST_CACHE_TTL = 24 * 60 * 60 * 1e3;
|
|
699
|
+
var AkropolysClient = _AkropolysClient;
|
|
700
|
+
var instance = null;
|
|
701
|
+
function initAkropolys(config) {
|
|
702
|
+
instance = new AkropolysClient(config);
|
|
703
|
+
return instance;
|
|
704
|
+
}
|
|
705
|
+
function getAkropolysClient() {
|
|
706
|
+
if (!instance) {
|
|
707
|
+
const siteId = getEnvVar("NEXT_PUBLIC_AKROPOLYS_SITE_ID");
|
|
708
|
+
const apiUrl = getEnvVar("NEXT_PUBLIC_AKROPOLYS_API_URL");
|
|
709
|
+
const apiToken = getEnvVar("NEXT_PUBLIC_AKROPOLYS_API_TOKEN");
|
|
710
|
+
if (siteId && apiUrl && apiToken) {
|
|
711
|
+
instance = new AkropolysClient({ siteId, apiUrl, apiToken });
|
|
712
|
+
} else {
|
|
713
|
+
throw new Error("[Akropolys] Call initAkropolys() or set NEXT_PUBLIC_AKROPOLYS_* environment variables before using the client.");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return instance;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/Provider.tsx
|
|
720
|
+
import { createContext, useContext, useEffect, useRef } from "react";
|
|
721
|
+
import { jsx } from "react/jsx-runtime";
|
|
722
|
+
var AkropolysContext = createContext(null);
|
|
723
|
+
function AkropolysProvider({
|
|
724
|
+
siteId,
|
|
725
|
+
apiUrl,
|
|
726
|
+
apiToken,
|
|
727
|
+
shopperId,
|
|
728
|
+
vertical,
|
|
729
|
+
authLoading,
|
|
730
|
+
onCheckout,
|
|
731
|
+
onError,
|
|
732
|
+
children
|
|
733
|
+
}) {
|
|
734
|
+
const clientRef = useRef(null);
|
|
735
|
+
if (!clientRef.current) {
|
|
736
|
+
clientRef.current = new AkropolysClient({
|
|
737
|
+
siteId,
|
|
738
|
+
apiUrl,
|
|
739
|
+
apiToken,
|
|
740
|
+
shopperId,
|
|
741
|
+
vertical,
|
|
742
|
+
authLoading,
|
|
743
|
+
onCheckout,
|
|
744
|
+
onError
|
|
745
|
+
});
|
|
746
|
+
} else {
|
|
747
|
+
clientRef.current.reRegister();
|
|
748
|
+
}
|
|
749
|
+
useEffect(() => {
|
|
750
|
+
var _a;
|
|
751
|
+
(_a = clientRef.current) == null ? void 0 : _a.setShopperId(shopperId);
|
|
752
|
+
}, [shopperId]);
|
|
753
|
+
useEffect(() => {
|
|
754
|
+
var _a;
|
|
755
|
+
(_a = clientRef.current) == null ? void 0 : _a.setAuthLoading(!!authLoading);
|
|
756
|
+
}, [authLoading]);
|
|
757
|
+
useEffect(() => {
|
|
758
|
+
if (clientRef.current) {
|
|
759
|
+
clientRef.current.onError = onError;
|
|
760
|
+
clientRef.current.onCheckout = onCheckout;
|
|
761
|
+
}
|
|
762
|
+
}, [onError, onCheckout]);
|
|
763
|
+
useEffect(() => {
|
|
764
|
+
var _a;
|
|
765
|
+
(_a = clientRef.current) == null ? void 0 : _a.reRegister();
|
|
766
|
+
}, []);
|
|
767
|
+
useEffect(() => {
|
|
768
|
+
return () => {
|
|
769
|
+
var _a;
|
|
770
|
+
(_a = clientRef.current) == null ? void 0 : _a.destroy();
|
|
771
|
+
};
|
|
772
|
+
}, []);
|
|
773
|
+
return /* @__PURE__ */ jsx(AkropolysContext.Provider, { value: clientRef.current, children });
|
|
774
|
+
}
|
|
775
|
+
function useAkropolysContext() {
|
|
776
|
+
const context = useContext(AkropolysContext);
|
|
777
|
+
if (!context) {
|
|
778
|
+
return getAkropolysClient();
|
|
779
|
+
}
|
|
780
|
+
return context;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/hooks/useAkropolys.ts
|
|
784
|
+
import { useRef as useRef2 } from "react";
|
|
785
|
+
function useAkropolys(config) {
|
|
786
|
+
const clientRef = useRef2(null);
|
|
787
|
+
if (!clientRef.current) {
|
|
788
|
+
console.warn("[Akropolys] useAkropolys() is deprecated. Please wrap your application in <AkropolysProvider> instead.");
|
|
789
|
+
clientRef.current = initAkropolys(config);
|
|
790
|
+
}
|
|
791
|
+
return clientRef.current;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/hooks/useSearch.ts
|
|
795
|
+
import { useState, useCallback, useRef as useRef3 } from "react";
|
|
796
|
+
function useSearch() {
|
|
797
|
+
const client = useAkropolysContext();
|
|
798
|
+
const [results, setResults] = useState([]);
|
|
799
|
+
const [loading, setLoading] = useState(false);
|
|
800
|
+
const [error, setError] = useState(null);
|
|
801
|
+
const genRef = useRef3(0);
|
|
802
|
+
const search = useCallback(async (query, limit = 8) => {
|
|
803
|
+
var _a, _b;
|
|
804
|
+
if (!query.trim()) {
|
|
805
|
+
setResults([]);
|
|
806
|
+
setLoading(false);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const gen = ++genRef.current;
|
|
810
|
+
setLoading(true);
|
|
811
|
+
setError(null);
|
|
812
|
+
try {
|
|
813
|
+
const res = await client.api.searchAutocomplete(query, limit);
|
|
814
|
+
if (gen === genRef.current) {
|
|
815
|
+
setResults((_a = res.results) != null ? _a : []);
|
|
816
|
+
}
|
|
817
|
+
} catch (e) {
|
|
818
|
+
if (gen === genRef.current) {
|
|
819
|
+
let msg = (_b = e == null ? void 0 : e.message) != null ? _b : "Search failed";
|
|
820
|
+
try {
|
|
821
|
+
const parsed = JSON.parse(msg);
|
|
822
|
+
if (parsed && parsed.error) {
|
|
823
|
+
msg = parsed.error;
|
|
824
|
+
}
|
|
825
|
+
} catch (e2) {
|
|
826
|
+
}
|
|
827
|
+
setError(msg);
|
|
828
|
+
}
|
|
829
|
+
} finally {
|
|
830
|
+
if (gen === genRef.current) setLoading(false);
|
|
831
|
+
}
|
|
832
|
+
}, [client]);
|
|
833
|
+
const clear = useCallback(() => {
|
|
834
|
+
genRef.current++;
|
|
835
|
+
setResults([]);
|
|
836
|
+
setError(null);
|
|
837
|
+
setLoading(false);
|
|
838
|
+
}, []);
|
|
839
|
+
return { results, loading, error, search, clear };
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/hooks/useIngest.ts
|
|
843
|
+
import { useCallback as useCallback2 } from "react";
|
|
844
|
+
var recentlyIngested = /* @__PURE__ */ new Set();
|
|
845
|
+
function getProductKey(p) {
|
|
846
|
+
return p.url || p.id || p.productId || p.slug || p.name || p.title || p.productName || null;
|
|
847
|
+
}
|
|
848
|
+
function useIngest() {
|
|
849
|
+
const client = useAkropolysContext();
|
|
850
|
+
const ingest = useCallback2((product) => {
|
|
851
|
+
const key = getProductKey(product);
|
|
852
|
+
if (key) {
|
|
853
|
+
if (recentlyIngested.has(key)) return;
|
|
854
|
+
recentlyIngested.add(key);
|
|
855
|
+
}
|
|
856
|
+
client.queueIngest(product).catch(() => {
|
|
857
|
+
});
|
|
858
|
+
}, [client]);
|
|
859
|
+
const ingestBatch = useCallback2((products) => {
|
|
860
|
+
const toIngest = products.filter((p) => {
|
|
861
|
+
const key = getProductKey(p);
|
|
862
|
+
if (!key) return true;
|
|
863
|
+
if (recentlyIngested.has(key)) return false;
|
|
864
|
+
recentlyIngested.add(key);
|
|
865
|
+
return true;
|
|
866
|
+
});
|
|
867
|
+
if (!toIngest.length) return;
|
|
868
|
+
client.queueIngestBatch(toIngest).catch(() => {
|
|
869
|
+
});
|
|
870
|
+
}, [client]);
|
|
871
|
+
return { ingest, ingestBatch, loading: false, error: null };
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/hooks/useListIngest.ts
|
|
875
|
+
import { useEffect as useEffect2, useRef as useRef4 } from "react";
|
|
876
|
+
function useListIngest(products) {
|
|
877
|
+
const { ingestBatch } = useIngest();
|
|
878
|
+
const processedIdsRef = useRef4(/* @__PURE__ */ new Set());
|
|
879
|
+
const listKey = (products || []).map((p) => p.url || p.id || p.productId || p.slug || p.name || p.title || p.productName || "").join(",");
|
|
880
|
+
useEffect2(() => {
|
|
881
|
+
if (!products || !products.length) return;
|
|
882
|
+
const newItems = products.filter((item) => {
|
|
883
|
+
const id = item.id || item.productId || item.url || item.slug || item.name || item.title || item.productName;
|
|
884
|
+
if (!id) return true;
|
|
885
|
+
if (processedIdsRef.current.has(id)) {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
processedIdsRef.current.add(id);
|
|
889
|
+
return true;
|
|
890
|
+
});
|
|
891
|
+
if (newItems.length > 0) {
|
|
892
|
+
ingestBatch(newItems);
|
|
893
|
+
}
|
|
894
|
+
}, [listKey, ingestBatch]);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/hooks/usePageIngest.ts
|
|
898
|
+
import { useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
899
|
+
function usePageIngest(product) {
|
|
900
|
+
var _a;
|
|
901
|
+
const ingestedRef = useRef5(null);
|
|
902
|
+
useEffect3(() => {
|
|
903
|
+
if (!product) return;
|
|
904
|
+
const url = product.url || (typeof window !== "undefined" ? window.location.href : "");
|
|
905
|
+
if (ingestedRef.current === url) return;
|
|
906
|
+
ingestedRef.current = url;
|
|
907
|
+
try {
|
|
908
|
+
getAkropolysClient().queueIngest(__spreadProps(__spreadValues({}, product), { url }));
|
|
909
|
+
} catch (e) {
|
|
910
|
+
}
|
|
911
|
+
}, [(_a = product == null ? void 0 : product.url) != null ? _a : product == null ? void 0 : product.name]);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/hooks/useKiku.ts
|
|
915
|
+
import { useState as useState2, useCallback as useCallback3, useRef as useRef6, useEffect as useEffect4 } from "react";
|
|
916
|
+
function useKiku(options = {}) {
|
|
917
|
+
var _a;
|
|
918
|
+
const client = useAkropolysContext();
|
|
919
|
+
const [messages, setMessages] = useState2((_a = options.initialMessages) != null ? _a : []);
|
|
920
|
+
const [sources, setSources] = useState2([]);
|
|
921
|
+
const [loading, setLoading] = useState2(false);
|
|
922
|
+
const [streaming, setStreaming] = useState2(false);
|
|
923
|
+
const [error, setError] = useState2(null);
|
|
924
|
+
const [lastAction, setLastAction] = useState2(null);
|
|
925
|
+
const [lastIntent, setLastIntent] = useState2(null);
|
|
926
|
+
const activeStreamRef = useRef6(null);
|
|
927
|
+
const onTokenRef = useRef6(options.onToken);
|
|
928
|
+
const onMetaRef = useRef6(options.onMeta);
|
|
929
|
+
const onDoneRef = useRef6(options.onDone);
|
|
930
|
+
const onErrorRef = useRef6(options.onError);
|
|
931
|
+
useEffect4(() => {
|
|
932
|
+
onTokenRef.current = options.onToken;
|
|
933
|
+
onMetaRef.current = options.onMeta;
|
|
934
|
+
onDoneRef.current = options.onDone;
|
|
935
|
+
onErrorRef.current = options.onError;
|
|
936
|
+
}, [options.onToken, options.onMeta, options.onDone, options.onError]);
|
|
937
|
+
useEffect4(() => {
|
|
938
|
+
return () => {
|
|
939
|
+
var _a2;
|
|
940
|
+
(_a2 = activeStreamRef.current) == null ? void 0 : _a2.destroy();
|
|
941
|
+
};
|
|
942
|
+
}, []);
|
|
943
|
+
const send = useCallback3(async (query, displayQuery) => {
|
|
944
|
+
var _a2, _b, _c;
|
|
945
|
+
if (!query.trim() || loading) return;
|
|
946
|
+
(_a2 = activeStreamRef.current) == null ? void 0 : _a2.destroy();
|
|
947
|
+
const userMsg = { role: "user", content: displayQuery != null ? displayQuery : query };
|
|
948
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
949
|
+
setLoading(true);
|
|
950
|
+
setStreaming(false);
|
|
951
|
+
setError(null);
|
|
952
|
+
try {
|
|
953
|
+
const history = messages.map((m) => ({ role: m.role, content: m.content }));
|
|
954
|
+
const stream = client.chat(query, history);
|
|
955
|
+
activeStreamRef.current = stream;
|
|
956
|
+
let messageInitialized = false;
|
|
957
|
+
let lastMeta = null;
|
|
958
|
+
stream.on("meta", (meta) => {
|
|
959
|
+
var _a3, _b2;
|
|
960
|
+
lastMeta = meta;
|
|
961
|
+
setSources((_a3 = meta.sources) != null ? _a3 : []);
|
|
962
|
+
if (meta.intent) setLastIntent(meta.intent);
|
|
963
|
+
if (meta.action) setLastAction(meta.action);
|
|
964
|
+
(_b2 = onMetaRef.current) == null ? void 0 : _b2.call(onMetaRef, meta);
|
|
965
|
+
});
|
|
966
|
+
stream.on("token", (token) => {
|
|
967
|
+
var _a3;
|
|
968
|
+
if (!messageInitialized) {
|
|
969
|
+
setLoading(false);
|
|
970
|
+
setStreaming(true);
|
|
971
|
+
setMessages((prev) => [...prev, { role: "assistant", content: token }]);
|
|
972
|
+
messageInitialized = true;
|
|
973
|
+
} else {
|
|
974
|
+
setMessages((prev) => {
|
|
975
|
+
const next = [...prev];
|
|
976
|
+
if (next.length > 0 && next[next.length - 1].role === "assistant") {
|
|
977
|
+
next[next.length - 1] = __spreadProps(__spreadValues({}, next[next.length - 1]), {
|
|
978
|
+
content: next[next.length - 1].content + token
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
return next;
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
(_a3 = onTokenRef.current) == null ? void 0 : _a3.call(onTokenRef, token);
|
|
985
|
+
});
|
|
986
|
+
stream.on("done", (fullMessage) => {
|
|
987
|
+
var _a3;
|
|
988
|
+
setLoading(false);
|
|
989
|
+
setStreaming(false);
|
|
990
|
+
const metaAction = lastMeta == null ? void 0 : lastMeta.action;
|
|
991
|
+
const metaCheckout = lastMeta == null ? void 0 : lastMeta.checkout;
|
|
992
|
+
const isCartAction = (metaAction == null ? void 0 : metaAction.type) === "add_to_cart" || (metaAction == null ? void 0 : metaAction.type) === "remove_from_cart" || (metaAction == null ? void 0 : metaAction.type) === "clear_cart" || (metaAction == null ? void 0 : metaAction.type) === "view_cart";
|
|
993
|
+
if (isCartAction || metaCheckout) {
|
|
994
|
+
setMessages((prev) => {
|
|
995
|
+
const next = [...prev];
|
|
996
|
+
if (next.length > 0 && next[next.length - 1].role === "assistant") {
|
|
997
|
+
next[next.length - 1] = __spreadProps(__spreadValues({}, next[next.length - 1]), {
|
|
998
|
+
cartSnapshot: metaCheckout,
|
|
999
|
+
actionType: metaAction == null ? void 0 : metaAction.type
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
return next;
|
|
1003
|
+
});
|
|
1004
|
+
window.dispatchEvent(new CustomEvent("akropolys:cart_updated", { detail: metaCheckout }));
|
|
1005
|
+
}
|
|
1006
|
+
if ((metaAction == null ? void 0 : metaAction.type) === "checkout") {
|
|
1007
|
+
window.dispatchEvent(new CustomEvent("akropolys:trigger_checkout", { detail: metaCheckout }));
|
|
1008
|
+
}
|
|
1009
|
+
if ((metaAction == null ? void 0 : metaAction.type) === "awaiting_payment") {
|
|
1010
|
+
window.dispatchEvent(new CustomEvent("akropolys:awaiting_payment", { detail: metaAction }));
|
|
1011
|
+
}
|
|
1012
|
+
if (metaCheckout && client.onCheckout) {
|
|
1013
|
+
client.onCheckout(metaCheckout);
|
|
1014
|
+
}
|
|
1015
|
+
(_a3 = onDoneRef.current) == null ? void 0 : _a3.call(onDoneRef, fullMessage);
|
|
1016
|
+
});
|
|
1017
|
+
stream.on("error", (err) => {
|
|
1018
|
+
var _a3;
|
|
1019
|
+
setLoading(false);
|
|
1020
|
+
setStreaming(false);
|
|
1021
|
+
setError(err.message);
|
|
1022
|
+
setMessages((prev) => prev.slice(0, -1));
|
|
1023
|
+
(_a3 = onErrorRef.current) == null ? void 0 : _a3.call(onErrorRef, err);
|
|
1024
|
+
});
|
|
1025
|
+
} catch (err) {
|
|
1026
|
+
setLoading(false);
|
|
1027
|
+
setStreaming(false);
|
|
1028
|
+
setError((_b = err == null ? void 0 : err.message) != null ? _b : "Chat request failed");
|
|
1029
|
+
setMessages((prev) => prev.slice(0, -1));
|
|
1030
|
+
(_c = onErrorRef.current) == null ? void 0 : _c.call(onErrorRef, err);
|
|
1031
|
+
}
|
|
1032
|
+
}, [client, messages, loading]);
|
|
1033
|
+
const reset = useCallback3(() => {
|
|
1034
|
+
var _a2;
|
|
1035
|
+
(_a2 = activeStreamRef.current) == null ? void 0 : _a2.destroy();
|
|
1036
|
+
setMessages([]);
|
|
1037
|
+
setSources([]);
|
|
1038
|
+
setStreaming(false);
|
|
1039
|
+
setError(null);
|
|
1040
|
+
setLoading(false);
|
|
1041
|
+
setLastAction(null);
|
|
1042
|
+
setLastIntent(null);
|
|
1043
|
+
}, []);
|
|
1044
|
+
return { messages, sources, loading, streaming, error, lastAction, lastIntent, send, reset };
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/hooks/useCart.ts
|
|
1048
|
+
import { useState as useState3, useEffect as useEffect5, useCallback as useCallback4 } from "react";
|
|
1049
|
+
function useCart() {
|
|
1050
|
+
const client = useAkropolysContext();
|
|
1051
|
+
const [cart, setCart] = useState3(null);
|
|
1052
|
+
const [loading, setLoading] = useState3(false);
|
|
1053
|
+
const shopperId = client.getShopperId();
|
|
1054
|
+
const fetchCart = useCallback4(async () => {
|
|
1055
|
+
if (!shopperId) return;
|
|
1056
|
+
setLoading(true);
|
|
1057
|
+
try {
|
|
1058
|
+
const res = await client.api.getCart();
|
|
1059
|
+
setCart(res);
|
|
1060
|
+
} catch (e) {
|
|
1061
|
+
console.error("[Akropolys] Failed to fetch cart", e);
|
|
1062
|
+
} finally {
|
|
1063
|
+
setLoading(false);
|
|
1064
|
+
}
|
|
1065
|
+
}, [client, shopperId]);
|
|
1066
|
+
useEffect5(() => {
|
|
1067
|
+
fetchCart();
|
|
1068
|
+
const handleCartUpdate = (e) => {
|
|
1069
|
+
if (e.detail) {
|
|
1070
|
+
setCart(e.detail);
|
|
1071
|
+
} else {
|
|
1072
|
+
fetchCart();
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
if (typeof window !== "undefined") {
|
|
1076
|
+
window.addEventListener("akropolys:cart_updated", handleCartUpdate);
|
|
1077
|
+
return () => window.removeEventListener("akropolys:cart_updated", handleCartUpdate);
|
|
1078
|
+
}
|
|
1079
|
+
}, [fetchCart, shopperId]);
|
|
1080
|
+
return { cart, loading, fetchCart };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/hooks/usePaymentPolling.ts
|
|
1084
|
+
import { useState as useState4, useEffect as useEffect6, useRef as useRef7 } from "react";
|
|
1085
|
+
function usePaymentPolling({
|
|
1086
|
+
client,
|
|
1087
|
+
merchantReference,
|
|
1088
|
+
onSuccess,
|
|
1089
|
+
onFailure,
|
|
1090
|
+
intervalMs = 3e3,
|
|
1091
|
+
timeoutMs = 3e5
|
|
1092
|
+
// 5 minutes default
|
|
1093
|
+
}) {
|
|
1094
|
+
const [status, setStatus] = useState4("IDLE");
|
|
1095
|
+
const [error, setError] = useState4(null);
|
|
1096
|
+
const onSuccessRef = useRef7(onSuccess);
|
|
1097
|
+
const onFailureRef = useRef7(onFailure);
|
|
1098
|
+
useEffect6(() => {
|
|
1099
|
+
onSuccessRef.current = onSuccess;
|
|
1100
|
+
onFailureRef.current = onFailure;
|
|
1101
|
+
}, [onSuccess, onFailure]);
|
|
1102
|
+
useEffect6(() => {
|
|
1103
|
+
if (!merchantReference) {
|
|
1104
|
+
setStatus("IDLE");
|
|
1105
|
+
setError(null);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
setStatus("PENDING");
|
|
1109
|
+
setError(null);
|
|
1110
|
+
const startTime = Date.now();
|
|
1111
|
+
let timerId = null;
|
|
1112
|
+
async function checkStatus() {
|
|
1113
|
+
try {
|
|
1114
|
+
if (Date.now() - startTime >= timeoutMs) {
|
|
1115
|
+
setStatus("FAILED");
|
|
1116
|
+
setError("Payment session timed out");
|
|
1117
|
+
if (onFailureRef.current) onFailureRef.current("Payment session timed out");
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const res = await client.getPaymentStatus(merchantReference);
|
|
1121
|
+
if (res.status === "COMPLETED") {
|
|
1122
|
+
setStatus("COMPLETED");
|
|
1123
|
+
if (onSuccessRef.current) onSuccessRef.current();
|
|
1124
|
+
} else if (res.status === "FAILED") {
|
|
1125
|
+
setStatus("FAILED");
|
|
1126
|
+
setError("Payment failed");
|
|
1127
|
+
if (onFailureRef.current) onFailureRef.current("Payment failed");
|
|
1128
|
+
} else {
|
|
1129
|
+
timerId = setTimeout(checkStatus, intervalMs);
|
|
1130
|
+
}
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
console.error("[Akropolys Polling Error]", err);
|
|
1133
|
+
timerId = setTimeout(checkStatus, intervalMs);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
timerId = setTimeout(checkStatus, intervalMs);
|
|
1137
|
+
return () => {
|
|
1138
|
+
if (timerId) {
|
|
1139
|
+
clearTimeout(timerId);
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}, [client, merchantReference, intervalMs, timeoutMs]);
|
|
1143
|
+
return { status, error };
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
export {
|
|
1147
|
+
AkropolysAPI,
|
|
1148
|
+
KikuStream,
|
|
1149
|
+
setSDKDefaultVertical,
|
|
1150
|
+
AkropolysClient,
|
|
1151
|
+
initAkropolys,
|
|
1152
|
+
getAkropolysClient,
|
|
1153
|
+
AkropolysProvider,
|
|
1154
|
+
useAkropolysContext,
|
|
1155
|
+
useAkropolys,
|
|
1156
|
+
useSearch,
|
|
1157
|
+
useIngest,
|
|
1158
|
+
useListIngest,
|
|
1159
|
+
usePageIngest,
|
|
1160
|
+
useKiku,
|
|
1161
|
+
useCart,
|
|
1162
|
+
usePaymentPolling
|
|
1163
|
+
};
|
|
1164
|
+
//# sourceMappingURL=chunk-BDDPLGUO.mjs.map
|