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