@yassirbenmoussa/aicommerce-sdk 1.1.0 → 1.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/dist/ai-commerce.min.js +651 -2
- package/dist/index.cjs +1284 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +248 -9
- package/dist/index.d.ts +248 -9
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +1284 -1
- package/dist/index.mjs.map +1 -1
- package/dist/widget.min.js +653 -0
- package/dist/widget.min.js.map +1 -0
- package/package.json +3 -4
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,68 @@ var init_client = __esm({
|
|
|
31
31
|
AICommerce = class _AICommerce {
|
|
32
32
|
constructor(config) {
|
|
33
33
|
this.sessionToken = null;
|
|
34
|
+
// ============================================
|
|
35
|
+
// Products API
|
|
36
|
+
// ============================================
|
|
37
|
+
/**
|
|
38
|
+
* Products API namespace
|
|
39
|
+
*/
|
|
40
|
+
this.products = {
|
|
41
|
+
/**
|
|
42
|
+
* Create a new product
|
|
43
|
+
*/
|
|
44
|
+
create: async (product) => {
|
|
45
|
+
return this.request("/api/v1/products", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: JSON.stringify(product)
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Batch upsert products (create or update)
|
|
52
|
+
*/
|
|
53
|
+
batchUpsert: async (products) => {
|
|
54
|
+
return this.request("/api/v1/products", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: JSON.stringify({ products })
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* List products with pagination
|
|
61
|
+
*/
|
|
62
|
+
list: async (options) => {
|
|
63
|
+
const params = new URLSearchParams();
|
|
64
|
+
if (options?.page) params.set("page", String(options.page));
|
|
65
|
+
if (options?.perPage) params.set("perPage", String(options.perPage));
|
|
66
|
+
if (options?.search) params.set("search", options.search);
|
|
67
|
+
if (options?.categoryId) params.set("categoryId", options.categoryId);
|
|
68
|
+
if (options?.isActive !== void 0) params.set("isActive", String(options.isActive));
|
|
69
|
+
const query = params.toString();
|
|
70
|
+
return this.request(`/api/v1/products${query ? `?${query}` : ""}`);
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Get a single product by ID
|
|
74
|
+
*/
|
|
75
|
+
get: async (productId) => {
|
|
76
|
+
return this.request(`/api/v1/products/${productId}`);
|
|
77
|
+
},
|
|
78
|
+
/**
|
|
79
|
+
* Update a product
|
|
80
|
+
*/
|
|
81
|
+
update: async (productId, data) => {
|
|
82
|
+
return this.request(`/api/v1/products/${productId}`, {
|
|
83
|
+
method: "PUT",
|
|
84
|
+
body: JSON.stringify(data)
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Delete a product
|
|
89
|
+
*/
|
|
90
|
+
delete: async (productId) => {
|
|
91
|
+
return this.request(`/api/v1/products/${productId}`, {
|
|
92
|
+
method: "DELETE"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
34
96
|
if (!config.apiKey) {
|
|
35
97
|
throw new Error("AICommerce: apiKey is required");
|
|
36
98
|
}
|
|
@@ -127,6 +189,49 @@ var init_client = __esm({
|
|
|
127
189
|
}
|
|
128
190
|
return response;
|
|
129
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Send an audio message and get product recommendations
|
|
194
|
+
*
|
|
195
|
+
* @param audioBlob - Audio blob (from MediaRecorder or file input)
|
|
196
|
+
* @param context - Optional context for better recommendations
|
|
197
|
+
* @returns Chat response with AI reply and products
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* // Record audio using MediaRecorder
|
|
202
|
+
* const mediaRecorder = new MediaRecorder(stream);
|
|
203
|
+
* const chunks: Blob[] = [];
|
|
204
|
+
* mediaRecorder.ondataavailable = (e) => chunks.push(e.data);
|
|
205
|
+
* mediaRecorder.onstop = async () => {
|
|
206
|
+
* const audioBlob = new Blob(chunks, { type: 'audio/webm' });
|
|
207
|
+
* const response = await client.chatWithAudio(audioBlob);
|
|
208
|
+
* console.log(response.reply);
|
|
209
|
+
* };
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
async chatWithAudio(audioBlob, context) {
|
|
213
|
+
const arrayBuffer = await audioBlob.arrayBuffer();
|
|
214
|
+
const base64 = btoa(
|
|
215
|
+
new Uint8Array(arrayBuffer).reduce(
|
|
216
|
+
(data, byte) => data + String.fromCharCode(byte),
|
|
217
|
+
""
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
const request = {
|
|
221
|
+
audioBase64: base64,
|
|
222
|
+
audioMimeType: audioBlob.type || "audio/webm",
|
|
223
|
+
context,
|
|
224
|
+
sessionToken: this.sessionToken || void 0
|
|
225
|
+
};
|
|
226
|
+
const response = await this.request("/api/v1/chat", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify(request)
|
|
229
|
+
});
|
|
230
|
+
if (response.sessionToken) {
|
|
231
|
+
this.sessionToken = response.sessionToken;
|
|
232
|
+
}
|
|
233
|
+
return response;
|
|
234
|
+
}
|
|
130
235
|
/**
|
|
131
236
|
* Create a new chat session
|
|
132
237
|
*
|
|
@@ -157,6 +262,47 @@ var init_client = __esm({
|
|
|
157
262
|
setSessionToken(token) {
|
|
158
263
|
this.sessionToken = token;
|
|
159
264
|
}
|
|
265
|
+
// ============================================
|
|
266
|
+
// Upload API
|
|
267
|
+
// ============================================
|
|
268
|
+
/**
|
|
269
|
+
* Upload an image file
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```typescript
|
|
273
|
+
* // Upload from File input
|
|
274
|
+
* const file = document.querySelector('input[type="file"]').files[0];
|
|
275
|
+
* const result = await client.upload(file);
|
|
276
|
+
* console.log(result.url);
|
|
277
|
+
*
|
|
278
|
+
* // Upload and associate with product
|
|
279
|
+
* const result = await client.upload(file, { productId: 'prod_123', isPrimary: true });
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
async upload(file, options) {
|
|
283
|
+
const formData = new FormData();
|
|
284
|
+
formData.append("file", file);
|
|
285
|
+
if (options?.folder) formData.append("folder", options.folder);
|
|
286
|
+
if (options?.productId) formData.append("productId", options.productId);
|
|
287
|
+
if (options?.isPrimary) formData.append("isPrimary", "true");
|
|
288
|
+
const url = `${this.baseUrl}/api/v1/upload`;
|
|
289
|
+
const response = await fetch(url, {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: {
|
|
292
|
+
"X-API-Key": this.apiKey
|
|
293
|
+
},
|
|
294
|
+
body: formData
|
|
295
|
+
});
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
const errorData = await response.json().catch(() => ({}));
|
|
298
|
+
throw new AICommerceError(
|
|
299
|
+
errorData.message || errorData.error || `HTTP ${response.status}`,
|
|
300
|
+
errorData.code || "UPLOAD_ERROR",
|
|
301
|
+
response.status
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return response.json();
|
|
305
|
+
}
|
|
160
306
|
/**
|
|
161
307
|
* Static method for one-off chat requests
|
|
162
308
|
*
|
|
@@ -188,14 +334,1151 @@ var init_client = __esm({
|
|
|
188
334
|
}
|
|
189
335
|
});
|
|
190
336
|
|
|
337
|
+
// src/widget-styles.ts
|
|
338
|
+
function hexToRgb(hex) {
|
|
339
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
340
|
+
return result ? {
|
|
341
|
+
r: parseInt(result[1], 16),
|
|
342
|
+
g: parseInt(result[2], 16),
|
|
343
|
+
b: parseInt(result[3], 16)
|
|
344
|
+
} : { r: 99, g: 102, b: 241 };
|
|
345
|
+
}
|
|
346
|
+
function createWidgetStyles(config) {
|
|
347
|
+
const primary = config.primaryColor;
|
|
348
|
+
const rgb = hexToRgb(primary);
|
|
349
|
+
const isLeft = config.position === "bottom-left";
|
|
350
|
+
return `
|
|
351
|
+
/* AI Commerce Widget Styles */
|
|
352
|
+
#aicommerce-widget {
|
|
353
|
+
--aic-primary: ${primary};
|
|
354
|
+
--aic-primary-rgb: ${rgb.r}, ${rgb.g}, ${rgb.b};
|
|
355
|
+
--aic-primary-light: rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.1);
|
|
356
|
+
--aic-primary-dark: rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.9);
|
|
357
|
+
--aic-bg: #ffffff;
|
|
358
|
+
--aic-bg-secondary: #f8fafc;
|
|
359
|
+
--aic-text: #1e293b;
|
|
360
|
+
--aic-text-secondary: #64748b;
|
|
361
|
+
--aic-border: #e2e8f0;
|
|
362
|
+
--aic-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
363
|
+
--aic-radius: 16px;
|
|
364
|
+
--aic-z-index: ${config.zIndex};
|
|
365
|
+
|
|
366
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
|
367
|
+
font-size: 14px;
|
|
368
|
+
line-height: 1.5;
|
|
369
|
+
position: fixed;
|
|
370
|
+
bottom: 20px;
|
|
371
|
+
${isLeft ? "left: 20px;" : "right: 20px;"}
|
|
372
|
+
z-index: var(--aic-z-index);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* Dark theme */
|
|
376
|
+
#aicommerce-widget.aicommerce-theme-dark,
|
|
377
|
+
@media (prefers-color-scheme: dark) {
|
|
378
|
+
#aicommerce-widget.aicommerce-theme-auto {
|
|
379
|
+
--aic-bg: #1e293b;
|
|
380
|
+
--aic-bg-secondary: #0f172a;
|
|
381
|
+
--aic-text: #f1f5f9;
|
|
382
|
+
--aic-text-secondary: #94a3b8;
|
|
383
|
+
--aic-border: #334155;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Launcher Button */
|
|
388
|
+
.aicommerce-launcher {
|
|
389
|
+
width: 60px;
|
|
390
|
+
height: 60px;
|
|
391
|
+
border-radius: 50%;
|
|
392
|
+
background: linear-gradient(135deg, var(--aic-primary), var(--aic-primary-dark));
|
|
393
|
+
border: none;
|
|
394
|
+
cursor: pointer;
|
|
395
|
+
box-shadow: 0 4px 20px rgba(var(--aic-primary-rgb), 0.4);
|
|
396
|
+
display: flex;
|
|
397
|
+
align-items: center;
|
|
398
|
+
justify-content: center;
|
|
399
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
400
|
+
animation: aic-pulse 2s infinite;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.aicommerce-launcher:hover {
|
|
404
|
+
transform: scale(1.1);
|
|
405
|
+
box-shadow: 0 6px 30px rgba(var(--aic-primary-rgb), 0.5);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.aicommerce-launcher-icon {
|
|
409
|
+
font-size: 24px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.aicommerce-hidden {
|
|
413
|
+
display: none !important;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@keyframes aic-pulse {
|
|
417
|
+
0%, 100% { box-shadow: 0 4px 20px rgba(var(--aic-primary-rgb), 0.4); }
|
|
418
|
+
50% { box-shadow: 0 4px 30px rgba(var(--aic-primary-rgb), 0.6); }
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Chat Window */
|
|
422
|
+
.aicommerce-chat {
|
|
423
|
+
position: absolute;
|
|
424
|
+
bottom: 0;
|
|
425
|
+
${isLeft ? "left: 0;" : "right: 0;"}
|
|
426
|
+
width: 380px;
|
|
427
|
+
max-width: calc(100vw - 40px);
|
|
428
|
+
height: 600px;
|
|
429
|
+
max-height: calc(100vh - 100px);
|
|
430
|
+
background: var(--aic-bg);
|
|
431
|
+
border-radius: var(--aic-radius);
|
|
432
|
+
box-shadow: var(--aic-shadow);
|
|
433
|
+
display: flex;
|
|
434
|
+
flex-direction: column;
|
|
435
|
+
overflow: hidden;
|
|
436
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
437
|
+
transform-origin: bottom ${isLeft ? "left" : "right"};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.aicommerce-chat.aicommerce-closed {
|
|
441
|
+
opacity: 0;
|
|
442
|
+
transform: scale(0.9) translateY(20px);
|
|
443
|
+
pointer-events: none;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.aicommerce-chat.aicommerce-open {
|
|
447
|
+
opacity: 1;
|
|
448
|
+
transform: scale(1) translateY(0);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* Header */
|
|
452
|
+
.aicommerce-header {
|
|
453
|
+
background: linear-gradient(135deg, var(--aic-primary), var(--aic-primary-dark));
|
|
454
|
+
color: white;
|
|
455
|
+
padding: 16px 20px;
|
|
456
|
+
display: flex;
|
|
457
|
+
align-items: center;
|
|
458
|
+
justify-content: space-between;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.aicommerce-header-info {
|
|
462
|
+
display: flex;
|
|
463
|
+
align-items: center;
|
|
464
|
+
gap: 12px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.aicommerce-avatar {
|
|
468
|
+
width: 40px;
|
|
469
|
+
height: 40px;
|
|
470
|
+
border-radius: 50%;
|
|
471
|
+
background: rgba(255, 255, 255, 0.2);
|
|
472
|
+
display: flex;
|
|
473
|
+
align-items: center;
|
|
474
|
+
justify-content: center;
|
|
475
|
+
font-size: 20px;
|
|
476
|
+
overflow: hidden;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.aicommerce-avatar img {
|
|
480
|
+
width: 100%;
|
|
481
|
+
height: 100%;
|
|
482
|
+
object-fit: cover;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.aicommerce-header-text {
|
|
486
|
+
display: flex;
|
|
487
|
+
flex-direction: column;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.aicommerce-bot-name {
|
|
491
|
+
font-weight: 600;
|
|
492
|
+
font-size: 16px;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.aicommerce-status {
|
|
496
|
+
font-size: 12px;
|
|
497
|
+
opacity: 0.9;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.aicommerce-close {
|
|
501
|
+
width: 32px;
|
|
502
|
+
height: 32px;
|
|
503
|
+
border-radius: 50%;
|
|
504
|
+
background: rgba(255, 255, 255, 0.2);
|
|
505
|
+
border: none;
|
|
506
|
+
color: white;
|
|
507
|
+
cursor: pointer;
|
|
508
|
+
display: flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
justify-content: center;
|
|
511
|
+
font-size: 16px;
|
|
512
|
+
transition: background 0.2s;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.aicommerce-close:hover {
|
|
516
|
+
background: rgba(255, 255, 255, 0.3);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* Messages */
|
|
520
|
+
.aicommerce-messages {
|
|
521
|
+
flex: 1;
|
|
522
|
+
overflow-y: auto;
|
|
523
|
+
padding: 20px;
|
|
524
|
+
display: flex;
|
|
525
|
+
flex-direction: column;
|
|
526
|
+
gap: 16px;
|
|
527
|
+
background: var(--aic-bg-secondary);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.aicommerce-message {
|
|
531
|
+
max-width: 85%;
|
|
532
|
+
animation: aic-slide-in 0.3s ease-out;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.aicommerce-message.aicommerce-user {
|
|
536
|
+
align-self: flex-end;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.aicommerce-message.aicommerce-assistant {
|
|
540
|
+
align-self: flex-start;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.aicommerce-message-content {
|
|
544
|
+
padding: 12px 16px;
|
|
545
|
+
border-radius: 16px;
|
|
546
|
+
line-height: 1.5;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.aicommerce-user .aicommerce-message-content {
|
|
550
|
+
background: var(--aic-primary);
|
|
551
|
+
color: white;
|
|
552
|
+
border-bottom-right-radius: 4px;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.aicommerce-assistant .aicommerce-message-content {
|
|
556
|
+
background: var(--aic-bg);
|
|
557
|
+
color: var(--aic-text);
|
|
558
|
+
border-bottom-left-radius: 4px;
|
|
559
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
@keyframes aic-slide-in {
|
|
563
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
564
|
+
to { opacity: 1; transform: translateY(0); }
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/* Typing Indicator */
|
|
568
|
+
.aicommerce-typing {
|
|
569
|
+
display: flex;
|
|
570
|
+
gap: 4px;
|
|
571
|
+
padding: 12px 16px;
|
|
572
|
+
background: var(--aic-bg);
|
|
573
|
+
border-radius: 16px;
|
|
574
|
+
width: fit-content;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.aicommerce-typing span {
|
|
578
|
+
width: 8px;
|
|
579
|
+
height: 8px;
|
|
580
|
+
background: var(--aic-text-secondary);
|
|
581
|
+
border-radius: 50%;
|
|
582
|
+
animation: aic-bounce 1.4s infinite ease-in-out;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.aicommerce-typing span:nth-child(1) { animation-delay: -0.32s; }
|
|
586
|
+
.aicommerce-typing span:nth-child(2) { animation-delay: -0.16s; }
|
|
587
|
+
|
|
588
|
+
@keyframes aic-bounce {
|
|
589
|
+
0%, 80%, 100% { transform: scale(0); }
|
|
590
|
+
40% { transform: scale(1); }
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/* Product Cards */
|
|
594
|
+
.aicommerce-products {
|
|
595
|
+
display: flex;
|
|
596
|
+
gap: 16px;
|
|
597
|
+
margin-top: 12px;
|
|
598
|
+
overflow-x: auto;
|
|
599
|
+
padding-bottom: 16px;
|
|
600
|
+
width: 100%;
|
|
601
|
+
max-width: 100%;
|
|
602
|
+
cursor: grab;
|
|
603
|
+
user-select: none;
|
|
604
|
+
-webkit-user-select: none;
|
|
605
|
+
scrollbar-width: none; /* Firefox */
|
|
606
|
+
}
|
|
607
|
+
.aicommerce-products::-webkit-scrollbar {
|
|
608
|
+
display: none; /* Chrome/Safari */
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.aicommerce-product-card {
|
|
612
|
+
flex-shrink: 0;
|
|
613
|
+
width: 280px;
|
|
614
|
+
background: var(--aic-bg);
|
|
615
|
+
border-radius: 12px;
|
|
616
|
+
overflow: hidden;
|
|
617
|
+
cursor: pointer;
|
|
618
|
+
transition: all 0.2s;
|
|
619
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.aicommerce-product-card:hover {
|
|
623
|
+
transform: translateY(-2px);
|
|
624
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.aicommerce-product-image {
|
|
628
|
+
width: 100%;
|
|
629
|
+
aspect-ratio: 16/9;
|
|
630
|
+
height: auto;
|
|
631
|
+
object-fit: cover;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.aicommerce-product-placeholder {
|
|
635
|
+
width: 100%;
|
|
636
|
+
aspect-ratio: 16/9;
|
|
637
|
+
height: auto;
|
|
638
|
+
background: var(--aic-bg-secondary);
|
|
639
|
+
display: flex;
|
|
640
|
+
align-items: center;
|
|
641
|
+
justify-content: center;
|
|
642
|
+
font-size: 32px;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.aicommerce-product-info {
|
|
646
|
+
padding: 10px;
|
|
647
|
+
display: flex;
|
|
648
|
+
flex-direction: column;
|
|
649
|
+
gap: 4px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.aicommerce-product-name {
|
|
653
|
+
font-weight: 500;
|
|
654
|
+
font-size: 13px;
|
|
655
|
+
color: var(--aic-text);
|
|
656
|
+
white-space: nowrap;
|
|
657
|
+
overflow: hidden;
|
|
658
|
+
text-overflow: ellipsis;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.aicommerce-product-price {
|
|
662
|
+
font-weight: 600;
|
|
663
|
+
font-size: 14px;
|
|
664
|
+
color: var(--aic-primary);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.aicommerce-product-desc {
|
|
668
|
+
font-size: 12px;
|
|
669
|
+
color: var(--aic-text-secondary);
|
|
670
|
+
line-height: 1.4;
|
|
671
|
+
display: -webkit-box;
|
|
672
|
+
-webkit-line-clamp: 2;
|
|
673
|
+
-webkit-box-orient: vertical;
|
|
674
|
+
overflow: hidden;
|
|
675
|
+
margin-top: 4px;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/* Audio Player */
|
|
679
|
+
.aicommerce-audio-player {
|
|
680
|
+
display: flex;
|
|
681
|
+
align-items: center;
|
|
682
|
+
gap: 12px;
|
|
683
|
+
min-width: 240px;
|
|
684
|
+
padding: 4px 0;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.aicommerce-audio-btn {
|
|
688
|
+
width: 40px;
|
|
689
|
+
height: 40px;
|
|
690
|
+
border-radius: 50%;
|
|
691
|
+
display: flex;
|
|
692
|
+
align-items: center;
|
|
693
|
+
justify-content: center;
|
|
694
|
+
border: none;
|
|
695
|
+
cursor: pointer;
|
|
696
|
+
transition: all 0.2s;
|
|
697
|
+
background: rgba(255, 255, 255, 0.25);
|
|
698
|
+
color: white;
|
|
699
|
+
flex-shrink: 0;
|
|
700
|
+
padding: 0;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.aicommerce-audio-btn:hover {
|
|
704
|
+
background: rgba(255, 255, 255, 0.35);
|
|
705
|
+
transform: scale(1.05);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.aicommerce-audio-btn:active {
|
|
709
|
+
transform: scale(0.95);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* Invert colors for assistant (since background is white/gray) */
|
|
713
|
+
.aicommerce-assistant .aicommerce-audio-btn {
|
|
714
|
+
background: var(--aic-primary);
|
|
715
|
+
color: white;
|
|
716
|
+
}
|
|
717
|
+
.aicommerce-assistant .aicommerce-audio-btn:hover {
|
|
718
|
+
background: var(--aic-primary-dark);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.aicommerce-audio-waveform {
|
|
722
|
+
flex: 1;
|
|
723
|
+
display: flex;
|
|
724
|
+
flex-direction: column;
|
|
725
|
+
gap: 6px;
|
|
726
|
+
min-width: 0; /* Prevent overflow */
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.aicommerce-waveform-bars {
|
|
730
|
+
display: flex;
|
|
731
|
+
align-items: center;
|
|
732
|
+
gap: 2px;
|
|
733
|
+
height: 24px;
|
|
734
|
+
cursor: pointer;
|
|
735
|
+
width: 100%;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.aicommerce-waveform-bar {
|
|
739
|
+
width: 3px;
|
|
740
|
+
border-radius: 2px;
|
|
741
|
+
min-height: 3px;
|
|
742
|
+
transition: background-color 0.1s;
|
|
743
|
+
flex-shrink: 0;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.aicommerce-audio-time {
|
|
747
|
+
display: flex;
|
|
748
|
+
justify-content: space-between;
|
|
749
|
+
font-size: 11px;
|
|
750
|
+
font-weight: 500;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.aicommerce-user .aicommerce-audio-time {
|
|
754
|
+
color: rgba(255, 255, 255, 0.8);
|
|
755
|
+
}
|
|
756
|
+
.aicommerce-assistant .aicommerce-audio-time {
|
|
757
|
+
color: var(--aic-text-secondary);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/* RTL Support */
|
|
761
|
+
.aicommerce-rtl {
|
|
762
|
+
direction: rtl;
|
|
763
|
+
text-align: right;
|
|
764
|
+
}
|
|
765
|
+
.aicommerce-ltr {
|
|
766
|
+
direction: ltr;
|
|
767
|
+
text-align: left;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* Input Area */
|
|
771
|
+
.aicommerce-input-container {
|
|
772
|
+
padding: 16px 20px;
|
|
773
|
+
background: var(--aic-bg);
|
|
774
|
+
border-top: 1px solid var(--aic-border);
|
|
775
|
+
display: flex;
|
|
776
|
+
gap: 12px;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.aicommerce-input {
|
|
780
|
+
flex: 1;
|
|
781
|
+
padding: 12px 16px;
|
|
782
|
+
border: 1px solid var(--aic-border);
|
|
783
|
+
border-radius: 24px;
|
|
784
|
+
background: var(--aic-bg-secondary);
|
|
785
|
+
color: var(--aic-text);
|
|
786
|
+
font-size: 14px;
|
|
787
|
+
outline: none;
|
|
788
|
+
transition: all 0.2s;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.aicommerce-input:focus {
|
|
792
|
+
border-color: var(--aic-primary);
|
|
793
|
+
box-shadow: 0 0 0 3px var(--aic-primary-light);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.aicommerce-input::placeholder {
|
|
797
|
+
color: var(--aic-text-secondary);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.aicommerce-send {
|
|
801
|
+
width: 44px;
|
|
802
|
+
height: 44px;
|
|
803
|
+
border-radius: 50%;
|
|
804
|
+
background: var(--aic-primary);
|
|
805
|
+
border: none;
|
|
806
|
+
color: white;
|
|
807
|
+
cursor: pointer;
|
|
808
|
+
display: flex;
|
|
809
|
+
align-items: center;
|
|
810
|
+
justify-content: center;
|
|
811
|
+
transition: all 0.2s;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.aicommerce-send:hover:not(:disabled) {
|
|
815
|
+
background: var(--aic-primary-dark);
|
|
816
|
+
transform: scale(1.05);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.aicommerce-send:disabled {
|
|
820
|
+
opacity: 0.6;
|
|
821
|
+
cursor: not-allowed;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/* Microphone Button */
|
|
825
|
+
.aicommerce-mic {
|
|
826
|
+
width: 44px;
|
|
827
|
+
height: 44px;
|
|
828
|
+
border-radius: 50%;
|
|
829
|
+
background: var(--aic-bg-secondary);
|
|
830
|
+
border: 1px solid var(--aic-border);
|
|
831
|
+
color: var(--aic-text-secondary);
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
display: flex;
|
|
834
|
+
align-items: center;
|
|
835
|
+
justify-content: center;
|
|
836
|
+
transition: all 0.2s;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.aicommerce-mic:hover:not(:disabled) {
|
|
840
|
+
background: var(--aic-primary-light);
|
|
841
|
+
border-color: var(--aic-primary);
|
|
842
|
+
color: var(--aic-primary);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.aicommerce-mic.aicommerce-recording {
|
|
846
|
+
background: #ef4444;
|
|
847
|
+
border-color: #ef4444;
|
|
848
|
+
color: white;
|
|
849
|
+
animation: aic-recording-pulse 1s infinite;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.aicommerce-mic:disabled {
|
|
853
|
+
opacity: 0.6;
|
|
854
|
+
cursor: not-allowed;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
@keyframes aic-recording-pulse {
|
|
858
|
+
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
|
|
859
|
+
50% { transform: scale(1.05); box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/* Mobile Responsive */
|
|
863
|
+
@media (max-width: 420px) {
|
|
864
|
+
#aicommerce-widget {
|
|
865
|
+
bottom: 16px;
|
|
866
|
+
${isLeft ? "left: 16px;" : "right: 16px;"}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.aicommerce-chat {
|
|
870
|
+
width: calc(100vw - 32px);
|
|
871
|
+
height: calc(100vh - 100px);
|
|
872
|
+
border-radius: 12px;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.aicommerce-launcher {
|
|
876
|
+
width: 56px;
|
|
877
|
+
height: 56px;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/* Scrollbar */
|
|
882
|
+
.aicommerce-messages::-webkit-scrollbar {
|
|
883
|
+
width: 6px;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.aicommerce-messages::-webkit-scrollbar-track {
|
|
887
|
+
background: transparent;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.aicommerce-messages::-webkit-scrollbar-thumb {
|
|
891
|
+
background: var(--aic-border);
|
|
892
|
+
border-radius: 3px;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.aicommerce-messages::-webkit-scrollbar-thumb:hover {
|
|
896
|
+
background: var(--aic-text-secondary);
|
|
897
|
+
}
|
|
898
|
+
`;
|
|
899
|
+
}
|
|
900
|
+
function injectStyles(css) {
|
|
901
|
+
const style = document.createElement("style");
|
|
902
|
+
style.id = "aicommerce-widget-styles";
|
|
903
|
+
style.textContent = css;
|
|
904
|
+
const existing = document.getElementById("aicommerce-widget-styles");
|
|
905
|
+
if (existing) {
|
|
906
|
+
existing.remove();
|
|
907
|
+
}
|
|
908
|
+
document.head.appendChild(style);
|
|
909
|
+
return style;
|
|
910
|
+
}
|
|
911
|
+
var init_widget_styles = __esm({
|
|
912
|
+
"src/widget-styles.ts"() {
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
// src/widget.ts
|
|
917
|
+
var widget_exports = {};
|
|
918
|
+
__export(widget_exports, {
|
|
919
|
+
AICommerceWidget: () => AICommerceWidget,
|
|
920
|
+
createWidget: () => createWidget
|
|
921
|
+
});
|
|
922
|
+
function createWidget(config) {
|
|
923
|
+
if (!config.apiKey) {
|
|
924
|
+
throw new Error("AICommerceWidget: apiKey is required");
|
|
925
|
+
}
|
|
926
|
+
const client = new AICommerce({
|
|
927
|
+
apiKey: config.apiKey,
|
|
928
|
+
baseUrl: config.baseUrl
|
|
929
|
+
});
|
|
930
|
+
const state = {
|
|
931
|
+
isOpen: false,
|
|
932
|
+
isLoading: true,
|
|
933
|
+
isRecording: false,
|
|
934
|
+
messages: [],
|
|
935
|
+
storeConfig: null
|
|
936
|
+
};
|
|
937
|
+
let mediaRecorder = null;
|
|
938
|
+
let audioChunks = [];
|
|
939
|
+
let container = null;
|
|
940
|
+
let styleElement = null;
|
|
941
|
+
let resolvedConfig;
|
|
942
|
+
async function fetchStoreConfig() {
|
|
943
|
+
try {
|
|
944
|
+
const baseUrl = config.baseUrl || detectBaseUrl();
|
|
945
|
+
const response = await fetch(`${baseUrl}/api/v1/store`, {
|
|
946
|
+
headers: { "x-api-key": config.apiKey }
|
|
947
|
+
});
|
|
948
|
+
if (!response.ok) return null;
|
|
949
|
+
const data = await response.json();
|
|
950
|
+
return data.store;
|
|
951
|
+
} catch (error) {
|
|
952
|
+
console.error("Failed to fetch store config:", error);
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
function detectBaseUrl() {
|
|
957
|
+
if (typeof window !== "undefined") {
|
|
958
|
+
const script = document.querySelector("script[data-aicommerce-url]");
|
|
959
|
+
if (script) {
|
|
960
|
+
return script.getAttribute("data-aicommerce-url") || "";
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return "https://api.aicommerce.dev";
|
|
964
|
+
}
|
|
965
|
+
async function initialize() {
|
|
966
|
+
state.storeConfig = await fetchStoreConfig();
|
|
967
|
+
resolvedConfig = {
|
|
968
|
+
apiKey: config.apiKey,
|
|
969
|
+
baseUrl: config.baseUrl || detectBaseUrl(),
|
|
970
|
+
position: config.position || "bottom-right",
|
|
971
|
+
theme: config.theme || "auto",
|
|
972
|
+
primaryColor: config.primaryColor || state.storeConfig?.primaryColor || "#6366f1",
|
|
973
|
+
welcomeMessage: config.welcomeMessage || state.storeConfig?.welcomeMessage || "Hi! How can I help you find the perfect product today?",
|
|
974
|
+
botName: config.botName || state.storeConfig?.chatBotName || "Shopping Assistant",
|
|
975
|
+
zIndex: config.zIndex || 9999,
|
|
976
|
+
buttonText: config.buttonText || "\u{1F4AC}",
|
|
977
|
+
hideLauncher: config.hideLauncher || false,
|
|
978
|
+
onOpen: config.onOpen,
|
|
979
|
+
onClose: config.onClose,
|
|
980
|
+
onProductClick: config.onProductClick,
|
|
981
|
+
onMessage: config.onMessage
|
|
982
|
+
};
|
|
983
|
+
const styles = createWidgetStyles(resolvedConfig);
|
|
984
|
+
styleElement = injectStyles(styles);
|
|
985
|
+
container = document.createElement("div");
|
|
986
|
+
container.id = "aicommerce-widget";
|
|
987
|
+
container.className = `aicommerce-widget aicommerce-${resolvedConfig.position} aicommerce-theme-${resolvedConfig.theme}`;
|
|
988
|
+
document.body.appendChild(container);
|
|
989
|
+
render();
|
|
990
|
+
state.messages.push({
|
|
991
|
+
role: "assistant",
|
|
992
|
+
content: resolvedConfig.welcomeMessage
|
|
993
|
+
});
|
|
994
|
+
state.isLoading = false;
|
|
995
|
+
render();
|
|
996
|
+
}
|
|
997
|
+
function render() {
|
|
998
|
+
if (!container) return;
|
|
999
|
+
const html = `
|
|
1000
|
+
${!resolvedConfig.hideLauncher ? `
|
|
1001
|
+
<button class="aicommerce-launcher ${state.isOpen ? "aicommerce-hidden" : ""}" aria-label="Open chat">
|
|
1002
|
+
<span class="aicommerce-launcher-icon">${resolvedConfig.buttonText}</span>
|
|
1003
|
+
</button>
|
|
1004
|
+
` : ""}
|
|
1005
|
+
|
|
1006
|
+
<div class="aicommerce-chat ${state.isOpen ? "aicommerce-open" : "aicommerce-closed"}">
|
|
1007
|
+
<div class="aicommerce-header">
|
|
1008
|
+
<div class="aicommerce-header-info">
|
|
1009
|
+
<div class="aicommerce-avatar">
|
|
1010
|
+
${state.storeConfig?.logo ? `<img src="${state.storeConfig.logo}" alt="${resolvedConfig.botName}" />` : `<span>\u{1F916}</span>`}
|
|
1011
|
+
</div>
|
|
1012
|
+
<div class="aicommerce-header-text">
|
|
1013
|
+
<span class="aicommerce-bot-name">${resolvedConfig.botName}</span>
|
|
1014
|
+
<span class="aicommerce-status">Online</span>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
<button class="aicommerce-close" aria-label="Close chat">\u2715</button>
|
|
1018
|
+
</div>
|
|
1019
|
+
|
|
1020
|
+
<div class="aicommerce-messages">
|
|
1021
|
+
${state.messages.map((msg, index) => {
|
|
1022
|
+
const isRtl = isArabic(msg.content);
|
|
1023
|
+
const isUser = msg.role === "user";
|
|
1024
|
+
return `
|
|
1025
|
+
<div class="aicommerce-message aicommerce-${msg.role}">
|
|
1026
|
+
<div class="aicommerce-message-content ${isRtl ? "aicommerce-rtl" : "aicommerce-ltr"}">
|
|
1027
|
+
${msg.audioUrl ? renderAudioPlayer(msg, index, isUser) : escapeHtml(msg.content)}
|
|
1028
|
+
</div>
|
|
1029
|
+
${msg.products && msg.products.length > 0 ? `
|
|
1030
|
+
<div class="aicommerce-products">
|
|
1031
|
+
${msg.products.map((product) => `
|
|
1032
|
+
<div class="aicommerce-product-card" data-product-id="${product.id}">
|
|
1033
|
+
${product.image || product.imageUrl ? `
|
|
1034
|
+
<img src="${product.image || product.imageUrl}" alt="${escapeHtml(product.name)}" class="aicommerce-product-image" />
|
|
1035
|
+
` : `
|
|
1036
|
+
<div class="aicommerce-product-placeholder">\u{1F4E6}</div>
|
|
1037
|
+
`}
|
|
1038
|
+
<div class="aicommerce-product-info">
|
|
1039
|
+
<span class="aicommerce-product-name" title="${escapeHtml(product.name)}">${escapeHtml(product.name)}</span>
|
|
1040
|
+
${product.description ? `<p class="aicommerce-product-desc">${escapeHtml(product.description)}</p>` : ""}
|
|
1041
|
+
<span class="aicommerce-product-price">${formatPrice(product.price, product.currency)}</span>
|
|
1042
|
+
</div>
|
|
1043
|
+
</div>
|
|
1044
|
+
`).join("")}
|
|
1045
|
+
</div>
|
|
1046
|
+
` : ""}
|
|
1047
|
+
</div>
|
|
1048
|
+
`;
|
|
1049
|
+
}).join("")}
|
|
1050
|
+
${state.isLoading ? `
|
|
1051
|
+
<div class="aicommerce-message aicommerce-assistant">
|
|
1052
|
+
<div class="aicommerce-typing">
|
|
1053
|
+
<span></span><span></span><span></span>
|
|
1054
|
+
</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
` : ""}
|
|
1057
|
+
</div>
|
|
1058
|
+
|
|
1059
|
+
<div class="aicommerce-input-container">
|
|
1060
|
+
<input
|
|
1061
|
+
type="text"
|
|
1062
|
+
class="aicommerce-input"
|
|
1063
|
+
placeholder="Type your message..."
|
|
1064
|
+
${state.isLoading || state.isRecording ? "disabled" : ""}
|
|
1065
|
+
/>
|
|
1066
|
+
<button class="aicommerce-mic ${state.isRecording ? "aicommerce-recording" : ""}" ${state.isLoading ? "disabled" : ""} aria-label="${state.isRecording ? "Stop recording" : "Voice input"}">
|
|
1067
|
+
${state.isRecording ? `
|
|
1068
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
1069
|
+
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
|
1070
|
+
</svg>
|
|
1071
|
+
` : `
|
|
1072
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1073
|
+
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
|
|
1074
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
|
1075
|
+
<line x1="12" y1="19" x2="12" y2="23"/>
|
|
1076
|
+
<line x1="8" y1="23" x2="16" y2="23"/>
|
|
1077
|
+
</svg>
|
|
1078
|
+
`}
|
|
1079
|
+
</button>
|
|
1080
|
+
<button class="aicommerce-send" ${state.isLoading || state.isRecording ? "disabled" : ""} aria-label="Send message">
|
|
1081
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1082
|
+
<path d="M22 2L11 13M22 2L15 22L11 13M22 2L2 9L11 13"/>
|
|
1083
|
+
</svg>
|
|
1084
|
+
</button>
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
`;
|
|
1088
|
+
container.innerHTML = html;
|
|
1089
|
+
attachEventListeners();
|
|
1090
|
+
const messagesEl = container.querySelector(".aicommerce-messages");
|
|
1091
|
+
if (messagesEl) {
|
|
1092
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function renderAudioPlayer(msg, index, isUser) {
|
|
1096
|
+
return `
|
|
1097
|
+
<div class="aicommerce-audio-player" data-message-index="${index}">
|
|
1098
|
+
<button class="aicommerce-audio-btn">
|
|
1099
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
|
1100
|
+
</button>
|
|
1101
|
+
<div class="aicommerce-audio-waveform">
|
|
1102
|
+
<div class="aicommerce-waveform-bars">
|
|
1103
|
+
${(msg.waveformBars || Array(40).fill(10)).map((height) => `
|
|
1104
|
+
<div class="aicommerce-waveform-bar" style="height: ${height}%; background-color: ${isUser ? "rgba(255,255,255,0.4)" : "rgba(99,102,241,0.3)"}"></div>
|
|
1105
|
+
`).join("")}
|
|
1106
|
+
</div>
|
|
1107
|
+
<div class="aicommerce-audio-time">
|
|
1108
|
+
<span class="aicommerce-current-time">0:00</span>
|
|
1109
|
+
<span>${formatTime(msg.audioDuration || 0)}</span>
|
|
1110
|
+
</div>
|
|
1111
|
+
</div>
|
|
1112
|
+
<audio src="${msg.audioUrl}" preload="metadata"></audio>
|
|
1113
|
+
</div>
|
|
1114
|
+
`;
|
|
1115
|
+
}
|
|
1116
|
+
function attachEventListeners() {
|
|
1117
|
+
if (!container) return;
|
|
1118
|
+
const launcherEl = container.querySelector(".aicommerce-launcher");
|
|
1119
|
+
if (launcherEl) {
|
|
1120
|
+
launcherEl.addEventListener("click", () => open());
|
|
1121
|
+
}
|
|
1122
|
+
const closeEl = container.querySelector(".aicommerce-close");
|
|
1123
|
+
if (closeEl) {
|
|
1124
|
+
closeEl.addEventListener("click", () => close());
|
|
1125
|
+
}
|
|
1126
|
+
const inputEl = container.querySelector(".aicommerce-input");
|
|
1127
|
+
const sendEl = container.querySelector(".aicommerce-send");
|
|
1128
|
+
if (inputEl) {
|
|
1129
|
+
inputEl.addEventListener("keypress", (e) => {
|
|
1130
|
+
if (e.key === "Enter" && inputEl.value.trim()) {
|
|
1131
|
+
handleSend(inputEl.value.trim());
|
|
1132
|
+
inputEl.value = "";
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
if (sendEl && inputEl) {
|
|
1137
|
+
sendEl.addEventListener("click", () => {
|
|
1138
|
+
if (inputEl.value.trim()) {
|
|
1139
|
+
handleSend(inputEl.value.trim());
|
|
1140
|
+
inputEl.value = "";
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
const micEl = container.querySelector(".aicommerce-mic");
|
|
1145
|
+
if (micEl) {
|
|
1146
|
+
micEl.addEventListener("click", () => handleMicClick());
|
|
1147
|
+
}
|
|
1148
|
+
const productCards = container.querySelectorAll(".aicommerce-product-card");
|
|
1149
|
+
productCards.forEach((card) => {
|
|
1150
|
+
card.addEventListener("click", () => {
|
|
1151
|
+
const productId = card.getAttribute("data-product-id");
|
|
1152
|
+
const product = state.messages.flatMap((m) => m.products || []).find((p) => p.id === productId);
|
|
1153
|
+
if (product && resolvedConfig.onProductClick) {
|
|
1154
|
+
resolvedConfig.onProductClick(product);
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
const sliders = container.querySelectorAll(".aicommerce-products");
|
|
1159
|
+
sliders.forEach((slider) => {
|
|
1160
|
+
let isDown = false;
|
|
1161
|
+
let startX = 0;
|
|
1162
|
+
let scrollLeft = 0;
|
|
1163
|
+
slider.addEventListener("mousedown", (e) => {
|
|
1164
|
+
isDown = true;
|
|
1165
|
+
slider.style.cursor = "grabbing";
|
|
1166
|
+
startX = e.pageX - slider.offsetLeft;
|
|
1167
|
+
scrollLeft = slider.scrollLeft;
|
|
1168
|
+
});
|
|
1169
|
+
slider.addEventListener("mouseleave", () => {
|
|
1170
|
+
isDown = false;
|
|
1171
|
+
slider.style.cursor = "grab";
|
|
1172
|
+
});
|
|
1173
|
+
slider.addEventListener("mouseup", () => {
|
|
1174
|
+
isDown = false;
|
|
1175
|
+
slider.style.cursor = "grab";
|
|
1176
|
+
});
|
|
1177
|
+
slider.addEventListener("mousemove", (e) => {
|
|
1178
|
+
if (!isDown) return;
|
|
1179
|
+
e.preventDefault();
|
|
1180
|
+
const x = e.pageX - slider.offsetLeft;
|
|
1181
|
+
const walk = (x - startX) * 2;
|
|
1182
|
+
slider.scrollLeft = scrollLeft - walk;
|
|
1183
|
+
});
|
|
1184
|
+
});
|
|
1185
|
+
const audioPlayers = container.querySelectorAll(".aicommerce-audio-player");
|
|
1186
|
+
audioPlayers.forEach((player) => {
|
|
1187
|
+
const audio = player.querySelector("audio");
|
|
1188
|
+
const btn = player.querySelector(".aicommerce-audio-btn");
|
|
1189
|
+
const bars = player.querySelectorAll(".aicommerce-waveform-bar");
|
|
1190
|
+
const timeDisplay = player.querySelector(".aicommerce-current-time");
|
|
1191
|
+
if (!audio || !btn) return;
|
|
1192
|
+
btn.addEventListener("click", () => {
|
|
1193
|
+
const isPlaying = !audio.paused;
|
|
1194
|
+
if (!isPlaying) {
|
|
1195
|
+
container?.querySelectorAll("audio").forEach((a) => {
|
|
1196
|
+
if (a !== audio && !a.paused) {
|
|
1197
|
+
a.pause();
|
|
1198
|
+
const parent = a.closest(".aicommerce-audio-player");
|
|
1199
|
+
const otherBtn = parent?.querySelector(".aicommerce-audio-btn");
|
|
1200
|
+
if (otherBtn) otherBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`;
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
audio.play();
|
|
1204
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>`;
|
|
1205
|
+
} else {
|
|
1206
|
+
audio.pause();
|
|
1207
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`;
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
audio.addEventListener("timeupdate", () => {
|
|
1211
|
+
if (timeDisplay) timeDisplay.textContent = formatTime(audio.currentTime);
|
|
1212
|
+
if (audio.duration) {
|
|
1213
|
+
const progress = audio.currentTime / audio.duration * 100;
|
|
1214
|
+
bars.forEach((bar, i) => {
|
|
1215
|
+
const barPos = i / bars.length * 100;
|
|
1216
|
+
if (barPos <= progress) {
|
|
1217
|
+
bar.style.backgroundColor = player.closest(".aicommerce-user") ? "rgba(255,255,255,1)" : "var(--aic-primary)";
|
|
1218
|
+
} else {
|
|
1219
|
+
bar.style.backgroundColor = player.closest(".aicommerce-user") ? "rgba(255,255,255,0.4)" : "rgba(99,102,241,0.3)";
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
audio.addEventListener("ended", () => {
|
|
1225
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`;
|
|
1226
|
+
});
|
|
1227
|
+
const waveform = player.querySelector(".aicommerce-waveform-bars");
|
|
1228
|
+
if (waveform) {
|
|
1229
|
+
waveform.addEventListener("click", (e) => {
|
|
1230
|
+
const rect = waveform.getBoundingClientRect();
|
|
1231
|
+
const x = e.clientX - rect.left;
|
|
1232
|
+
const percent = x / rect.width;
|
|
1233
|
+
if (audio.duration) {
|
|
1234
|
+
audio.currentTime = percent * audio.duration;
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
async function handleMicClick() {
|
|
1241
|
+
if (state.isRecording) {
|
|
1242
|
+
if (mediaRecorder && mediaRecorder.state !== "inactive") {
|
|
1243
|
+
mediaRecorder.stop();
|
|
1244
|
+
}
|
|
1245
|
+
} else {
|
|
1246
|
+
try {
|
|
1247
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1248
|
+
audioChunks = [];
|
|
1249
|
+
mediaRecorder = new MediaRecorder(stream, {
|
|
1250
|
+
mimeType: MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4"
|
|
1251
|
+
});
|
|
1252
|
+
mediaRecorder.ondataavailable = (e) => {
|
|
1253
|
+
if (e.data.size > 0) {
|
|
1254
|
+
audioChunks.push(e.data);
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
mediaRecorder.onstop = async () => {
|
|
1258
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
1259
|
+
if (audioChunks.length > 0) {
|
|
1260
|
+
const audioBlob = new Blob(audioChunks, { type: mediaRecorder?.mimeType || "audio/webm" });
|
|
1261
|
+
await handleAudioSend(audioBlob);
|
|
1262
|
+
}
|
|
1263
|
+
state.isRecording = false;
|
|
1264
|
+
render();
|
|
1265
|
+
};
|
|
1266
|
+
mediaRecorder.start();
|
|
1267
|
+
state.isRecording = true;
|
|
1268
|
+
render();
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
console.error("Failed to start recording:", error);
|
|
1271
|
+
state.messages.push({
|
|
1272
|
+
role: "assistant",
|
|
1273
|
+
content: "Unable to access microphone. Please check your permissions."
|
|
1274
|
+
});
|
|
1275
|
+
render();
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async function handleAudioSend(audioBlob) {
|
|
1280
|
+
const audioUrl = URL.createObjectURL(audioBlob);
|
|
1281
|
+
let waveformBars = Array(40).fill(10);
|
|
1282
|
+
let audioDuration = 0;
|
|
1283
|
+
try {
|
|
1284
|
+
waveformBars = await analyzeAudio(audioBlob);
|
|
1285
|
+
const audio = new Audio(audioUrl);
|
|
1286
|
+
await new Promise((resolve) => {
|
|
1287
|
+
audio.onloadedmetadata = () => {
|
|
1288
|
+
audioDuration = audio.duration;
|
|
1289
|
+
resolve();
|
|
1290
|
+
};
|
|
1291
|
+
audio.onerror = () => resolve();
|
|
1292
|
+
});
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
console.error("Audio analysis failed", e);
|
|
1295
|
+
}
|
|
1296
|
+
state.messages.push({
|
|
1297
|
+
role: "user",
|
|
1298
|
+
content: "Voice message",
|
|
1299
|
+
audioUrl,
|
|
1300
|
+
audioDuration,
|
|
1301
|
+
waveformBars
|
|
1302
|
+
});
|
|
1303
|
+
state.isLoading = true;
|
|
1304
|
+
render();
|
|
1305
|
+
try {
|
|
1306
|
+
const response = await client.chatWithAudio(audioBlob);
|
|
1307
|
+
state.messages.push({
|
|
1308
|
+
role: "assistant",
|
|
1309
|
+
content: response.reply,
|
|
1310
|
+
products: response.products
|
|
1311
|
+
});
|
|
1312
|
+
if (resolvedConfig.onMessage) {
|
|
1313
|
+
resolvedConfig.onMessage("Voice message", response);
|
|
1314
|
+
}
|
|
1315
|
+
return response;
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
state.messages.push({
|
|
1318
|
+
role: "assistant",
|
|
1319
|
+
content: "Sorry, I encountered an error processing your voice message. Please try again."
|
|
1320
|
+
});
|
|
1321
|
+
throw error;
|
|
1322
|
+
} finally {
|
|
1323
|
+
state.isLoading = false;
|
|
1324
|
+
render();
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function isArabic(text) {
|
|
1328
|
+
return /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(text);
|
|
1329
|
+
}
|
|
1330
|
+
function formatTime(seconds) {
|
|
1331
|
+
const mins = Math.floor(seconds / 60);
|
|
1332
|
+
const secs = Math.floor(seconds % 60);
|
|
1333
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
1334
|
+
}
|
|
1335
|
+
async function analyzeAudio(blob) {
|
|
1336
|
+
try {
|
|
1337
|
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
1338
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
1339
|
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
1340
|
+
const channelData = audioBuffer.getChannelData(0);
|
|
1341
|
+
const bars = 40;
|
|
1342
|
+
const step = Math.floor(channelData.length / bars);
|
|
1343
|
+
const calculatedBars = [];
|
|
1344
|
+
for (let i = 0; i < bars; i++) {
|
|
1345
|
+
const start = i * step;
|
|
1346
|
+
const end = start + step;
|
|
1347
|
+
let sum = 0;
|
|
1348
|
+
for (let j = start; j < end; j++) {
|
|
1349
|
+
if (channelData[j]) sum += channelData[j] * channelData[j];
|
|
1350
|
+
}
|
|
1351
|
+
const rms = Math.sqrt(sum / step);
|
|
1352
|
+
const height = Math.min(100, Math.max(10, rms * 400));
|
|
1353
|
+
calculatedBars.push(height);
|
|
1354
|
+
}
|
|
1355
|
+
return calculatedBars;
|
|
1356
|
+
} catch (e) {
|
|
1357
|
+
console.error("Analysis error", e);
|
|
1358
|
+
return Array.from({ length: 40 }, () => 20 + Math.random() * 60);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
async function handleSend(message) {
|
|
1362
|
+
state.messages.push({ role: "user", content: message });
|
|
1363
|
+
state.isLoading = true;
|
|
1364
|
+
render();
|
|
1365
|
+
try {
|
|
1366
|
+
const response = await client.chat(message);
|
|
1367
|
+
state.messages.push({
|
|
1368
|
+
role: "assistant",
|
|
1369
|
+
content: response.reply,
|
|
1370
|
+
products: response.products
|
|
1371
|
+
});
|
|
1372
|
+
if (resolvedConfig.onMessage) {
|
|
1373
|
+
resolvedConfig.onMessage(message, response);
|
|
1374
|
+
}
|
|
1375
|
+
return response;
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
state.messages.push({
|
|
1378
|
+
role: "assistant",
|
|
1379
|
+
content: "Sorry, I encountered an error. Please try again."
|
|
1380
|
+
});
|
|
1381
|
+
throw error;
|
|
1382
|
+
} finally {
|
|
1383
|
+
state.isLoading = false;
|
|
1384
|
+
render();
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function open() {
|
|
1388
|
+
state.isOpen = true;
|
|
1389
|
+
render();
|
|
1390
|
+
resolvedConfig.onOpen?.();
|
|
1391
|
+
setTimeout(() => {
|
|
1392
|
+
const input = container?.querySelector(".aicommerce-input");
|
|
1393
|
+
input?.focus();
|
|
1394
|
+
}, 100);
|
|
1395
|
+
}
|
|
1396
|
+
function close() {
|
|
1397
|
+
state.isOpen = false;
|
|
1398
|
+
render();
|
|
1399
|
+
resolvedConfig.onClose?.();
|
|
1400
|
+
}
|
|
1401
|
+
function toggle() {
|
|
1402
|
+
if (state.isOpen) {
|
|
1403
|
+
close();
|
|
1404
|
+
} else {
|
|
1405
|
+
open();
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
function destroy() {
|
|
1409
|
+
if (container) {
|
|
1410
|
+
container.remove();
|
|
1411
|
+
container = null;
|
|
1412
|
+
}
|
|
1413
|
+
if (styleElement) {
|
|
1414
|
+
styleElement.remove();
|
|
1415
|
+
styleElement = null;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function updateConfig(newConfig) {
|
|
1419
|
+
Object.assign(resolvedConfig, newConfig);
|
|
1420
|
+
if (newConfig.primaryColor) {
|
|
1421
|
+
const styles = createWidgetStyles(resolvedConfig);
|
|
1422
|
+
if (styleElement) {
|
|
1423
|
+
styleElement.textContent = styles;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
render();
|
|
1427
|
+
}
|
|
1428
|
+
function escapeHtml(text) {
|
|
1429
|
+
const div = document.createElement("div");
|
|
1430
|
+
div.textContent = text;
|
|
1431
|
+
return div.innerHTML;
|
|
1432
|
+
}
|
|
1433
|
+
function formatPrice(price, currency) {
|
|
1434
|
+
const symbols = {
|
|
1435
|
+
USD: "$",
|
|
1436
|
+
EUR: "\u20AC",
|
|
1437
|
+
GBP: "\xA3",
|
|
1438
|
+
MAD: "DH",
|
|
1439
|
+
SAR: "SAR",
|
|
1440
|
+
AED: "AED",
|
|
1441
|
+
JPY: "\xA5",
|
|
1442
|
+
CNY: "\xA5"
|
|
1443
|
+
};
|
|
1444
|
+
const symbol = symbols[currency || "USD"] || currency || "$";
|
|
1445
|
+
return `${price.toFixed(2)} ${symbol}`;
|
|
1446
|
+
}
|
|
1447
|
+
initialize();
|
|
1448
|
+
return {
|
|
1449
|
+
open,
|
|
1450
|
+
close,
|
|
1451
|
+
toggle,
|
|
1452
|
+
destroy,
|
|
1453
|
+
sendMessage: handleSend,
|
|
1454
|
+
updateConfig
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
var AICommerceWidget;
|
|
1458
|
+
var init_widget = __esm({
|
|
1459
|
+
"src/widget.ts"() {
|
|
1460
|
+
init_client();
|
|
1461
|
+
init_widget_styles();
|
|
1462
|
+
AICommerceWidget = {
|
|
1463
|
+
init: createWidget,
|
|
1464
|
+
VERSION: "1.0.0"
|
|
1465
|
+
};
|
|
1466
|
+
if (typeof window !== "undefined") {
|
|
1467
|
+
window.AICommerceWidget = AICommerceWidget;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
|
|
191
1472
|
// src/index.ts
|
|
192
1473
|
init_client();
|
|
1474
|
+
init_widget();
|
|
193
1475
|
var VERSION = "1.0.0";
|
|
194
1476
|
if (typeof window !== "undefined") {
|
|
195
1477
|
window.AICommerce = (init_client(), __toCommonJS(client_exports)).AICommerce;
|
|
196
1478
|
window.AICommerceError = (init_client(), __toCommonJS(client_exports)).AICommerceError;
|
|
1479
|
+
window.AICommerceWidget = (init_widget(), __toCommonJS(widget_exports)).AICommerceWidget;
|
|
197
1480
|
}
|
|
198
1481
|
|
|
199
|
-
export { AICommerce, AICommerceError, VERSION };
|
|
1482
|
+
export { AICommerce, AICommerceError, AICommerceWidget, VERSION, createWidget };
|
|
200
1483
|
//# sourceMappingURL=index.mjs.map
|
|
201
1484
|
//# sourceMappingURL=index.mjs.map
|