@yassirbenmoussa/aicommerce-sdk 1.9.3 → 1.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var marked = require('marked');
4
+
3
5
  var __defProp = Object.defineProperty;
4
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -767,7 +769,7 @@ function createWidgetStyles(config) {
767
769
  display: flex;
768
770
  align-items: center;
769
771
  gap: 2px;
770
- height: 24px;
772
+ height: 32px;
771
773
  cursor: pointer;
772
774
  width: 100%;
773
775
  }
@@ -775,9 +777,10 @@ function createWidgetStyles(config) {
775
777
  .aicommerce-waveform-bar {
776
778
  width: 3px;
777
779
  border-radius: 2px;
778
- min-height: 3px;
780
+ min-height: 4px;
779
781
  transition: background-color 0.1s;
780
- flex-shrink: 0;
782
+ flex: 0 0 3px;
783
+ display: block !important;
781
784
  }
782
785
 
783
786
  .aicommerce-audio-time {
@@ -1232,6 +1235,42 @@ function createWidgetStyles(config) {
1232
1235
  flex-shrink: 0;
1233
1236
  }
1234
1237
 
1238
+ .aicommerce-product-actions {
1239
+ display: flex;
1240
+ flex-direction: column;
1241
+ gap: 8px;
1242
+ margin-top: 8px;
1243
+ }
1244
+
1245
+ .aicommerce-product-actions .aicommerce-add-to-cart {
1246
+ margin-top: 0;
1247
+ flex: 1;
1248
+ }
1249
+
1250
+ .aicommerce-buy-now {
1251
+ width: 100%;
1252
+ padding: 8px 12px;
1253
+ background: transparent;
1254
+ color: var(--aic-primary);
1255
+ border: 1px solid var(--aic-primary);
1256
+ border-radius: 6px;
1257
+ font-size: 12px;
1258
+ font-weight: 500;
1259
+ cursor: pointer;
1260
+ transition: all 0.2s;
1261
+ display: flex;
1262
+ align-items: center;
1263
+ justify-content: center;
1264
+ }
1265
+
1266
+ .aicommerce-buy-now:hover {
1267
+ background: rgba(0, 0, 0, 0.05);
1268
+ }
1269
+
1270
+ .aicommerce-buy-now:active {
1271
+ transform: translateY(1px);
1272
+ }
1273
+
1235
1274
  /* Spinner animation for loading state */
1236
1275
  @keyframes aicommerce-spin {
1237
1276
  to { transform: rotate(360deg); }
@@ -1240,6 +1279,63 @@ function createWidgetStyles(config) {
1240
1279
  .aicommerce-spinner {
1241
1280
  animation: aicommerce-spin 1s linear infinite;
1242
1281
  }
1282
+
1283
+ /* ============================================
1284
+ Markdown Content Styles
1285
+ ============================================ */
1286
+ .aicommerce-message-content p {
1287
+ margin: 0 0 0.5em 0;
1288
+ }
1289
+ .aicommerce-message-content p:last-child {
1290
+ margin-bottom: 0;
1291
+ }
1292
+
1293
+ .aicommerce-message-content strong {
1294
+ font-weight: 600;
1295
+ }
1296
+
1297
+ .aicommerce-message-content em {
1298
+ font-style: italic;
1299
+ }
1300
+
1301
+ .aicommerce-message-content a {
1302
+ text-decoration: underline;
1303
+ color: inherit;
1304
+ }
1305
+ .aicommerce-user .aicommerce-message-content a {
1306
+ color: rgba(255, 255, 255, 0.95);
1307
+ }
1308
+ .aicommerce-assistant .aicommerce-message-content a {
1309
+ color: var(--aic-primary);
1310
+ }
1311
+
1312
+ .aicommerce-message-content code {
1313
+ background: rgba(0, 0, 0, 0.1);
1314
+ padding: 0.1em 0.4em;
1315
+ border-radius: 4px;
1316
+ font-family: 'Courier New', Courier, monospace;
1317
+ font-size: 0.9em;
1318
+ }
1319
+ .aicommerce-user .aicommerce-message-content code {
1320
+ background: rgba(255, 255, 255, 0.2);
1321
+ }
1322
+
1323
+ .aicommerce-message-content ul,
1324
+ .aicommerce-message-content ol {
1325
+ margin: 0.5em 0;
1326
+ padding-left: 1.5em;
1327
+ }
1328
+
1329
+ .aicommerce-message-content li {
1330
+ margin-bottom: 0.25em;
1331
+ }
1332
+
1333
+ .aicommerce-message-content blockquote {
1334
+ border-left: 3px solid var(--aic-primary);
1335
+ margin: 0.5em 0;
1336
+ padding-left: 1em;
1337
+ opacity: 0.9;
1338
+ }
1243
1339
  `;
1244
1340
  }
1245
1341
  function injectStyles(css) {
@@ -1271,14 +1367,15 @@ function createWidget(config) {
1271
1367
  const client = new exports.AICommerce({
1272
1368
  apiKey: config.apiKey,
1273
1369
  storeId: config.storeId,
1274
- baseUrl: config.baseUrl
1370
+ baseUrl: config.baseUrl,
1371
+ timeout: 12e4
1372
+ // 60s timeout for audio processing
1275
1373
  });
1276
1374
  const state = {
1277
1375
  isOpen: false,
1278
1376
  isLoading: true,
1279
1377
  isRecording: false,
1280
- messages: [],
1281
- storeConfig: null
1378
+ messages: []
1282
1379
  };
1283
1380
  let mediaRecorder = null;
1284
1381
  let audioChunks = [];
@@ -1309,7 +1406,7 @@ function createWidget(config) {
1309
1406
  return "https://api.aicommerce.dev";
1310
1407
  }
1311
1408
  async function initialize() {
1312
- state.storeConfig = await fetchStoreConfig();
1409
+ state.storeConfig = await fetchStoreConfig() || void 0;
1313
1410
  const displayMode = config.displayMode || "widget";
1314
1411
  const isEmbedded = displayMode === "embedded";
1315
1412
  resolvedConfig = {
@@ -1333,6 +1430,9 @@ function createWidget(config) {
1333
1430
  onClose: config.onClose,
1334
1431
  onProductClick: config.onProductClick,
1335
1432
  onAddToCart: config.onAddToCart,
1433
+ showAddToCart: config.showAddToCart,
1434
+ onBuyNow: config.onBuyNow,
1435
+ showBuyNow: config.showBuyNow,
1336
1436
  onMessage: config.onMessage
1337
1437
  };
1338
1438
  const styles = createWidgetStyles(resolvedConfig);
@@ -1392,6 +1492,62 @@ function createWidget(config) {
1392
1492
  throw new Error("Failed to add to cart");
1393
1493
  }
1394
1494
  document.dispatchEvent(new CustomEvent("cart:refresh"));
1495
+ document.dispatchEvent(new CustomEvent("cart:change"));
1496
+ document.dispatchEvent(new CustomEvent("cart:add"));
1497
+ document.dispatchEvent(new CustomEvent("cart:updated"));
1498
+ document.dispatchEvent(new CustomEvent("CartJS:change"));
1499
+ const win = window;
1500
+ if (win.Shopify && win.Shopify.onCartUpdate) {
1501
+ win.Shopify.onCartUpdate();
1502
+ }
1503
+ fetch("/cart.js").then((res) => res.json()).then((cart) => {
1504
+ document.dispatchEvent(new CustomEvent("cart:build", { detail: { cart } }));
1505
+ document.dispatchEvent(new CustomEvent("ajaxCart.afterCartLoad", { detail: { cart } }));
1506
+ const count = cart.item_count;
1507
+ const selectors = [
1508
+ ".cart-count",
1509
+ "#cart-count",
1510
+ ".site-header__cart-count",
1511
+ "[data-cart-count]",
1512
+ ".cart-count-bubble",
1513
+ ".header__cart-count",
1514
+ ".header-cart__count"
1515
+ ];
1516
+ selectors.forEach((selector) => {
1517
+ document.querySelectorAll(selector).forEach((el) => {
1518
+ el.textContent = count.toString();
1519
+ el.classList.remove("hide", "hidden");
1520
+ });
1521
+ });
1522
+ const cartIconBubble = document.getElementById("cart-icon-bubble");
1523
+ if (cartIconBubble) {
1524
+ let countBubble = cartIconBubble.querySelector(".cart-count-bubble");
1525
+ if (count > 0) {
1526
+ if (!countBubble) {
1527
+ countBubble = document.createElement("div");
1528
+ countBubble.className = "cart-count-bubble";
1529
+ countBubble.innerHTML = `<span aria-hidden="true">${count}</span><span class="visually-hidden">${count} item</span>`;
1530
+ cartIconBubble.appendChild(countBubble);
1531
+ } else {
1532
+ countBubble.innerHTML = `<span aria-hidden="true">${count}</span><span class="visually-hidden">${count} item</span>`;
1533
+ countBubble.classList.remove("hidden", "hide");
1534
+ }
1535
+ } else if (countBubble) {
1536
+ countBubble.classList.add("hidden");
1537
+ }
1538
+ }
1539
+ }).catch(() => {
1540
+ });
1541
+ }
1542
+ function handleBuyNow(variantId) {
1543
+ let numericVariantId = variantId;
1544
+ if (variantId.includes("gid://")) {
1545
+ const match = variantId.match(/\/(\d+)$/);
1546
+ if (match) {
1547
+ numericVariantId = match[1];
1548
+ }
1549
+ }
1550
+ window.location.href = `/cart/${numericVariantId}:1`;
1395
1551
  }
1396
1552
  function render() {
1397
1553
  if (!container) return;
@@ -1471,7 +1627,7 @@ function createWidget(config) {
1471
1627
  return `
1472
1628
  <div class="aicommerce-message aicommerce-${msg.role}">
1473
1629
  <div class="aicommerce-message-content ${isRtl ? "aicommerce-rtl" : "aicommerce-ltr"}">
1474
- ${msg.audioUrl ? renderAudioPlayer(msg, index, isUser) : escapeHtml(msg.content)}
1630
+ ${msg.audioUrl ? renderAudioPlayer(msg, index, isUser) : marked.marked.parse(msg.content)}
1475
1631
  </div>
1476
1632
  ${msg.products && msg.products.length > 0 ? `
1477
1633
  <div class="aicommerce-products">
@@ -1486,13 +1642,22 @@ function createWidget(config) {
1486
1642
  <span class="aicommerce-product-name" title="${escapeHtml(product.name)}">${escapeHtml(product.name)}</span>
1487
1643
  ${product.description ? `<p class="aicommerce-product-desc">${escapeHtml(product.description)}</p>` : ""}
1488
1644
  <span class="aicommerce-product-price">${formatPrice(product.price, product.currency)}</span>
1489
- <button class="aicommerce-add-to-cart" data-product-id="${product.id}">
1490
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1491
- <circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
1492
- <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
1493
- </svg>
1494
- ${resolvedConfig.addToCartText || "Add to Cart"}
1495
- </button>
1645
+ <div class="aicommerce-product-actions">
1646
+ ${resolvedConfig.showAddToCart !== false ? `
1647
+ <button class="aicommerce-add-to-cart" data-product-id="${product.id}">
1648
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1649
+ <circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
1650
+ <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
1651
+ </svg>
1652
+ ${resolvedConfig.addToCartText || "Add to Cart"}
1653
+ </button>
1654
+ ` : ""}
1655
+ ${resolvedConfig.showBuyNow !== false ? `
1656
+ <button class="aicommerce-buy-now" data-variant-id="${product.variantId || ""}">
1657
+ Buy Now
1658
+ </button>
1659
+ ` : ""}
1660
+ </div>
1496
1661
  </div>
1497
1662
  </div>
1498
1663
  `).join("")}
@@ -1519,7 +1684,7 @@ function createWidget(config) {
1519
1684
  return `
1520
1685
  <div class="aicommerce-message aicommerce-${msg.role}">
1521
1686
  <div class="aicommerce-message-content ${isRtl ? "aicommerce-rtl" : "aicommerce-ltr"}">
1522
- ${msg.audioUrl ? renderAudioPlayer(msg, index, isUser) : escapeHtml(msg.content)}
1687
+ ${msg.audioUrl ? renderAudioPlayer(msg, index, isUser) : marked.marked.parse(msg.content)}
1523
1688
  </div>
1524
1689
  ${msg.products && msg.products.length > 0 ? `
1525
1690
  <div class="aicommerce-products">
@@ -1534,13 +1699,22 @@ function createWidget(config) {
1534
1699
  <span class="aicommerce-product-name" title="${escapeHtml(product.name)}">${escapeHtml(product.name)}</span>
1535
1700
  ${product.description ? `<p class="aicommerce-product-desc">${escapeHtml(product.description)}</p>` : ""}
1536
1701
  <span class="aicommerce-product-price">${formatPrice(product.price, product.currency)}</span>
1537
- <button class="aicommerce-add-to-cart" data-product-id="${product.id}">
1538
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1539
- <circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
1540
- <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
1541
- </svg>
1542
- ${resolvedConfig.addToCartText || "Add to Cart"}
1543
- </button>
1702
+ <div class="aicommerce-product-actions">
1703
+ ${resolvedConfig.showAddToCart !== false ? `
1704
+ <button class="aicommerce-add-to-cart" data-product-id="${product.id}">
1705
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1706
+ <circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
1707
+ <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
1708
+ </svg>
1709
+ ${resolvedConfig.addToCartText || "Add to Cart"}
1710
+ </button>
1711
+ ` : ""}
1712
+ ${resolvedConfig.showBuyNow !== false ? `
1713
+ <button class="aicommerce-buy-now" data-variant-id="${product.variantId || ""}">
1714
+ Buy Now
1715
+ </button>
1716
+ ` : ""}
1717
+ </div>
1544
1718
  </div>
1545
1719
  </div>
1546
1720
  `).join("")}
@@ -1706,6 +1880,21 @@ function createWidget(config) {
1706
1880
  }
1707
1881
  });
1708
1882
  });
1883
+ const buyNowBtns = container.querySelectorAll(".aicommerce-buy-now");
1884
+ buyNowBtns.forEach((btn) => {
1885
+ btn.addEventListener("click", async (e) => {
1886
+ e.stopPropagation();
1887
+ const productCard = btn.closest(".aicommerce-product-card");
1888
+ const productId = productCard?.getAttribute("data-product-id");
1889
+ const variantId = btn.getAttribute("data-variant-id");
1890
+ const product = state.messages.flatMap((m) => m.products || []).find((p) => p.id === productId);
1891
+ if (product && resolvedConfig.onBuyNow) {
1892
+ await resolvedConfig.onBuyNow(product);
1893
+ } else if (variantId) {
1894
+ handleBuyNow(variantId);
1895
+ }
1896
+ });
1897
+ });
1709
1898
  const sliders = container.querySelectorAll(".aicommerce-products");
1710
1899
  sliders.forEach((slider) => {
1711
1900
  let isDown = false;
@@ -1797,8 +1986,9 @@ function createWidget(config) {
1797
1986
  try {
1798
1987
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1799
1988
  audioChunks = [];
1989
+ const mimeType = MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4";
1800
1990
  mediaRecorder = new MediaRecorder(stream, {
1801
- mimeType: MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4"
1991
+ mimeType
1802
1992
  });
1803
1993
  mediaRecorder.ondataavailable = (e) => {
1804
1994
  if (e.data.size > 0) {
@@ -1806,13 +1996,17 @@ function createWidget(config) {
1806
1996
  }
1807
1997
  };
1808
1998
  mediaRecorder.onstop = async () => {
1809
- stream.getTracks().forEach((track) => track.stop());
1810
- if (audioChunks.length > 0) {
1811
- const audioBlob = new Blob(audioChunks, { type: mediaRecorder?.mimeType || "audio/webm" });
1812
- await handleAudioSend(audioBlob);
1813
- }
1814
1999
  state.isRecording = false;
1815
2000
  render();
2001
+ stream.getTracks().forEach((track) => track.stop());
2002
+ try {
2003
+ if (audioChunks.length > 0) {
2004
+ const audioBlob = new Blob(audioChunks, { type: mimeType });
2005
+ await handleAudioSend(audioBlob);
2006
+ }
2007
+ } catch (error) {
2008
+ console.error("Failed to process audio:", error);
2009
+ }
1816
2010
  };
1817
2011
  mediaRecorder.start();
1818
2012
  state.isRecording = true;
@@ -1829,10 +2023,8 @@ function createWidget(config) {
1829
2023
  }
1830
2024
  async function handleAudioSend(audioBlob) {
1831
2025
  const audioUrl = URL.createObjectURL(audioBlob);
1832
- let waveformBars = Array(40).fill(10);
1833
2026
  let audioDuration = 0;
1834
2027
  try {
1835
- waveformBars = await analyzeAudio(audioBlob);
1836
2028
  const audio = new Audio(audioUrl);
1837
2029
  await new Promise((resolve) => {
1838
2030
  audio.onloadedmetadata = () => {
@@ -1841,6 +2033,12 @@ function createWidget(config) {
1841
2033
  };
1842
2034
  audio.onerror = () => resolve();
1843
2035
  });
2036
+ } catch (e) {
2037
+ console.error("Audio duration check failed", e);
2038
+ }
2039
+ let waveformBars = Array(40).fill(10);
2040
+ try {
2041
+ waveformBars = await analyzeAudio(audioBlob);
1844
2042
  } catch (e) {
1845
2043
  console.error("Audio analysis failed", e);
1846
2044
  }
@@ -2010,6 +2208,12 @@ var init_widget = __esm({
2010
2208
  "src/widget.ts"() {
2011
2209
  init_client();
2012
2210
  init_widget_styles();
2211
+ marked.marked.setOptions({
2212
+ breaks: true,
2213
+ // Convert \n to <br>
2214
+ gfm: true
2215
+ // GitHub Flavored Markdown
2216
+ });
2013
2217
  exports.AICommerceWidget = {
2014
2218
  init: createWidget,
2015
2219
  VERSION: "1.0.0"