@jidou-ai/chat-widget 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/widget.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var JidouChatWidget=(()=>{var U=Object.defineProperty;var Me=Object.getOwnPropertyDescriptor;var ke=Object.getOwnPropertyNames;var _e=Object.prototype.hasOwnProperty;var Le=(i,t)=>{for(var e in t)U(i,e,{get:t[e],enumerable:!0})},Ie=(i,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of ke(t))!_e.call(i,s)&&s!==e&&U(i,s,{get:()=>t[s],enumerable:!(n=Me(t,s))||n.enumerable});return i};var Ae=i=>Ie(U({},"__esModule",{value:!0}),i);var qe={};Le(qe,{Widget:()=>A});var E=class{constructor(){this.listeners=new Map}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}off(t,e){let n=this.listeners.get(t);n&&n.delete(e)}emit(t,e){let n=this.listeners.get(t);if(n)for(let s of n)try{s(e)}catch(o){console.error(`Error in event listener for ${String(t)}:`,o)}}once(t,e){let n=s=>{this.off(t,n),e(s)};this.on(t,n)}removeAllListeners(t){t?this.listeners.delete(t):this.listeners.clear()}};var ye={isOpen:!1,displayMode:"floating",connectionState:"disconnected",messages:[],isTyping:!1,unreadCount:0,sessionId:null},O=class extends E{constructor(t){super(),this.state={...ye,...t}}getState(){return{...this.state}}get(t){return this.state[t]}set(t,e){let n=this.state[t];if(n!==e)switch(this.state[t]=e,this.emit("change",{key:t,value:e,prev:n}),t){case"isOpen":this.emit("openChange",e);break;case"displayMode":this.emit("displayModeChange",e);break;case"connectionState":this.emit("connectionStateChange",e);break;case"unreadCount":this.emit("unreadCountChange",e);break;case"messages":this.emit("messagesChange",e);break;case"isTyping":this.emit("isTypingChange",e);break}}addMessage(t){let e=[...this.state.messages,t];this.set("messages",e),!this.state.isOpen&&t.role==="assistant"&&this.set("unreadCount",this.state.unreadCount+1)}updateMessage(t,e){let n=this.state.messages.map(s=>s.id===t?{...s,...e}:s);this.set("messages",n)}removeMessage(t){let e=this.state.messages.filter(n=>n.id!==t);this.set("messages",e)}setMessages(t){this.set("messages",t)}clearMessages(){this.set("messages",[])}open(){this.set("isOpen",!0),this.set("unreadCount",0)}close(){this.set("isOpen",!1)}toggle(){this.state.isOpen?this.close():this.open()}setDisplayMode(t){this.set("displayMode",t)}setConnectionState(t){this.set("connectionState",t)}setTyping(t){this.set("isTyping",t)}setSessionId(t){this.set("sessionId",t)}reset(){this.state={...ye}}};function He(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{let t=Math.random()*16|0;return(i==="x"?t:t&3|8).toString(16)})}function Ne(){return Math.random().toString(36).substring(2,10)}function V(){return`sess_${He()}`}function xe(){return`msg_${Ne()}`}var q="jidou_";function T(i){try{let t=localStorage.getItem(q+i);return t===null?null:JSON.parse(t)}catch{return null}}function L(i,t){try{return localStorage.setItem(q+i,JSON.stringify(t)),!0}catch{return!1}}function J(i){try{localStorage.removeItem(q+i)}catch{}}var G="session_id",Re=3e4,We=1e4,Oe=5,Q=[1e3,2e3,4e3,8e3,16e3],B=class extends E{constructor(e,n){super();this.ws=null;this.connectionState="disconnected";this.reconnectAttempts=0;this.reconnectTimeoutId=null;this.heartbeatIntervalId=null;this.heartbeatTimeoutId=null;this.isManualClose=!1;this.clientId=e,this.wsUrl=n;let s=T(G);s?this.sessionId=s:(this.sessionId=V(),L(G,this.sessionId))}getSessionId(){return this.sessionId}getConnectionState(){return this.connectionState}connect(){if(this.ws&&this.ws.readyState===WebSocket.OPEN)return;this.isManualClose=!1,this.setConnectionState("connecting");let e=`${this.wsUrl}?clientId=${encodeURIComponent(this.clientId)}&sessionId=${encodeURIComponent(this.sessionId)}`;try{this.ws=new WebSocket(e),this.setupEventHandlers()}catch(n){this.handleError(n instanceof Error?n:new Error(String(n)))}}disconnect(){this.isManualClose=!0,this.cleanup(),this.ws&&(this.ws.close(1e3,"Manual disconnect"),this.ws=null),this.setConnectionState("disconnected"),this.emit("disconnected",void 0)}send(e,n){if(!this.ws||this.ws.readyState!==WebSocket.OPEN){console.warn("WebSocket is not connected");return}let s={type:e,payload:n,timestamp:Date.now()};try{this.ws.send(JSON.stringify(s))}catch(o){this.handleError(o instanceof Error?o:new Error(String(o)))}}sendMessage(e){this.send("message:send",{content:e})}sendTypingStart(){this.send("typing:start")}sendTypingStop(){this.send("typing:stop")}setupEventHandlers(){this.ws&&(this.ws.onopen=()=>{this.reconnectAttempts=0,this.startHeartbeat(),this.send("session:init",{sessionId:this.sessionId,clientId:this.clientId})},this.ws.onmessage=e=>{this.handleMessage(e.data)},this.ws.onclose=e=>{this.stopHeartbeat(),this.isManualClose||(this.setConnectionState("disconnected"),this.attemptReconnect())},this.ws.onerror=()=>{})}handleMessage(e){try{let n=JSON.parse(e);switch(n.type){case"connection:ack":{let s=n.payload;this.setConnectionState("connected"),this.emit("connected",{sessionId:s.sessionId,isResumed:s.isResumed});break}case"session:expired":this.sessionId=V(),L(G,this.sessionId),this.emit("sessionExpired",void 0);break;case"message:receive":{let s=n.payload;this.emit("message",s);break}case"bot:typing":this.emit("typing",!0);break;case"pong":this.handlePong();break;case"error":{let s=n.payload;this.handleError(new Error(s.message));break}}}catch(n){console.error("Failed to parse WebSocket message:",n)}}setConnectionState(e){this.connectionState!==e&&(this.connectionState=e,this.emit("connectionStateChange",e))}attemptReconnect(){if(this.isManualClose)return;if(this.reconnectAttempts>=Oe){this.setConnectionState("failed"),this.emit("error",new Error("Max reconnection attempts reached"));return}let e=Q[this.reconnectAttempts]||Q[Q.length-1];this.reconnectAttempts++,this.setConnectionState("reconnecting"),this.emit("reconnect",this.reconnectAttempts),this.reconnectTimeoutId=setTimeout(()=>{this.connect()},e)}startHeartbeat(){this.stopHeartbeat(),this.heartbeatIntervalId=setInterval(()=>{this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.send("ping"),this.startHeartbeatTimeout())},Re)}stopHeartbeat(){this.heartbeatIntervalId&&(clearInterval(this.heartbeatIntervalId),this.heartbeatIntervalId=null),this.clearHeartbeatTimeout()}startHeartbeatTimeout(){this.clearHeartbeatTimeout(),this.heartbeatTimeoutId=setTimeout(()=>{console.warn("Heartbeat timeout, reconnecting..."),this.ws&&this.ws.close()},We)}clearHeartbeatTimeout(){this.heartbeatTimeoutId&&(clearTimeout(this.heartbeatTimeoutId),this.heartbeatTimeoutId=null)}handlePong(){this.clearHeartbeatTimeout()}handleError(e){console.error("WebSocket error:",e),this.emit("error",e)}cleanup(){this.stopHeartbeat(),this.reconnectTimeoutId&&(clearTimeout(this.reconnectTimeoutId),this.reconnectTimeoutId=null),this.reconnectAttempts=0}destroy(){this.disconnect(),this.removeAllListeners()}};var M="chat_history",ve=100,je=7*24*60*60*1e3,D=class{constructor(){this.sessionId=null}setSessionId(t){this.sessionId=t}getSessionId(){return this.sessionId}getValidHistory(t){let e=T(M);return e?Date.now()-e.savedAt>je?(this.clear(),null):t&&e.sessionId!==t?null:e:null}save(t){if(!this.sessionId)return!1;let e={sessionId:this.sessionId,messages:t.slice(-ve),savedAt:Date.now()};return L(M,e)}load(){let t=this.getValidHistory(this.sessionId||void 0);return(t==null?void 0:t.messages)||null}loadForSession(t){let e=this.getValidHistory(t);return(e==null?void 0:e.messages)||null}appendMessage(t){if(!this.sessionId)return!1;let e=this.getValidHistory(this.sessionId),n=e?[...e.messages,t].slice(-ve):[t];return this.save(n)}updateMessage(t,e){let n=this.getValidHistory(this.sessionId||void 0);if(!n)return!1;let s=n.messages.map(o=>o.id===t?{...o,...e}:o);return this.save(s)}clear(){J(M)}hasHistory(){return!!this.getValidHistory(this.sessionId||void 0)}getMessageCount(){let t=T(M);return(t==null?void 0:t.messages.length)||0}static cleanup(){let t=T(M);t&&Date.now()-t.savedAt>je&&J(M)}};var u=class{constructor(t){this.options=t,this.element=this.render()}getElement(){return this.element}mount(t){t.appendChild(this.element)}unmount(){this.element.parentNode&&this.element.parentNode.removeChild(this.element)}update(t){this.options={...this.options,...t},this.refresh()}refresh(){let t=this.render();this.element.parentNode&&this.element.parentNode.replaceChild(t,this.element),this.element=t}destroy(){this.unmount()}};var we="http://www.w3.org/2000/svg";function y(...i){let t=document.createElementNS(we,"svg");return t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.setAttribute("stroke","currentColor"),t.setAttribute("stroke-width","2"),t.setAttribute("stroke-linecap","round"),t.setAttribute("stroke-linejoin","round"),i.forEach(e=>t.appendChild(e)),t}function k(i,t){let e=document.createElementNS(we,i);return Object.entries(t).forEach(([n,s])=>e.setAttribute(n,s)),e}var I=i=>k("path",{d:i}),x=(i,t,e,n)=>k("line",{x1:i,y1:t,x2:e,y2:n}),X=(i,t,e)=>k("circle",{cx:i,cy:t,r:e}),Be=(i,t,e,n,s="")=>k("rect",{x:i,y:t,width:e,height:n,...s&&{rx:s}}),C=i=>k("polyline",{points:i}),De=i=>k("polygon",{points:i}),ze={chat:()=>y(I("M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z")),close:()=>y(x("18","6","6","18"),x("6","6","18","18")),send:()=>y(x("22","2","11","13"),De("22 2 15 22 11 13 2 9 22 2")),minimize:()=>y(x("5","12","19","12")),expand:()=>y(C("15 3 21 3 21 9"),C("9 21 3 21 3 15"),x("21","3","14","10"),x("3","21","10","14")),collapse:()=>y(C("4 14 10 14 10 20"),C("20 10 14 10 14 4"),x("14","10","21","3"),x("3","21","10","14")),bot:()=>y(Be("3","11","18","10","2"),X("12","5","2"),I("M12 7v4"),x("8","16","8","16"),x("16","16","16","16")),check:()=>y(C("20 6 9 17 4 12")),checkDouble:()=>y(I("M18 6L7 17l-5-5"),I("M22 10l-7.5 7.5L13 16")),clock:()=>y(X("12","12","10"),C("12 6 12 12 16 14")),alert:()=>y(X("12","12","10"),x("12","8","12","12"),x("12","16","12.01","16")),refresh:()=>y(C("23 4 23 10 17 10"),I("M20.49 15a9 9 0 1 1-2.12-9.36L23 10"))};function m(i){return ze[i]()}var Z={en:{"status.connecting":"Connecting...","status.connected":"Connected","status.disconnected":"Disconnected","status.reconnecting":"Reconnecting...","status.failed":"Connection failed","chat.window":"Chat window","chat.closeChat":"Close chat","chat.openChat":"Open chat","chat.collapse":"Minimize","chat.expand":"Fullscreen","chat.title":"AI Assistant","input.placeholder":"Type a message...","input.ariaLabel":"Message input","input.send":"Send message","branding.poweredBy":"Powered by Jidou","message.today":"Today","message.yesterday":"Yesterday"},"zh-TW":{"status.connecting":"\u9023\u7DDA\u4E2D...","status.connected":"\u5DF2\u9023\u7DDA","status.disconnected":"\u5DF2\u65B7\u7DDA","status.reconnecting":"\u91CD\u65B0\u9023\u7DDA\u4E2D...","status.failed":"\u9023\u7DDA\u5931\u6557","chat.window":"\u804A\u5929\u8996\u7A97","chat.closeChat":"\u95DC\u9589\u804A\u5929","chat.openChat":"\u958B\u555F\u804A\u5929","chat.collapse":"\u7E2E\u5C0F","chat.expand":"\u5168\u87A2\u5E55","chat.title":"AI \u52A9\u7406","input.placeholder":"\u8F38\u5165\u8A0A\u606F...","input.ariaLabel":"\u8A0A\u606F\u8F38\u5165","input.send":"\u50B3\u9001\u8A0A\u606F","branding.poweredBy":"\u7531 Jidou \u63D0\u4F9B","message.today":"\u4ECA\u5929","message.yesterday":"\u6628\u5929"},"zh-CN":{"status.connecting":"\u8FDE\u63A5\u4E2D...","status.connected":"\u5DF2\u8FDE\u63A5","status.disconnected":"\u5DF2\u65AD\u5F00","status.reconnecting":"\u91CD\u65B0\u8FDE\u63A5\u4E2D...","status.failed":"\u8FDE\u63A5\u5931\u8D25","chat.window":"\u804A\u5929\u7A97\u53E3","chat.closeChat":"\u5173\u95ED\u804A\u5929","chat.openChat":"\u6253\u5F00\u804A\u5929","chat.collapse":"\u6700\u5C0F\u5316","chat.expand":"\u5168\u5C4F","chat.title":"AI \u52A9\u624B","input.placeholder":"\u8F93\u5165\u6D88\u606F...","input.ariaLabel":"\u6D88\u606F\u8F93\u5165","input.send":"\u53D1\u9001\u6D88\u606F","branding.poweredBy":"\u7531 Jidou \u63D0\u4F9B","message.today":"\u4ECA\u5929","message.yesterday":"\u6628\u5929"},ja:{"status.connecting":"\u63A5\u7D9A\u4E2D...","status.connected":"\u63A5\u7D9A\u6E08\u307F","status.disconnected":"\u5207\u65AD","status.reconnecting":"\u518D\u63A5\u7D9A\u4E2D...","status.failed":"\u63A5\u7D9A\u5931\u6557","chat.window":"\u30C1\u30E3\u30C3\u30C8\u30A6\u30A3\u30F3\u30C9\u30A6","chat.closeChat":"\u30C1\u30E3\u30C3\u30C8\u3092\u9589\u3058\u308B","chat.openChat":"\u30C1\u30E3\u30C3\u30C8\u3092\u958B\u304F","chat.collapse":"\u6700\u5C0F\u5316","chat.expand":"\u30D5\u30EB\u30B9\u30AF\u30EA\u30FC\u30F3","chat.title":"AI\u30A2\u30B7\u30B9\u30BF\u30F3\u30C8","input.placeholder":"\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u5165\u529B...","input.ariaLabel":"\u30E1\u30C3\u30BB\u30FC\u30B8\u5165\u529B","input.send":"\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1","branding.poweredBy":"Powered by Jidou","message.today":"\u4ECA\u65E5","message.yesterday":"\u6628\u65E5"}};var ee="en";function Fe(){if(typeof navigator>"u")return"en";let t=(navigator.language||navigator.userLanguage||"en").toLowerCase();return t.startsWith("zh-tw")||t.startsWith("zh-hant")?"zh-TW":t.startsWith("zh")?"zh-CN":t.startsWith("ja")?"ja":(t.startsWith("en"),"en")}function te(i){i==="auto"?ee=Fe():ee=i}function h(i){var n;let t=(n=Z[ee])==null?void 0:n[i];if(t)return t;let e=Z.en[i];return e||(console.warn(`Missing translation for key: ${i}`),i)}te("auto");var z=class extends u{render(){var b,f,w;let{config:t,isOpen:e,unreadCount:n,onClick:s}=this.options,o=t.position||"bottom-right",a=t.size||"medium",r=t.shape||"circle",c=document.createElement("button");if(c.className=this.getClassName(o,a,r,e),c.setAttribute("aria-label",e?h("chat.closeChat"):h("chat.openChat")),c.setAttribute("type","button"),t.offsetX!==void 0){let d=o.includes("right");c.style[d?"right":"left"]=`${t.offsetX}px`}t.offsetY!==void 0&&(c.style.bottom=`${t.offsetY}px`);let p=document.createElement("span");if(p.className="jd-launcher__icon",((b=t.icon)==null?void 0:b.type)==="custom"&&t.icon.url){let d=document.createElement("img");d.className="jd-launcher__img",d.src=t.icon.url,d.alt="",p.appendChild(d),c.appendChild(p)}else if(((f=t.icon)==null?void 0:f.type)==="text"&&t.icon.text){let d=document.createElement("span");d.className="jd-launcher__text",d.textContent=t.icon.text,c.appendChild(d)}else{let d=e?m("close"):m("chat");p.appendChild(d),c.appendChild(p)}if(n>0&&!e){let d=document.createElement("span");d.className="jd-launcher__badge",d.textContent=n>99?"99+":String(n),d.setAttribute("aria-label",`${n} unread messages`),c.appendChild(d)}if((w=t.tooltip)!=null&&w.show&&t.tooltip.text&&!e){let d=document.createElement("span");d.className="jd-launcher__tooltip",d.textContent=t.tooltip.text,c.appendChild(d)}return c.addEventListener("click",s),c}getClassName(t,e,n,s){let o=["jd-launcher",`jd-launcher--${t}`,`jd-launcher--${e}`,`jd-launcher--${n}`];return s&&o.push("jd-launcher--open"),o.join(" ")}show(){this.element.classList.remove("jd-launcher--hidden")}hide(){this.element.classList.add("jd-launcher--hidden")}setOpen(t){this.update({...this.options,isOpen:t})}setUnreadCount(t){this.update({...this.options,unreadCount:t})}};function ne(i,t=!0){i.scrollTo({top:i.scrollHeight,behavior:t?"smooth":"auto"})}function Ce(i,t=50){return i.scrollHeight-i.scrollTop-i.clientHeight<t}var Pe=["__proto__","constructor","prototype"];function se(i,...t){let e={...i};for(let n of t)if(n)for(let s of Object.keys(n)){if(Pe.includes(s))continue;let o=n[s],a=e[s];o!==void 0&&typeof o=="object"&&o!==null&&!Array.isArray(o)&&typeof a=="object"&&a!==null&&!Array.isArray(a)?e[s]=se(a,o):o!==void 0&&(e[s]=o)}return e}function Se(i){return new Date(i).toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})}function ie(){return Date.now()}var F=class extends u{constructor(){super(...arguments);this.shouldAutoScroll=!0}render(){let{messages:e,isTyping:n}=this.options,s=document.createElement("div");if(s.className="jd-message-list jd-scrollbar",s.addEventListener("scroll",()=>{this.shouldAutoScroll=Ce(s)}),e.length===0&&!n){let o=this.renderEmptyState();return s.appendChild(o),s}for(let o of e){let a=this.renderMessage(o);s.appendChild(a)}if(n){let o=this.renderTypingIndicator();s.appendChild(o)}return requestAnimationFrame(()=>{this.shouldAutoScroll&&ne(s,!1)}),s}renderMessage(e){let n=document.createElement("div");n.className=`jd-message jd-message--${e.role}`,n.setAttribute("data-message-id",e.id);let s=document.createElement("div");s.className="jd-message__bubble",s.textContent=e.content,n.appendChild(s);let o=document.createElement("div");if(o.className="jd-message__time",o.textContent=Se(e.timestamp),n.appendChild(o),e.role==="user"&&e.status){let a=document.createElement("div");a.className=`jd-message__status jd-message__status--${e.status}`;let r=document.createElement("span");e.status==="sending"?r.appendChild(m("clock")):e.status==="sent"?r.appendChild(m("check")):e.status==="error"&&r.appendChild(m("alert")),a.appendChild(r),n.appendChild(a)}return n}renderTypingIndicator(){let e=document.createElement("div");e.className="jd-typing-indicator";for(let n=0;n<3;n++){let s=document.createElement("span");s.className="jd-typing-indicator__dot",e.appendChild(s)}return e}renderEmptyState(){let e=document.createElement("div");e.className="jd-welcome";let n=document.createElement("div");n.className="jd-welcome__icon",n.appendChild(m("bot")),e.appendChild(n);let s=document.createElement("h3");s.className="jd-welcome__title",s.textContent="Hi! How can I help you?",e.appendChild(s);let o=document.createElement("p");return o.className="jd-welcome__subtitle",o.textContent="Ask me anything",e.appendChild(o),e}setMessages(e){this.options.messages=e,this.refresh()}addMessage(e){this.options.messages=[...this.options.messages,e],this.refresh()}updateMessage(e,n){this.options.messages=this.options.messages.map(s=>s.id===e?{...s,...n}:s),this.refresh()}setTyping(e){this.options.isTyping=e,this.refresh()}scrollToBottom(){ne(this.element)}};var P=class extends u{constructor(){super(...arguments);this.isTyping=!1;this.typingTimeout=null}render(){let{placeholder:e,maxLength:n,disabled:s}=this.options,o=document.createElement("div");o.className="jd-input-area";let a=document.createElement("div");return a.className="jd-input-wrapper",this.textarea=document.createElement("textarea"),this.textarea.className="jd-input",this.textarea.placeholder=e||h("input.placeholder"),this.textarea.rows=1,this.textarea.disabled=s||!1,n&&(this.textarea.maxLength=n),this.textarea.setAttribute("aria-label",h("input.ariaLabel")),this.textarea.addEventListener("input",this.handleInput.bind(this)),this.textarea.addEventListener("keydown",this.handleKeyDown.bind(this)),a.appendChild(this.textarea),o.appendChild(a),this.sendButton=document.createElement("button"),this.sendButton.className="jd-send-btn",this.sendButton.type="button",this.sendButton.disabled=!0,this.sendButton.setAttribute("aria-label",h("input.send")),this.sendButton.appendChild(m("send")),this.sendButton.addEventListener("click",this.handleSend.bind(this)),o.appendChild(this.sendButton),o}handleInput(){var n,s,o,a;if(!this.textarea||!this.sendButton)return;let e=this.textarea.value.trim();this.sendButton.disabled=e.length===0,this.textarea.style.height="auto",this.textarea.style.height=`${Math.min(this.textarea.scrollHeight,120)}px`,e.length>0&&!this.isTyping&&(this.isTyping=!0,(s=(n=this.options).onTypingStart)==null||s.call(n)),this.typingTimeout&&clearTimeout(this.typingTimeout),e.length>0?this.typingTimeout=setTimeout(()=>{var r,c;this.isTyping&&(this.isTyping=!1,(c=(r=this.options).onTypingStop)==null||c.call(r))},1e3):this.isTyping&&(this.isTyping=!1,(a=(o=this.options).onTypingStop)==null||a.call(o))}handleKeyDown(e){e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this.handleSend())}handleSend(){var n,s;if(!this.textarea)return;let e=this.textarea.value.trim();e.length!==0&&(this.isTyping&&(this.isTyping=!1,(s=(n=this.options).onTypingStop)==null||s.call(n)),this.typingTimeout&&(clearTimeout(this.typingTimeout),this.typingTimeout=null),this.textarea.value="",this.textarea.style.height="auto",this.sendButton&&(this.sendButton.disabled=!0),this.options.onSend(e))}focus(){var e;(e=this.textarea)==null||e.focus()}clear(){this.textarea&&(this.textarea.value="",this.textarea.style.height="auto"),this.sendButton&&(this.sendButton.disabled=!0)}setDisabled(e){var n;this.textarea&&(this.textarea.disabled=e),this.sendButton&&(this.sendButton.disabled=e||!((n=this.textarea)!=null&&n.value.trim()))}getValue(){var e;return((e=this.textarea)==null?void 0:e.value.trim())||""}destroy(){this.typingTimeout&&clearTimeout(this.typingTimeout),super.destroy()}};var Ke={connecting:"status.connecting",connected:"status.connected",disconnected:"status.disconnected",reconnecting:"status.reconnecting",failed:"status.failed"},K=class extends u{render(){let{state:t,reconnectAttempt:e}=this.options,n=document.createElement("div");n.className=`jd-connection-status jd-connection-status--${t}`;let s=document.createElement("span");s.className="jd-connection-status__dot",n.appendChild(s);let o=document.createElement("span");o.className="jd-connection-status__text";let a=h(Ke[t]);return t==="reconnecting"&&e&&(a=`${h("status.reconnecting").replace("...","")} (${e}/5)...`),o.textContent=a,n.appendChild(o),t==="connected"&&(n.style.display="none"),n}setState(t,e){this.update({state:t,reconnectAttempt:e})}};var $=class extends u{render(){let{items:t,onSelect:e}=this.options,n=document.createElement("div");n.className="jd-quick-replies";for(let s of t){let o=document.createElement("button");if(o.className="jd-quick-reply",o.type="button",s.icon){let r=document.createElement("span");r.className="jd-quick-reply__icon",r.textContent=s.icon,o.appendChild(r)}let a=document.createElement("span");a.textContent=s.text,o.appendChild(a),o.addEventListener("click",()=>e(s)),n.appendChild(o)}return n}setVisible(t){this.element.style.display=t?"flex":"none"}};var Y=class extends u{constructor(){super(...arguments);this.quickReplies=null}render(){var v,ce,le,he,pe,ue,ge,me,fe;let{config:e,displayMode:n,position:s,connectionState:o,reconnectAttempt:a,messages:r,isTyping:c,quickReplies:p,showBranding:b=!0,onClose:f,onCollapse:w,onExpand:d,onSend:H,onTypingStart:N,onTypingStop:R,onQuickReplySelect:_}=this.options,l=document.createElement("div");if(l.className=this.getClassName(n,s),l.setAttribute("role","dialog"),l.setAttribute("aria-label",h("chat.window")),n==="floating"){let j=e.width||380,S=e.height||600;l.style.width=typeof j=="number"?`${j}px`:j,l.style.height=typeof S=="number"?`${S}px`:S,e.borderRadius&&(l.style.borderRadius=`${e.borderRadius}px`)}if(((v=e.header)==null?void 0:v.show)!==!1){let j=this.renderHeader(e,n,f,w,d);l.appendChild(j)}let g=document.createElement("div");g.className="jd-chat-body";let W=new K({state:o,reconnectAttempt:a});if(this.connectionStatus=W,g.appendChild(W.getElement()),this.messageList=new F({messages:r,isTyping:c}),g.appendChild(this.messageList.getElement()),p&&p.length>0&&_&&(this.quickReplies=new $({items:p,onSelect:j=>{var S;_(j),(S=this.quickReplies)==null||S.setVisible(!1)}}),g.appendChild(this.quickReplies.getElement())),l.appendChild(g),this.inputArea=new P({placeholder:((le=(ce=e.footer)==null?void 0:ce.input)==null?void 0:le.placeholder)||h("input.placeholder"),maxLength:(pe=(he=e.footer)==null?void 0:he.input)==null?void 0:pe.maxLength,disabled:o!=="connected",onSend:H,onTypingStart:N,onTypingStop:R}),l.appendChild(this.inputArea.getElement()),b&&((ge=(ue=e.footer)==null?void 0:ue.branding)==null?void 0:ge.show)!==!1){let j=this.renderBranding((fe=(me=e.footer)==null?void 0:me.branding)==null?void 0:fe.text);l.appendChild(j)}return l}getClassName(e,n){let s=["jd-chat-window",`jd-chat-window--${e}`];return e==="floating"&&s.push(`jd-chat-window--${n}`),s.join(" ")}renderHeader(e,n,s,o,a){var w,d,H,N,R,_;let r=document.createElement("header");if(r.className="jd-chat-header",((d=(w=e.header)==null?void 0:w.logo)==null?void 0:d.show)!==!1){let l=document.createElement("div");if(l.className="jd-chat-header__logo",(N=(H=e.header)==null?void 0:H.logo)!=null&&N.url){let g=document.createElement("img");g.src=e.header.logo.url,g.alt="",e.header.logo.size&&(g.style.width=`${e.header.logo.size}px`,g.style.height=`${e.header.logo.size}px`),l.appendChild(g)}else l.appendChild(m("bot"));r.appendChild(l)}let c=document.createElement("div");c.className="jd-chat-header__content";let p=document.createElement("h2");if(p.className="jd-chat-header__title",p.textContent=((R=e.header)==null?void 0:R.title)||h("chat.title"),c.appendChild(p),(_=e.header)!=null&&_.subtitle){let l=document.createElement("p");l.className="jd-chat-header__subtitle",l.textContent=e.header.subtitle,c.appendChild(l)}r.appendChild(c);let b=document.createElement("div");b.className="jd-chat-header__actions";let f=(l,g,W)=>{let v=document.createElement("button");v.className="jd-chat-header__btn",v.type="button",v.setAttribute("aria-label",g),v.appendChild(m(l)),v.addEventListener("click",W),b.appendChild(v)};return n==="fullscreen"&&o&&f("collapse",h("chat.collapse"),o),n==="floating"&&(a&&f("expand",h("chat.expand"),a),f("close",h("chat.closeChat"),s)),r.appendChild(b),r}renderBranding(e){let n=document.createElement("div");n.className="jd-branding";let s=document.createElement("a");return s.href="https://jidou.ai",s.target="_blank",s.rel="noopener noreferrer",s.textContent=e||h("branding.poweredBy"),n.appendChild(s),n}show(){var e;this.element.classList.remove("jd-chat-window--hidden"),(e=this.inputArea)==null||e.focus()}hide(){this.element.classList.add("jd-chat-window--hidden")}setMessages(e){var n;(n=this.messageList)==null||n.setMessages(e)}addMessage(e){var n;(n=this.messageList)==null||n.addMessage(e)}updateMessage(e,n){var s;(s=this.messageList)==null||s.updateMessage(e,n)}setTyping(e){var n;(n=this.messageList)==null||n.setTyping(e)}setConnectionState(e,n){var s,o;(s=this.connectionStatus)==null||s.setState(e,n),(o=this.inputArea)==null||o.setDisabled(e!=="connected")}focusInput(){var e;(e=this.inputArea)==null||e.focus()}destroy(){var e,n,s,o;(e=this.messageList)==null||e.destroy(),(n=this.inputArea)==null||n.destroy(),(s=this.connectionStatus)==null||s.destroy(),(o=this.quickReplies)==null||o.destroy(),super.destroy()}};var oe=`
1
+ "use strict";var JidouChatWidget=(()=>{var U=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var _e=Object.getOwnPropertyNames;var Le=Object.prototype.hasOwnProperty;var Ie=(o,t)=>{for(var e in t)U(o,e,{get:t[e],enumerable:!0})},Ae=(o,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of _e(t))!Le.call(o,s)&&s!==e&&U(o,s,{get:()=>t[s],enumerable:!(n=ke(t,s))||n.enumerable});return o};var Ne=o=>Ae(U({},"__esModule",{value:!0}),o);var Je={};Ie(Je,{Widget:()=>L});var T=class{constructor(){this.listeners=new Map}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}off(t,e){let n=this.listeners.get(t);n&&n.delete(e)}emit(t,e){let n=this.listeners.get(t);if(n)for(let s of n)try{s(e)}catch(i){console.error(`Error in event listener for ${String(t)}:`,i)}}once(t,e){let n=s=>{this.off(t,n),e(s)};this.on(t,n)}removeAllListeners(t){t?this.listeners.delete(t):this.listeners.clear()}};var ye={isOpen:!1,displayMode:"floating",connectionState:"disconnected",messages:[],isTyping:!1,unreadCount:0,sessionId:null},B=class extends T{constructor(t){super(),this.state={...ye,...t}}getState(){return{...this.state}}get(t){return this.state[t]}set(t,e){let n=this.state[t];if(n!==e)switch(this.state[t]=e,this.emit("change",{key:t,value:e,prev:n}),t){case"isOpen":this.emit("openChange",e);break;case"displayMode":this.emit("displayModeChange",e);break;case"connectionState":this.emit("connectionStateChange",e);break;case"unreadCount":this.emit("unreadCountChange",e);break;case"messages":this.emit("messagesChange",e);break;case"isTyping":this.emit("isTypingChange",e);break}}addMessage(t){let e=[...this.state.messages,t];this.set("messages",e),!this.state.isOpen&&t.role==="assistant"&&this.set("unreadCount",this.state.unreadCount+1)}updateMessage(t,e){let n=this.state.messages.map(s=>s.id===t?{...s,...e}:s);this.set("messages",n)}removeMessage(t){let e=this.state.messages.filter(n=>n.id!==t);this.set("messages",e)}setMessages(t){this.set("messages",t)}clearMessages(){this.set("messages",[])}open(){this.set("isOpen",!0),this.set("unreadCount",0)}close(){this.set("isOpen",!1)}toggle(){this.state.isOpen?this.close():this.open()}setDisplayMode(t){this.set("displayMode",t)}setConnectionState(t){this.set("connectionState",t)}setTyping(t){this.set("isTyping",t)}setSessionId(t){this.set("sessionId",t)}reset(){this.state={...ye}}};function He(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,o=>{let t=Math.random()*16|0;return(o==="x"?t:t&3|8).toString(16)})}function Be(){return Math.random().toString(36).substring(2,10)}function V(){return`sess_${He()}`}function xe(){return`msg_${Be()}`}var q="jidou_";function R(o){try{let t=localStorage.getItem(q+o);return t===null?null:JSON.parse(t)}catch{return null}}function M(o,t){try{return localStorage.setItem(q+o,JSON.stringify(t)),!0}catch{return!1}}function be(o){try{localStorage.removeItem(q+o)}catch{}}var W="session_id",Re=3e4,We=1e4,ze=5,J=[1e3,2e3,4e3,8e3,16e3],z=class extends T{constructor(e,n){super();this.ws=null;this.connectionState="disconnected";this.reconnectAttempts=0;this.reconnectTimeoutId=null;this.heartbeatIntervalId=null;this.heartbeatTimeoutId=null;this.isManualClose=!1;this.clientId=e,this.wsUrl=n;let s=R(W);s?this.sessionId=s:(this.sessionId=V(),M(W,this.sessionId))}getSessionId(){return this.sessionId}getConnectionState(){return this.connectionState}connect(){if(this.ws&&this.ws.readyState===WebSocket.OPEN)return;this.isManualClose=!1,this.setConnectionState("connecting");let e=`${this.wsUrl}?clientId=${encodeURIComponent(this.clientId)}&sessionId=${encodeURIComponent(this.sessionId)}`;try{this.ws=new WebSocket(e),this.setupEventHandlers()}catch(n){this.handleError(n instanceof Error?n:new Error(String(n)))}}disconnect(){this.isManualClose=!0,this.cleanup(),this.ws&&(this.ws.close(1e3,"Manual disconnect"),this.ws=null),this.setConnectionState("disconnected"),this.emit("disconnected",void 0)}send(e,n){if(!this.ws||this.ws.readyState!==WebSocket.OPEN){console.warn("WebSocket is not connected");return}let s={type:e,payload:n,timestamp:Date.now()};try{this.ws.send(JSON.stringify(s))}catch(i){this.handleError(i instanceof Error?i:new Error(String(i)))}}sendMessage(e){this.send("message:send",{content:e})}sendTypingStart(){this.send("typing:start")}sendTypingStop(){this.send("typing:stop")}setupEventHandlers(){this.ws&&(this.ws.onopen=()=>{this.reconnectAttempts=0,this.startHeartbeat(),this.send("session:init",{sessionId:this.sessionId,clientId:this.clientId})},this.ws.onmessage=e=>{this.handleMessage(e.data)},this.ws.onclose=e=>{this.stopHeartbeat(),this.isManualClose||(this.setConnectionState("disconnected"),this.attemptReconnect())},this.ws.onerror=()=>{})}handleMessage(e){try{let n=JSON.parse(e);switch(n.type){case"connection:ack":{this.setConnectionState("connected");break}case"session:ready":{let s=n.payload;this.sessionId=s.sessionId,M(W,this.sessionId),this.emit("connected",{sessionId:s.sessionId,isResumed:s.restored});break}case"session:expired":this.sessionId=V(),M(W,this.sessionId),this.emit("sessionExpired",void 0);break;case"message:receive":{let s=n.payload;this.emit("message",s);break}case"bot:typing":this.emit("typing",!0);break;case"pong":this.handlePong();break;case"error":{let s=n.payload;this.handleError(new Error(s.message));break}}}catch(n){console.error("Failed to parse WebSocket message:",n)}}setConnectionState(e){this.connectionState!==e&&(this.connectionState=e,this.emit("connectionStateChange",e))}attemptReconnect(){if(this.isManualClose)return;if(this.reconnectAttempts>=ze){this.setConnectionState("failed"),this.emit("error",new Error("Max reconnection attempts reached"));return}let e=J[this.reconnectAttempts]||J[J.length-1];this.reconnectAttempts++,this.setConnectionState("reconnecting"),this.emit("reconnect",this.reconnectAttempts),this.reconnectTimeoutId=setTimeout(()=>{this.connect()},e)}startHeartbeat(){this.stopHeartbeat(),this.heartbeatIntervalId=setInterval(()=>{this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.send("ping"),this.startHeartbeatTimeout())},Re)}stopHeartbeat(){this.heartbeatIntervalId&&(clearInterval(this.heartbeatIntervalId),this.heartbeatIntervalId=null),this.clearHeartbeatTimeout()}startHeartbeatTimeout(){this.clearHeartbeatTimeout(),this.heartbeatTimeoutId=setTimeout(()=>{console.warn("Heartbeat timeout, reconnecting..."),this.ws&&this.ws.close()},We)}clearHeartbeatTimeout(){this.heartbeatTimeoutId&&(clearTimeout(this.heartbeatTimeoutId),this.heartbeatTimeoutId=null)}handlePong(){this.clearHeartbeatTimeout()}handleError(e){console.error("WebSocket error:",e),this.emit("error",e)}cleanup(){this.stopHeartbeat(),this.reconnectTimeoutId&&(clearTimeout(this.reconnectTimeoutId),this.reconnectTimeoutId=null),this.reconnectAttempts=0}destroy(){this.disconnect(),this.removeAllListeners()}};var G="chat_history_",je=100,we=7*24*60*60*1e3,O=class{constructor(){this.sessionId=null}getStorageKey(t){let e=t||this.sessionId;return e?`${G}${e}`:G}setSessionId(t){this.sessionId=t}getSessionId(){return this.sessionId}getValidHistory(t){let e=this.getStorageKey(t),n=R(e);return n?Date.now()-n.savedAt>we?(this.clear(t),null):n:null}save(t){if(!this.sessionId)return!1;let e={sessionId:this.sessionId,messages:t.slice(-je),savedAt:Date.now()};return M(this.getStorageKey(),e)}load(){let t=this.getValidHistory();return(t==null?void 0:t.messages)||null}loadForSession(t){let e=this.getValidHistory(t);return(e==null?void 0:e.messages)||null}appendMessage(t){if(!this.sessionId)return!1;let e=this.getValidHistory(),n=e?[...e.messages,t].slice(-je):[t];return this.save(n)}updateMessage(t,e){let n=this.getValidHistory();if(!n)return!1;let s=n.messages.map(i=>i.id===t?{...i,...e}:i);return this.save(s)}clear(t){be(this.getStorageKey(t))}hasHistory(){return!!this.getValidHistory()}getMessageCount(){let t=this.getValidHistory();return(t==null?void 0:t.messages.length)||0}static cleanup(){Object.keys(localStorage).filter(e=>e.startsWith(G)).forEach(e=>{try{let n=localStorage.getItem(e);if(n){let s=JSON.parse(n);Date.now()-s.savedAt>we&&localStorage.removeItem(e)}}catch{}})}};var m=class{constructor(t){this.options=t,this.element=this.render()}getElement(){return this.element}mount(t){t.appendChild(this.element)}unmount(){this.element.parentNode&&this.element.parentNode.removeChild(this.element)}update(t){this.options={...this.options,...t},this.refresh()}refresh(){let t=this.render();this.element.parentNode&&this.element.parentNode.replaceChild(t,this.element),this.element=t}destroy(){this.unmount()}};var Ce="http://www.w3.org/2000/svg";function y(...o){let t=document.createElementNS(Ce,"svg");return t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","none"),t.setAttribute("stroke","currentColor"),t.setAttribute("stroke-width","2"),t.setAttribute("stroke-linecap","round"),t.setAttribute("stroke-linejoin","round"),o.forEach(e=>t.appendChild(e)),t}function k(o,t){let e=document.createElementNS(Ce,o);return Object.entries(t).forEach(([n,s])=>e.setAttribute(n,s)),e}var _=o=>k("path",{d:o}),x=(o,t,e,n)=>k("line",{x1:o,y1:t,x2:e,y2:n}),Q=(o,t,e)=>k("circle",{cx:o,cy:t,r:e}),Oe=(o,t,e,n,s="")=>k("rect",{x:o,y:t,width:e,height:n,...s&&{rx:s}}),S=o=>k("polyline",{points:o}),De=o=>k("polygon",{points:o}),Fe={chat:()=>y(_("M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z")),close:()=>y(x("18","6","6","18"),x("6","6","18","18")),send:()=>y(x("22","2","11","13"),De("22 2 15 22 11 13 2 9 22 2")),minimize:()=>y(x("5","12","19","12")),expand:()=>y(S("15 3 21 3 21 9"),S("9 21 3 21 3 15"),x("21","3","14","10"),x("3","21","10","14")),collapse:()=>y(S("4 14 10 14 10 20"),S("20 10 14 10 14 4"),x("14","10","21","3"),x("3","21","10","14")),bot:()=>y(Oe("3","11","18","10","2"),Q("12","5","2"),_("M12 7v4"),x("8","16","8","16"),x("16","16","16","16")),check:()=>y(S("20 6 9 17 4 12")),checkDouble:()=>y(_("M18 6L7 17l-5-5"),_("M22 10l-7.5 7.5L13 16")),clock:()=>y(Q("12","12","10"),S("12 6 12 12 16 14")),alert:()=>y(Q("12","12","10"),x("12","8","12","12"),x("12","16","12.01","16")),refresh:()=>y(S("23 4 23 10 17 10"),_("M20.49 15a9 9 0 1 1-2.12-9.36L23 10"))};function f(o){return Fe[o]()}var X={en:{"status.connecting":"Connecting...","status.connected":"Connected","status.disconnected":"Disconnected","status.reconnecting":"Reconnecting...","status.failed":"Connection failed","chat.window":"Chat window","chat.closeChat":"Close chat","chat.openChat":"Open chat","chat.collapse":"Minimize","chat.expand":"Fullscreen","chat.title":"AI Assistant","input.placeholder":"Type a message...","input.ariaLabel":"Message input","input.send":"Send message","branding.poweredBy":"Powered by Jidou","message.today":"Today","message.yesterday":"Yesterday"},"zh-TW":{"status.connecting":"\u9023\u7DDA\u4E2D...","status.connected":"\u5DF2\u9023\u7DDA","status.disconnected":"\u5DF2\u65B7\u7DDA","status.reconnecting":"\u91CD\u65B0\u9023\u7DDA\u4E2D...","status.failed":"\u9023\u7DDA\u5931\u6557","chat.window":"\u804A\u5929\u8996\u7A97","chat.closeChat":"\u95DC\u9589\u804A\u5929","chat.openChat":"\u958B\u555F\u804A\u5929","chat.collapse":"\u7E2E\u5C0F","chat.expand":"\u5168\u87A2\u5E55","chat.title":"AI \u52A9\u7406","input.placeholder":"\u8F38\u5165\u8A0A\u606F...","input.ariaLabel":"\u8A0A\u606F\u8F38\u5165","input.send":"\u50B3\u9001\u8A0A\u606F","branding.poweredBy":"\u7531 Jidou \u63D0\u4F9B","message.today":"\u4ECA\u5929","message.yesterday":"\u6628\u5929"},"zh-CN":{"status.connecting":"\u8FDE\u63A5\u4E2D...","status.connected":"\u5DF2\u8FDE\u63A5","status.disconnected":"\u5DF2\u65AD\u5F00","status.reconnecting":"\u91CD\u65B0\u8FDE\u63A5\u4E2D...","status.failed":"\u8FDE\u63A5\u5931\u8D25","chat.window":"\u804A\u5929\u7A97\u53E3","chat.closeChat":"\u5173\u95ED\u804A\u5929","chat.openChat":"\u6253\u5F00\u804A\u5929","chat.collapse":"\u6700\u5C0F\u5316","chat.expand":"\u5168\u5C4F","chat.title":"AI \u52A9\u624B","input.placeholder":"\u8F93\u5165\u6D88\u606F...","input.ariaLabel":"\u6D88\u606F\u8F93\u5165","input.send":"\u53D1\u9001\u6D88\u606F","branding.poweredBy":"\u7531 Jidou \u63D0\u4F9B","message.today":"\u4ECA\u5929","message.yesterday":"\u6628\u5929"},ja:{"status.connecting":"\u63A5\u7D9A\u4E2D...","status.connected":"\u63A5\u7D9A\u6E08\u307F","status.disconnected":"\u5207\u65AD","status.reconnecting":"\u518D\u63A5\u7D9A\u4E2D...","status.failed":"\u63A5\u7D9A\u5931\u6557","chat.window":"\u30C1\u30E3\u30C3\u30C8\u30A6\u30A3\u30F3\u30C9\u30A6","chat.closeChat":"\u30C1\u30E3\u30C3\u30C8\u3092\u9589\u3058\u308B","chat.openChat":"\u30C1\u30E3\u30C3\u30C8\u3092\u958B\u304F","chat.collapse":"\u6700\u5C0F\u5316","chat.expand":"\u30D5\u30EB\u30B9\u30AF\u30EA\u30FC\u30F3","chat.title":"AI\u30A2\u30B7\u30B9\u30BF\u30F3\u30C8","input.placeholder":"\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u5165\u529B...","input.ariaLabel":"\u30E1\u30C3\u30BB\u30FC\u30B8\u5165\u529B","input.send":"\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1","branding.poweredBy":"Powered by Jidou","message.today":"\u4ECA\u65E5","message.yesterday":"\u6628\u65E5"}};var Z="en";function Ke(){if(typeof navigator>"u")return"en";let t=(navigator.language||navigator.userLanguage||"en").toLowerCase();return t.startsWith("zh-tw")||t.startsWith("zh-hant")?"zh-TW":t.startsWith("zh")?"zh-CN":t.startsWith("ja")?"ja":(t.startsWith("en"),"en")}function ee(o){o==="auto"?Z=Ke():Z=o}function h(o){var n;let t=(n=X[Z])==null?void 0:n[o];if(t)return t;let e=X.en[o];return e||(console.warn(`Missing translation for key: ${o}`),o)}ee("auto");var D=class extends m{render(){var b,u,w;let{config:t,isOpen:e,unreadCount:n,onClick:s}=this.options,i=t.position||"bottom-right",a=t.size||"medium",r=t.shape||"circle",d=document.createElement("button");if(d.className=this.getClassName(i,a,r,e),d.setAttribute("aria-label",e?h("chat.closeChat"):h("chat.openChat")),d.setAttribute("type","button"),t.offsetX!==void 0){let c=i.includes("right");d.style[c?"right":"left"]=`${t.offsetX}px`}t.offsetY!==void 0&&(d.style.bottom=`${t.offsetY}px`);let l=document.createElement("span");if(l.className="jd-launcher__icon",((b=t.icon)==null?void 0:b.type)==="custom"&&t.icon.url){let c=document.createElement("img");c.className="jd-launcher__img",c.src=t.icon.url,c.alt="",l.appendChild(c),d.appendChild(l)}else if(((u=t.icon)==null?void 0:u.type)==="text"&&t.icon.text){let c=document.createElement("span");c.className="jd-launcher__text",c.textContent=t.icon.text,d.appendChild(c)}else{let c=e?f("close"):f("chat");l.appendChild(c),d.appendChild(l)}if(n>0&&!e){let c=document.createElement("span");c.className="jd-launcher__badge",c.textContent=n>99?"99+":String(n),c.setAttribute("aria-label",`${n} unread messages`),d.appendChild(c)}if((w=t.tooltip)!=null&&w.show&&t.tooltip.text&&!e){let c=document.createElement("span");c.className="jd-launcher__tooltip",c.textContent=t.tooltip.text,d.appendChild(c)}return d.addEventListener("click",s),d}getClassName(t,e,n,s){let i=["jd-launcher",`jd-launcher--${t}`,`jd-launcher--${e}`,`jd-launcher--${n}`];return s&&i.push("jd-launcher--open"),i.join(" ")}show(){this.element.classList.remove("jd-launcher--hidden")}hide(){this.element.classList.add("jd-launcher--hidden")}setOpen(t){this.update({...this.options,isOpen:t})}setUnreadCount(t){this.update({...this.options,unreadCount:t})}};function te(o,t=!0){o.scrollTo({top:o.scrollHeight,behavior:t?"smooth":"auto"})}function Se(o,t=50){return o.scrollHeight-o.scrollTop-o.clientHeight<t}var Pe=["__proto__","constructor","prototype"];function ne(o,...t){let e={...o};for(let n of t)if(n)for(let s of Object.keys(n)){if(Pe.includes(s))continue;let i=n[s],a=e[s];i!==void 0&&typeof i=="object"&&i!==null&&!Array.isArray(i)&&typeof a=="object"&&a!==null&&!Array.isArray(a)?e[s]=ne(a,i):i!==void 0&&(e[s]=i)}return e}function Ee(o){return new Date(o).toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"})}function se(){return Date.now()}var F=class extends m{constructor(){super(...arguments);this.shouldAutoScroll=!0}render(){let{messages:e,isTyping:n}=this.options,s=document.createElement("div");if(s.className="jd-message-list jd-scrollbar",s.addEventListener("scroll",()=>{this.shouldAutoScroll=Se(s)}),e.length===0&&!n){let i=this.renderEmptyState();return s.appendChild(i),s}for(let i of e){let a=this.renderMessage(i);s.appendChild(a)}if(n){let i=this.renderTypingIndicator();s.appendChild(i)}return requestAnimationFrame(()=>{this.shouldAutoScroll&&te(s,!1)}),s}renderMessage(e){let n=document.createElement("div");n.className=`jd-message jd-message--${e.role}`,n.setAttribute("data-message-id",e.id);let s=document.createElement("div");if(s.className="jd-message__bubble",e.role==="assistant"&&e.structContent){let a=this.renderStructContent(e.structContent);s.appendChild(a)}else s.textContent=e.content;n.appendChild(s);let i=document.createElement("div");if(i.className="jd-message__time",i.textContent=Ee(e.timestamp),n.appendChild(i),e.role==="user"&&e.status){let a=document.createElement("div");a.className=`jd-message__status jd-message__status--${e.status}`;let r=document.createElement("span");e.status==="sending"?r.appendChild(f("clock")):e.status==="sent"?r.appendChild(f("check")):e.status==="error"&&r.appendChild(f("alert")),a.appendChild(r),n.appendChild(a)}return n}renderStructContent(e){let n=document.createElement("div");n.className="jd-struct";let s=(e==null?void 0:e.blocks)||[];for(let i of s){let a=this.renderBlock(i);a&&n.appendChild(a)}return n}renderBlock(e){switch(e.type){case"text":return this.renderTextBlock(e);case"image":return this.renderImageBlock(e);case"button":return this.renderButtonBlock(e);case"actions":return this.renderActionsBlock(e);case"divider":return this.renderDividerBlock();case"header":return this.renderHeaderBlock(e);case"card":return this.renderCardBlock(e);case"carousel":return this.renderCarouselBlock(e);case"container":return this.renderContainerBlock(e);default:return null}}renderTextBlock(e){var s,i,a,r,d;let n=document.createElement("p");return n.className="jd-struct-text",n.textContent=e.text||"",(s=e.style)!=null&&s.size&&n.setAttribute("data-size",e.style.size),(i=e.style)!=null&&i.bold&&(n.style.fontWeight="700"),(a=e.style)!=null&&a.italic&&(n.style.fontStyle="italic"),(r=e.style)!=null&&r.strike&&(n.style.textDecoration="line-through"),(d=e.style)!=null&&d.color&&(n.style.color=e.style.color),e.align&&(n.style.textAlign=e.align),n}renderImageBlock(e){let n=document.createElement("img");return n.className="jd-struct-image",n.src=e.url||"",n.alt=e.alt_text||"",e.title&&(n.title=e.title),n}renderButtonBlock(e){let n=document.createElement("button");n.className="jd-struct-button",n.textContent=e.text||"";let s=e.style||"primary";n.setAttribute("data-style",s);let i=e.action;return i&&n.addEventListener("click",()=>{var a,r,d,l;i.type==="url"&&i.url?window.open(i.url,"_blank","noopener,noreferrer"):i.type==="message"&&i.text?(r=(a=this.options).onAction)==null||r.call(a,i.text):i.type==="postback"&&((l=(d=this.options).onAction)==null||l.call(d,i.display_text||i.data||""))}),n}renderActionsBlock(e){let n=document.createElement("div");n.className="jd-struct-actions";let s=e.layout||"vertical";n.setAttribute("data-layout",s);let i=e.buttons||[];for(let a of i)n.appendChild(this.renderButtonBlock(a));return n}renderDividerBlock(){let e=document.createElement("hr");return e.className="jd-struct-divider",e}renderHeaderBlock(e){let n=document.createElement("div");n.className="jd-struct-header";let s=document.createElement("div");if(s.className="jd-struct-header__title",s.textContent=e.title||"",n.appendChild(s),e.subtitle){let i=document.createElement("div");i.className="jd-struct-header__subtitle",i.textContent=e.subtitle,n.appendChild(i)}return n}renderCardBlock(e){let n=document.createElement("div");if(n.className="jd-struct-card",e.header){let s=document.createElement("div");s.className="jd-struct-card__header",e.header.background_color&&(s.style.backgroundColor=e.header.background_color);let i=document.createElement("div");if(i.className="jd-struct-card__title",i.textContent=e.header.title||"",s.appendChild(i),e.header.subtitle){let a=document.createElement("div");a.className="jd-struct-card__subtitle",a.textContent=e.header.subtitle,s.appendChild(a)}n.appendChild(s)}if(e.hero){let s=this.renderImageBlock(e.hero);s.className="jd-struct-card__hero",n.appendChild(s)}if(e.body){let s=document.createElement("div");s.className="jd-struct-card__body";for(let i of e.body){let a=this.renderBlock(i);a&&s.appendChild(a)}n.appendChild(s)}if(e.footer){let s=document.createElement("div");s.className="jd-struct-card__footer";for(let i of e.footer){let a=this.renderBlock(i);a&&s.appendChild(a)}n.appendChild(s)}if(e.actions&&e.actions.length>0){let s=document.createElement("div");s.className="jd-struct-card__actions";for(let i of e.actions)s.appendChild(this.renderButtonBlock(i));n.appendChild(s)}return n}renderCarouselBlock(e){let n=document.createElement("div");n.className="jd-struct-carousel";let s=e.cards||[];for(let i of s)n.appendChild(this.renderCardBlock(i));return n}renderContainerBlock(e){let n=document.createElement("div");n.className="jd-struct-container",n.setAttribute("data-layout",e.layout||"vertical"),e.style&&(e.style.background_color&&(n.style.backgroundColor=e.style.background_color),e.style.border_color&&(n.style.borderColor=e.style.border_color),e.style.border_radius&&(n.style.borderRadius=e.style.border_radius),e.style.padding&&(n.style.padding=e.style.padding));let s=e.contents||[];for(let i of s){let a=this.renderBlock(i);a&&n.appendChild(a)}return n}renderTypingIndicator(){let e=document.createElement("div");e.className="jd-typing-indicator";for(let n=0;n<3;n++){let s=document.createElement("span");s.className="jd-typing-indicator__dot",e.appendChild(s)}return e}renderEmptyState(){let e=document.createElement("div");e.className="jd-welcome";let n=document.createElement("div");n.className="jd-welcome__icon",n.appendChild(f("bot")),e.appendChild(n);let s=document.createElement("h3");s.className="jd-welcome__title",s.textContent="Hi! How can I help you?",e.appendChild(s);let i=document.createElement("p");return i.className="jd-welcome__subtitle",i.textContent="Ask me anything",e.appendChild(i),e}setMessages(e){this.options.messages=e,this.refresh()}addMessage(e){this.options.messages=[...this.options.messages,e],this.refresh()}updateMessage(e,n){this.options.messages=this.options.messages.map(s=>s.id===e?{...s,...n}:s),this.refresh()}setTyping(e){this.options.isTyping=e,this.refresh()}scrollToBottom(){te(this.element)}};var K=class extends m{constructor(){super(...arguments);this.isTyping=!1;this.typingTimeout=null}render(){let{placeholder:e,maxLength:n,disabled:s}=this.options,i=document.createElement("div");i.className="jd-input-area";let a=document.createElement("div");return a.className="jd-input-wrapper",this.textarea=document.createElement("textarea"),this.textarea.className="jd-input",this.textarea.placeholder=e||h("input.placeholder"),this.textarea.rows=1,this.textarea.disabled=s||!1,n&&(this.textarea.maxLength=n),this.textarea.setAttribute("aria-label",h("input.ariaLabel")),this.textarea.addEventListener("input",this.handleInput.bind(this)),this.textarea.addEventListener("keydown",this.handleKeyDown.bind(this)),a.appendChild(this.textarea),i.appendChild(a),this.sendButton=document.createElement("button"),this.sendButton.className="jd-send-btn",this.sendButton.type="button",this.sendButton.disabled=!0,this.sendButton.setAttribute("aria-label",h("input.send")),this.sendButton.appendChild(f("send")),this.sendButton.addEventListener("click",this.handleSend.bind(this)),i.appendChild(this.sendButton),i}handleInput(){var n,s,i,a;if(!this.textarea||!this.sendButton)return;let e=this.textarea.value.trim();this.sendButton.disabled=e.length===0,this.textarea.style.height="auto",this.textarea.style.height=`${Math.min(this.textarea.scrollHeight,120)}px`,e.length>0&&!this.isTyping&&(this.isTyping=!0,(s=(n=this.options).onTypingStart)==null||s.call(n)),this.typingTimeout&&clearTimeout(this.typingTimeout),e.length>0?this.typingTimeout=setTimeout(()=>{var r,d;this.isTyping&&(this.isTyping=!1,(d=(r=this.options).onTypingStop)==null||d.call(r))},1e3):this.isTyping&&(this.isTyping=!1,(a=(i=this.options).onTypingStop)==null||a.call(i))}handleKeyDown(e){e.isComposing||e.keyCode===229||e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this.handleSend())}handleSend(){var n,s;if(!this.textarea)return;let e=this.textarea.value.trim();e.length!==0&&(this.isTyping&&(this.isTyping=!1,(s=(n=this.options).onTypingStop)==null||s.call(n)),this.typingTimeout&&(clearTimeout(this.typingTimeout),this.typingTimeout=null),this.textarea.value="",this.textarea.style.height="auto",this.sendButton&&(this.sendButton.disabled=!0),this.options.onSend(e))}focus(){var e;(e=this.textarea)==null||e.focus()}clear(){this.textarea&&(this.textarea.value="",this.textarea.style.height="auto"),this.sendButton&&(this.sendButton.disabled=!0)}setDisabled(e){var n;this.textarea&&(this.textarea.disabled=e),this.sendButton&&(this.sendButton.disabled=e||!((n=this.textarea)!=null&&n.value.trim()))}getValue(){var e;return((e=this.textarea)==null?void 0:e.value.trim())||""}destroy(){this.typingTimeout&&clearTimeout(this.typingTimeout),super.destroy()}};var $e={connecting:"status.connecting",connected:"status.connected",disconnected:"status.disconnected",reconnecting:"status.reconnecting",failed:"status.failed"},P=class extends m{render(){let{state:t,reconnectAttempt:e}=this.options,n=document.createElement("div");n.className=`jd-connection-status jd-connection-status--${t}`;let s=document.createElement("span");s.className="jd-connection-status__dot",n.appendChild(s);let i=document.createElement("span");i.className="jd-connection-status__text";let a=h($e[t]);return t==="reconnecting"&&e&&(a=`${h("status.reconnecting").replace("...","")} (${e}/5)...`),i.textContent=a,n.appendChild(i),t==="connected"&&(n.style.display="none"),n}setState(t,e){this.update({state:t,reconnectAttempt:e})}};var $=class extends m{render(){let{items:t,onSelect:e}=this.options,n=document.createElement("div");n.className="jd-quick-replies";for(let s of t){let i=document.createElement("button");if(i.className="jd-quick-reply",i.type="button",s.icon){let r=document.createElement("span");r.className="jd-quick-reply__icon",r.textContent=s.icon,i.appendChild(r)}let a=document.createElement("span");a.textContent=s.text,i.appendChild(a),i.addEventListener("click",()=>e(s)),n.appendChild(i)}return n}setVisible(t){this.element.style.display=t?"flex":"none"}};var Y=class extends m{constructor(){super(...arguments);this.quickReplies=null}render(){var de,ce,le,pe,he,ue,ge,me,fe;let{config:e,displayMode:n,position:s,connectionState:i,reconnectAttempt:a,messages:r,isTyping:d,quickReplies:l,showBranding:b=!0,onClose:u,onCollapse:w,onExpand:c,onSend:I,onAction:A,onTypingStart:N,onTypingStop:H,onQuickReplySelect:g}=this.options,p=document.createElement("div");if(p.className=this.getClassName(n,s),p.setAttribute("role","dialog"),p.setAttribute("aria-label",h("chat.window")),n==="floating"){let j=e.width||380,E=e.height||600;p.style.width=typeof j=="number"?`${j}px`:j,p.style.height=typeof E=="number"?`${E}px`:E,e.borderRadius&&(p.style.borderRadius=`${e.borderRadius}px`)}if(((de=e.header)==null?void 0:de.show)!==!1){let j=this.renderHeader(e,n,u,w,c);p.appendChild(j)}let C=document.createElement("div");C.className="jd-chat-body";let v=new P({state:i,reconnectAttempt:a});if(this.connectionStatus=v,C.appendChild(v.getElement()),this.messageList=new F({messages:r,isTyping:d,onAction:A}),C.appendChild(this.messageList.getElement()),l&&l.length>0&&g&&(this.quickReplies=new $({items:l,onSelect:j=>{var E;g(j),(E=this.quickReplies)==null||E.setVisible(!1)}}),C.appendChild(this.quickReplies.getElement())),p.appendChild(C),this.inputArea=new K({placeholder:((le=(ce=e.footer)==null?void 0:ce.input)==null?void 0:le.placeholder)||h("input.placeholder"),maxLength:(he=(pe=e.footer)==null?void 0:pe.input)==null?void 0:he.maxLength,disabled:i!=="connected",onSend:I,onTypingStart:N,onTypingStop:H}),p.appendChild(this.inputArea.getElement()),b&&((ge=(ue=e.footer)==null?void 0:ue.branding)==null?void 0:ge.show)!==!1){let j=this.renderBranding((fe=(me=e.footer)==null?void 0:me.branding)==null?void 0:fe.text);p.appendChild(j)}return p}getClassName(e,n){let s=["jd-chat-window",`jd-chat-window--${e}`];return e==="floating"&&s.push(`jd-chat-window--${n}`),s.join(" ")}renderHeader(e,n,s,i,a){var w,c,I,A,N,H;let r=document.createElement("header");if(r.className="jd-chat-header",((c=(w=e.header)==null?void 0:w.logo)==null?void 0:c.show)!==!1){let g=document.createElement("div");if(g.className="jd-chat-header__logo",(A=(I=e.header)==null?void 0:I.logo)!=null&&A.url){let p=document.createElement("img");p.src=e.header.logo.url,p.alt="",e.header.logo.size&&(p.style.width=`${e.header.logo.size}px`,p.style.height=`${e.header.logo.size}px`),g.appendChild(p)}else g.appendChild(f("bot"));r.appendChild(g)}let d=document.createElement("div");d.className="jd-chat-header__content";let l=document.createElement("h2");if(l.className="jd-chat-header__title",l.textContent=((N=e.header)==null?void 0:N.title)||h("chat.title"),d.appendChild(l),(H=e.header)!=null&&H.subtitle){let g=document.createElement("p");g.className="jd-chat-header__subtitle",g.textContent=e.header.subtitle,d.appendChild(g)}r.appendChild(d);let b=document.createElement("div");b.className="jd-chat-header__actions";let u=(g,p,C)=>{let v=document.createElement("button");v.className=`jd-chat-header__btn jd-chat-header__btn--${g}`,v.type="button",v.setAttribute("aria-label",p),v.appendChild(f(g)),v.addEventListener("click",C),b.appendChild(v)};return n==="fullscreen"&&i&&u("collapse",h("chat.collapse"),i),n==="floating"&&(a&&u("expand",h("chat.expand"),a),u("close",h("chat.closeChat"),s)),r.appendChild(b),r}renderBranding(e){let n=document.createElement("div");n.className="jd-branding";let s=document.createElement("a");return s.href="https://jidou.ai",s.target="_blank",s.rel="noopener noreferrer",s.textContent=e||h("branding.poweredBy"),n.appendChild(s),n}show(){var e;this.element.classList.remove("jd-chat-window--hidden"),(e=this.inputArea)==null||e.focus()}hide(){this.element.classList.add("jd-chat-window--hidden")}setMessages(e){var n;(n=this.messageList)==null||n.setMessages(e)}addMessage(e){var n;(n=this.messageList)==null||n.addMessage(e)}updateMessage(e,n){var s;(s=this.messageList)==null||s.updateMessage(e,n)}setTyping(e){var n;(n=this.messageList)==null||n.setTyping(e)}setConnectionState(e,n){var s,i;(s=this.connectionStatus)==null||s.setState(e,n),(i=this.inputArea)==null||i.setDisabled(e!=="connected")}focusInput(){var e;(e=this.inputArea)==null||e.focus()}destroy(){var e,n,s,i;(e=this.messageList)==null||e.destroy(),(n=this.inputArea)==null||n.destroy(),(s=this.connectionStatus)==null||s.destroy(),(i=this.quickReplies)==null||i.destroy(),super.destroy()}};var ie=`
2
2
  /* CSS Reset for Shadow DOM */
3
3
  *,
4
4
  *::before,
@@ -223,7 +223,7 @@
223
223
  white-space: nowrap;
224
224
  border: 0;
225
225
  }
226
- `;var ae=`
226
+ `;var oe=`
227
227
  .jd-launcher {
228
228
  position: fixed;
229
229
  z-index: var(--jd-z-base);
@@ -404,7 +404,16 @@
404
404
  visibility: hidden;
405
405
  transform: scale(0.8);
406
406
  }
407
- `;var re=`
407
+
408
+ /* Mobile: hide launcher when chat is open */
409
+ @media (max-width: 480px) {
410
+ .jd-launcher--open {
411
+ opacity: 0;
412
+ visibility: hidden;
413
+ pointer-events: none;
414
+ }
415
+ }
416
+ `;var ae=`
408
417
  /* Container */
409
418
  .jd-chat-window {
410
419
  position: fixed;
@@ -465,13 +474,22 @@
465
474
  /* Mobile responsive */
466
475
  @media (max-width: 480px) {
467
476
  .jd-chat-window--floating {
468
- width: 100%;
469
- height: 100%;
470
- max-height: 100vh;
471
- bottom: 0;
472
- right: 0;
473
- left: 0;
474
- border-radius: 0;
477
+ position: fixed !important;
478
+ top: 0 !important;
479
+ left: 0 !important;
480
+ right: 0 !important;
481
+ bottom: 0 !important;
482
+ width: 100vw !important;
483
+ height: 100vh !important;
484
+ max-width: 100vw !important;
485
+ max-height: 100vh !important;
486
+ border-radius: 0 !important;
487
+ z-index: 999999 !important;
488
+ }
489
+
490
+ /* Hide expand button on mobile - already fullscreen */
491
+ .jd-chat-header__btn--expand {
492
+ display: none !important;
475
493
  }
476
494
  }
477
495
 
@@ -638,7 +656,7 @@
638
656
  .jd-connection-status--failed .jd-connection-status__dot {
639
657
  background: #EF4444;
640
658
  }
641
- `;var de=`
659
+ `;var re=`
642
660
  /* Message List Container */
643
661
  .jd-message-list {
644
662
  flex: 1;
@@ -944,6 +962,211 @@
944
962
  height: 20px;
945
963
  }
946
964
 
965
+ /* ============================================
966
+ Structured Content (structContent)
967
+ ============================================ */
968
+
969
+ /* Container for all struct blocks */
970
+ .jd-struct {
971
+ display: flex;
972
+ flex-direction: column;
973
+ gap: 8px;
974
+ width: 100%;
975
+ }
976
+
977
+ /* Text block */
978
+ .jd-struct-text {
979
+ margin: 0;
980
+ font-family: var(--jd-font);
981
+ font-size: 14px;
982
+ line-height: 1.5;
983
+ color: inherit;
984
+ word-wrap: break-word;
985
+ }
986
+ .jd-struct-text[data-size="xs"] { font-size: 11px; }
987
+ .jd-struct-text[data-size="sm"] { font-size: 13px; }
988
+ .jd-struct-text[data-size="md"] { font-size: 14px; }
989
+ .jd-struct-text[data-size="lg"] { font-size: 16px; }
990
+ .jd-struct-text[data-size="xl"] { font-size: 18px; }
991
+ .jd-struct-text[data-size="xxl"] { font-size: 22px; }
992
+
993
+ /* Image block */
994
+ .jd-struct-image {
995
+ width: 100%;
996
+ border-radius: 8px;
997
+ display: block;
998
+ object-fit: cover;
999
+ }
1000
+
1001
+ /* Button block */
1002
+ .jd-struct-button {
1003
+ display: inline-flex;
1004
+ align-items: center;
1005
+ justify-content: center;
1006
+ padding: 8px 16px;
1007
+ border: none;
1008
+ border-radius: 8px;
1009
+ font-family: var(--jd-font);
1010
+ font-size: 14px;
1011
+ font-weight: 500;
1012
+ cursor: pointer;
1013
+ transition: opacity var(--jd-transition), background var(--jd-transition);
1014
+ width: 100%;
1015
+ text-align: center;
1016
+ }
1017
+ .jd-struct-button:hover { opacity: 0.85; }
1018
+ .jd-struct-button[data-style="primary"] {
1019
+ background: var(--jd-primary);
1020
+ color: #fff;
1021
+ }
1022
+ .jd-struct-button[data-style="secondary"] {
1023
+ background: var(--jd-surface);
1024
+ color: var(--jd-text);
1025
+ border: 1px solid var(--jd-border);
1026
+ }
1027
+ .jd-struct-button[data-style="danger"] {
1028
+ background: #EF4444;
1029
+ color: #fff;
1030
+ }
1031
+ .jd-struct-button[data-style="link"] {
1032
+ background: transparent;
1033
+ color: var(--jd-primary);
1034
+ padding: 4px 0;
1035
+ text-decoration: underline;
1036
+ }
1037
+
1038
+ /* Actions block (button group) */
1039
+ .jd-struct-actions {
1040
+ display: flex;
1041
+ gap: 8px;
1042
+ }
1043
+ .jd-struct-actions[data-layout="vertical"] {
1044
+ flex-direction: column;
1045
+ }
1046
+ .jd-struct-actions[data-layout="horizontal"] {
1047
+ flex-direction: row;
1048
+ }
1049
+ .jd-struct-actions[data-layout="horizontal"] .jd-struct-button {
1050
+ flex: 1;
1051
+ width: auto;
1052
+ }
1053
+
1054
+ /* Divider */
1055
+ .jd-struct-divider {
1056
+ border: none;
1057
+ border-top: 1px solid var(--jd-border);
1058
+ margin: 4px 0;
1059
+ }
1060
+
1061
+ /* Header block */
1062
+ .jd-struct-header {
1063
+ display: flex;
1064
+ flex-direction: column;
1065
+ gap: 2px;
1066
+ }
1067
+ .jd-struct-header__title {
1068
+ font-family: var(--jd-font);
1069
+ font-size: 16px;
1070
+ font-weight: 600;
1071
+ color: inherit;
1072
+ }
1073
+ .jd-struct-header__subtitle {
1074
+ font-family: var(--jd-font);
1075
+ font-size: 13px;
1076
+ color: var(--jd-text-secondary);
1077
+ }
1078
+
1079
+ /* Card block */
1080
+ .jd-struct-card {
1081
+ border: 1px solid var(--jd-border);
1082
+ border-radius: 12px;
1083
+ overflow: hidden;
1084
+ background: var(--jd-background);
1085
+ }
1086
+ .jd-struct-card__header {
1087
+ padding: 12px 16px;
1088
+ }
1089
+ .jd-struct-card__title {
1090
+ font-family: var(--jd-font);
1091
+ font-size: 15px;
1092
+ font-weight: 600;
1093
+ color: var(--jd-text);
1094
+ }
1095
+ .jd-struct-card__subtitle {
1096
+ font-family: var(--jd-font);
1097
+ font-size: 13px;
1098
+ color: var(--jd-text-secondary);
1099
+ margin-top: 2px;
1100
+ }
1101
+ .jd-struct-card__hero {
1102
+ width: 100%;
1103
+ border-radius: 0;
1104
+ display: block;
1105
+ object-fit: cover;
1106
+ }
1107
+ .jd-struct-card__body {
1108
+ padding: 12px 16px;
1109
+ display: flex;
1110
+ flex-direction: column;
1111
+ gap: 8px;
1112
+ }
1113
+ .jd-struct-card__footer {
1114
+ padding: 8px 16px 12px;
1115
+ display: flex;
1116
+ flex-direction: column;
1117
+ gap: 6px;
1118
+ border-top: 1px solid var(--jd-border);
1119
+ }
1120
+ .jd-struct-card__actions {
1121
+ padding: 8px 16px 12px;
1122
+ display: flex;
1123
+ flex-direction: column;
1124
+ gap: 6px;
1125
+ border-top: 1px solid var(--jd-border);
1126
+ }
1127
+
1128
+ /* Carousel block */
1129
+ .jd-struct-carousel {
1130
+ display: flex;
1131
+ gap: 12px;
1132
+ overflow-x: auto;
1133
+ scroll-snap-type: x mandatory;
1134
+ -webkit-overflow-scrolling: touch;
1135
+ scrollbar-width: none;
1136
+ padding-bottom: 4px;
1137
+ }
1138
+ .jd-struct-carousel::-webkit-scrollbar { display: none; }
1139
+ .jd-struct-carousel > .jd-struct-card {
1140
+ min-width: 240px;
1141
+ max-width: 280px;
1142
+ flex-shrink: 0;
1143
+ scroll-snap-align: start;
1144
+ }
1145
+
1146
+ /* Container block (generic layout) */
1147
+ .jd-struct-container {
1148
+ display: flex;
1149
+ gap: 8px;
1150
+ }
1151
+ .jd-struct-container[data-layout="vertical"] {
1152
+ flex-direction: column;
1153
+ }
1154
+ .jd-struct-container[data-layout="horizontal"] {
1155
+ flex-direction: row;
1156
+ }
1157
+ .jd-struct-container[data-layout="baseline"] {
1158
+ flex-direction: row;
1159
+ align-items: baseline;
1160
+ }
1161
+
1162
+ /* Wider bubble for struct content */
1163
+ .jd-message--assistant .jd-message__bubble:has(.jd-struct) {
1164
+ padding: 12px;
1165
+ }
1166
+ .jd-message--assistant:has(.jd-struct) {
1167
+ max-width: 95%;
1168
+ }
1169
+
947
1170
  /* Empty State */
948
1171
  .jd-empty-state {
949
1172
  flex: 1;
@@ -962,8 +1185,8 @@
962
1185
  margin-bottom: 16px;
963
1186
  opacity: 0.5;
964
1187
  }
965
- `;function Ee(){return[oe,ae,re,de].join(`
966
- `)}var $e=/^(#[0-9A-Fa-f]{3,8}|rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)|rgba\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*[\d.]+\s*\)|hsl\(\s*\d{1,3}\s*,\s*[\d.]+%\s*,\s*[\d.]+%\s*\)|hsla\(\s*\d{1,3}\s*,\s*[\d.]+%\s*,\s*[\d.]+%\s*,\s*[\d.]+\s*\)|[a-zA-Z]+)$/;function Ye(i){return typeof i=="string"&&$e.test(i.trim())}function Ue(i){return typeof i=="number"&&Number.isFinite(i)&&i>=0&&i<=2147483647}var Ve={displayMode:"floating",language:"auto",theme:"light",colors:{primary:"#4F46E5"},launcher:{show:!0,position:"bottom-right",size:"medium",shape:"circle"},chatWindow:{width:380,height:600,borderRadius:16,header:{logo:{show:!0}},footer:{branding:{show:!0}}},behavior:{persistence:{enabled:!0,duration:7}},zIndex:9999,wsUrl:"wss://chat.jidou.ai/ws"},A=class{constructor(t){this.shadowRoot=null;this.launcher=null;this.chatWindow=null;this.container=null;this.eventListeners=new Map;this.config=se(Ve,t),te(this.config.language||"auto"),this.stateManager=new O({displayMode:this.config.displayMode||"floating"}),this.historyManager=new D,this.wsClient=new B(this.config.clientId,this.config.wsUrl||"wss://chat.jidou.ai/ws"),this.setupStateListeners(),this.setupWSListeners()}init(){var t,e;if(this.createContainer(),this.render(),this.loadHistory(),this.triggerHook("onLoad"),this.wsClient.connect(),this.config.displayMode!=="floating"&&this.open(),(e=(t=this.config.behavior)==null?void 0:t.autoOpen)!=null&&e.enabled&&this.config.displayMode==="floating"){let n=this.config.behavior.autoOpen.delay||0;setTimeout(()=>this.open(),n)}this.triggerHook("onReady")}createContainer(){this.container=document.createElement("div"),this.container.id="jidou-chat-widget";let t=Ue(this.config.zIndex)?this.config.zIndex:9999;this.config.displayMode==="embedded"?this.container.style.cssText=`
1188
+ `;function Te(){return[ie,oe,ae,re].join(`
1189
+ `)}var Ye=/^(#[0-9A-Fa-f]{3,8}|rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)|rgba\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*[\d.]+\s*\)|hsl\(\s*\d{1,3}\s*,\s*[\d.]+%\s*,\s*[\d.]+%\s*\)|hsla\(\s*\d{1,3}\s*,\s*[\d.]+%\s*,\s*[\d.]+%\s*,\s*[\d.]+\s*\)|[a-zA-Z]+)$/;function Ue(o){return typeof o=="string"&&Ye.test(o.trim())}function Ve(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0&&o<=2147483647}var qe={displayMode:"floating",language:"auto",theme:"light",colors:{primary:"#4F46E5"},launcher:{show:!0,position:"bottom-right",size:"medium",shape:"circle"},chatWindow:{width:380,height:600,borderRadius:16,header:{logo:{show:!0}},footer:{branding:{show:!0}}},behavior:{persistence:{enabled:!0,duration:7}},zIndex:9999,wsUrl:"wss://chat.jidou.ai/ws"},L=class{constructor(t){this.shadowRoot=null;this.launcher=null;this.chatWindow=null;this.container=null;this.eventListeners=new Map;this.config=ne(qe,t),ee(this.config.language||"auto"),this.stateManager=new B({displayMode:this.config.displayMode||"floating"}),this.historyManager=new O,this.wsClient=new z(this.config.clientId,this.config.wsUrl||"wss://chat.jidou.ai/ws"),this.setupStateListeners(),this.setupWSListeners()}init(){var t,e;if(this.createContainer(),this.render(),this.triggerHook("onLoad"),this.wsClient.connect(),this.config.displayMode!=="floating"&&this.open(),(e=(t=this.config.behavior)==null?void 0:t.autoOpen)!=null&&e.enabled&&this.config.displayMode==="floating"){let n=this.config.behavior.autoOpen.delay||0;setTimeout(()=>this.open(),n)}this.triggerHook("onReady")}createContainer(){this.container=document.createElement("div"),this.container.id="jidou-chat-widget";let t=Ve(this.config.zIndex)?this.config.zIndex:9999;this.config.displayMode==="embedded"?this.container.style.cssText=`
967
1190
  position: relative;
968
1191
  width: 100%;
969
1192
  height: 100%;
@@ -977,4 +1200,4 @@
977
1200
  z-index: ${t};
978
1201
  pointer-events: none;
979
1202
  overflow: visible;
980
- `,this.shadowRoot=this.container.attachShadow({mode:"open"});let e=document.createElement("style");if(e.textContent=Ee(),this.config.colors&&(e.textContent+=this.generateCustomStyles()),this.config.theme==="dark"){let n=document.createElement("div");n.className="theme-dark",this.shadowRoot.appendChild(e),this.shadowRoot.appendChild(n)}else this.shadowRoot.appendChild(e);if(this.config.displayMode==="embedded"&&this.config.container){let n=typeof this.config.container=="string"?document.querySelector(this.config.container):this.config.container;if(n){n.appendChild(this.container);return}}document.body.appendChild(this.container)}generateCustomStyles(){let{colors:t}=this.config;if(!t)return"";let e={primary:t.primary,background:t.background,text:t.text,"bot-bubble":t.botBubble,"user-bubble":t.userBubble},n=Object.entries(e).filter(([,s])=>Ye(s)).map(([s,o])=>`--jd-${s}: ${o}`);return n.length>0?`:host { ${n.join("; ")}; }`:""}render(){var n,s,o,a,r,c,p,b;if(!this.shadowRoot)return;let t=this.stateManager.getState(),e=((n=this.config.launcher)==null?void 0:n.position)||"bottom-right";t.displayMode==="floating"&&((s=this.config.launcher)==null?void 0:s.show)!==!1&&(this.launcher=new z({config:this.config.launcher||{},isOpen:t.isOpen,unreadCount:t.unreadCount,onClick:()=>this.toggle()}),this.launcher.mount(this.shadowRoot)),this.chatWindow=new Y({config:this.config.chatWindow||{},displayMode:t.displayMode,position:e,connectionState:t.connectionState,messages:t.messages,isTyping:t.isTyping,quickReplies:(r=(a=(o=this.config.chatWindow)==null?void 0:o.welcomeScreen)==null?void 0:a.quickReplies)==null?void 0:r.items,showBranding:(b=(p=(c=this.config.chatWindow)==null?void 0:c.footer)==null?void 0:p.branding)==null?void 0:b.show,onClose:()=>this.close(),onCollapse:()=>this.setDisplayMode("floating"),onExpand:()=>this.setDisplayMode("fullscreen"),onSend:f=>this.handleSendMessage(f),onTypingStart:()=>this.wsClient.sendTypingStart(),onTypingStop:()=>this.wsClient.sendTypingStop(),onQuickReplySelect:f=>this.handleQuickReply(f)}),this.chatWindow.mount(this.shadowRoot),t.displayMode==="floating"&&!t.isOpen&&this.chatWindow.hide()}setupStateListeners(){this.stateManager.on("openChange",t=>{var e,n,s;(e=this.launcher)==null||e.setOpen(t),t?((n=this.chatWindow)==null||n.show(),this.triggerHook("onOpen")):((s=this.chatWindow)==null||s.hide(),this.triggerHook("onClose"))}),this.stateManager.on("unreadCountChange",t=>{var e;(e=this.launcher)==null||e.setUnreadCount(t)}),this.stateManager.on("connectionStateChange",t=>{var e;(e=this.chatWindow)==null||e.setConnectionState(t),this.triggerHook("onConnectionStateChange",t)}),this.stateManager.on("messagesChange",t=>{var e;(e=this.chatWindow)==null||e.setMessages(t),this.historyManager.save(t)}),this.stateManager.on("isTypingChange",t=>{var e;(e=this.chatWindow)==null||e.setTyping(t)})}setupWSListeners(){this.wsClient.on("connectionStateChange",t=>{this.stateManager.setConnectionState(t)}),this.wsClient.on("connected",({sessionId:t,isResumed:e})=>{this.stateManager.setSessionId(t),this.historyManager.setSessionId(t),e||this.triggerHook("onConversationStarted",t)}),this.wsClient.on("reconnect",t=>{var e;(e=this.chatWindow)==null||e.setConnectionState("reconnecting",t),this.triggerHook("onReconnect",t)}),this.wsClient.on("message",t=>{let e={id:t.id,role:"assistant",content:t.content,timestamp:t.timestamp||ie()};this.stateManager.addMessage(e),this.stateManager.setTyping(!1),this.triggerHook("onMessageReceived",t.content)}),this.wsClient.on("typing",t=>{this.stateManager.setTyping(t)}),this.wsClient.on("sessionExpired",()=>{this.historyManager.clear(),this.stateManager.clearMessages()}),this.wsClient.on("error",t=>{this.triggerHook("onError",t)})}loadHistory(){let t=this.wsClient.getSessionId();this.historyManager.setSessionId(t);let e=this.historyManager.load();e&&e.length>0&&this.stateManager.setMessages(e)}handleSendMessage(t){let e={id:xe(),role:"user",content:t,timestamp:ie(),status:"sending"};this.stateManager.addMessage(e),this.triggerHook("onMessageSent",t),this.wsClient.sendMessage(t),this.stateManager.updateMessage(e.id,{status:"sent"})}handleQuickReply(t){let e=t.value||t.text;this.handleSendMessage(e)}triggerHook(t,...e){var o;let n=(o=this.config.hooks)==null?void 0:o[t];if(n)try{n(...e)}catch(a){console.error(`Error in ${t} hook:`,a)}let s=this.eventListeners.get(t);if(s)for(let a of s)try{a(...e)}catch(r){console.error(`Error in ${t} listener:`,r)}}open(){this.stateManager.open()}close(){this.stateManager.close()}toggle(){this.stateManager.toggle()}setDisplayMode(t,e){t!==this.stateManager.get("displayMode")&&(this.config.displayMode=t,e!=null&&e.container&&(this.config.container=e.container),this.stateManager.setDisplayMode(t),this.rerenderUI(),this.triggerHook("onDisplayModeChange",t))}rerenderUI(){this.cleanupUI(),this.createContainer(),this.render(),this.config.displayMode!=="floating"&&this.open()}cleanupUI(){var t,e,n;(t=this.launcher)==null||t.destroy(),(e=this.chatWindow)==null||e.destroy(),(n=this.container)==null||n.remove(),this.shadowRoot=null,this.container=null,this.launcher=null,this.chatWindow=null}getDisplayMode(){return this.stateManager.get("displayMode")}sendMessage(t){this.handleSendMessage(t)}on(t,e){this.eventListeners.has(t)||this.eventListeners.set(t,new Set),this.eventListeners.get(t).add(e)}off(t,e){var n;(n=this.eventListeners.get(t))==null||n.delete(e)}isOpen(){return this.stateManager.get("isOpen")}getState(){return this.stateManager.getState()}destroy(){this.cleanupUI(),this.wsClient.destroy(),this.stateManager.removeAllListeners(),this.eventListeners.clear()}};function Te(){if(typeof window>"u")return;let i=window.JidouChatSettings;if(!(i!=null&&i.clientId)){console.warn("[JidouChat] Missing clientId in JidouChatSettings");return}try{let t=new A(i);t.init(),window.JidouChat={open:()=>t.open(),close:()=>t.close(),toggle:()=>t.toggle(),setDisplayMode:(e,n)=>t.setDisplayMode(e,n),getDisplayMode:()=>t.getDisplayMode(),sendMessage:e=>t.sendMessage(e),on:(e,n)=>t.on(e,n),off:(e,n)=>t.off(e,n),isOpen:()=>t.isOpen(),getState:()=>t.getState(),destroy:()=>t.destroy()}}catch(t){console.error("[JidouChat] Failed to initialize widget:",t)}}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Te):Te());return Ae(qe);})();
1203
+ `,this.shadowRoot=this.container.attachShadow({mode:"open"});let e=document.createElement("style");if(e.textContent=Te(),this.config.colors&&(e.textContent+=this.generateCustomStyles()),this.config.theme==="dark"){let n=document.createElement("div");n.className="theme-dark",this.shadowRoot.appendChild(e),this.shadowRoot.appendChild(n)}else this.shadowRoot.appendChild(e);if(this.config.displayMode==="embedded"&&this.config.container){let n=typeof this.config.container=="string"?document.querySelector(this.config.container):this.config.container;if(n){n.appendChild(this.container);return}}document.body.appendChild(this.container)}generateCustomStyles(){let{colors:t}=this.config;if(!t)return"";let e={primary:t.primary,background:t.background,text:t.text,"bot-bubble":t.botBubble,"user-bubble":t.userBubble},n=Object.entries(e).filter(([,s])=>Ue(s)).map(([s,i])=>`--jd-${s}: ${i}`);return n.length>0?`:host { ${n.join("; ")}; }`:""}render(){var n,s,i,a,r,d,l,b;if(!this.shadowRoot)return;let t=this.stateManager.getState(),e=((n=this.config.launcher)==null?void 0:n.position)||"bottom-right";t.displayMode==="floating"&&((s=this.config.launcher)==null?void 0:s.show)!==!1&&(this.launcher=new D({config:this.config.launcher||{},isOpen:t.isOpen,unreadCount:t.unreadCount,onClick:()=>this.toggle()}),this.launcher.mount(this.shadowRoot)),this.chatWindow=new Y({config:this.config.chatWindow||{},displayMode:t.displayMode,position:e,connectionState:t.connectionState,messages:t.messages,isTyping:t.isTyping,quickReplies:(r=(a=(i=this.config.chatWindow)==null?void 0:i.welcomeScreen)==null?void 0:a.quickReplies)==null?void 0:r.items,showBranding:(b=(l=(d=this.config.chatWindow)==null?void 0:d.footer)==null?void 0:l.branding)==null?void 0:b.show,onClose:()=>this.close(),onCollapse:()=>this.setDisplayMode("floating"),onExpand:()=>this.setDisplayMode("fullscreen"),onSend:u=>this.handleSendMessage(u),onAction:u=>this.handleSendMessage(u),onTypingStart:()=>this.wsClient.sendTypingStart(),onTypingStop:()=>this.wsClient.sendTypingStop(),onQuickReplySelect:u=>this.handleQuickReply(u)}),this.chatWindow.mount(this.shadowRoot),t.displayMode==="floating"&&!t.isOpen&&this.chatWindow.hide()}setupStateListeners(){this.stateManager.on("openChange",t=>{var e,n,s;(e=this.launcher)==null||e.setOpen(t),t?((n=this.chatWindow)==null||n.show(),this.triggerHook("onOpen")):((s=this.chatWindow)==null||s.hide(),this.triggerHook("onClose"))}),this.stateManager.on("unreadCountChange",t=>{var e;(e=this.launcher)==null||e.setUnreadCount(t)}),this.stateManager.on("connectionStateChange",t=>{var e;(e=this.chatWindow)==null||e.setConnectionState(t),this.triggerHook("onConnectionStateChange",t)}),this.stateManager.on("messagesChange",t=>{var e;(e=this.chatWindow)==null||e.setMessages(t),this.historyManager.save(t)}),this.stateManager.on("isTypingChange",t=>{var e;(e=this.chatWindow)==null||e.setTyping(t)})}setupWSListeners(){this.wsClient.on("connectionStateChange",t=>{this.stateManager.setConnectionState(t)}),this.wsClient.on("connected",({sessionId:t,isResumed:e})=>{if(this.stateManager.setSessionId(t),this.historyManager.setSessionId(t),e){let n=this.historyManager.load();n&&n.length>0&&this.stateManager.setMessages(n)}else this.stateManager.clearMessages(),this.triggerHook("onConversationStarted",t)}),this.wsClient.on("reconnect",t=>{var e;(e=this.chatWindow)==null||e.setConnectionState("reconnecting",t),this.triggerHook("onReconnect",t)}),this.wsClient.on("message",t=>{let e={id:t.id,role:"assistant",content:t.content,structContent:t.structContent,timestamp:t.timestamp||se()};this.stateManager.addMessage(e),this.stateManager.setTyping(!1),this.triggerHook("onMessageReceived",t.content)}),this.wsClient.on("typing",t=>{this.stateManager.setTyping(t)}),this.wsClient.on("sessionExpired",()=>{this.historyManager.clear(),this.stateManager.clearMessages()}),this.wsClient.on("error",t=>{this.triggerHook("onError",t)})}handleSendMessage(t){let e={id:xe(),role:"user",content:t,timestamp:se(),status:"sending"};this.stateManager.addMessage(e),this.triggerHook("onMessageSent",t),this.wsClient.sendMessage(t),this.stateManager.updateMessage(e.id,{status:"sent"})}handleQuickReply(t){let e=t.value||t.text;this.handleSendMessage(e)}triggerHook(t,...e){var i;let n=(i=this.config.hooks)==null?void 0:i[t];if(n)try{n(...e)}catch(a){console.error(`Error in ${t} hook:`,a)}let s=this.eventListeners.get(t);if(s)for(let a of s)try{a(...e)}catch(r){console.error(`Error in ${t} listener:`,r)}}open(){this.stateManager.open()}close(){this.stateManager.close()}toggle(){this.stateManager.toggle()}setDisplayMode(t,e){t!==this.stateManager.get("displayMode")&&(this.config.displayMode=t,e!=null&&e.container&&(this.config.container=e.container),this.stateManager.setDisplayMode(t),this.rerenderUI(),this.triggerHook("onDisplayModeChange",t))}rerenderUI(){this.cleanupUI(),this.createContainer(),this.render(),this.config.displayMode!=="floating"&&this.open()}cleanupUI(){var t,e,n;(t=this.launcher)==null||t.destroy(),(e=this.chatWindow)==null||e.destroy(),(n=this.container)==null||n.remove(),this.shadowRoot=null,this.container=null,this.launcher=null,this.chatWindow=null}getDisplayMode(){return this.stateManager.get("displayMode")}sendMessage(t){this.handleSendMessage(t)}on(t,e){this.eventListeners.has(t)||this.eventListeners.set(t,new Set),this.eventListeners.get(t).add(e)}off(t,e){var n;(n=this.eventListeners.get(t))==null||n.delete(e)}isOpen(){return this.stateManager.get("isOpen")}getState(){return this.stateManager.getState()}destroy(){this.cleanupUI(),this.wsClient.destroy(),this.stateManager.removeAllListeners(),this.eventListeners.clear()}};function Me(){if(typeof window>"u")return;let o=window.JidouChatSettings;if(!(o!=null&&o.clientId)){console.warn("[JidouChat] Missing clientId in JidouChatSettings");return}try{let t=new L(o);t.init(),window.JidouChat={open:()=>t.open(),close:()=>t.close(),toggle:()=>t.toggle(),setDisplayMode:(e,n)=>t.setDisplayMode(e,n),getDisplayMode:()=>t.getDisplayMode(),sendMessage:e=>t.sendMessage(e),on:(e,n)=>t.on(e,n),off:(e,n)=>t.off(e,n),isOpen:()=>t.isOpen(),getState:()=>t.getState(),destroy:()=>t.destroy()}}catch(t){console.error("[JidouChat] Failed to initialize widget:",t)}}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Me):Me());return Ne(Je);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jidou-ai/chat-widget",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Jidou AI Web Chat Widget - Embeddable AI chat widget",
5
5
  "main": "dist/widget.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -28,6 +28,7 @@ export interface ChatWindowOptions {
28
28
  onCollapse?: () => void;
29
29
  onExpand?: () => void;
30
30
  onSend: (message: string) => void;
31
+ onAction?: (text: string) => void;
31
32
  onTypingStart?: () => void;
32
33
  onTypingStop?: () => void;
33
34
  onQuickReplySelect?: (item: QuickReply) => void;
@@ -54,6 +55,7 @@ export class ChatWindow extends BaseComponent<ChatWindowOptions> {
54
55
  onCollapse,
55
56
  onExpand,
56
57
  onSend,
58
+ onAction,
57
59
  onTypingStart,
58
60
  onTypingStop,
59
61
  onQuickReplySelect,
@@ -97,6 +99,7 @@ export class ChatWindow extends BaseComponent<ChatWindowOptions> {
97
99
  this.messageList = new MessageList({
98
100
  messages,
99
101
  isTyping,
102
+ onAction,
100
103
  });
101
104
  body.appendChild(this.messageList.getElement());
102
105
 
@@ -203,7 +206,7 @@ export class ChatWindow extends BaseComponent<ChatWindowOptions> {
203
206
 
204
207
  const addButton = (icon: Parameters<typeof createIcon>[0], label: string, onClick: () => void) => {
205
208
  const btn = document.createElement('button');
206
- btn.className = 'jd-chat-header__btn';
209
+ btn.className = `jd-chat-header__btn jd-chat-header__btn--${icon}`;
207
210
  btn.type = 'button';
208
211
  btn.setAttribute('aria-label', label);
209
212
  btn.appendChild(createIcon(icon));
@@ -94,6 +94,9 @@ export class InputArea extends BaseComponent<InputAreaOptions> {
94
94
  }
95
95
 
96
96
  private handleKeyDown(e: KeyboardEvent): void {
97
+ // Ignore Enter during IME composition (e.g. Zhuyin, Pinyin, Japanese)
98
+ if (e.isComposing || e.keyCode === 229) return;
99
+
97
100
  // Send on Enter (without Shift)
98
101
  if (e.key === 'Enter' && !e.shiftKey) {
99
102
  e.preventDefault();
@@ -6,6 +6,7 @@ import type { Message } from '../types';
6
6
  export interface MessageListOptions {
7
7
  messages: Message[];
8
8
  isTyping: boolean;
9
+ onAction?: (text: string) => void;
9
10
  }
10
11
 
11
12
  export class MessageList extends BaseComponent<MessageListOptions> {
@@ -57,7 +58,15 @@ export class MessageList extends BaseComponent<MessageListOptions> {
57
58
 
58
59
  const bubble = document.createElement('div');
59
60
  bubble.className = 'jd-message__bubble';
60
- bubble.textContent = message.content;
61
+
62
+ // Use structured content for assistant messages when available
63
+ if (message.role === 'assistant' && message.structContent) {
64
+ const structEl = this.renderStructContent(message.structContent);
65
+ bubble.appendChild(structEl);
66
+ } else {
67
+ bubble.textContent = message.content;
68
+ }
69
+
61
70
  messageEl.appendChild(bubble);
62
71
 
63
72
  // Time
@@ -87,6 +96,225 @@ export class MessageList extends BaseComponent<MessageListOptions> {
87
96
  return messageEl;
88
97
  }
89
98
 
99
+ // ============================================
100
+ // Structured Content Renderers
101
+ // ============================================
102
+
103
+ private renderStructContent(unified: any): HTMLElement {
104
+ const container = document.createElement('div');
105
+ container.className = 'jd-struct';
106
+
107
+ const blocks: any[] = unified?.blocks || [];
108
+ for (const block of blocks) {
109
+ const el = this.renderBlock(block);
110
+ if (el) container.appendChild(el);
111
+ }
112
+
113
+ return container;
114
+ }
115
+
116
+ private renderBlock(block: any): HTMLElement | null {
117
+ switch (block.type) {
118
+ case 'text': return this.renderTextBlock(block);
119
+ case 'image': return this.renderImageBlock(block);
120
+ case 'button': return this.renderButtonBlock(block);
121
+ case 'actions': return this.renderActionsBlock(block);
122
+ case 'divider': return this.renderDividerBlock();
123
+ case 'header': return this.renderHeaderBlock(block);
124
+ case 'card': return this.renderCardBlock(block);
125
+ case 'carousel': return this.renderCarouselBlock(block);
126
+ case 'container': return this.renderContainerBlock(block);
127
+ default: return null;
128
+ }
129
+ }
130
+
131
+ private renderTextBlock(block: any): HTMLElement {
132
+ const el = document.createElement('p');
133
+ el.className = 'jd-struct-text';
134
+ el.textContent = block.text || '';
135
+
136
+ if (block.style?.size) el.setAttribute('data-size', block.style.size);
137
+ if (block.style?.bold) el.style.fontWeight = '700';
138
+ if (block.style?.italic) el.style.fontStyle = 'italic';
139
+ if (block.style?.strike) el.style.textDecoration = 'line-through';
140
+ if (block.style?.color) el.style.color = block.style.color;
141
+ if (block.align) el.style.textAlign = block.align;
142
+
143
+ return el;
144
+ }
145
+
146
+ private renderImageBlock(block: any): HTMLElement {
147
+ const img = document.createElement('img');
148
+ img.className = 'jd-struct-image';
149
+ img.src = block.url || '';
150
+ img.alt = block.alt_text || '';
151
+ if (block.title) img.title = block.title;
152
+ return img;
153
+ }
154
+
155
+ private renderButtonBlock(block: any): HTMLElement {
156
+ const btn = document.createElement('button');
157
+ btn.className = 'jd-struct-button';
158
+ btn.textContent = block.text || '';
159
+
160
+ const style = block.style || 'primary';
161
+ btn.setAttribute('data-style', style);
162
+
163
+ const action = block.action;
164
+ if (action) {
165
+ btn.addEventListener('click', () => {
166
+ if (action.type === 'url' && action.url) {
167
+ window.open(action.url, '_blank', 'noopener,noreferrer');
168
+ } else if (action.type === 'message' && action.text) {
169
+ this.options.onAction?.(action.text);
170
+ } else if (action.type === 'postback') {
171
+ this.options.onAction?.(action.display_text || action.data || '');
172
+ }
173
+ });
174
+ }
175
+
176
+ return btn;
177
+ }
178
+
179
+ private renderActionsBlock(block: any): HTMLElement {
180
+ const container = document.createElement('div');
181
+ container.className = 'jd-struct-actions';
182
+ const layout = block.layout || 'vertical';
183
+ container.setAttribute('data-layout', layout);
184
+
185
+ const buttons: any[] = block.buttons || [];
186
+ for (const btn of buttons) {
187
+ container.appendChild(this.renderButtonBlock(btn));
188
+ }
189
+
190
+ return container;
191
+ }
192
+
193
+ private renderDividerBlock(): HTMLElement {
194
+ const hr = document.createElement('hr');
195
+ hr.className = 'jd-struct-divider';
196
+ return hr;
197
+ }
198
+
199
+ private renderHeaderBlock(block: any): HTMLElement {
200
+ const container = document.createElement('div');
201
+ container.className = 'jd-struct-header';
202
+
203
+ const title = document.createElement('div');
204
+ title.className = 'jd-struct-header__title';
205
+ title.textContent = block.title || '';
206
+ container.appendChild(title);
207
+
208
+ if (block.subtitle) {
209
+ const subtitle = document.createElement('div');
210
+ subtitle.className = 'jd-struct-header__subtitle';
211
+ subtitle.textContent = block.subtitle;
212
+ container.appendChild(subtitle);
213
+ }
214
+
215
+ return container;
216
+ }
217
+
218
+ private renderCardBlock(block: any): HTMLElement {
219
+ const card = document.createElement('div');
220
+ card.className = 'jd-struct-card';
221
+
222
+ // Header
223
+ if (block.header) {
224
+ const header = document.createElement('div');
225
+ header.className = 'jd-struct-card__header';
226
+ if (block.header.background_color) {
227
+ header.style.backgroundColor = block.header.background_color;
228
+ }
229
+ const title = document.createElement('div');
230
+ title.className = 'jd-struct-card__title';
231
+ title.textContent = block.header.title || '';
232
+ header.appendChild(title);
233
+
234
+ if (block.header.subtitle) {
235
+ const subtitle = document.createElement('div');
236
+ subtitle.className = 'jd-struct-card__subtitle';
237
+ subtitle.textContent = block.header.subtitle;
238
+ header.appendChild(subtitle);
239
+ }
240
+ card.appendChild(header);
241
+ }
242
+
243
+ // Hero image
244
+ if (block.hero) {
245
+ const hero = this.renderImageBlock(block.hero);
246
+ hero.className = 'jd-struct-card__hero';
247
+ card.appendChild(hero);
248
+ }
249
+
250
+ // Body
251
+ if (block.body) {
252
+ const body = document.createElement('div');
253
+ body.className = 'jd-struct-card__body';
254
+ for (const child of block.body) {
255
+ const el = this.renderBlock(child);
256
+ if (el) body.appendChild(el);
257
+ }
258
+ card.appendChild(body);
259
+ }
260
+
261
+ // Footer
262
+ if (block.footer) {
263
+ const footer = document.createElement('div');
264
+ footer.className = 'jd-struct-card__footer';
265
+ for (const child of block.footer) {
266
+ const el = this.renderBlock(child);
267
+ if (el) footer.appendChild(el);
268
+ }
269
+ card.appendChild(footer);
270
+ }
271
+
272
+ // Actions
273
+ if (block.actions && block.actions.length > 0) {
274
+ const actions = document.createElement('div');
275
+ actions.className = 'jd-struct-card__actions';
276
+ for (const btn of block.actions) {
277
+ actions.appendChild(this.renderButtonBlock(btn));
278
+ }
279
+ card.appendChild(actions);
280
+ }
281
+
282
+ return card;
283
+ }
284
+
285
+ private renderCarouselBlock(block: any): HTMLElement {
286
+ const container = document.createElement('div');
287
+ container.className = 'jd-struct-carousel';
288
+
289
+ const cards: any[] = block.cards || [];
290
+ for (const cardBlock of cards) {
291
+ container.appendChild(this.renderCardBlock(cardBlock));
292
+ }
293
+
294
+ return container;
295
+ }
296
+
297
+ private renderContainerBlock(block: any): HTMLElement {
298
+ const container = document.createElement('div');
299
+ container.className = 'jd-struct-container';
300
+ container.setAttribute('data-layout', block.layout || 'vertical');
301
+
302
+ if (block.style) {
303
+ if (block.style.background_color) container.style.backgroundColor = block.style.background_color;
304
+ if (block.style.border_color) container.style.borderColor = block.style.border_color;
305
+ if (block.style.border_radius) container.style.borderRadius = block.style.border_radius;
306
+ if (block.style.padding) container.style.padding = block.style.padding;
307
+ }
308
+
309
+ const contents: any[] = block.contents || [];
310
+ for (const child of contents) {
311
+ const el = this.renderBlock(child);
312
+ if (el) container.appendChild(el);
313
+ }
314
+
315
+ return container;
316
+ }
317
+
90
318
  private renderTypingIndicator(): HTMLElement {
91
319
  const container = document.createElement('div');
92
320
  container.className = 'jd-typing-indicator';
@@ -96,7 +96,7 @@ export class Widget implements JidouChatAPI {
96
96
  init(): void {
97
97
  this.createContainer();
98
98
  this.render();
99
- this.loadHistory();
99
+ // History is now loaded after WebSocket connects in the 'connected' event handler
100
100
  this.triggerHook('onLoad');
101
101
 
102
102
  // Connect WebSocket
@@ -233,6 +233,7 @@ export class Widget implements JidouChatAPI {
233
233
  onCollapse: () => this.setDisplayMode('floating'),
234
234
  onExpand: () => this.setDisplayMode('fullscreen'),
235
235
  onSend: (message) => this.handleSendMessage(message),
236
+ onAction: (text) => this.handleSendMessage(text),
236
237
  onTypingStart: () => this.wsClient.sendTypingStart(),
237
238
  onTypingStop: () => this.wsClient.sendTypingStop(),
238
239
  onQuickReplySelect: (item) => this.handleQuickReply(item),
@@ -287,9 +288,14 @@ export class Widget implements JidouChatAPI {
287
288
  this.historyManager.setSessionId(sessionId);
288
289
 
289
290
  if (isResumed) {
290
- // Session was resumed
291
+ // Session was resumed - load history for this session
292
+ const messages = this.historyManager.load();
293
+ if (messages && messages.length > 0) {
294
+ this.stateManager.setMessages(messages);
295
+ }
291
296
  } else {
292
- // New session
297
+ // New session - clear any old messages and start fresh
298
+ this.stateManager.clearMessages();
293
299
  this.triggerHook('onConversationStarted', sessionId);
294
300
  }
295
301
  });
@@ -304,6 +310,7 @@ export class Widget implements JidouChatAPI {
304
310
  id: payload.id,
305
311
  role: 'assistant',
306
312
  content: payload.content,
313
+ structContent: payload.structContent,
307
314
  timestamp: payload.timestamp || now(),
308
315
  };
309
316
 
@@ -327,16 +334,6 @@ export class Widget implements JidouChatAPI {
327
334
  });
328
335
  }
329
336
 
330
- private loadHistory(): void {
331
- const sessionId = this.wsClient.getSessionId();
332
- this.historyManager.setSessionId(sessionId);
333
-
334
- const messages = this.historyManager.load();
335
- if (messages && messages.length > 0) {
336
- this.stateManager.setMessages(messages);
337
- }
338
- }
339
-
340
337
  private handleSendMessage(content: string): void {
341
338
  const message: Message = {
342
339
  id: generateMessageId(),
@@ -1,13 +1,18 @@
1
1
  import type { Message, LocalChatHistory } from '../types';
2
2
  import * as storage from '../utils/storage';
3
3
 
4
- const STORAGE_KEY = 'chat_history';
4
+ const STORAGE_KEY_PREFIX = 'chat_history_';
5
5
  const MAX_MESSAGES = 100;
6
6
  const EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
7
7
 
8
8
  export class ChatHistoryManager {
9
9
  private sessionId: string | null = null;
10
10
 
11
+ private getStorageKey(sessionId?: string): string {
12
+ const id = sessionId || this.sessionId;
13
+ return id ? `${STORAGE_KEY_PREFIX}${id}` : STORAGE_KEY_PREFIX;
14
+ }
15
+
11
16
  setSessionId(sessionId: string): void {
12
17
  this.sessionId = sessionId;
13
18
  }
@@ -17,15 +22,12 @@ export class ChatHistoryManager {
17
22
  }
18
23
 
19
24
  private getValidHistory(sessionId?: string): LocalChatHistory | null {
20
- const history = storage.getItem<LocalChatHistory>(STORAGE_KEY);
25
+ const key = this.getStorageKey(sessionId);
26
+ const history = storage.getItem<LocalChatHistory>(key);
21
27
  if (!history) return null;
22
28
 
23
29
  if (Date.now() - history.savedAt > EXPIRY_MS) {
24
- this.clear();
25
- return null;
26
- }
27
-
28
- if (sessionId && history.sessionId !== sessionId) {
30
+ this.clear(sessionId);
29
31
  return null;
30
32
  }
31
33
 
@@ -41,11 +43,11 @@ export class ChatHistoryManager {
41
43
  savedAt: Date.now(),
42
44
  };
43
45
 
44
- return storage.setItem(STORAGE_KEY, history);
46
+ return storage.setItem(this.getStorageKey(), history);
45
47
  }
46
48
 
47
49
  load(): Message[] | null {
48
- const history = this.getValidHistory(this.sessionId || undefined);
50
+ const history = this.getValidHistory();
49
51
  return history?.messages || null;
50
52
  }
51
53
 
@@ -57,7 +59,7 @@ export class ChatHistoryManager {
57
59
  appendMessage(message: Message): boolean {
58
60
  if (!this.sessionId) return false;
59
61
 
60
- const history = this.getValidHistory(this.sessionId);
62
+ const history = this.getValidHistory();
61
63
  const messages = history
62
64
  ? [...history.messages, message].slice(-MAX_MESSAGES)
63
65
  : [message];
@@ -66,7 +68,7 @@ export class ChatHistoryManager {
66
68
  }
67
69
 
68
70
  updateMessage(messageId: string, updates: Partial<Message>): boolean {
69
- const history = this.getValidHistory(this.sessionId || undefined);
71
+ const history = this.getValidHistory();
70
72
  if (!history) return false;
71
73
 
72
74
  const messages = history.messages.map((msg) =>
@@ -76,23 +78,36 @@ export class ChatHistoryManager {
76
78
  return this.save(messages);
77
79
  }
78
80
 
79
- clear(): void {
80
- storage.removeItem(STORAGE_KEY);
81
+ clear(sessionId?: string): void {
82
+ storage.removeItem(this.getStorageKey(sessionId));
81
83
  }
82
84
 
83
85
  hasHistory(): boolean {
84
- return !!this.getValidHistory(this.sessionId || undefined);
86
+ return !!this.getValidHistory();
85
87
  }
86
88
 
87
89
  getMessageCount(): number {
88
- const history = storage.getItem<LocalChatHistory>(STORAGE_KEY);
90
+ const history = this.getValidHistory();
89
91
  return history?.messages.length || 0;
90
92
  }
91
93
 
92
94
  static cleanup(): void {
93
- const history = storage.getItem<LocalChatHistory>(STORAGE_KEY);
94
- if (history && Date.now() - history.savedAt > EXPIRY_MS) {
95
- storage.removeItem(STORAGE_KEY);
96
- }
95
+ // Clean up expired histories
96
+ // This is a simplified version - in production you might want to
97
+ // iterate through all chat_history_* keys
98
+ const keys = Object.keys(localStorage).filter(k => k.startsWith(STORAGE_KEY_PREFIX));
99
+ keys.forEach(key => {
100
+ try {
101
+ const data = localStorage.getItem(key);
102
+ if (data) {
103
+ const history = JSON.parse(data) as LocalChatHistory;
104
+ if (Date.now() - history.savedAt > EXPIRY_MS) {
105
+ localStorage.removeItem(key);
106
+ }
107
+ }
108
+ } catch {
109
+ // Ignore parse errors
110
+ }
111
+ });
97
112
  }
98
113
  }
@@ -163,11 +163,19 @@ export class WebSocketClient extends EventEmitter<WebSocketEvents> {
163
163
 
164
164
  switch (message.type) {
165
165
  case 'connection:ack': {
166
- const payload = message.payload as ConnectionAckPayload;
166
+ // Connection acknowledged, wait for session:ready
167
167
  this.setConnectionState('connected');
168
+ break;
169
+ }
170
+
171
+ case 'session:ready': {
172
+ const payload = message.payload as { sessionId: string; restored: boolean };
173
+ // Update session ID from server
174
+ this.sessionId = payload.sessionId;
175
+ storage.setItem(STORAGE_SESSION_KEY, this.sessionId);
168
176
  this.emit('connected', {
169
177
  sessionId: payload.sessionId,
170
- isResumed: payload.isResumed,
178
+ isResumed: payload.restored,
171
179
  });
172
180
  break;
173
181
  }
@@ -63,13 +63,22 @@ export const CHAT_WINDOW_STYLES = `
63
63
  /* Mobile responsive */
64
64
  @media (max-width: 480px) {
65
65
  .jd-chat-window--floating {
66
- width: 100%;
67
- height: 100%;
68
- max-height: 100vh;
69
- bottom: 0;
70
- right: 0;
71
- left: 0;
72
- border-radius: 0;
66
+ position: fixed !important;
67
+ top: 0 !important;
68
+ left: 0 !important;
69
+ right: 0 !important;
70
+ bottom: 0 !important;
71
+ width: 100vw !important;
72
+ height: 100vh !important;
73
+ max-width: 100vw !important;
74
+ max-height: 100vh !important;
75
+ border-radius: 0 !important;
76
+ z-index: 999999 !important;
77
+ }
78
+
79
+ /* Hide expand button on mobile - already fullscreen */
80
+ .jd-chat-header__btn--expand {
81
+ display: none !important;
73
82
  }
74
83
  }
75
84
 
@@ -183,4 +183,13 @@ export const LAUNCHER_STYLES = `
183
183
  visibility: hidden;
184
184
  transform: scale(0.8);
185
185
  }
186
+
187
+ /* Mobile: hide launcher when chat is open */
188
+ @media (max-width: 480px) {
189
+ .jd-launcher--open {
190
+ opacity: 0;
191
+ visibility: hidden;
192
+ pointer-events: none;
193
+ }
194
+ }
186
195
  `;
@@ -308,6 +308,211 @@ export const MESSAGE_STYLES = `
308
308
  height: 20px;
309
309
  }
310
310
 
311
+ /* ============================================
312
+ Structured Content (structContent)
313
+ ============================================ */
314
+
315
+ /* Container for all struct blocks */
316
+ .jd-struct {
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 8px;
320
+ width: 100%;
321
+ }
322
+
323
+ /* Text block */
324
+ .jd-struct-text {
325
+ margin: 0;
326
+ font-family: var(--jd-font);
327
+ font-size: 14px;
328
+ line-height: 1.5;
329
+ color: inherit;
330
+ word-wrap: break-word;
331
+ }
332
+ .jd-struct-text[data-size="xs"] { font-size: 11px; }
333
+ .jd-struct-text[data-size="sm"] { font-size: 13px; }
334
+ .jd-struct-text[data-size="md"] { font-size: 14px; }
335
+ .jd-struct-text[data-size="lg"] { font-size: 16px; }
336
+ .jd-struct-text[data-size="xl"] { font-size: 18px; }
337
+ .jd-struct-text[data-size="xxl"] { font-size: 22px; }
338
+
339
+ /* Image block */
340
+ .jd-struct-image {
341
+ width: 100%;
342
+ border-radius: 8px;
343
+ display: block;
344
+ object-fit: cover;
345
+ }
346
+
347
+ /* Button block */
348
+ .jd-struct-button {
349
+ display: inline-flex;
350
+ align-items: center;
351
+ justify-content: center;
352
+ padding: 8px 16px;
353
+ border: none;
354
+ border-radius: 8px;
355
+ font-family: var(--jd-font);
356
+ font-size: 14px;
357
+ font-weight: 500;
358
+ cursor: pointer;
359
+ transition: opacity var(--jd-transition), background var(--jd-transition);
360
+ width: 100%;
361
+ text-align: center;
362
+ }
363
+ .jd-struct-button:hover { opacity: 0.85; }
364
+ .jd-struct-button[data-style="primary"] {
365
+ background: var(--jd-primary);
366
+ color: #fff;
367
+ }
368
+ .jd-struct-button[data-style="secondary"] {
369
+ background: var(--jd-surface);
370
+ color: var(--jd-text);
371
+ border: 1px solid var(--jd-border);
372
+ }
373
+ .jd-struct-button[data-style="danger"] {
374
+ background: #EF4444;
375
+ color: #fff;
376
+ }
377
+ .jd-struct-button[data-style="link"] {
378
+ background: transparent;
379
+ color: var(--jd-primary);
380
+ padding: 4px 0;
381
+ text-decoration: underline;
382
+ }
383
+
384
+ /* Actions block (button group) */
385
+ .jd-struct-actions {
386
+ display: flex;
387
+ gap: 8px;
388
+ }
389
+ .jd-struct-actions[data-layout="vertical"] {
390
+ flex-direction: column;
391
+ }
392
+ .jd-struct-actions[data-layout="horizontal"] {
393
+ flex-direction: row;
394
+ }
395
+ .jd-struct-actions[data-layout="horizontal"] .jd-struct-button {
396
+ flex: 1;
397
+ width: auto;
398
+ }
399
+
400
+ /* Divider */
401
+ .jd-struct-divider {
402
+ border: none;
403
+ border-top: 1px solid var(--jd-border);
404
+ margin: 4px 0;
405
+ }
406
+
407
+ /* Header block */
408
+ .jd-struct-header {
409
+ display: flex;
410
+ flex-direction: column;
411
+ gap: 2px;
412
+ }
413
+ .jd-struct-header__title {
414
+ font-family: var(--jd-font);
415
+ font-size: 16px;
416
+ font-weight: 600;
417
+ color: inherit;
418
+ }
419
+ .jd-struct-header__subtitle {
420
+ font-family: var(--jd-font);
421
+ font-size: 13px;
422
+ color: var(--jd-text-secondary);
423
+ }
424
+
425
+ /* Card block */
426
+ .jd-struct-card {
427
+ border: 1px solid var(--jd-border);
428
+ border-radius: 12px;
429
+ overflow: hidden;
430
+ background: var(--jd-background);
431
+ }
432
+ .jd-struct-card__header {
433
+ padding: 12px 16px;
434
+ }
435
+ .jd-struct-card__title {
436
+ font-family: var(--jd-font);
437
+ font-size: 15px;
438
+ font-weight: 600;
439
+ color: var(--jd-text);
440
+ }
441
+ .jd-struct-card__subtitle {
442
+ font-family: var(--jd-font);
443
+ font-size: 13px;
444
+ color: var(--jd-text-secondary);
445
+ margin-top: 2px;
446
+ }
447
+ .jd-struct-card__hero {
448
+ width: 100%;
449
+ border-radius: 0;
450
+ display: block;
451
+ object-fit: cover;
452
+ }
453
+ .jd-struct-card__body {
454
+ padding: 12px 16px;
455
+ display: flex;
456
+ flex-direction: column;
457
+ gap: 8px;
458
+ }
459
+ .jd-struct-card__footer {
460
+ padding: 8px 16px 12px;
461
+ display: flex;
462
+ flex-direction: column;
463
+ gap: 6px;
464
+ border-top: 1px solid var(--jd-border);
465
+ }
466
+ .jd-struct-card__actions {
467
+ padding: 8px 16px 12px;
468
+ display: flex;
469
+ flex-direction: column;
470
+ gap: 6px;
471
+ border-top: 1px solid var(--jd-border);
472
+ }
473
+
474
+ /* Carousel block */
475
+ .jd-struct-carousel {
476
+ display: flex;
477
+ gap: 12px;
478
+ overflow-x: auto;
479
+ scroll-snap-type: x mandatory;
480
+ -webkit-overflow-scrolling: touch;
481
+ scrollbar-width: none;
482
+ padding-bottom: 4px;
483
+ }
484
+ .jd-struct-carousel::-webkit-scrollbar { display: none; }
485
+ .jd-struct-carousel > .jd-struct-card {
486
+ min-width: 240px;
487
+ max-width: 280px;
488
+ flex-shrink: 0;
489
+ scroll-snap-align: start;
490
+ }
491
+
492
+ /* Container block (generic layout) */
493
+ .jd-struct-container {
494
+ display: flex;
495
+ gap: 8px;
496
+ }
497
+ .jd-struct-container[data-layout="vertical"] {
498
+ flex-direction: column;
499
+ }
500
+ .jd-struct-container[data-layout="horizontal"] {
501
+ flex-direction: row;
502
+ }
503
+ .jd-struct-container[data-layout="baseline"] {
504
+ flex-direction: row;
505
+ align-items: baseline;
506
+ }
507
+
508
+ /* Wider bubble for struct content */
509
+ .jd-message--assistant .jd-message__bubble:has(.jd-struct) {
510
+ padding: 12px;
511
+ }
512
+ .jd-message--assistant:has(.jd-struct) {
513
+ max-width: 95%;
514
+ }
515
+
311
516
  /* Empty State */
312
517
  .jd-empty-state {
313
518
  flex: 1;
@@ -33,6 +33,7 @@ export interface Message {
33
33
  id: string;
34
34
  role: 'user' | 'assistant';
35
35
  content: string;
36
+ structContent?: any;
36
37
  timestamp: number;
37
38
  status?: 'sending' | 'sent' | 'error';
38
39
  }
@@ -293,6 +294,7 @@ export interface ConnectionAckPayload {
293
294
  export interface MessagePayload {
294
295
  id: string;
295
296
  content: string;
297
+ structContent?: any;
296
298
  role?: 'user' | 'assistant';
297
299
  timestamp?: number;
298
300
  }