vanilla-agent 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- "use strict";var SiteAgentInstaller=(()=>{(function(){"use strict";if(window.__siteAgentInstallerLoaded)return;window.__siteAgentInstallerLoaded=!0;let n=window.siteAgentConfig||{},o=n.version||"latest",l=n.cdn||"jsdelivr",d=n.autoInit!==!1,c=()=>{if(n.cssUrl&&n.jsUrl)return{cssUrl:n.cssUrl,jsUrl:n.jsUrl};let t=`/npm/vanilla-agent@${o}/dist`;return l==="unpkg"?{cssUrl:`https://unpkg.com${t}/widget.css`,jsUrl:`https://unpkg.com${t}/index.global.js`}:{cssUrl:`https://cdn.jsdelivr.net${t}/widget.css`,jsUrl:`https://cdn.jsdelivr.net${t}/index.global.js`}},{cssUrl:a,jsUrl:r}=c(),g=()=>!!document.head.querySelector("link[data-vanilla-agent]")||!!document.head.querySelector('link[href*="widget.css"]'),u=()=>!!window.ChatWidget,f=()=>new Promise((i,t)=>{if(g()){i();return}let e=document.createElement("link");e.rel="stylesheet",e.href=a,e.setAttribute("data-vanilla-agent","true"),e.onload=()=>i(),e.onerror=()=>t(new Error(`Failed to load CSS from ${a}`)),document.head.appendChild(e)}),h=()=>new Promise((i,t)=>{if(u()){i();return}let e=document.createElement("script");e.src=r,e.async=!0,e.onload=()=>i(),e.onerror=()=>t(new Error(`Failed to load JS from ${r}`)),document.head.appendChild(e)}),w=()=>{if(!window.ChatWidget||!window.ChatWidget.initChatWidget){console.warn("ChatWidget not available. Make sure the script loaded successfully.");return}let i=n.target||"body",t={...n.config};if(n.apiUrl&&!t.apiUrl&&(t.apiUrl=n.apiUrl),!(!t.apiUrl&&Object.keys(t).length===0))try{window.ChatWidget.initChatWidget({target:i,config:t})}catch(e){console.error("Failed to initialize ChatWidget:",e)}},s=async()=>{try{await f(),await h(),d&&(n.config||n.apiUrl)&&setTimeout(w,0)}catch(i){console.error("Failed to install ChatWidget:",i)}};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s):s()})();})();
1
+ "use strict";var SiteAgentInstaller=(()=>{(function(){"use strict";if(window.__siteAgentInstallerLoaded)return;window.__siteAgentInstallerLoaded=!0;let n=window.siteAgentConfig||{},o=n.version||"latest",l=n.cdn||"jsdelivr",d=n.autoInit!==!1,c=()=>{if(n.cssUrl&&n.jsUrl)return{cssUrl:n.cssUrl,jsUrl:n.jsUrl};let e=`/npm/vanilla-agent@${o}/dist`;return l==="unpkg"?{cssUrl:`https://unpkg.com${e}/widget.css`,jsUrl:`https://unpkg.com${e}/index.global.js`}:{cssUrl:`https://cdn.jsdelivr.net${e}/widget.css`,jsUrl:`https://cdn.jsdelivr.net${e}/index.global.js`}},{cssUrl:r,jsUrl:s}=c(),g=()=>!!document.head.querySelector("link[data-vanilla-agent]")||!!document.head.querySelector('link[href*="widget.css"]'),u=()=>!!window.AgentWidget,f=()=>new Promise((i,e)=>{if(g()){i();return}let t=document.createElement("link");t.rel="stylesheet",t.href=r,t.setAttribute("data-vanilla-agent","true"),t.onload=()=>i(),t.onerror=()=>e(new Error(`Failed to load CSS from ${r}`)),document.head.appendChild(t)}),w=()=>new Promise((i,e)=>{if(u()){i();return}let t=document.createElement("script");t.src=s,t.async=!0,t.onload=()=>i(),t.onerror=()=>e(new Error(`Failed to load JS from ${s}`)),document.head.appendChild(t)}),p=()=>{if(!window.AgentWidget||!window.AgentWidget.initAgentWidget){console.warn("AgentWidget not available. Make sure the script loaded successfully.");return}let i=n.target||"body",e={...n.config};if(n.apiUrl&&!e.apiUrl&&(e.apiUrl=n.apiUrl),!(!e.apiUrl&&Object.keys(e).length===0))try{window.AgentWidget.initAgentWidget({target:i,config:e})}catch(t){console.error("Failed to initialize AgentWidget:",t)}},a=async()=>{try{await f(),await w(),d&&(n.config||n.apiUrl)&&setTimeout(p,0)}catch(i){console.error("Failed to install AgentWidget:",i)}};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",a):a()})();})();
2
2
  //# sourceMappingURL=install.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/install.ts"],"sourcesContent":["/**\n * Standalone installer script for easy script tag installation\n * This script automatically loads CSS and JS, then initializes the widget\n * if configuration is provided via window.siteAgentConfig\n */\n\ninterface SiteAgentInstallConfig {\n version?: string;\n cdn?: \"unpkg\" | \"jsdelivr\";\n cssUrl?: string;\n jsUrl?: string;\n target?: string | HTMLElement;\n config?: any;\n autoInit?: boolean;\n}\n\ndeclare global {\n interface Window {\n siteAgentConfig?: SiteAgentInstallConfig;\n ChatWidget?: any;\n }\n}\n\n(function() {\n \"use strict\";\n\n // Prevent double installation\n if ((window as any).__siteAgentInstallerLoaded) {\n return;\n }\n (window as any).__siteAgentInstallerLoaded = true;\n\n const config: SiteAgentInstallConfig = window.siteAgentConfig || {};\n const version = config.version || \"latest\";\n const cdn = config.cdn || \"jsdelivr\";\n const autoInit = config.autoInit !== false; // Default to true\n\n // Determine CDN base URL\n const getCdnBase = () => {\n if (config.cssUrl && config.jsUrl) {\n return { cssUrl: config.cssUrl, jsUrl: config.jsUrl };\n }\n \n const packageName = \"vanilla-agent\";\n const basePath = `/npm/${packageName}@${version}/dist`;\n \n if (cdn === \"unpkg\") {\n return {\n cssUrl: `https://unpkg.com${basePath}/widget.css`,\n jsUrl: `https://unpkg.com${basePath}/index.global.js`\n };\n } else {\n return {\n cssUrl: `https://cdn.jsdelivr.net${basePath}/widget.css`,\n jsUrl: `https://cdn.jsdelivr.net${basePath}/index.global.js`\n };\n }\n };\n\n const { cssUrl, jsUrl } = getCdnBase();\n\n // Check if CSS is already loaded\n const isCssLoaded = () => {\n return !!document.head.querySelector('link[data-vanilla-agent]') ||\n !!document.head.querySelector(`link[href*=\"widget.css\"]`);\n };\n\n // Check if JS is already loaded\n const isJsLoaded = () => {\n return !!(window as any).ChatWidget;\n };\n\n // Load CSS\n const loadCSS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isCssLoaded()) {\n resolve();\n return;\n }\n\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = cssUrl;\n link.setAttribute(\"data-vanilla-agent\", \"true\");\n link.onload = () => resolve();\n link.onerror = () => reject(new Error(`Failed to load CSS from ${cssUrl}`));\n document.head.appendChild(link);\n });\n };\n\n // Load JS\n const loadJS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isJsLoaded()) {\n resolve();\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = jsUrl;\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error(`Failed to load JS from ${jsUrl}`));\n document.head.appendChild(script);\n });\n };\n\n // Initialize widget\n const initWidget = () => {\n if (!window.ChatWidget || !window.ChatWidget.initChatWidget) {\n console.warn(\"ChatWidget not available. Make sure the script loaded successfully.\");\n return;\n }\n\n const target = config.target || \"body\";\n // Merge apiUrl from top-level config into widget config if present\n const widgetConfig = { ...config.config };\n if ((config as any).apiUrl && !widgetConfig.apiUrl) {\n widgetConfig.apiUrl = (config as any).apiUrl;\n }\n\n // Only initialize if config is provided\n if (!widgetConfig.apiUrl && Object.keys(widgetConfig).length === 0) {\n return;\n }\n\n try {\n window.ChatWidget.initChatWidget({\n target,\n config: widgetConfig\n });\n } catch (error) {\n console.error(\"Failed to initialize ChatWidget:\", error);\n }\n };\n\n // Main installation flow\n const install = async () => {\n try {\n await loadCSS();\n await loadJS();\n \n if (autoInit && (config.config || (config as any).apiUrl)) {\n // Wait a tick to ensure ChatWidget is fully initialized\n setTimeout(initWidget, 0);\n }\n } catch (error) {\n console.error(\"Failed to install ChatWidget:\", error);\n }\n };\n\n // Start installation\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", install);\n } else {\n install();\n }\n})();\n\n"],"mappings":"2CAuBC,UAAW,CACV,aAGA,GAAK,OAAe,2BAClB,OAED,OAAe,2BAA6B,GAE7C,IAAMA,EAAiC,OAAO,iBAAmB,CAAC,EAC5DC,EAAUD,EAAO,SAAW,SAC5BE,EAAMF,EAAO,KAAO,WACpBG,EAAWH,EAAO,WAAa,GAG/BI,EAAa,IAAM,CACvB,GAAIJ,EAAO,QAAUA,EAAO,MAC1B,MAAO,CAAE,OAAQA,EAAO,OAAQ,MAAOA,EAAO,KAAM,EAItD,IAAMK,EAAW,sBAAuBJ,CAAO,QAE/C,OAAIC,IAAQ,QACH,CACL,OAAQ,oBAAoBG,CAAQ,cACpC,MAAO,oBAAoBA,CAAQ,kBACrC,EAEO,CACL,OAAQ,2BAA2BA,CAAQ,cAC3C,MAAO,2BAA2BA,CAAQ,kBAC5C,CAEJ,EAEM,CAAE,OAAAC,EAAQ,MAAAC,CAAM,EAAIH,EAAW,EAG/BI,EAAc,IACX,CAAC,CAAC,SAAS,KAAK,cAAc,0BAA0B,GACxD,CAAC,CAAC,SAAS,KAAK,cAAc,0BAA0B,EAI3DC,EAAa,IACV,CAAC,CAAE,OAAe,WAIrBC,EAAU,IACP,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,GAAIJ,EAAY,EAAG,CACjBG,EAAQ,EACR,MACF,CAEA,IAAME,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,IAAM,aACXA,EAAK,KAAOP,EACZO,EAAK,aAAa,qBAAsB,MAAM,EAC9CA,EAAK,OAAS,IAAMF,EAAQ,EAC5BE,EAAK,QAAU,IAAMD,EAAO,IAAI,MAAM,2BAA2BN,CAAM,EAAE,CAAC,EAC1E,SAAS,KAAK,YAAYO,CAAI,CAChC,CAAC,EAIGC,EAAS,IACN,IAAI,QAAQ,CAACH,EAASC,IAAW,CACtC,GAAIH,EAAW,EAAG,CAChBE,EAAQ,EACR,MACF,CAEA,IAAMI,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,IAAMR,EACbQ,EAAO,MAAQ,GACfA,EAAO,OAAS,IAAMJ,EAAQ,EAC9BI,EAAO,QAAU,IAAMH,EAAO,IAAI,MAAM,0BAA0BL,CAAK,EAAE,CAAC,EAC1E,SAAS,KAAK,YAAYQ,CAAM,CAClC,CAAC,EAIGC,EAAa,IAAM,CACvB,GAAI,CAAC,OAAO,YAAc,CAAC,OAAO,WAAW,eAAgB,CAC3D,QAAQ,KAAK,qEAAqE,EAClF,MACF,CAEA,IAAMC,EAASjB,EAAO,QAAU,OAE1BkB,EAAe,CAAE,GAAGlB,EAAO,MAAO,EAMxC,GALKA,EAAe,QAAU,CAACkB,EAAa,SAC1CA,EAAa,OAAUlB,EAAe,QAIpC,GAACkB,EAAa,QAAU,OAAO,KAAKA,CAAY,EAAE,SAAW,GAIjE,GAAI,CACF,OAAO,WAAW,eAAe,CAC/B,OAAAD,EACA,OAAQC,CACV,CAAC,CACH,OAASC,EAAO,CACd,QAAQ,MAAM,mCAAoCA,CAAK,CACzD,CACF,EAGMC,EAAU,SAAY,CAC1B,GAAI,CACF,MAAMV,EAAQ,EACd,MAAMI,EAAO,EAETX,IAAaH,EAAO,QAAWA,EAAe,SAEhD,WAAWgB,EAAY,CAAC,CAE5B,OAASG,EAAO,CACd,QAAQ,MAAM,gCAAiCA,CAAK,CACtD,CACF,EAGI,SAAS,aAAe,UAC1B,SAAS,iBAAiB,mBAAoBC,CAAO,EAErDA,EAAQ,CAEZ,GAAG","names":["config","version","cdn","autoInit","getCdnBase","basePath","cssUrl","jsUrl","isCssLoaded","isJsLoaded","loadCSS","resolve","reject","link","loadJS","script","initWidget","target","widgetConfig","error","install"]}
1
+ {"version":3,"sources":["../src/install.ts"],"sourcesContent":["/**\n * Standalone installer script for easy script tag installation\n * This script automatically loads CSS and JS, then initializes the widget\n * if configuration is provided via window.siteAgentConfig\n */\n\ninterface SiteAgentInstallConfig {\n version?: string;\n cdn?: \"unpkg\" | \"jsdelivr\";\n cssUrl?: string;\n jsUrl?: string;\n target?: string | HTMLElement;\n config?: any;\n autoInit?: boolean;\n}\n\ndeclare global {\n interface Window {\n siteAgentConfig?: SiteAgentInstallConfig;\n AgentWidget?: any;\n }\n}\n\n(function() {\n \"use strict\";\n\n // Prevent double installation\n if ((window as any).__siteAgentInstallerLoaded) {\n return;\n }\n (window as any).__siteAgentInstallerLoaded = true;\n\n const config: SiteAgentInstallConfig = window.siteAgentConfig || {};\n const version = config.version || \"latest\";\n const cdn = config.cdn || \"jsdelivr\";\n const autoInit = config.autoInit !== false; // Default to true\n\n // Determine CDN base URL\n const getCdnBase = () => {\n if (config.cssUrl && config.jsUrl) {\n return { cssUrl: config.cssUrl, jsUrl: config.jsUrl };\n }\n \n const packageName = \"vanilla-agent\";\n const basePath = `/npm/${packageName}@${version}/dist`;\n \n if (cdn === \"unpkg\") {\n return {\n cssUrl: `https://unpkg.com${basePath}/widget.css`,\n jsUrl: `https://unpkg.com${basePath}/index.global.js`\n };\n } else {\n return {\n cssUrl: `https://cdn.jsdelivr.net${basePath}/widget.css`,\n jsUrl: `https://cdn.jsdelivr.net${basePath}/index.global.js`\n };\n }\n };\n\n const { cssUrl, jsUrl } = getCdnBase();\n\n // Check if CSS is already loaded\n const isCssLoaded = () => {\n return !!document.head.querySelector('link[data-vanilla-agent]') ||\n !!document.head.querySelector(`link[href*=\"widget.css\"]`);\n };\n\n // Check if JS is already loaded\n const isJsLoaded = () => {\n return !!(window as any).AgentWidget;\n };\n\n // Load CSS\n const loadCSS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isCssLoaded()) {\n resolve();\n return;\n }\n\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.href = cssUrl;\n link.setAttribute(\"data-vanilla-agent\", \"true\");\n link.onload = () => resolve();\n link.onerror = () => reject(new Error(`Failed to load CSS from ${cssUrl}`));\n document.head.appendChild(link);\n });\n };\n\n // Load JS\n const loadJS = (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (isJsLoaded()) {\n resolve();\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = jsUrl;\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error(`Failed to load JS from ${jsUrl}`));\n document.head.appendChild(script);\n });\n };\n\n // Initialize widget\n const initWidget = () => {\n if (!window.AgentWidget || !window.AgentWidget.initAgentWidget) {\n console.warn(\"AgentWidget not available. Make sure the script loaded successfully.\");\n return;\n }\n\n const target = config.target || \"body\";\n // Merge apiUrl from top-level config into widget config if present\n const widgetConfig = { ...config.config };\n if ((config as any).apiUrl && !widgetConfig.apiUrl) {\n widgetConfig.apiUrl = (config as any).apiUrl;\n }\n\n // Only initialize if config is provided\n if (!widgetConfig.apiUrl && Object.keys(widgetConfig).length === 0) {\n return;\n }\n\n try {\n window.AgentWidget.initAgentWidget({\n target,\n config: widgetConfig\n });\n } catch (error) {\n console.error(\"Failed to initialize AgentWidget:\", error);\n }\n };\n\n // Main installation flow\n const install = async () => {\n try {\n await loadCSS();\n await loadJS();\n \n if (autoInit && (config.config || (config as any).apiUrl)) {\n // Wait a tick to ensure AgentWidget is fully initialized\n setTimeout(initWidget, 0);\n }\n } catch (error) {\n console.error(\"Failed to install AgentWidget:\", error);\n }\n };\n\n // Start installation\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", install);\n } else {\n install();\n }\n})();\n\n"],"mappings":"2CAuBC,UAAW,CACV,aAGA,GAAK,OAAe,2BAClB,OAED,OAAe,2BAA6B,GAE7C,IAAMA,EAAiC,OAAO,iBAAmB,CAAC,EAC5DC,EAAUD,EAAO,SAAW,SAC5BE,EAAMF,EAAO,KAAO,WACpBG,EAAWH,EAAO,WAAa,GAG/BI,EAAa,IAAM,CACvB,GAAIJ,EAAO,QAAUA,EAAO,MAC1B,MAAO,CAAE,OAAQA,EAAO,OAAQ,MAAOA,EAAO,KAAM,EAItD,IAAMK,EAAW,sBAAuBJ,CAAO,QAE/C,OAAIC,IAAQ,QACH,CACL,OAAQ,oBAAoBG,CAAQ,cACpC,MAAO,oBAAoBA,CAAQ,kBACrC,EAEO,CACL,OAAQ,2BAA2BA,CAAQ,cAC3C,MAAO,2BAA2BA,CAAQ,kBAC5C,CAEJ,EAEM,CAAE,OAAAC,EAAQ,MAAAC,CAAM,EAAIH,EAAW,EAG/BI,EAAc,IACX,CAAC,CAAC,SAAS,KAAK,cAAc,0BAA0B,GACxD,CAAC,CAAC,SAAS,KAAK,cAAc,0BAA0B,EAI3DC,EAAa,IACV,CAAC,CAAE,OAAe,YAIrBC,EAAU,IACP,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,GAAIJ,EAAY,EAAG,CACjBG,EAAQ,EACR,MACF,CAEA,IAAME,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,IAAM,aACXA,EAAK,KAAOP,EACZO,EAAK,aAAa,qBAAsB,MAAM,EAC9CA,EAAK,OAAS,IAAMF,EAAQ,EAC5BE,EAAK,QAAU,IAAMD,EAAO,IAAI,MAAM,2BAA2BN,CAAM,EAAE,CAAC,EAC1E,SAAS,KAAK,YAAYO,CAAI,CAChC,CAAC,EAIGC,EAAS,IACN,IAAI,QAAQ,CAACH,EAASC,IAAW,CACtC,GAAIH,EAAW,EAAG,CAChBE,EAAQ,EACR,MACF,CAEA,IAAMI,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,IAAMR,EACbQ,EAAO,MAAQ,GACfA,EAAO,OAAS,IAAMJ,EAAQ,EAC9BI,EAAO,QAAU,IAAMH,EAAO,IAAI,MAAM,0BAA0BL,CAAK,EAAE,CAAC,EAC1E,SAAS,KAAK,YAAYQ,CAAM,CAClC,CAAC,EAIGC,EAAa,IAAM,CACvB,GAAI,CAAC,OAAO,aAAe,CAAC,OAAO,YAAY,gBAAiB,CAC9D,QAAQ,KAAK,sEAAsE,EACnF,MACF,CAEA,IAAMC,EAASjB,EAAO,QAAU,OAE1BkB,EAAe,CAAE,GAAGlB,EAAO,MAAO,EAMxC,GALKA,EAAe,QAAU,CAACkB,EAAa,SAC1CA,EAAa,OAAUlB,EAAe,QAIpC,GAACkB,EAAa,QAAU,OAAO,KAAKA,CAAY,EAAE,SAAW,GAIjE,GAAI,CACF,OAAO,YAAY,gBAAgB,CACjC,OAAAD,EACA,OAAQC,CACV,CAAC,CACH,OAASC,EAAO,CACd,QAAQ,MAAM,oCAAqCA,CAAK,CAC1D,CACF,EAGMC,EAAU,SAAY,CAC1B,GAAI,CACF,MAAMV,EAAQ,EACd,MAAMI,EAAO,EAETX,IAAaH,EAAO,QAAWA,EAAe,SAEhD,WAAWgB,EAAY,CAAC,CAE5B,OAASG,EAAO,CACd,QAAQ,MAAM,iCAAkCA,CAAK,CACvD,CACF,EAGI,SAAS,aAAe,UAC1B,SAAS,iBAAiB,mBAAoBC,CAAO,EAErDA,EAAQ,CAEZ,GAAG","names":["config","version","cdn","autoInit","getCdnBase","basePath","cssUrl","jsUrl","isCssLoaded","isJsLoaded","loadCSS","resolve","reject","link","loadJS","script","initWidget","target","widgetConfig","error","install"]}
package/dist/widget.css CHANGED
@@ -572,6 +572,10 @@
572
572
  width: 360px;
573
573
  }
574
574
 
575
+ .tvw-w-\[400px\] {
576
+ width: 400px;
577
+ }
578
+
575
579
  .tvw-h-\[640px\] {
576
580
  height: 640px;
577
581
  }
@@ -687,6 +691,35 @@ form:focus-within textarea {
687
691
  opacity: 1;
688
692
  }
689
693
 
694
+ /* Clear chat button tooltip */
695
+ .tvw-clear-chat-button-wrapper {
696
+ position: relative;
697
+ display: inline-flex;
698
+ align-items: center;
699
+ justify-content: center;
700
+ }
701
+
702
+ .tvw-clear-chat-tooltip {
703
+ background-color: #111827;
704
+ color: #ffffff;
705
+ padding: 6px 12px;
706
+ border-radius: 0.5rem;
707
+ font-size: 12px;
708
+ white-space: nowrap;
709
+ pointer-events: none;
710
+ z-index: 10000;
711
+ }
712
+
713
+ .tvw-clear-chat-tooltip-arrow {
714
+ content: "";
715
+ position: absolute;
716
+ top: 100%;
717
+ left: 50%;
718
+ transform: translateX(-50%);
719
+ border: 4px solid transparent;
720
+ border-top-color: #111827;
721
+ }
722
+
690
723
  /* Typing indicator animation */
691
724
  @keyframes typing {
692
725
  0%, 100% {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-agent",
3
- "version": "0.2.0",
3
+ "version": "1.0.0",
4
4
  "description": "Themeable, plugable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -53,7 +53,7 @@
53
53
  "scripts": {
54
54
  "build": "rimraf dist && npm run build:styles && npm run build:client && npm run build:installer",
55
55
  "build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
56
- "build:client": "tsup src/index.ts --format esm,cjs,iife --global-name ChatWidget --minify --sourcemap --splitting false --dts --loader \".css=text\"",
56
+ "build:client": "tsup src/index.ts --format esm,cjs,iife --global-name AgentWidget --minify --sourcemap --splitting false --dts --loader \".css=text\"",
57
57
  "build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
58
58
  "lint": "eslint . --ext .ts",
59
59
  "typecheck": "tsc --noEmit"
package/src/client.ts CHANGED
@@ -1,20 +1,20 @@
1
- import { ChatWidgetConfig, ChatWidgetMessage, ChatWidgetEvent } from "./types";
1
+ import { AgentWidgetConfig, AgentWidgetMessage, AgentWidgetEvent } from "./types";
2
2
 
3
3
  type DispatchOptions = {
4
- messages: ChatWidgetMessage[];
4
+ messages: AgentWidgetMessage[];
5
5
  signal?: AbortSignal;
6
6
  };
7
7
 
8
- type SSEHandler = (event: ChatWidgetEvent) => void;
8
+ type SSEHandler = (event: AgentWidgetEvent) => void;
9
9
 
10
10
  const DEFAULT_ENDPOINT = "https://api.travrse.ai/v1/dispatch";
11
11
 
12
- export class ChatWidgetClient {
12
+ export class AgentWidgetClient {
13
13
  private readonly apiUrl: string;
14
14
  private readonly headers: Record<string, string>;
15
15
  private readonly debug: boolean;
16
16
 
17
- constructor(private config: ChatWidgetConfig = {}) {
17
+ constructor(private config: AgentWidgetConfig = {}) {
18
18
  this.apiUrl = config.apiUrl ?? DEFAULT_ENDPOINT;
19
19
  this.headers = {
20
20
  "Content-Type": "application/json",
@@ -51,7 +51,7 @@ export class ChatWidgetClient {
51
51
 
52
52
  if (this.debug) {
53
53
  // eslint-disable-next-line no-console
54
- console.debug("[ChatWidgetClient] dispatch body", body);
54
+ console.debug("[AgentWidgetClient] dispatch body", body);
55
55
  }
56
56
 
57
57
  const response = await fetch(this.apiUrl, {
@@ -89,7 +89,7 @@ export class ChatWidgetClient {
89
89
  let sequenceCounter = 0;
90
90
  const nextSequence = () => baseSequence + sequenceCounter++;
91
91
 
92
- const cloneMessage = (msg: ChatWidgetMessage): ChatWidgetMessage => {
92
+ const cloneMessage = (msg: AgentWidgetMessage): AgentWidgetMessage => {
93
93
  const reasoning = msg.reasoning
94
94
  ? {
95
95
  ...msg.reasoning,
@@ -117,16 +117,16 @@ export class ChatWidgetClient {
117
117
  };
118
118
  };
119
119
 
120
- const emitMessage = (msg: ChatWidgetMessage) => {
120
+ const emitMessage = (msg: AgentWidgetMessage) => {
121
121
  onEvent({
122
122
  type: "message",
123
123
  message: cloneMessage(msg)
124
124
  });
125
125
  };
126
126
 
127
- let assistantMessage: ChatWidgetMessage | null = null;
128
- const reasoningMessages = new Map<string, ChatWidgetMessage>();
129
- const toolMessages = new Map<string, ChatWidgetMessage>();
127
+ let assistantMessage: AgentWidgetMessage | null = null;
128
+ const reasoningMessages = new Map<string, AgentWidgetMessage>();
129
+ const toolMessages = new Map<string, AgentWidgetMessage>();
130
130
  const reasoningContext = {
131
131
  lastId: null as string | null,
132
132
  byStep: new Map<string, string>()
@@ -224,7 +224,7 @@ export class ChatWidgetClient {
224
224
  return existing;
225
225
  }
226
226
 
227
- const message: ChatWidgetMessage = {
227
+ const message: AgentWidgetMessage = {
228
228
  id: `reason-${reasoningId}`,
229
229
  role: "assistant",
230
230
  content: "",
@@ -286,7 +286,7 @@ export class ChatWidgetClient {
286
286
  return existing;
287
287
  }
288
288
 
289
- const message: ChatWidgetMessage = {
289
+ const message: AgentWidgetMessage = {
290
290
  id: `tool-${toolId}`,
291
291
  role: "assistant",
292
292
  content: "",
@@ -556,7 +556,7 @@ export class ChatWidgetClient {
556
556
  } else {
557
557
  const existingAssistant = assistantMessage;
558
558
  if (existingAssistant) {
559
- const assistantFinal = existingAssistant as ChatWidgetMessage;
559
+ const assistantFinal = existingAssistant as AgentWidgetMessage;
560
560
  assistantFinal.streaming = false;
561
561
  emitMessage(assistantFinal);
562
562
  }
@@ -1,6 +1,6 @@
1
1
  import { createElement } from "../utils/dom";
2
- import { ChatWidgetMessage, ChatWidgetConfig } from "../types";
3
- import { ChatWidgetSession } from "../session";
2
+ import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
3
+ import { AgentWidgetSession } from "../session";
4
4
 
5
5
  export const formDefinitions: Record<
6
6
  string,
@@ -40,9 +40,9 @@ export const formDefinitions: Record<
40
40
 
41
41
  export const enhanceWithForms = (
42
42
  bubble: HTMLElement,
43
- message: ChatWidgetMessage,
44
- config: ChatWidgetConfig,
45
- session: ChatWidgetSession
43
+ message: AgentWidgetMessage,
44
+ config: AgentWidgetConfig,
45
+ session: AgentWidgetSession
46
46
  ) => {
47
47
  const placeholders = bubble.querySelectorAll<HTMLElement>("[data-tv-form]");
48
48
  if (placeholders.length) {
@@ -163,3 +163,5 @@ export const enhanceWithForms = (
163
163
 
164
164
 
165
165
 
166
+
167
+
@@ -1,16 +1,16 @@
1
1
  import { createElement } from "../utils/dom";
2
- import { ChatWidgetConfig } from "../types";
2
+ import { AgentWidgetConfig } from "../types";
3
3
  import { positionMap } from "../utils/positioning";
4
4
  import { renderLucideIcon } from "../utils/icons";
5
5
 
6
6
  export interface LauncherButton {
7
7
  element: HTMLButtonElement;
8
- update: (config: ChatWidgetConfig) => void;
8
+ update: (config: AgentWidgetConfig) => void;
9
9
  destroy: () => void;
10
10
  }
11
11
 
12
12
  export const createLauncherButton = (
13
- config: ChatWidgetConfig | undefined,
13
+ config: AgentWidgetConfig | undefined,
14
14
  onToggle: () => void
15
15
  ): LauncherButton => {
16
16
  const button = createElement("button") as HTMLButtonElement;
@@ -26,7 +26,7 @@ export const createLauncherButton = (
26
26
  `;
27
27
  button.addEventListener("click", onToggle);
28
28
 
29
- const update = (newConfig: ChatWidgetConfig) => {
29
+ const update = (newConfig: AgentWidgetConfig) => {
30
30
  const launcher = newConfig.launcher ?? {};
31
31
 
32
32
  const titleEl = button.querySelector("[data-role='launcher-title']");
@@ -1,14 +1,14 @@
1
1
  import { createElement } from "../utils/dom";
2
- import { ChatWidgetMessage } from "../types";
2
+ import { AgentWidgetMessage } from "../types";
3
3
 
4
4
  export type MessageTransform = (context: {
5
5
  text: string;
6
- message: ChatWidgetMessage;
6
+ message: AgentWidgetMessage;
7
7
  streaming: boolean;
8
8
  }) => string;
9
9
 
10
10
  export const createStandardBubble = (
11
- message: ChatWidgetMessage,
11
+ message: AgentWidgetMessage,
12
12
  transform: MessageTransform
13
13
  ): HTMLElement => {
14
14
  const classes = [
@@ -1,5 +1,5 @@
1
1
  import { createElement, createFragment } from "../utils/dom";
2
- import { ChatWidgetMessage } from "../types";
2
+ import { AgentWidgetMessage } from "../types";
3
3
  import { MessageTransform } from "./message-bubble";
4
4
  import { createStandardBubble } from "./message-bubble";
5
5
  import { createReasoningBubble } from "./reasoning-bubble";
@@ -7,7 +7,7 @@ import { createToolBubble } from "./tool-bubble";
7
7
 
8
8
  export const renderMessages = (
9
9
  container: HTMLElement,
10
- messages: ChatWidgetMessage[],
10
+ messages: AgentWidgetMessage[],
11
11
  transform: MessageTransform,
12
12
  showReasoning: boolean,
13
13
  showToolCalls: boolean
@@ -41,3 +41,5 @@ export const renderMessages = (
41
41
 
42
42
 
43
43
 
44
+
45
+
@@ -1,6 +1,6 @@
1
1
  import { createElement } from "../utils/dom";
2
2
  import { renderLucideIcon } from "../utils/icons";
3
- import { ChatWidgetConfig } from "../types";
3
+ import { AgentWidgetConfig } from "../types";
4
4
  import { positionMap } from "../utils/positioning";
5
5
 
6
6
  export interface PanelWrapper {
@@ -8,7 +8,7 @@ export interface PanelWrapper {
8
8
  panel: HTMLElement;
9
9
  }
10
10
 
11
- export const createWrapper = (config?: ChatWidgetConfig): PanelWrapper => {
11
+ export const createWrapper = (config?: AgentWidgetConfig): PanelWrapper => {
12
12
  const launcherEnabled = config?.launcher?.enabled ?? true;
13
13
 
14
14
  if (!launcherEnabled) {
@@ -40,7 +40,7 @@ export const createWrapper = (config?: ChatWidgetConfig): PanelWrapper => {
40
40
  "tvw-relative tvw-min-h-[320px]"
41
41
  );
42
42
  const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
43
- const width = launcherWidth ?? "min(360px, calc(100vw - 24px))";
43
+ const width = launcherWidth ?? "min(400px, calc(100vw - 24px))";
44
44
  panel.style.width = width;
45
45
  panel.style.maxWidth = width;
46
46
 
@@ -63,10 +63,13 @@ export interface PanelElements {
63
63
  introTitle: HTMLElement;
64
64
  introSubtitle: HTMLElement;
65
65
  closeButton: HTMLButtonElement;
66
+ closeButtonWrapper: HTMLElement;
67
+ clearChatButton: HTMLButtonElement | null;
68
+ clearChatButtonWrapper: HTMLElement | null;
66
69
  iconHolder: HTMLElement;
67
70
  }
68
71
 
69
- export const buildPanel = (config?: ChatWidgetConfig, showClose = true): PanelElements => {
72
+ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelElements => {
70
73
  const container = createElement(
71
74
  "div",
72
75
  "tvw-flex tvw-h-full tvw-w-full tvw-flex-col tvw-bg-cw-surface tvw-text-cw-primary tvw-rounded-2xl tvw-overflow-hidden tvw-shadow-2xl tvw-border tvw-border-cw-border"
@@ -141,19 +144,184 @@ export const buildPanel = (config?: ChatWidgetConfig, showClose = true): PanelEl
141
144
  header.append(headerCopy);
142
145
  }
143
146
 
147
+ // Create clear chat button if enabled
148
+ const clearChatConfig = launcher.clearChat ?? {};
149
+ const clearChatEnabled = clearChatConfig.enabled ?? true;
150
+ let clearChatButton: HTMLButtonElement | null = null;
151
+ let clearChatButtonWrapper: HTMLElement | null = null;
152
+
153
+ if (clearChatEnabled) {
154
+ const clearChatSize = clearChatConfig.size ?? "32px";
155
+ const clearChatIconName = clearChatConfig.iconName ?? "refresh-cw";
156
+ const clearChatIconColor = clearChatConfig.iconColor ?? "";
157
+ const clearChatBgColor = clearChatConfig.backgroundColor ?? "";
158
+ const clearChatBorderWidth = clearChatConfig.borderWidth ?? "";
159
+ const clearChatBorderColor = clearChatConfig.borderColor ?? "";
160
+ const clearChatBorderRadius = clearChatConfig.borderRadius ?? "";
161
+ const clearChatPaddingX = clearChatConfig.paddingX ?? "";
162
+ const clearChatPaddingY = clearChatConfig.paddingY ?? "";
163
+ const clearChatTooltipText = clearChatConfig.tooltipText ?? "Clear chat";
164
+ const clearChatShowTooltip = clearChatConfig.showTooltip ?? true;
165
+
166
+ // Create button wrapper for tooltip
167
+ clearChatButtonWrapper = createElement(
168
+ "div",
169
+ "tvw-relative tvw-ml-auto tvw-clear-chat-button-wrapper"
170
+ );
171
+
172
+ clearChatButton = createElement(
173
+ "button",
174
+ "tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
175
+ ) as HTMLButtonElement;
176
+
177
+ clearChatButton.style.height = clearChatSize;
178
+ clearChatButton.style.width = clearChatSize;
179
+ clearChatButton.type = "button";
180
+ clearChatButton.setAttribute("aria-label", clearChatTooltipText);
181
+
182
+ // Add icon
183
+ const iconSvg = renderLucideIcon(clearChatIconName, "20px", clearChatIconColor || "", 2);
184
+ if (iconSvg) {
185
+ clearChatButton.appendChild(iconSvg);
186
+ }
187
+
188
+ // Apply styling from config
189
+ if (clearChatIconColor) {
190
+ clearChatButton.style.color = clearChatIconColor;
191
+ clearChatButton.classList.remove("tvw-text-cw-muted");
192
+ }
193
+
194
+ if (clearChatBgColor) {
195
+ clearChatButton.style.backgroundColor = clearChatBgColor;
196
+ clearChatButton.classList.remove("hover:tvw-bg-gray-100");
197
+ }
198
+
199
+ if (clearChatBorderWidth || clearChatBorderColor) {
200
+ const borderWidth = clearChatBorderWidth || "0px";
201
+ const borderColor = clearChatBorderColor || "transparent";
202
+ clearChatButton.style.border = `${borderWidth} solid ${borderColor}`;
203
+ clearChatButton.classList.remove("tvw-border-none");
204
+ }
205
+
206
+ if (clearChatBorderRadius) {
207
+ clearChatButton.style.borderRadius = clearChatBorderRadius;
208
+ clearChatButton.classList.remove("tvw-rounded-full");
209
+ }
210
+
211
+ // Apply padding styling
212
+ if (clearChatPaddingX) {
213
+ clearChatButton.style.paddingLeft = clearChatPaddingX;
214
+ clearChatButton.style.paddingRight = clearChatPaddingX;
215
+ } else {
216
+ clearChatButton.style.paddingLeft = "";
217
+ clearChatButton.style.paddingRight = "";
218
+ }
219
+ if (clearChatPaddingY) {
220
+ clearChatButton.style.paddingTop = clearChatPaddingY;
221
+ clearChatButton.style.paddingBottom = clearChatPaddingY;
222
+ } else {
223
+ clearChatButton.style.paddingTop = "";
224
+ clearChatButton.style.paddingBottom = "";
225
+ }
226
+
227
+ clearChatButtonWrapper.appendChild(clearChatButton);
228
+
229
+ // Add tooltip with portaling to document.body to escape overflow clipping
230
+ if (clearChatShowTooltip && clearChatTooltipText && clearChatButton && clearChatButtonWrapper) {
231
+ let portaledTooltip: HTMLElement | null = null;
232
+
233
+ const showTooltip = () => {
234
+ if (portaledTooltip || !clearChatButton) return; // Already showing or button doesn't exist
235
+
236
+ // Create tooltip element
237
+ portaledTooltip = createElement("div", "tvw-clear-chat-tooltip");
238
+ portaledTooltip.textContent = clearChatTooltipText;
239
+
240
+ // Add arrow
241
+ const arrow = createElement("div");
242
+ arrow.className = "tvw-clear-chat-tooltip-arrow";
243
+ portaledTooltip.appendChild(arrow);
244
+
245
+ // Get button position
246
+ const buttonRect = clearChatButton.getBoundingClientRect();
247
+
248
+ // Position tooltip above button
249
+ portaledTooltip.style.position = "fixed";
250
+ portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
251
+ portaledTooltip.style.top = `${buttonRect.top - 8}px`;
252
+ portaledTooltip.style.transform = "translate(-50%, -100%)";
253
+
254
+ // Append to body
255
+ document.body.appendChild(portaledTooltip);
256
+ };
257
+
258
+ const hideTooltip = () => {
259
+ if (portaledTooltip && portaledTooltip.parentNode) {
260
+ portaledTooltip.parentNode.removeChild(portaledTooltip);
261
+ portaledTooltip = null;
262
+ }
263
+ };
264
+
265
+ // Add event listeners
266
+ clearChatButtonWrapper.addEventListener("mouseenter", showTooltip);
267
+ clearChatButtonWrapper.addEventListener("mouseleave", hideTooltip);
268
+ clearChatButton.addEventListener("focus", showTooltip);
269
+ clearChatButton.addEventListener("blur", hideTooltip);
270
+
271
+ // Store cleanup function on the button for later use
272
+ (clearChatButtonWrapper as any)._cleanupTooltip = () => {
273
+ hideTooltip();
274
+ if (clearChatButtonWrapper) {
275
+ clearChatButtonWrapper.removeEventListener("mouseenter", showTooltip);
276
+ clearChatButtonWrapper.removeEventListener("mouseleave", hideTooltip);
277
+ }
278
+ if (clearChatButton) {
279
+ clearChatButton.removeEventListener("focus", showTooltip);
280
+ clearChatButton.removeEventListener("blur", hideTooltip);
281
+ }
282
+ };
283
+ }
284
+
285
+ header.appendChild(clearChatButtonWrapper);
286
+ }
287
+
288
+ // Create close button wrapper for tooltip positioning
289
+ const closeButtonWrapper = createElement(
290
+ "div",
291
+ closeButtonPlacement === "top-right"
292
+ ? "tvw-absolute tvw-top-4 tvw-right-4 tvw-z-50"
293
+ : (clearChatEnabled
294
+ ? ""
295
+ : "tvw-ml-auto")
296
+ );
297
+
144
298
  // Create close button with base classes
145
299
  const closeButton = createElement(
146
300
  "button",
147
- closeButtonPlacement === "top-right"
148
- ? "tvw-absolute tvw-top-4 tvw-right-4 tvw-z-50 tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
149
- : "tvw-ml-auto tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
301
+ "tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
150
302
  ) as HTMLButtonElement;
151
303
  closeButton.style.height = closeButtonSize;
152
304
  closeButton.style.width = closeButtonSize;
153
305
  closeButton.type = "button";
154
- closeButton.setAttribute("aria-label", "Close chat");
155
- closeButton.textContent = "×";
306
+
307
+ // Get tooltip config
308
+ const closeButtonTooltipText = launcher.closeButtonTooltipText ?? "Close chat";
309
+ const closeButtonShowTooltip = launcher.closeButtonShowTooltip ?? true;
310
+
311
+ closeButton.setAttribute("aria-label", closeButtonTooltipText);
156
312
  closeButton.style.display = showClose ? "" : "none";
313
+
314
+ // Add icon or fallback text
315
+ const closeButtonIconName = launcher.closeButtonIconName ?? "x";
316
+ const closeButtonIconText = launcher.closeButtonIconText ?? "×";
317
+
318
+ // Try to render Lucide icon, fallback to text if not provided or fails
319
+ const iconSvg = renderLucideIcon(closeButtonIconName, "20px", launcher.closeButtonColor || "", 2);
320
+ if (iconSvg) {
321
+ closeButton.appendChild(iconSvg);
322
+ } else {
323
+ closeButton.textContent = closeButtonIconText;
324
+ }
157
325
 
158
326
  // Apply close button styling from config
159
327
  if (launcher.closeButtonColor) {
@@ -190,15 +358,85 @@ export const buildPanel = (config?: ChatWidgetConfig, showClose = true): PanelEl
190
358
  closeButton.style.borderRadius = "";
191
359
  closeButton.classList.add("tvw-rounded-full");
192
360
  }
193
-
194
- // Position close button based on placement
361
+
362
+ // Apply padding styling
363
+ if (launcher.closeButtonPaddingX) {
364
+ closeButton.style.paddingLeft = launcher.closeButtonPaddingX;
365
+ closeButton.style.paddingRight = launcher.closeButtonPaddingX;
366
+ } else {
367
+ closeButton.style.paddingLeft = "";
368
+ closeButton.style.paddingRight = "";
369
+ }
370
+ if (launcher.closeButtonPaddingY) {
371
+ closeButton.style.paddingTop = launcher.closeButtonPaddingY;
372
+ closeButton.style.paddingBottom = launcher.closeButtonPaddingY;
373
+ } else {
374
+ closeButton.style.paddingTop = "";
375
+ closeButton.style.paddingBottom = "";
376
+ }
377
+
378
+ closeButtonWrapper.appendChild(closeButton);
379
+
380
+ // Add tooltip with portaling to document.body to escape overflow clipping
381
+ if (closeButtonShowTooltip && closeButtonTooltipText) {
382
+ let portaledTooltip: HTMLElement | null = null;
383
+
384
+ const showTooltip = () => {
385
+ if (portaledTooltip) return; // Already showing
386
+
387
+ // Create tooltip element
388
+ portaledTooltip = createElement("div", "tvw-clear-chat-tooltip");
389
+ portaledTooltip.textContent = closeButtonTooltipText;
390
+
391
+ // Add arrow
392
+ const arrow = createElement("div");
393
+ arrow.className = "tvw-clear-chat-tooltip-arrow";
394
+ portaledTooltip.appendChild(arrow);
395
+
396
+ // Get button position
397
+ const buttonRect = closeButton.getBoundingClientRect();
398
+
399
+ // Position tooltip above button
400
+ portaledTooltip.style.position = "fixed";
401
+ portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
402
+ portaledTooltip.style.top = `${buttonRect.top - 8}px`;
403
+ portaledTooltip.style.transform = "translate(-50%, -100%)";
404
+
405
+ // Append to body
406
+ document.body.appendChild(portaledTooltip);
407
+ };
408
+
409
+ const hideTooltip = () => {
410
+ if (portaledTooltip && portaledTooltip.parentNode) {
411
+ portaledTooltip.parentNode.removeChild(portaledTooltip);
412
+ portaledTooltip = null;
413
+ }
414
+ };
415
+
416
+ // Add event listeners
417
+ closeButtonWrapper.addEventListener("mouseenter", showTooltip);
418
+ closeButtonWrapper.addEventListener("mouseleave", hideTooltip);
419
+ closeButton.addEventListener("focus", showTooltip);
420
+ closeButton.addEventListener("blur", hideTooltip);
421
+
422
+ // Store cleanup function on the wrapper for later use
423
+ (closeButtonWrapper as any)._cleanupTooltip = () => {
424
+ hideTooltip();
425
+ closeButtonWrapper.removeEventListener("mouseenter", showTooltip);
426
+ closeButtonWrapper.removeEventListener("mouseleave", hideTooltip);
427
+ closeButton.removeEventListener("focus", showTooltip);
428
+ closeButton.removeEventListener("blur", hideTooltip);
429
+ };
430
+ }
431
+
432
+ // Position close button wrapper based on placement
195
433
  if (closeButtonPlacement === "top-right") {
196
434
  // Make container position relative for absolute positioning
197
435
  container.style.position = "relative";
198
- container.appendChild(closeButton);
436
+ container.appendChild(closeButtonWrapper);
199
437
  } else {
200
438
  // Inline placement: append to header
201
- header.appendChild(closeButton);
439
+ header.appendChild(closeButtonWrapper);
202
440
  }
203
441
 
204
442
  const body = createElement(
@@ -547,6 +785,9 @@ export const buildPanel = (config?: ChatWidgetConfig, showClose = true): PanelEl
547
785
  introTitle,
548
786
  introSubtitle,
549
787
  closeButton,
788
+ closeButtonWrapper,
789
+ clearChatButton,
790
+ clearChatButtonWrapper,
550
791
  iconHolder
551
792
  };
552
793
  };
@@ -1,11 +1,11 @@
1
1
  import { createElement } from "../utils/dom";
2
- import { ChatWidgetMessage } from "../types";
2
+ import { AgentWidgetMessage } from "../types";
3
3
  import { describeReasonStatus } from "../utils/formatting";
4
4
 
5
5
  // Expansion state per widget instance
6
6
  const reasoningExpansionState = new Set<string>();
7
7
 
8
- export const createReasoningBubble = (message: ChatWidgetMessage): HTMLElement => {
8
+ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement => {
9
9
  const reasoning = message.reasoning;
10
10
  const bubble = createElement(
11
11
  "div",