brainerce 1.32.0 → 1.33.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 CHANGED
@@ -4867,11 +4867,12 @@ import { BrainerceBot } from 'brainerce/bot';
4867
4867
 
4868
4868
  const bot = await BrainerceBot.mount({
4869
4869
  connectionId: 'vc_abc123',
4870
- // Optional: make the cards' "Add to cart" button use YOUR cart so your
4871
- // header count stays in sync. Return false to make the widget fall back
4872
- // to navigating to the product page.
4873
- onAddToCart: async ({ productId, quantity }) => {
4874
- await client.smartAddToCart({ productId, quantity });
4870
+ // Optional: make cart adds (card buttons, the in-card variant picker, and
4871
+ // bot-initiated adds) go through YOUR cart so your header count stays in
4872
+ // sync. variantId is null for simple products. Return false to make the
4873
+ // widget fall back to navigating to the product page.
4874
+ onAddToCart: async ({ productId, variantId, quantity }) => {
4875
+ await client.smartAddToCart({ productId, variantId: variantId ?? undefined, quantity });
4875
4876
  return true;
4876
4877
  },
4877
4878
  });
@@ -4879,9 +4880,11 @@ const bot = await BrainerceBot.mount({
4879
4880
  bot?.destroy(); // optional teardown
4880
4881
  ```
4881
4882
 
4882
- The widget persists an anonymous session in `localStorage`, restores conversations on revisit, streams answers, and shows product recommendation cards (image, price, add-to-cart / view buttons). It also includes a leave-a-message form that lands in the merchant's Inquiries inbox.
4883
+ The widget persists an anonymous session in `localStorage`, restores conversations on revisit, streams answers, and shows product recommendation cards (image, price, add-to-cart / view buttons). Multi-variant products get an **in-card variant picker** (attribute chips, live variant price/image) so shoppers can choose options and add without leaving the chat; shoppers can also just ask the assistant to add an item ("add the moka to my cart") and it happens through the same chain. A leave-a-message form lands in the merchant's Inquiries inbox, and zero-result searches feed the merchant's "unmet demand" analytics.
4884
+
4885
+ **Add to cart resolution** (never a dead button): the widget first calls your `onAddToCart` option; without one it dispatches a cancelable `brainerce:bot:add-to-cart` `CustomEvent` on `window` (`detail: { productId, variantId, quantity, connectionId }` — call `preventDefault()` after handling it); if nothing handles either, it navigates to the product page. Products too complex for in-chat picking (3+ attribute dimensions or 25+ variants) always navigate. Aside from your own cart handler, the widget is read-only by design — shoppers can never mutate the store through it.
4883
4886
 
4884
- **Add to cart resolution** (never a dead button): the widget first calls your `onAddToCart` option; without one it dispatches a cancelable `brainerce:bot:add-to-cart` `CustomEvent` on `window` (`detail: { productId, quantity, connectionId }` — call `preventDefault()` after handling it); if nothing handles either, it navigates to the product page. Products that need option selection always navigate. Aside from your own cart handler, the widget is read-only by design — shoppers can never mutate the store through it.
4887
+ Merchant-side display controls (Studio Storefront Bot): chat size (compact / full screen / shopper's choice), auto-open, position, and whether shoppers may expand the window (`allowExpand`).
4885
4888
 
4886
4889
  ## Webhooks
4887
4890
 
@@ -1,5 +1,5 @@
1
- "use strict";(()=>{var A="https://api.brainerce.com",R=new Set(["he","ar"]),$={en:{online:"Online",placeholder:"Ask anything\u2026",error:"Something went wrong \u2014 please try again.",leaveMessage:"Leave a message for the team",yourEmail:"Your email",yourMessage:"Your message",send:"Send",sent:"Thanks! The team will get back to you by email.",close:"Close",expand:"Expand",collapse:"Collapse",searching:"Searching the store\u2026",addToCart:"Add to cart",added:"Added",view:"View",chooseOptions:"View product",results:"From the store"},he:{online:"\u05DE\u05D7\u05D5\u05D1\u05E8",placeholder:"\u05E9\u05D0\u05DC\u05D5 \u05D0\u05D5\u05EA\u05D9 \u05D4\u05DB\u05DC\u2026",error:"\u05DE\u05E9\u05D4\u05D5 \u05D4\u05E9\u05EA\u05D1\u05E9 \u2014 \u05E0\u05E1\u05D5 \u05E9\u05D5\u05D1.",leaveMessage:"\u05D4\u05E9\u05D0\u05D9\u05E8\u05D5 \u05D4\u05D5\u05D3\u05E2\u05D4 \u05DC\u05E6\u05D5\u05D5\u05EA",yourEmail:"\u05D4\u05D0\u05D9\u05DE\u05D9\u05D9\u05DC \u05E9\u05DC\u05DB\u05DD",yourMessage:"\u05D4\u05D4\u05D5\u05D3\u05E2\u05D4 \u05E9\u05DC\u05DB\u05DD",send:"\u05E9\u05DC\u05D9\u05D7\u05D4",sent:"\u05EA\u05D5\u05D3\u05D4! \u05D4\u05E6\u05D5\u05D5\u05EA \u05D9\u05D7\u05D6\u05D5\u05E8 \u05D0\u05DC\u05D9\u05DB\u05DD \u05D1\u05DE\u05D9\u05D9\u05DC.",close:"\u05E1\u05D2\u05D9\u05E8\u05D4",expand:"\u05D4\u05E8\u05D7\u05D1\u05D4",collapse:"\u05DB\u05D9\u05D5\u05D5\u05E5",searching:"\u05DE\u05D7\u05E4\u05E9 \u05D1\u05D7\u05E0\u05D5\u05EA\u2026",addToCart:"\u05D4\u05D5\u05E1\u05E4\u05D4 \u05DC\u05E1\u05DC",added:"\u05E0\u05D5\u05E1\u05E3",view:"\u05E6\u05E4\u05D9\u05D9\u05D4",chooseOptions:"\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",results:"\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA"}},U={chat:'<svg viewBox="0 0 24 24" fill="none"><path d="M12 3C7.03 3 3 6.58 3 11c0 2.04.86 3.9 2.28 5.32-.15 1.23-.62 2.39-1.1 3.21-.13.23.05.52.31.47 1.56-.27 3.07-.93 4.13-1.62A10.6 10.6 0 0 0 12 19c4.97 0 9-3.58 9-8s-4.03-8-9-8Z" fill="currentColor"/></svg>',close:'<svg viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',expand:'<svg viewBox="0 0 24 24" fill="none"><path d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',collapse:'<svg viewBox="0 0 24 24" fill="none"><path d="M20 10h-6V4M4 14h6v6M20 4l-6 6M4 20l6-6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',mail:'<svg viewBox="0 0 24 24" fill="none"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke="currentColor" stroke-width="1.7"/><path d="m4.5 7.5 7.5 5.5 7.5-5.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',send:'<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',cart:'<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',check:'<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',arrow:'<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'},z=typeof DOMParser<"u"?new DOMParser:null;function f(b){let e=(U[b]??U.chat).replace("<svg ",'<svg xmlns="http://www.w3.org/2000/svg" '),t=z?.parseFromString(e,"image/svg+xml")?.documentElement;return!t||t.nodeName==="parsererror"?document.createTextNode(""):(t.setAttribute("aria-hidden","true"),t.setAttribute("class","bb-ic"),t)}function O(b){let e=new Uint8Array(16);crypto.getRandomValues(e);let n=btoa(String.fromCharCode(...e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");return`${b}${n}`}function T(b){return/^\/(?!\/)/.test(b)||/^https?:\/\//i.test(b)}var S=/\*\*([^*\n]+)\*\*|\[([^\]\n]+)\]\(([^)\s]+)\)/g;function H(b,e){let n=0;S.lastIndex=0;for(let t=S.exec(e);t;t=S.exec(e)){if(t.index>n&&b.appendChild(document.createTextNode(e.slice(n,t.index))),t[1]!==void 0){let i=document.createElement("strong");i.textContent=t[1],b.appendChild(i)}else if(T(t[3])){let i=document.createElement("a");i.href=t[3],i.target="_blank",i.rel="noopener noreferrer",i.textContent=t[2],b.appendChild(i)}else b.appendChild(document.createTextNode(t[2]));n=t.index+t[0].length}n<e.length&&b.appendChild(document.createTextNode(e.slice(n)))}function B(b,e){b.replaceChildren();let n=e.split(`
2
- `),t=null,i=null;for(let o of n){let r=/^\s*[-•*]\s+(.*)$/.exec(o);if(r){i=null,t||(t=document.createElement("ul"),b.appendChild(t));let d=document.createElement("li");H(d,r[1]),t.appendChild(d);continue}if(t=null,!o.trim()){i=null;continue}i?i.appendChild(document.createElement("br")):(i=document.createElement("p"),b.appendChild(i)),H(i,o)}}var N=class b{constructor(e){this.settings={enabled:!1};this.locale="en";this.sessionId=null;this.conversationId=null;this.busy=!1;this.opened=!1;this.expanded=!1;this.destroyed=!1;this.pendingText="";this.cardsRow=null;this.cardIds=new Set;this.connectionId=e.connectionId,this.baseUrl=(e.baseUrl||A).replace(/\/$/,""),this.storageKey=`brainerce-bot:${this.connectionId}`,this.onAddToCart=e.onAddToCart}static async mount(e){if(!e?.connectionId)return console.warn("[BrainerceBot] connectionId is required"),null;let n=new b(e);return await n.boot(e.target??document.body)?n:null}destroy(){this.destroyed=!0,this.host?.remove()}async boot(e){try{let n=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/settings`);if(!n.ok)return!1;this.settings=await n.json()}catch{return!1}return this.settings.enabled?(this.locale=this.settings.languages?.[0]??"en",this.restoreIds(),this.render(e),this.settings.displayMode==="auto_open"&&setTimeout(()=>!this.destroyed&&this.open(),3e3),!0):!1}t(e){return($[this.locale]??$.en)[e]??$.en[e]??e}restoreIds(){try{let e=localStorage.getItem(this.storageKey);if(e){let n=JSON.parse(e);this.sessionId=n.sessionId??null,this.conversationId=n.conversationId??null}}catch{}}persistIds(){try{localStorage.setItem(this.storageKey,JSON.stringify({sessionId:this.sessionId,conversationId:this.conversationId}))}catch{}}css(e,n,t){let i=this.settings.bubbleShape==="square",o=i?"14px":"22px",r=i?"8px":"15px",d="5px",a=this.settings.displayMode??"floating",l=a==="side_rail",s=a==="full_screen";return`
1
+ "use strict";(()=>{var R="https://api.brainerce.com",O=new Set(["he","ar"]),S={en:{online:"Online",placeholder:"Ask anything\u2026",error:"Something went wrong \u2014 please try again.",leaveMessage:"Leave a message for the team",yourEmail:"Your email",yourMessage:"Your message",send:"Send",sent:"Thanks! The team will get back to you by email.",close:"Close",expand:"Expand",collapse:"Collapse",searching:"Searching the store\u2026",addToCart:"Add to cart",added:"Added",view:"View",chooseOptions:"View product",results:"From the store",poweredBy:"Powered by Brainerce",addFailed:"I couldn\u2019t add that to the cart \u2014 try the button on the product card."},he:{online:"\u05DE\u05D7\u05D5\u05D1\u05E8",placeholder:"\u05E9\u05D0\u05DC\u05D5 \u05D0\u05D5\u05EA\u05D9 \u05D4\u05DB\u05DC\u2026",error:"\u05DE\u05E9\u05D4\u05D5 \u05D4\u05E9\u05EA\u05D1\u05E9 \u2014 \u05E0\u05E1\u05D5 \u05E9\u05D5\u05D1.",leaveMessage:"\u05D4\u05E9\u05D0\u05D9\u05E8\u05D5 \u05D4\u05D5\u05D3\u05E2\u05D4 \u05DC\u05E6\u05D5\u05D5\u05EA",yourEmail:"\u05D4\u05D0\u05D9\u05DE\u05D9\u05D9\u05DC \u05E9\u05DC\u05DB\u05DD",yourMessage:"\u05D4\u05D4\u05D5\u05D3\u05E2\u05D4 \u05E9\u05DC\u05DB\u05DD",send:"\u05E9\u05DC\u05D9\u05D7\u05D4",sent:"\u05EA\u05D5\u05D3\u05D4! \u05D4\u05E6\u05D5\u05D5\u05EA \u05D9\u05D7\u05D6\u05D5\u05E8 \u05D0\u05DC\u05D9\u05DB\u05DD \u05D1\u05DE\u05D9\u05D9\u05DC.",close:"\u05E1\u05D2\u05D9\u05E8\u05D4",expand:"\u05D4\u05E8\u05D7\u05D1\u05D4",collapse:"\u05DB\u05D9\u05D5\u05D5\u05E5",searching:"\u05DE\u05D7\u05E4\u05E9 \u05D1\u05D7\u05E0\u05D5\u05EA\u2026",addToCart:"\u05D4\u05D5\u05E1\u05E4\u05D4 \u05DC\u05E1\u05DC",added:"\u05E0\u05D5\u05E1\u05E3",view:"\u05E6\u05E4\u05D9\u05D9\u05D4",chooseOptions:"\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",results:"\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA",poweredBy:"\u05DE\u05D5\u05E4\u05E2\u05DC \u05E2\u05DC \u05D9\u05D3\u05D9 Brainerce",addFailed:"\u05DC\u05D0 \u05D4\u05E6\u05DC\u05D7\u05EA\u05D9 \u05DC\u05D4\u05D5\u05E1\u05D9\u05E3 \u05DC\u05E1\u05DC \u2014 \u05E0\u05E1\u05D5 \u05D3\u05E8\u05DA \u05D4\u05DB\u05E4\u05EA\u05D5\u05E8 \u05D1\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D4\u05DE\u05D5\u05E6\u05E8."}},H={chat:'<svg viewBox="0 0 24 24" fill="none"><path d="M12 3C7.03 3 3 6.58 3 11c0 2.04.86 3.9 2.28 5.32-.15 1.23-.62 2.39-1.1 3.21-.13.23.05.52.31.47 1.56-.27 3.07-.93 4.13-1.62A10.6 10.6 0 0 0 12 19c4.97 0 9-3.58 9-8s-4.03-8-9-8Z" fill="currentColor"/></svg>',close:'<svg viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',expand:'<svg viewBox="0 0 24 24" fill="none"><path d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',collapse:'<svg viewBox="0 0 24 24" fill="none"><path d="M20 10h-6V4M4 14h6v6M20 4l-6 6M4 20l6-6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',mail:'<svg viewBox="0 0 24 24" fill="none"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke="currentColor" stroke-width="1.7"/><path d="m4.5 7.5 7.5 5.5 7.5-5.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',send:'<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',cart:'<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',check:'<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',arrow:'<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',brand:'<svg viewBox="0 0 24 24"><defs><linearGradient id="bb-brand-g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#34d399"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="url(#bb-brand-g)"/><text x="12" y="17" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14" font-weight="700" fill="#fff">B</text></svg>'},z=typeof DOMParser<"u"?new DOMParser:null;function m(b){let e=(H[b]??H.chat).replace("<svg ",'<svg xmlns="http://www.w3.org/2000/svg" '),t=z?.parseFromString(e,"image/svg+xml")?.documentElement;return!t||t.nodeName==="parsererror"?document.createTextNode(""):(t.setAttribute("aria-hidden","true"),t.setAttribute("class","bb-ic"),t)}function P(b){let e=new Uint8Array(16);crypto.getRandomValues(e);let n=btoa(String.fromCharCode(...e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");return`${b}${n}`}function T(b){return/^\/(?!\/)/.test(b)||/^https?:\/\//i.test(b)}var $=/\*\*([^*\n]+)\*\*|\[([^\]\n]+)\]\(([^)\s]+)\)/g;function A(b,e){let n=0;$.lastIndex=0;for(let t=$.exec(e);t;t=$.exec(e)){if(t.index>n&&b.appendChild(document.createTextNode(e.slice(n,t.index))),t[1]!==void 0){let i=document.createElement("strong");i.textContent=t[1],b.appendChild(i)}else if(T(t[3])){let i=document.createElement("a");i.href=t[3],i.target="_blank",i.rel="noopener noreferrer",i.textContent=t[2],b.appendChild(i)}else b.appendChild(document.createTextNode(t[2]));n=t.index+t[0].length}n<e.length&&b.appendChild(document.createTextNode(e.slice(n)))}function U(b,e){b.replaceChildren();let n=e.split(`
2
+ `),t=null,i=null;for(let o of n){let r=/^\s*[-•*]\s+(.*)$/.exec(o);if(r){i=null,t||(t=document.createElement("ul"),b.appendChild(t));let d=document.createElement("li");A(d,r[1]),t.appendChild(d);continue}if(t=null,!o.trim()){i=null;continue}i?i.appendChild(document.createElement("br")):(i=document.createElement("p"),b.appendChild(i)),A(i,o)}}var M=class b{constructor(e){this.settings={enabled:!1};this.locale="en";this.sessionId=null;this.conversationId=null;this.busy=!1;this.opened=!1;this.expanded=!1;this.destroyed=!1;this.pendingText="";this.cardsRow=null;this.cardIds=new Set;this.prevBodyOverflow=null;this.connectionId=e.connectionId,this.baseUrl=(e.baseUrl||R).replace(/\/$/,""),this.storageKey=`brainerce-bot:${this.connectionId}`,this.onAddToCart=e.onAddToCart}static async mount(e){if(!e?.connectionId)return console.warn("[BrainerceBot] connectionId is required"),null;let n=new b(e);return await n.boot(e.target??document.body)?n:null}destroy(){this.destroyed=!0,this.prevBodyOverflow!==null&&(document.body.style.overflow=this.prevBodyOverflow,this.prevBodyOverflow=null),this.host?.remove()}async boot(e){try{let n=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/settings`);if(!n.ok)return!1;this.settings=await n.json()}catch{return!1}return this.settings.enabled?(this.locale=this.settings.languages?.[0]??"en",this.restoreIds(),this.render(e),this.settings.displayMode==="auto_open"&&setTimeout(()=>!this.destroyed&&this.open(),3e3),!0):!1}t(e){return(S[this.locale]??S.en)[e]??S.en[e]??e}restoreIds(){try{let e=localStorage.getItem(this.storageKey);if(e){let n=JSON.parse(e);this.sessionId=n.sessionId??null,this.conversationId=n.conversationId??null}}catch{}}persistIds(){try{localStorage.setItem(this.storageKey,JSON.stringify({sessionId:this.sessionId,conversationId:this.conversationId}))}catch{}}css(e,n,t){let i=this.settings.bubbleShape==="square",o=i?"14px":"22px",r=i?"8px":"15px",d="5px",a=this.settings.displayMode??"floating",l=a==="side_rail",s=a==="full_screen";return`
3
3
  :host { all: initial; }
4
4
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0;
5
5
  font-family: -apple-system, "SF Pro Text", "Segoe UI Variable Text", "Segoe UI", system-ui, "Helvetica Neue", sans-serif;
@@ -237,6 +237,15 @@
237
237
  .bb-send .bb-ic { width: 17px; height: 17px; ${n==="rtl"?"transform: scaleX(-1);":""} }
238
238
  .bb-send:hover { filter: brightness(1.08); transform: scale(1.06); }
239
239
  .bb-send:disabled { opacity: .4; pointer-events: none; }
240
- `}render(e){let n=this.settings.accentColor||"#6366F1",t=R.has(this.locale)?"rtl":"ltr",i=this.settings.position==="start"?"left":"right",o=t==="rtl"?i==="left"?"right":"left":i,r=this.settings.displayName||"Assistant";this.host=document.createElement("div"),this.host.setAttribute("data-brainerce-bot",this.connectionId),this.root=this.host.attachShadow({mode:"open"});let d=document.createElement("style");d.textContent=this.css(n,t,o),this.root.appendChild(d);let a=document.createElement("div");a.className="bb",this.root.appendChild(a);let l=document.createElement("div");l.className="bb-scrim",l.addEventListener("click",()=>{this.expanded?this.toggleExpand():this.close()}),a.appendChild(l),this.windowEl=document.createElement("div"),this.windowEl.className="bb-window",a.appendChild(this.windowEl);let s=document.createElement("div");s.className="bb-header";let h=document.createElement("span");if(h.className="bb-avatar",this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",h.appendChild(p)}else h.appendChild(document.createTextNode(r.charAt(0).toUpperCase()));let u=document.createElement("span");u.className="bb-head-main";let g=document.createElement("span");g.className="bb-name",g.textContent=r;let v=document.createElement("span");v.className="bb-status",v.textContent=this.t("online"),u.appendChild(g),u.appendChild(v);let x=document.createElement("span");x.className="bb-actions",(this.settings.displayMode??"floating")!=="full_screen"&&this.settings.allowExpand!==!1&&(this.expandBtn=this.iconButton("expand",this.t("expand"),()=>this.toggleExpand()),x.appendChild(this.expandBtn)),x.appendChild(this.iconButton("mail",this.t("leaveMessage"),()=>this.toggleEscalation())),x.appendChild(this.iconButton("close",this.t("close"),()=>this.close())),s.appendChild(h),s.appendChild(u),s.appendChild(x),this.windowEl.appendChild(s),this.messagesEl=document.createElement("div"),this.messagesEl.className="bb-msgs",this.windowEl.appendChild(this.messagesEl),this.chipsEl=document.createElement("div"),this.chipsEl.className="bb-chips";for(let p of this.settings.starterQuestions??[]){let I=document.createElement("button");I.className="bb-chip",I.textContent=p,I.addEventListener("click",()=>this.send(p)),this.chipsEl.appendChild(I)}this.windowEl.appendChild(this.chipsEl);let c=document.createElement("div");c.className="bb-esc";let E=document.createElement("span");E.className="bb-esc-title",E.textContent=this.t("leaveMessage");let y=document.createElement("input");y.type="email",y.name="email",y.placeholder=this.t("yourEmail");let C=document.createElement("textarea");C.name="message",C.rows=2,C.placeholder=this.t("yourMessage");let k=document.createElement("button");k.type="button",k.className="bb-esc-send",k.textContent=this.t("send"),k.addEventListener("click",()=>this.submitEscalation(c)),c.appendChild(E),c.appendChild(y),c.appendChild(C),c.appendChild(k),this.windowEl.appendChild(c);let m=document.createElement("div");if(m.className="bb-composer",this.inputEl=document.createElement("textarea"),this.inputEl.className="bb-input",this.inputEl.rows=1,this.inputEl.placeholder=this.t("placeholder"),this.inputEl.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),this.send(this.inputEl?.value??""))}),this.inputEl.addEventListener("input",()=>this.syncSendState()),this.sendBtn=document.createElement("button"),this.sendBtn.className="bb-send",this.sendBtn.disabled=!0,this.sendBtn.setAttribute("aria-label",this.t("send")),this.sendBtn.appendChild(f("send")),this.sendBtn.addEventListener("click",()=>this.send(this.inputEl?.value??"")),m.appendChild(this.inputEl),m.appendChild(this.sendBtn),this.windowEl.appendChild(m),this.launcherEl=document.createElement("button"),this.launcherEl.className="bb-launcher",this.launcherEl.setAttribute("aria-label",r),this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",this.launcherEl.appendChild(p)}else{let p=f("chat");p.classList.add("bb-l-chat"),this.launcherEl.appendChild(p)}let L=f("close");L.classList.add("bb-l-close"),this.launcherEl.appendChild(L),this.launcherEl.addEventListener("click",()=>this.opened?this.close():this.open()),a.appendChild(this.launcherEl),e.appendChild(this.host)}iconButton(e,n,t){let i=document.createElement("button");return i.className="bb-iconbtn",i.title=n,i.setAttribute("aria-label",n),i.appendChild(f(e)),i.addEventListener("click",t),i}syncSendState(){this.sendBtn&&(this.sendBtn.disabled=!(this.inputEl?.value??"").trim()||this.busy)}open(){if(!this.windowEl||this.opened)return;this.opened=!0;let e=this.root?.querySelector(".bb");e?.classList.add("open");let n=(this.settings.displayMode??"floating")==="full_screen"||window.innerWidth<=520;e?.classList.toggle("big",n||this.expanded),this.messagesEl&&this.messagesEl.childElementCount===0&&this.primeThread(),this.inputEl?.focus()}close(){this.opened=!1;let e=this.root?.querySelector(".bb");e?.classList.remove("open"),this.expanded||e?.classList.remove("big")}toggleExpand(){if(this.expanded=!this.expanded,this.root?.querySelector(".bb")?.classList.toggle("expanded",this.expanded),this.root?.querySelector(".bb")?.classList.toggle("big",this.expanded),this.expandBtn){this.expandBtn.replaceChildren(f(this.expanded?"collapse":"expand"));let e=this.t(this.expanded?"collapse":"expand");this.expandBtn.title=e,this.expandBtn.setAttribute("aria-label",e)}}async primeThread(){if(this.conversationId&&this.sessionId)try{let e=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/conversations/${encodeURIComponent(this.conversationId)}?limit=50`,{headers:{"X-Bot-Session":this.sessionId}});if(e.ok){let n=await e.json();for(let t of n.data){let i=this.appendMessage(t.role==="assistant"?"bot":"user","");B(i,t.content)}if(n.data.length>0){this.chipsEl?.remove();return}}else this.conversationId=null,this.sessionId=null,this.persistIds()}catch{}if(this.settings.greeting){let e=this.appendMessage("bot","");B(e,this.settings.greeting)}}async send(e){let n=e.trim();if(!n||this.busy)return;this.busy=!0,this.inputEl&&(this.inputEl.value=""),this.syncSendState(),this.chipsEl?.remove(),this.appendMessage("user",n);let t=this.appendTyping();this.pendingText="",this.cardsRow=null,this.cardIds=new Set;let i=null;try{let o=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:n,turnId:O("trn_"),...this.conversationId?{conversationId:this.conversationId}:{},...this.sessionId?{anonymousSessionId:this.sessionId}:{},locale:this.locale})});if(!o.ok||!o.body)throw new Error(`chat failed (${o.status})`);let r=o.body.getReader(),d=new TextDecoder,a="";for(;;){let{value:l,done:s}=await r.read();if(s)break;a+=d.decode(l,{stream:!0});let h;for(;(h=a.indexOf(`
241
240
 
242
- `))>=0;){let u=a.slice(0,h);if(a=a.slice(h+2),!u.startsWith("data: "))continue;let g;try{g=JSON.parse(u.slice(6))}catch{continue}i=this.handleFrame(g,t,i)}}i&&this.pendingText&&B(i,this.pendingText)}catch{this.appendMessage("err",this.t("error"))}finally{t.remove(),this.busy=!1,this.syncSendState()}}handleFrame(e,n,t){switch(e.type){case"connected":return this.conversationId=e.conversationId||this.conversationId,this.sessionId=e.anonymousSessionId||this.sessionId,this.persistIds(),t;case"token":return t||(n.remove(),t=this.appendMessage("bot","")),this.pendingText+=e.text,t.textContent=this.pendingText,this.scrollDown(),t;case"tool":return n.classList.toggle("searching",e.status==="running"),t;case"card":return this.appendCard(e.card),t;case"error":return this.appendMessage("err",e.message||this.t("error")),t;case"done":default:return t}}appendCard(e){if(!this.messagesEl||this.cardIds.has(e.productId))return;if(this.cardIds.add(e.productId),!this.cardsRow){let s=document.createElement("div");s.className="bb-shelf";let h=document.createElement("div");h.className="bb-shelf-cap",h.textContent=this.t("results"),this.cardsRow=document.createElement("div"),this.cardsRow.className="bb-cards",s.appendChild(h),s.appendChild(this.cardsRow),this.messagesEl.appendChild(s)}let n=T(e.url)?e.url:null,t=document.createElement("div");t.className="bb-card";let i=()=>{this.beacon(e.botRef),n&&(window.location.href=n)},o=document.createElement("span");if(o.className="bb-card-img",e.imageUrl&&T(e.imageUrl)){let s=document.createElement("img");s.src=e.imageUrl,s.alt="",s.loading="lazy",o.appendChild(s)}else o.appendChild(f("cart"));o.addEventListener("click",i),t.appendChild(o);let r=document.createElement("span");r.className="bb-card-body";let d=document.createElement("span");d.className="bb-card-title",d.textContent=e.title,d.addEventListener("click",i);let a=document.createElement("span");a.className="bb-card-price",a.textContent=e.price.formatted,r.appendChild(d),r.appendChild(a);let l=document.createElement("span");if(l.className="bb-card-cta",e.requiresOptions){let s=document.createElement("button");s.className="bb-btn bb-btn-add",s.setAttribute("aria-label",this.t("addToCart")),s.appendChild(f("cart")),e.variants?.length?s.addEventListener("click",()=>this.togglePicker(r,e,o)):s.addEventListener("click",i),l.appendChild(s)}else{let s=document.createElement("button");s.className="bb-btn bb-btn-add",s.appendChild(f("cart")),s.appendChild(document.createTextNode(this.t("addToCart"))),s.addEventListener("click",()=>void this.addToCart(e,s));let h=document.createElement("button");h.className="bb-btn bb-btn-ghost",h.appendChild(document.createTextNode(this.t("view"))),h.addEventListener("click",i),l.appendChild(s),l.appendChild(h)}r.appendChild(l),t.appendChild(r),this.cardsRow.appendChild(t),this.scrollDown()}togglePicker(e,n,t){let i=e.parentElement,o=e.querySelector(".bb-pick");if(o){o.remove(),i?.classList.remove("picking");return}i?.classList.add("picking");let r=n.variants??[],d=[];for(let u of r)for(let g of Object.keys(u.attributes))d.includes(g)||d.push(g);let a={},l=document.createElement("span");l.className="bb-pick";let s=()=>r.find(u=>d.every(g=>a[g]&&u.attributes[g]===a[g]))??null,h=()=>{l.replaceChildren();for(let c of d){let E=document.createElement("span");E.className="bb-pick-key",E.textContent=c;let y=document.createElement("span");y.className="bb-pick-vals";let C=new Set;for(let k of r){let m=k.attributes[c];if(!m||C.has(m))continue;C.add(m);let L=r.some(I=>I.attributes[c]===m&&d.every(M=>M===c||!a[M]||I.attributes[M]===a[M])),p=document.createElement("button");p.className=`bb-chipv${a[c]===m?" sel":""}${L?"":" off"}`,p.textContent=m,p.addEventListener("click",()=>{a[c]===m?delete a[c]:a[c]=m,h()}),y.appendChild(p)}l.appendChild(E),l.appendChild(y)}let u=document.createElement("button");u.className="bb-pick-close",u.setAttribute("aria-label",this.t("close")),u.appendChild(f("close")),u.addEventListener("click",()=>{l.remove(),i?.classList.remove("picking")}),l.appendChild(u);let g=s(),v=document.createElement("span");v.className="bb-pick-foot";let x=document.createElement("span");x.className="bb-pick-price",x.textContent=g?g.price.formatted:"";let w=document.createElement("button");if(w.className="bb-btn bb-btn-add",w.appendChild(f("cart")),w.appendChild(document.createTextNode(this.t("addToCart"))),g||(w.disabled=!0),w.addEventListener("click",()=>{let c=s();c&&this.addToCart(n,w,c.id)}),v.appendChild(x),v.appendChild(w),l.appendChild(v),g?.imageUrl&&T(g.imageUrl)){let c=t.querySelector("img");c&&(c.src=g.imageUrl)}this.scrollDown()};h(),e.appendChild(l),this.scrollDown()}async addToCart(e,n,t){this.beacon(e.botRef),n.dataset.state="busy";let i=!1;try{if(this.onAddToCart)i=await this.onAddToCart({productId:e.productId,variantId:t??null,quantity:1})!==!1;else{let o=new CustomEvent("brainerce:bot:add-to-cart",{detail:{productId:e.productId,variantId:t??null,quantity:1,connectionId:this.connectionId},cancelable:!0,bubbles:!0,composed:!0});i=!window.dispatchEvent(o)}}catch{i=!1}i?(n.dataset.state="done",n.replaceChildren(f("check"),document.createTextNode(this.t("added"))),setTimeout(()=>{!this.destroyed&&n.isConnected&&(delete n.dataset.state,n.replaceChildren(f("cart"),document.createTextNode(this.t("addToCart"))))},2200)):(delete n.dataset.state,T(e.url)&&(window.location.href=e.url))}beacon(e){try{navigator.sendBeacon?.(`${this.baseUrl}/api/storefront-bot/attribution/click`,new Blob([JSON.stringify({botRef:e})],{type:"application/json"}))}catch{}}toggleEscalation(){this.root?.querySelector(".bb-esc")?.classList.toggle("open")}async submitEscalation(e){let n=e.querySelector('input[name="email"]')?.value.trim(),t=e.querySelector('textarea[name="message"]')?.value.trim();if(!(!n||!t||!this.conversationId||!this.sessionId))try{if((await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/escalate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:n,message:t,conversationId:this.conversationId,anonymousSessionId:this.sessionId,locale:this.locale})})).ok){let o=document.createElement("span");o.className="bb-esc-note",o.appendChild(f("check")),o.appendChild(document.createTextNode(this.t("sent"))),e.replaceChildren(o)}}catch{}}appendMessage(e,n){let t=document.createElement("div");return t.className=`bb-msg ${e}`,t.setAttribute("dir","auto"),t.textContent=n,this.messagesEl?.appendChild(t),this.scrollDown(),t}appendTyping(){let e=document.createElement("div");e.className="bb-typing";let n=document.createElement("span");n.className="bb-dots";for(let i=0;i<3;i++)n.appendChild(document.createElement("i"));let t=document.createElement("span");return t.className="bb-tool",t.textContent=this.t("searching"),e.appendChild(n),e.appendChild(t),this.messagesEl?.appendChild(e),this.scrollDown(),e}scrollDown(){this.messagesEl&&(this.messagesEl.scrollTop=this.messagesEl.scrollHeight)}};(()=>{let b=document.currentScript,e=b?.dataset.connectionId;if(!e){console.warn("[BrainerceBot] missing data-connection-id on the bot.js script tag");return}let n=b?.dataset.apiBase||void 0,t=()=>void N.mount({connectionId:e,baseUrl:n});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t()})();})();
241
+ /* Powered-by footer */
242
+ .bb-foot { flex-shrink: 0; display: flex; justify-content: center; align-items: center;
243
+ padding: 5px 12px 7px; background: #fff; }
244
+ .bb-foot a { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px;
245
+ font-weight: 500; color: #aab0bd; text-decoration: none; letter-spacing: .005em;
246
+ transition: color .12s ease; }
247
+ .bb-foot a:hover { color: #6b7280; }
248
+ .bb-foot .bb-ic { width: 13px; height: 13px; border-radius: 4px; }
249
+ `}render(e){let n=this.settings.accentColor||"#6366F1",t=O.has(this.locale)?"rtl":"ltr",i=this.settings.position==="start"?"left":"right",o=t==="rtl"?i==="left"?"right":"left":i,r=this.settings.displayName||"Assistant";this.host=document.createElement("div"),this.host.setAttribute("data-brainerce-bot",this.connectionId),this.root=this.host.attachShadow({mode:"open"});let d=document.createElement("style");d.textContent=this.css(n,t,o),this.root.appendChild(d);let a=document.createElement("div");a.className="bb",this.root.appendChild(a);let l=document.createElement("div");l.className="bb-scrim",l.addEventListener("click",()=>{this.expanded?this.toggleExpand():this.close()}),a.appendChild(l),this.windowEl=document.createElement("div"),this.windowEl.className="bb-window",a.appendChild(this.windowEl);let s=document.createElement("div");s.className="bb-header";let h=document.createElement("span");if(h.className="bb-avatar",this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",h.appendChild(p)}else h.appendChild(document.createTextNode(r.charAt(0).toUpperCase()));let u=document.createElement("span");u.className="bb-head-main";let g=document.createElement("span");g.className="bb-name",g.textContent=r;let w=document.createElement("span");w.className="bb-status",w.textContent=this.t("online"),u.appendChild(g),u.appendChild(w);let v=document.createElement("span");v.className="bb-actions",(this.settings.displayMode??"floating")!=="full_screen"&&this.settings.allowExpand!==!1&&(this.expandBtn=this.iconButton("expand",this.t("expand"),()=>this.toggleExpand()),v.appendChild(this.expandBtn)),v.appendChild(this.iconButton("mail",this.t("leaveMessage"),()=>this.toggleEscalation())),v.appendChild(this.iconButton("close",this.t("close"),()=>this.close())),s.appendChild(h),s.appendChild(u),s.appendChild(v),this.windowEl.appendChild(s),this.messagesEl=document.createElement("div"),this.messagesEl.className="bb-msgs",this.windowEl.appendChild(this.messagesEl),this.chipsEl=document.createElement("div"),this.chipsEl.className="bb-chips";for(let p of this.settings.starterQuestions??[]){let N=document.createElement("button");N.className="bb-chip",N.textContent=p,N.addEventListener("click",()=>this.send(p)),this.chipsEl.appendChild(N)}this.windowEl.appendChild(this.chipsEl);let c=document.createElement("div");c.className="bb-esc";let C=document.createElement("span");C.className="bb-esc-title",C.textContent=this.t("leaveMessage");let E=document.createElement("input");E.type="email",E.name="email",E.placeholder=this.t("yourEmail");let k=document.createElement("textarea");k.name="message",k.rows=2,k.placeholder=this.t("yourMessage");let I=document.createElement("button");I.type="button",I.className="bb-esc-send",I.textContent=this.t("send"),I.addEventListener("click",()=>this.submitEscalation(c)),c.appendChild(C),c.appendChild(E),c.appendChild(k),c.appendChild(I),this.windowEl.appendChild(c);let f=document.createElement("div");f.className="bb-composer",this.inputEl=document.createElement("textarea"),this.inputEl.className="bb-input",this.inputEl.rows=1,this.inputEl.placeholder=this.t("placeholder"),this.inputEl.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),this.send(this.inputEl?.value??""))}),this.inputEl.addEventListener("input",()=>this.syncSendState()),this.sendBtn=document.createElement("button"),this.sendBtn.className="bb-send",this.sendBtn.disabled=!0,this.sendBtn.setAttribute("aria-label",this.t("send")),this.sendBtn.appendChild(m("send")),this.sendBtn.addEventListener("click",()=>this.send(this.inputEl?.value??"")),f.appendChild(this.inputEl),f.appendChild(this.sendBtn),this.windowEl.appendChild(f);let B=document.createElement("div");B.className="bb-foot";let x=document.createElement("a");if(x.href="https://brainerce.com",x.target="_blank",x.rel="noopener noreferrer",x.appendChild(m("brand")),x.appendChild(document.createTextNode(this.t("poweredBy"))),B.appendChild(x),this.windowEl.appendChild(B),this.launcherEl=document.createElement("button"),this.launcherEl.className="bb-launcher",this.launcherEl.setAttribute("aria-label",r),this.settings.avatarUrl&&T(this.settings.avatarUrl)){let p=document.createElement("img");p.src=this.settings.avatarUrl,p.alt="",this.launcherEl.appendChild(p)}else{let p=m("chat");p.classList.add("bb-l-chat"),this.launcherEl.appendChild(p)}let L=m("close");L.classList.add("bb-l-close"),this.launcherEl.appendChild(L),this.launcherEl.addEventListener("click",()=>this.opened?this.close():this.open()),a.appendChild(this.launcherEl),e.appendChild(this.host)}iconButton(e,n,t){let i=document.createElement("button");return i.className="bb-iconbtn",i.title=n,i.setAttribute("aria-label",n),i.appendChild(m(e)),i.addEventListener("click",t),i}syncSendState(){this.sendBtn&&(this.sendBtn.disabled=!(this.inputEl?.value??"").trim()||this.busy)}open(){if(!this.windowEl||this.opened)return;this.opened=!0;let e=this.root?.querySelector(".bb");e?.classList.add("open");let n=(this.settings.displayMode??"floating")==="full_screen"||window.innerWidth<=520;e?.classList.toggle("big",n||this.expanded),this.syncBodyScroll(),this.messagesEl&&this.messagesEl.childElementCount===0&&this.primeThread(),this.inputEl?.focus()}close(){this.opened=!1;let e=this.root?.querySelector(".bb");e?.classList.remove("open"),this.expanded||e?.classList.remove("big"),this.syncBodyScroll()}syncBodyScroll(){let e=this.opened&&!!this.root?.querySelector(".bb")?.classList.contains("big");e&&this.prevBodyOverflow===null?(this.prevBodyOverflow=document.body.style.overflow||"",document.body.style.overflow="hidden"):!e&&this.prevBodyOverflow!==null&&(document.body.style.overflow=this.prevBodyOverflow,this.prevBodyOverflow=null)}toggleExpand(){if(this.expanded=!this.expanded,this.root?.querySelector(".bb")?.classList.toggle("expanded",this.expanded),this.root?.querySelector(".bb")?.classList.toggle("big",this.expanded),this.syncBodyScroll(),this.expandBtn){this.expandBtn.replaceChildren(m(this.expanded?"collapse":"expand"));let e=this.t(this.expanded?"collapse":"expand");this.expandBtn.title=e,this.expandBtn.setAttribute("aria-label",e)}}async primeThread(){if(this.conversationId&&this.sessionId)try{let e=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/conversations/${encodeURIComponent(this.conversationId)}?limit=50`,{headers:{"X-Bot-Session":this.sessionId}});if(e.ok){let n=await e.json();for(let t of n.data){let i=this.appendMessage(t.role==="assistant"?"bot":"user","");U(i,t.content)}if(n.data.length>0){this.chipsEl?.remove();return}}else this.conversationId=null,this.sessionId=null,this.persistIds()}catch{}if(this.settings.greeting){let e=this.appendMessage("bot","");U(e,this.settings.greeting)}}async send(e){let n=e.trim();if(!n||this.busy)return;this.busy=!0,this.inputEl&&(this.inputEl.value=""),this.syncSendState(),this.chipsEl?.remove(),this.appendMessage("user",n);let t=this.appendTyping();this.pendingText="",this.cardsRow=null,this.cardIds=new Set;let i=null;try{let o=await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:n,turnId:P("trn_"),...this.conversationId?{conversationId:this.conversationId}:{},...this.sessionId?{anonymousSessionId:this.sessionId}:{},locale:this.locale})});if(!o.ok||!o.body)throw new Error(`chat failed (${o.status})`);let r=o.body.getReader(),d=new TextDecoder,a="";for(;;){let{value:l,done:s}=await r.read();if(s)break;a+=d.decode(l,{stream:!0});let h;for(;(h=a.indexOf(`
250
+
251
+ `))>=0;){let u=a.slice(0,h);if(a=a.slice(h+2),!u.startsWith("data: "))continue;let g;try{g=JSON.parse(u.slice(6))}catch{continue}i=this.handleFrame(g,t,i)}}i&&this.pendingText&&U(i,this.pendingText)}catch{this.appendMessage("err",this.t("error"))}finally{t.remove(),this.busy=!1,this.syncSendState()}}handleFrame(e,n,t){switch(e.type){case"connected":return this.conversationId=e.conversationId||this.conversationId,this.sessionId=e.anonymousSessionId||this.sessionId,this.persistIds(),t;case"token":return t||(n.remove(),t=this.appendMessage("bot","")),this.pendingText+=e.text,t.textContent=this.pendingText,this.scrollDown(),t;case"tool":return n.classList.toggle("searching",e.status==="running"),t;case"card":return this.appendCard(e.card),t;case"action":return this.handleAction(e),t;case"error":return this.appendMessage("err",e.message||this.t("error")),t;case"done":default:return t}}appendCard(e){if(!this.messagesEl||this.cardIds.has(e.productId))return;if(this.cardIds.add(e.productId),!this.cardsRow){let s=document.createElement("div");s.className="bb-shelf";let h=document.createElement("div");h.className="bb-shelf-cap",h.textContent=this.t("results"),this.cardsRow=document.createElement("div"),this.cardsRow.className="bb-cards",s.appendChild(h),s.appendChild(this.cardsRow),this.messagesEl.appendChild(s)}let n=T(e.url)?e.url:null,t=document.createElement("div");t.className="bb-card";let i=()=>{this.beacon(e.botRef),n&&(window.location.href=n)},o=document.createElement("span");if(o.className="bb-card-img",e.imageUrl&&T(e.imageUrl)){let s=document.createElement("img");s.src=e.imageUrl,s.alt="",s.loading="lazy",o.appendChild(s)}else o.appendChild(m("cart"));o.addEventListener("click",i),t.appendChild(o);let r=document.createElement("span");r.className="bb-card-body";let d=document.createElement("span");d.className="bb-card-title",d.textContent=e.title,d.addEventListener("click",i);let a=document.createElement("span");a.className="bb-card-price",a.textContent=e.price.formatted,r.appendChild(d),r.appendChild(a);let l=document.createElement("span");if(l.className="bb-card-cta",e.requiresOptions){let s=document.createElement("button");s.className="bb-btn bb-btn-add",s.setAttribute("aria-label",this.t("addToCart")),s.appendChild(m("cart")),e.variants?.length?s.addEventListener("click",()=>this.togglePicker(r,e,o)):s.addEventListener("click",i),l.appendChild(s)}else{let s=document.createElement("button");s.className="bb-btn bb-btn-add",s.appendChild(m("cart")),s.appendChild(document.createTextNode(this.t("addToCart"))),s.addEventListener("click",()=>void this.addToCart(e,s));let h=document.createElement("button");h.className="bb-btn bb-btn-ghost",h.appendChild(document.createTextNode(this.t("view"))),h.addEventListener("click",i),l.appendChild(s),l.appendChild(h)}r.appendChild(l),t.appendChild(r),this.cardsRow.appendChild(t),this.scrollDown()}togglePicker(e,n,t){let i=e.parentElement,o=e.querySelector(".bb-pick");if(o){o.remove(),i?.classList.remove("picking");return}i?.classList.add("picking");let r=n.variants??[],d=[];for(let u of r)for(let g of Object.keys(u.attributes))d.includes(g)||d.push(g);let a={},l=document.createElement("span");l.className="bb-pick";let s=()=>r.find(u=>d.every(g=>a[g]&&u.attributes[g]===a[g]))??null,h=()=>{l.replaceChildren();for(let c of d){let C=document.createElement("span");C.className="bb-pick-key",C.textContent=c;let E=document.createElement("span");E.className="bb-pick-vals";let k=new Set;for(let I of r){let f=I.attributes[c];if(!f||k.has(f))continue;k.add(f);let B=r.some(L=>L.attributes[c]===f&&d.every(p=>p===c||!a[p]||L.attributes[p]===a[p])),x=document.createElement("button");x.className=`bb-chipv${a[c]===f?" sel":""}${B?"":" off"}`,x.textContent=f,x.addEventListener("click",()=>{a[c]===f?delete a[c]:a[c]=f,h()}),E.appendChild(x)}l.appendChild(C),l.appendChild(E)}let u=document.createElement("button");u.className="bb-pick-close",u.setAttribute("aria-label",this.t("close")),u.appendChild(m("close")),u.addEventListener("click",()=>{l.remove(),i?.classList.remove("picking")}),l.appendChild(u);let g=s(),w=document.createElement("span");w.className="bb-pick-foot";let v=document.createElement("span");v.className="bb-pick-price",v.textContent=g?g.price.formatted:"";let y=document.createElement("button");if(y.className="bb-btn bb-btn-add",y.appendChild(m("cart")),y.appendChild(document.createTextNode(this.t("addToCart"))),g||(y.disabled=!0),y.addEventListener("click",()=>{let c=s();c&&this.addToCart(n,y,c.id)}),w.appendChild(v),w.appendChild(y),l.appendChild(w),g?.imageUrl&&T(g.imageUrl)){let c=t.querySelector("img");c&&(c.src=g.imageUrl)}this.scrollDown()};h(),e.appendChild(l),this.scrollDown()}async addToCart(e,n,t){this.beacon(e.botRef),n.dataset.state="busy",await this.dispatchAdd(e.productId,t??null)?(n.dataset.state="done",n.replaceChildren(m("check"),document.createTextNode(this.t("added"))),setTimeout(()=>{!this.destroyed&&n.isConnected&&(delete n.dataset.state,n.replaceChildren(m("cart"),document.createTextNode(this.t("addToCart"))))},2200)):(delete n.dataset.state,T(e.url)&&(window.location.href=e.url))}async dispatchAdd(e,n){try{if(this.onAddToCart)return await this.onAddToCart({productId:e,variantId:n,quantity:1})!==!1;let t=new CustomEvent("brainerce:bot:add-to-cart",{detail:{productId:e,variantId:n,quantity:1,connectionId:this.connectionId},cancelable:!0,bubbles:!0,composed:!0});return!window.dispatchEvent(t)}catch{return!1}}async handleAction(e){if(e.action!=="add_to_cart")return;e.botRef&&this.beacon(e.botRef),await this.dispatchAdd(e.productId,e.variantId)||this.appendMessage("err",this.t("addFailed"))}beacon(e){try{navigator.sendBeacon?.(`${this.baseUrl}/api/storefront-bot/attribution/click`,new Blob([JSON.stringify({botRef:e})],{type:"application/json"}))}catch{}}toggleEscalation(){this.root?.querySelector(".bb-esc")?.classList.toggle("open")}async submitEscalation(e){let n=e.querySelector('input[name="email"]')?.value.trim(),t=e.querySelector('textarea[name="message"]')?.value.trim();if(!(!n||!t||!this.conversationId||!this.sessionId))try{if((await fetch(`${this.baseUrl}/api/storefront-bot/${encodeURIComponent(this.connectionId)}/escalate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:n,message:t,conversationId:this.conversationId,anonymousSessionId:this.sessionId,locale:this.locale})})).ok){let o=document.createElement("span");o.className="bb-esc-note",o.appendChild(m("check")),o.appendChild(document.createTextNode(this.t("sent"))),e.replaceChildren(o)}}catch{}}appendMessage(e,n){let t=document.createElement("div");return t.className=`bb-msg ${e}`,t.setAttribute("dir","auto"),t.textContent=n,this.messagesEl?.appendChild(t),this.scrollDown(),t}appendTyping(){let e=document.createElement("div");e.className="bb-typing";let n=document.createElement("span");n.className="bb-dots";for(let i=0;i<3;i++)n.appendChild(document.createElement("i"));let t=document.createElement("span");return t.className="bb-tool",t.textContent=this.t("searching"),e.appendChild(n),e.appendChild(t),this.messagesEl?.appendChild(e),this.scrollDown(),e}scrollDown(){this.messagesEl&&(this.messagesEl.scrollTop=this.messagesEl.scrollHeight)}};(()=>{let b=document.currentScript,e=b?.dataset.connectionId;if(!e){console.warn("[BrainerceBot] missing data-connection-id on the bot.js script tag");return}let n=b?.dataset.apiBase||void 0,t=()=>void M.mount({connectionId:e,baseUrl:n});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t()})();})();
@@ -74,6 +74,12 @@ declare class BrainerceBot {
74
74
  private syncSendState;
75
75
  private open;
76
76
  private close;
77
+ /**
78
+ * The large dialog owns the screen — the page behind must not scroll.
79
+ * Locks <body> while a big surface is open; restores on close/collapse.
80
+ */
81
+ private prevBodyOverflow;
82
+ private syncBodyScroll;
77
83
  private toggleExpand;
78
84
  /** First open: restore the server thread, or show the greeting. */
79
85
  private primeThread;
@@ -93,6 +99,14 @@ declare class BrainerceBot {
93
99
  * 3. fallback: navigate to the product page
94
100
  */
95
101
  private addToCart;
102
+ /**
103
+ * The host-cart chain shared by card buttons and bot-initiated actions:
104
+ * onAddToCart option -> cancelable CustomEvent. Returns whether a host
105
+ * took the add.
106
+ */
107
+ private dispatchAdd;
108
+ /** Bot-initiated widget actions (the model called the addToCart tool). */
109
+ private handleAction;
96
110
  /** The durable conversion signal — fire-and-forget, never blocks. */
97
111
  private beacon;
98
112
  private toggleEscalation;
@@ -74,6 +74,12 @@ declare class BrainerceBot {
74
74
  private syncSendState;
75
75
  private open;
76
76
  private close;
77
+ /**
78
+ * The large dialog owns the screen — the page behind must not scroll.
79
+ * Locks <body> while a big surface is open; restores on close/collapse.
80
+ */
81
+ private prevBodyOverflow;
82
+ private syncBodyScroll;
77
83
  private toggleExpand;
78
84
  /** First open: restore the server thread, or show the greeting. */
79
85
  private primeThread;
@@ -93,6 +99,14 @@ declare class BrainerceBot {
93
99
  * 3. fallback: navigate to the product page
94
100
  */
95
101
  private addToCart;
102
+ /**
103
+ * The host-cart chain shared by card buttons and bot-initiated actions:
104
+ * onAddToCart option -> cancelable CustomEvent. Returns whether a host
105
+ * took the add.
106
+ */
107
+ private dispatchAdd;
108
+ /** Bot-initiated widget actions (the model called the addToCart tool). */
109
+ private handleAction;
96
110
  /** The durable conversion signal — fire-and-forget, never blocks. */
97
111
  private beacon;
98
112
  private toggleEscalation;
package/dist/bot/index.js CHANGED
@@ -45,7 +45,9 @@ var CHROME = {
45
45
  added: "Added",
46
46
  view: "View",
47
47
  chooseOptions: "View product",
48
- results: "From the store"
48
+ results: "From the store",
49
+ poweredBy: "Powered by Brainerce",
50
+ addFailed: "I couldn\u2019t add that to the cart \u2014 try the button on the product card."
49
51
  },
50
52
  he: {
51
53
  online: "\u05DE\u05D7\u05D5\u05D1\u05E8",
@@ -64,7 +66,9 @@ var CHROME = {
64
66
  added: "\u05E0\u05D5\u05E1\u05E3",
65
67
  view: "\u05E6\u05E4\u05D9\u05D9\u05D4",
66
68
  chooseOptions: "\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",
67
- results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA"
69
+ results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA",
70
+ poweredBy: "\u05DE\u05D5\u05E4\u05E2\u05DC \u05E2\u05DC \u05D9\u05D3\u05D9 Brainerce",
71
+ addFailed: "\u05DC\u05D0 \u05D4\u05E6\u05DC\u05D7\u05EA\u05D9 \u05DC\u05D4\u05D5\u05E1\u05D9\u05E3 \u05DC\u05E1\u05DC \u2014 \u05E0\u05E1\u05D5 \u05D3\u05E8\u05DA \u05D4\u05DB\u05E4\u05EA\u05D5\u05E8 \u05D1\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D4\u05DE\u05D5\u05E6\u05E8."
68
72
  }
69
73
  };
70
74
  var ICONS = {
@@ -76,7 +80,10 @@ var ICONS = {
76
80
  send: '<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',
77
81
  cart: '<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',
78
82
  check: '<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
79
- arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'
83
+ arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
84
+ // Brainerce brand mark for the Powered-by footer: the gradient "B"
85
+ // (emerald→violet), matching the dashboard BrandedLoader identity.
86
+ brand: '<svg viewBox="0 0 24 24"><defs><linearGradient id="bb-brand-g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#34d399"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="url(#bb-brand-g)"/><text x="12" y="17" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14" font-weight="700" fill="#fff">B</text></svg>'
80
87
  };
81
88
  var svgParser = typeof DOMParser !== "undefined" ? new DOMParser() : null;
82
89
  function icon(name) {
@@ -170,6 +177,11 @@ var BrainerceBot = class _BrainerceBot {
170
177
  this.pendingText = "";
171
178
  this.cardsRow = null;
172
179
  this.cardIds = /* @__PURE__ */ new Set();
180
+ /**
181
+ * The large dialog owns the screen — the page behind must not scroll.
182
+ * Locks <body> while a big surface is open; restores on close/collapse.
183
+ */
184
+ this.prevBodyOverflow = null;
173
185
  this.connectionId = options.connectionId;
174
186
  this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
175
187
  this.storageKey = `brainerce-bot:${this.connectionId}`;
@@ -187,6 +199,10 @@ var BrainerceBot = class _BrainerceBot {
187
199
  }
188
200
  destroy() {
189
201
  this.destroyed = true;
202
+ if (this.prevBodyOverflow !== null) {
203
+ document.body.style.overflow = this.prevBodyOverflow;
204
+ this.prevBodyOverflow = null;
205
+ }
190
206
  this.host?.remove();
191
207
  }
192
208
  // ---------------------------------------------------------------------------
@@ -481,6 +497,15 @@ var BrainerceBot = class _BrainerceBot {
481
497
  .bb-send .bb-ic { width: 17px; height: 17px; ${dir === "rtl" ? "transform: scaleX(-1);" : ""} }
482
498
  .bb-send:hover { filter: brightness(1.08); transform: scale(1.06); }
483
499
  .bb-send:disabled { opacity: .4; pointer-events: none; }
500
+
501
+ /* Powered-by footer */
502
+ .bb-foot { flex-shrink: 0; display: flex; justify-content: center; align-items: center;
503
+ padding: 5px 12px 7px; background: #fff; }
504
+ .bb-foot a { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px;
505
+ font-weight: 500; color: #aab0bd; text-decoration: none; letter-spacing: .005em;
506
+ transition: color .12s ease; }
507
+ .bb-foot a:hover { color: #6b7280; }
508
+ .bb-foot .bb-ic { width: 13px; height: 13px; border-radius: 4px; }
484
509
  `;
485
510
  }
486
511
  render(target) {
@@ -601,6 +626,16 @@ var BrainerceBot = class _BrainerceBot {
601
626
  composer.appendChild(this.inputEl);
602
627
  composer.appendChild(this.sendBtn);
603
628
  this.windowEl.appendChild(composer);
629
+ const foot = document.createElement("div");
630
+ foot.className = "bb-foot";
631
+ const credit = document.createElement("a");
632
+ credit.href = "https://brainerce.com";
633
+ credit.target = "_blank";
634
+ credit.rel = "noopener noreferrer";
635
+ credit.appendChild(icon("brand"));
636
+ credit.appendChild(document.createTextNode(this.t("poweredBy")));
637
+ foot.appendChild(credit);
638
+ this.windowEl.appendChild(foot);
604
639
  this.launcherEl = document.createElement("button");
605
640
  this.launcherEl.className = "bb-launcher";
606
641
  this.launcherEl.setAttribute("aria-label", name);
@@ -640,6 +675,7 @@ var BrainerceBot = class _BrainerceBot {
640
675
  el?.classList.add("open");
641
676
  const takeover = (this.settings.displayMode ?? "floating") === "full_screen" || window.innerWidth <= 520;
642
677
  el?.classList.toggle("big", takeover || this.expanded);
678
+ this.syncBodyScroll();
643
679
  if (this.messagesEl && this.messagesEl.childElementCount === 0) {
644
680
  void this.primeThread();
645
681
  }
@@ -650,11 +686,23 @@ var BrainerceBot = class _BrainerceBot {
650
686
  const el = this.root?.querySelector(".bb");
651
687
  el?.classList.remove("open");
652
688
  if (!this.expanded) el?.classList.remove("big");
689
+ this.syncBodyScroll();
690
+ }
691
+ syncBodyScroll() {
692
+ const big = this.opened && !!this.root?.querySelector(".bb")?.classList.contains("big");
693
+ if (big && this.prevBodyOverflow === null) {
694
+ this.prevBodyOverflow = document.body.style.overflow || "";
695
+ document.body.style.overflow = "hidden";
696
+ } else if (!big && this.prevBodyOverflow !== null) {
697
+ document.body.style.overflow = this.prevBodyOverflow;
698
+ this.prevBodyOverflow = null;
699
+ }
653
700
  }
654
701
  toggleExpand() {
655
702
  this.expanded = !this.expanded;
656
703
  this.root?.querySelector(".bb")?.classList.toggle("expanded", this.expanded);
657
704
  this.root?.querySelector(".bb")?.classList.toggle("big", this.expanded);
705
+ this.syncBodyScroll();
658
706
  if (this.expandBtn) {
659
707
  this.expandBtn.replaceChildren(icon(this.expanded ? "collapse" : "expand"));
660
708
  const label = this.t(this.expanded ? "collapse" : "expand");
@@ -780,6 +828,9 @@ var BrainerceBot = class _BrainerceBot {
780
828
  case "card":
781
829
  this.appendCard(frame.card);
782
830
  return botBubble;
831
+ case "action":
832
+ void this.handleAction(frame);
833
+ return botBubble;
783
834
  case "error":
784
835
  this.appendMessage("err", frame.message || this.t("error"));
785
836
  return botBubble;
@@ -967,31 +1018,7 @@ var BrainerceBot = class _BrainerceBot {
967
1018
  async addToCart(card, btn, variantId) {
968
1019
  this.beacon(card.botRef);
969
1020
  btn.dataset.state = "busy";
970
- let handled = false;
971
- try {
972
- if (this.onAddToCart) {
973
- handled = await this.onAddToCart({
974
- productId: card.productId,
975
- variantId: variantId ?? null,
976
- quantity: 1
977
- }) !== false;
978
- } else {
979
- const ev = new CustomEvent("brainerce:bot:add-to-cart", {
980
- detail: {
981
- productId: card.productId,
982
- variantId: variantId ?? null,
983
- quantity: 1,
984
- connectionId: this.connectionId
985
- },
986
- cancelable: true,
987
- bubbles: true,
988
- composed: true
989
- });
990
- handled = !window.dispatchEvent(ev);
991
- }
992
- } catch {
993
- handled = false;
994
- }
1021
+ const handled = await this.dispatchAdd(card.productId, variantId ?? null);
995
1022
  if (handled) {
996
1023
  btn.dataset.state = "done";
997
1024
  btn.replaceChildren(icon("check"), document.createTextNode(this.t("added")));
@@ -1006,6 +1033,34 @@ var BrainerceBot = class _BrainerceBot {
1006
1033
  if (isSafeUrl(card.url)) window.location.href = card.url;
1007
1034
  }
1008
1035
  }
1036
+ /**
1037
+ * The host-cart chain shared by card buttons and bot-initiated actions:
1038
+ * onAddToCart option -> cancelable CustomEvent. Returns whether a host
1039
+ * took the add.
1040
+ */
1041
+ async dispatchAdd(productId, variantId) {
1042
+ try {
1043
+ if (this.onAddToCart) {
1044
+ return await this.onAddToCart({ productId, variantId, quantity: 1 }) !== false;
1045
+ }
1046
+ const ev = new CustomEvent("brainerce:bot:add-to-cart", {
1047
+ detail: { productId, variantId, quantity: 1, connectionId: this.connectionId },
1048
+ cancelable: true,
1049
+ bubbles: true,
1050
+ composed: true
1051
+ });
1052
+ return !window.dispatchEvent(ev);
1053
+ } catch {
1054
+ return false;
1055
+ }
1056
+ }
1057
+ /** Bot-initiated widget actions (the model called the addToCart tool). */
1058
+ async handleAction(frame) {
1059
+ if (frame.action !== "add_to_cart") return;
1060
+ if (frame.botRef) this.beacon(frame.botRef);
1061
+ const ok = await this.dispatchAdd(frame.productId, frame.variantId);
1062
+ if (!ok) this.appendMessage("err", this.t("addFailed"));
1063
+ }
1009
1064
  /** The durable conversion signal — fire-and-forget, never blocks. */
1010
1065
  beacon(botRef) {
1011
1066
  try {
@@ -19,7 +19,9 @@ var CHROME = {
19
19
  added: "Added",
20
20
  view: "View",
21
21
  chooseOptions: "View product",
22
- results: "From the store"
22
+ results: "From the store",
23
+ poweredBy: "Powered by Brainerce",
24
+ addFailed: "I couldn\u2019t add that to the cart \u2014 try the button on the product card."
23
25
  },
24
26
  he: {
25
27
  online: "\u05DE\u05D7\u05D5\u05D1\u05E8",
@@ -38,7 +40,9 @@ var CHROME = {
38
40
  added: "\u05E0\u05D5\u05E1\u05E3",
39
41
  view: "\u05E6\u05E4\u05D9\u05D9\u05D4",
40
42
  chooseOptions: "\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",
41
- results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA"
43
+ results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA",
44
+ poweredBy: "\u05DE\u05D5\u05E4\u05E2\u05DC \u05E2\u05DC \u05D9\u05D3\u05D9 Brainerce",
45
+ addFailed: "\u05DC\u05D0 \u05D4\u05E6\u05DC\u05D7\u05EA\u05D9 \u05DC\u05D4\u05D5\u05E1\u05D9\u05E3 \u05DC\u05E1\u05DC \u2014 \u05E0\u05E1\u05D5 \u05D3\u05E8\u05DA \u05D4\u05DB\u05E4\u05EA\u05D5\u05E8 \u05D1\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D4\u05DE\u05D5\u05E6\u05E8."
42
46
  }
43
47
  };
44
48
  var ICONS = {
@@ -50,7 +54,10 @@ var ICONS = {
50
54
  send: '<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',
51
55
  cart: '<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',
52
56
  check: '<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
53
- arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'
57
+ arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
58
+ // Brainerce brand mark for the Powered-by footer: the gradient "B"
59
+ // (emerald→violet), matching the dashboard BrandedLoader identity.
60
+ brand: '<svg viewBox="0 0 24 24"><defs><linearGradient id="bb-brand-g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#34d399"/><stop offset="1" stop-color="#8b5cf6"/></linearGradient></defs><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="url(#bb-brand-g)"/><text x="12" y="17" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="14" font-weight="700" fill="#fff">B</text></svg>'
54
61
  };
55
62
  var svgParser = typeof DOMParser !== "undefined" ? new DOMParser() : null;
56
63
  function icon(name) {
@@ -144,6 +151,11 @@ var BrainerceBot = class _BrainerceBot {
144
151
  this.pendingText = "";
145
152
  this.cardsRow = null;
146
153
  this.cardIds = /* @__PURE__ */ new Set();
154
+ /**
155
+ * The large dialog owns the screen — the page behind must not scroll.
156
+ * Locks <body> while a big surface is open; restores on close/collapse.
157
+ */
158
+ this.prevBodyOverflow = null;
147
159
  this.connectionId = options.connectionId;
148
160
  this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
149
161
  this.storageKey = `brainerce-bot:${this.connectionId}`;
@@ -161,6 +173,10 @@ var BrainerceBot = class _BrainerceBot {
161
173
  }
162
174
  destroy() {
163
175
  this.destroyed = true;
176
+ if (this.prevBodyOverflow !== null) {
177
+ document.body.style.overflow = this.prevBodyOverflow;
178
+ this.prevBodyOverflow = null;
179
+ }
164
180
  this.host?.remove();
165
181
  }
166
182
  // ---------------------------------------------------------------------------
@@ -455,6 +471,15 @@ var BrainerceBot = class _BrainerceBot {
455
471
  .bb-send .bb-ic { width: 17px; height: 17px; ${dir === "rtl" ? "transform: scaleX(-1);" : ""} }
456
472
  .bb-send:hover { filter: brightness(1.08); transform: scale(1.06); }
457
473
  .bb-send:disabled { opacity: .4; pointer-events: none; }
474
+
475
+ /* Powered-by footer */
476
+ .bb-foot { flex-shrink: 0; display: flex; justify-content: center; align-items: center;
477
+ padding: 5px 12px 7px; background: #fff; }
478
+ .bb-foot a { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px;
479
+ font-weight: 500; color: #aab0bd; text-decoration: none; letter-spacing: .005em;
480
+ transition: color .12s ease; }
481
+ .bb-foot a:hover { color: #6b7280; }
482
+ .bb-foot .bb-ic { width: 13px; height: 13px; border-radius: 4px; }
458
483
  `;
459
484
  }
460
485
  render(target) {
@@ -575,6 +600,16 @@ var BrainerceBot = class _BrainerceBot {
575
600
  composer.appendChild(this.inputEl);
576
601
  composer.appendChild(this.sendBtn);
577
602
  this.windowEl.appendChild(composer);
603
+ const foot = document.createElement("div");
604
+ foot.className = "bb-foot";
605
+ const credit = document.createElement("a");
606
+ credit.href = "https://brainerce.com";
607
+ credit.target = "_blank";
608
+ credit.rel = "noopener noreferrer";
609
+ credit.appendChild(icon("brand"));
610
+ credit.appendChild(document.createTextNode(this.t("poweredBy")));
611
+ foot.appendChild(credit);
612
+ this.windowEl.appendChild(foot);
578
613
  this.launcherEl = document.createElement("button");
579
614
  this.launcherEl.className = "bb-launcher";
580
615
  this.launcherEl.setAttribute("aria-label", name);
@@ -614,6 +649,7 @@ var BrainerceBot = class _BrainerceBot {
614
649
  el?.classList.add("open");
615
650
  const takeover = (this.settings.displayMode ?? "floating") === "full_screen" || window.innerWidth <= 520;
616
651
  el?.classList.toggle("big", takeover || this.expanded);
652
+ this.syncBodyScroll();
617
653
  if (this.messagesEl && this.messagesEl.childElementCount === 0) {
618
654
  void this.primeThread();
619
655
  }
@@ -624,11 +660,23 @@ var BrainerceBot = class _BrainerceBot {
624
660
  const el = this.root?.querySelector(".bb");
625
661
  el?.classList.remove("open");
626
662
  if (!this.expanded) el?.classList.remove("big");
663
+ this.syncBodyScroll();
664
+ }
665
+ syncBodyScroll() {
666
+ const big = this.opened && !!this.root?.querySelector(".bb")?.classList.contains("big");
667
+ if (big && this.prevBodyOverflow === null) {
668
+ this.prevBodyOverflow = document.body.style.overflow || "";
669
+ document.body.style.overflow = "hidden";
670
+ } else if (!big && this.prevBodyOverflow !== null) {
671
+ document.body.style.overflow = this.prevBodyOverflow;
672
+ this.prevBodyOverflow = null;
673
+ }
627
674
  }
628
675
  toggleExpand() {
629
676
  this.expanded = !this.expanded;
630
677
  this.root?.querySelector(".bb")?.classList.toggle("expanded", this.expanded);
631
678
  this.root?.querySelector(".bb")?.classList.toggle("big", this.expanded);
679
+ this.syncBodyScroll();
632
680
  if (this.expandBtn) {
633
681
  this.expandBtn.replaceChildren(icon(this.expanded ? "collapse" : "expand"));
634
682
  const label = this.t(this.expanded ? "collapse" : "expand");
@@ -754,6 +802,9 @@ var BrainerceBot = class _BrainerceBot {
754
802
  case "card":
755
803
  this.appendCard(frame.card);
756
804
  return botBubble;
805
+ case "action":
806
+ void this.handleAction(frame);
807
+ return botBubble;
757
808
  case "error":
758
809
  this.appendMessage("err", frame.message || this.t("error"));
759
810
  return botBubble;
@@ -941,31 +992,7 @@ var BrainerceBot = class _BrainerceBot {
941
992
  async addToCart(card, btn, variantId) {
942
993
  this.beacon(card.botRef);
943
994
  btn.dataset.state = "busy";
944
- let handled = false;
945
- try {
946
- if (this.onAddToCart) {
947
- handled = await this.onAddToCart({
948
- productId: card.productId,
949
- variantId: variantId ?? null,
950
- quantity: 1
951
- }) !== false;
952
- } else {
953
- const ev = new CustomEvent("brainerce:bot:add-to-cart", {
954
- detail: {
955
- productId: card.productId,
956
- variantId: variantId ?? null,
957
- quantity: 1,
958
- connectionId: this.connectionId
959
- },
960
- cancelable: true,
961
- bubbles: true,
962
- composed: true
963
- });
964
- handled = !window.dispatchEvent(ev);
965
- }
966
- } catch {
967
- handled = false;
968
- }
995
+ const handled = await this.dispatchAdd(card.productId, variantId ?? null);
969
996
  if (handled) {
970
997
  btn.dataset.state = "done";
971
998
  btn.replaceChildren(icon("check"), document.createTextNode(this.t("added")));
@@ -980,6 +1007,34 @@ var BrainerceBot = class _BrainerceBot {
980
1007
  if (isSafeUrl(card.url)) window.location.href = card.url;
981
1008
  }
982
1009
  }
1010
+ /**
1011
+ * The host-cart chain shared by card buttons and bot-initiated actions:
1012
+ * onAddToCart option -> cancelable CustomEvent. Returns whether a host
1013
+ * took the add.
1014
+ */
1015
+ async dispatchAdd(productId, variantId) {
1016
+ try {
1017
+ if (this.onAddToCart) {
1018
+ return await this.onAddToCart({ productId, variantId, quantity: 1 }) !== false;
1019
+ }
1020
+ const ev = new CustomEvent("brainerce:bot:add-to-cart", {
1021
+ detail: { productId, variantId, quantity: 1, connectionId: this.connectionId },
1022
+ cancelable: true,
1023
+ bubbles: true,
1024
+ composed: true
1025
+ });
1026
+ return !window.dispatchEvent(ev);
1027
+ } catch {
1028
+ return false;
1029
+ }
1030
+ }
1031
+ /** Bot-initiated widget actions (the model called the addToCart tool). */
1032
+ async handleAction(frame) {
1033
+ if (frame.action !== "add_to_cart") return;
1034
+ if (frame.botRef) this.beacon(frame.botRef);
1035
+ const ok = await this.dispatchAdd(frame.productId, frame.variantId);
1036
+ if (!ok) this.appendMessage("err", this.t("addFailed"));
1037
+ }
983
1038
  /** The durable conversion signal — fire-and-forget, never blocks. */
984
1039
  beacon(botRef) {
985
1040
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainerce",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "Official SDK for building e-commerce storefronts with Brainerce Platform. Perfect for vibe-coded sites, AI-built stores (Cursor, Lovable, v0), and custom storefronts.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",