UNPKG

neochat-widget

Version:

Universal JavaScript SDK and Widget for NeoChat AI Assistant

3 lines (2 loc) 11.3 kB
class e{constructor(e,t,s=""){this.apiKey=e,this.businessId=t,this.baseUrl=s.replace(/\/+$/,"")}getHeaders(){return{"Content-Type":"application/json","x-api-key":this.apiKey}}async handleResponse(e){if(!e.ok){const t=await e.json().catch(()=>({}));throw new Error(t.message||`API request failed with status ${e.status}`)}return e.json()}async sendMessage(e,t={}){const s=`${this.baseUrl}/api/v1/message`,i=await fetch(s,{method:"POST",headers:this.getHeaders(),body:JSON.stringify({message:e,conversation_id:t.conversationId,metadata:t.metadata||{},business_id:this.businessId})}),n=await this.handleResponse(i);return{id:Date.now().toString(),content:n.response,sender:"bot",timestamp:new Date}}async startTraining(e,t={}){const s=`${this.baseUrl}/api/v1/train`,i=await fetch(s,{method:"POST",headers:this.getHeaders(),body:JSON.stringify({documents:e,model_name:t.modelName||"default",parameters:t.parameters||{},business_id:this.businessId})});return(await this.handleResponse(i)).training_id}async getTrainingStatus(e){const t=`${this.baseUrl}/api/v1/train/${e}`,s=await fetch(t,{headers:this.getHeaders()});return this.handleResponse(s)}}class t{constructor(e,t,s){this.url=e,this.apiKey=t,this.businessId=s,this.socket=null,this.eventHandlers=new Map,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.reconnectDelay=1e3,this.isConnecting=!1,this.shouldReconnect=!0,this.initialize()}initialize(){this.socket&&this.socket.close(),this.isConnecting=!0;try{const e=new URL(this.url);e.searchParams.append("token",this.apiKey),e.searchParams.append("business_id",this.businessId),this.socket=new WebSocket(e.toString()),this.socket.onopen=()=>{this.reconnectAttempts=0,this.isConnecting=!1,this.emit("open",{type:"connected"})},this.socket.onmessage=e=>{try{const t=JSON.parse(e.data);this.emit("message",t)}catch(t){this.emit("error",{error:"Failed to parse message",data:e.data})}},this.socket.onerror=e=>{this.emit("error",{error:"WebSocket error",details:e})},this.socket.onclose=e=>{if(this.isConnecting=!1,this.emit("close",{code:e.code,reason:e.reason}),this.shouldReconnect&&this.reconnectAttempts<this.maxReconnectAttempts){this.reconnectAttempts++;const e=this.reconnectDelay*Math.pow(2,this.reconnectAttempts-1);setTimeout(()=>this.initialize(),e)}}}catch(e){this.isConnecting=!1,this.emit("error",{error:"Failed to initialize WebSocket",details:e})}}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set);const s=this.eventHandlers.get(e);return s.add(t),()=>{s.delete(t)}}send(e){var t;if((null===(t=this.socket)||void 0===t?void 0:t.readyState)===WebSocket.OPEN){const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t)}else this.emit("error",{error:"WebSocket is not connected"})}close(){this.shouldReconnect=!1,this.socket&&(this.socket.close(),this.socket=null)}get isConnected(){var e;return(null===(e=this.socket)||void 0===e?void 0:e.readyState)===WebSocket.OPEN}emit(e,t){const s=this.eventHandlers.get(e);s&&s.forEach(e=>{try{e(t)}catch(e){}})}}class s{constructor(t){this.wsClient=null,this.messages=[],this.isOpen=!1,this.widgetElement=null,this.messagesContainer=null,this.inputElement=null,this.sendButton=null,this.closeButton=null,this.typingIndicator=null,this.typingTimeout=null,this.config=t,this.container=this.getContainer(t.container),this.apiClient=new e(t.apiKey,t.businessId,t.apiBaseUrl),this.initializeWebSocket(),this.render(),this.attachEventListeners(),"function"==typeof this.config.onReady&&setTimeout(()=>this.config.onReady(),0)}getContainer(e){if("string"==typeof e){const t=document.querySelector(e);if(!t)throw new Error(`Container element not found: ${e}`);return t}return e}initializeWebSocket(){try{const e=this.getWebSocketUrl();this.wsClient=new t(e,this.config.apiKey,this.config.businessId),this.wsClient.on("message",e=>{this.handleWebSocketMessage(e)}),this.wsClient.on("error",e=>{"function"==typeof this.config.onError&&this.config.onError(new Error(`WebSocket error: ${(null==e?void 0:e.error)||"Unknown error"}`))}),this.wsClient.on("close",()=>{this.wsClient&&(this.wsClient=null,setTimeout(()=>this.initializeWebSocket(),5e3))})}catch(e){"function"==typeof this.config.onError&&this.config.onError(e instanceof Error?e:new Error("Failed to initialize WebSocket"))}}getWebSocketUrl(){return`${"https:"===window.location.protocol?"wss:":"ws:"}//${this.config.apiBaseUrl?new URL(this.config.apiBaseUrl).host:window.location.host}/api/v1/message/ws`}handleWebSocketMessage(e){"message"===e.type?(this.addMessage({id:e.id||Date.now().toString(),content:e.content,sender:"bot",timestamp:new Date(e.timestamp||Date.now()),metadata:e.metadata}),this.hideTypingIndicator()):"typing"===e.type&&this.showTypingIndicator()}render(){this.widgetElement=document.createElement("div"),this.widgetElement.className="neochat-widget",this.widgetElement.innerHTML='\n <div class="neochat-header">\n <span>NeoChat Assistant</span>\n <button class="neochat-close" aria-label="Close chat">×</button>\n </div>\n <div class="neochat-messages"></div>\n <div class="neochat-typing-indicator" style="display: none;">\n <div class="neochat-typing-dot"></div>\n <div class="neochat-typing-dot"></div>\n <div class="neochat-typing-dot"></div>\n </div>\n <div class="neochat-input-container">\n <div class="neochat-input-row">\n <input \n type="text" \n class="neochat-input" \n placeholder="Type your message..." \n aria-label="Type your message"\n >\n <button class="neochat-send-button" aria-label="Send message">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">\n <line x1="22" y1="2" x2="11" y2="13"></line>\n <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>\n </svg>\n </button>\n </div>\n </div>\n ',this.container.appendChild(this.widgetElement),this.messagesContainer=this.widgetElement.querySelector(".neochat-messages"),this.inputElement=this.widgetElement.querySelector(".neochat-input"),this.sendButton=this.widgetElement.querySelector(".neochat-send-button"),this.closeButton=this.widgetElement.querySelector(".neochat-close"),this.typingIndicator=this.widgetElement.querySelector(".neochat-typing-indicator"),this.toggle(!0)}attachEventListeners(){this.widgetElement&&this.inputElement&&this.sendButton&&this.closeButton&&(this.sendButton.addEventListener("click",()=>this.handleSendMessage()),this.inputElement.addEventListener("keydown",e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),this.handleSendMessage())}),this.closeButton.addEventListener("click",()=>this.toggle(!1)))}async handleSendMessage(){var e;if(!this.inputElement)return;const t=this.inputElement.value.trim();if(!t)return;this.inputElement.value="";const s={id:Date.now().toString(),content:t,sender:"user",timestamp:new Date};this.addMessage(s);try{if(this.showTypingIndicator(),null===(e=this.wsClient)||void 0===e?void 0:e.isConnected)this.wsClient.send({type:"message",content:t,business_id:this.config.businessId,metadata:{}});else{const e=await this.apiClient.sendMessage(t);this.addMessage(e),this.hideTypingIndicator()}}catch(e){this.addMessage({id:`error-${Date.now()}`,content:"Sorry, I encountered an error. Please try again.",sender:"bot",timestamp:new Date}),this.hideTypingIndicator(),"function"==typeof this.config.onError&&this.config.onError(e instanceof Error?e:new Error("Failed to send message"))}}addMessage(e){if(!this.messagesContainer)return;this.messages.push(e);const t=document.createElement("div");t.className=`neochat-message neochat-message-${e.sender}`,t.textContent=e.content,this.messagesContainer.appendChild(t),this.scrollToBottom(),"function"==typeof this.config.onMessage&&this.config.onMessage(e)}showTypingIndicator(){this.typingIndicator&&(this.typingTimeout&&window.clearTimeout(this.typingTimeout),this.typingIndicator.style.display="flex",this.scrollToBottom(),this.typingTimeout=window.setTimeout(()=>{this.hideTypingIndicator()},1e4))}hideTypingIndicator(){this.typingTimeout&&(window.clearTimeout(this.typingTimeout),this.typingTimeout=null),this.typingIndicator&&(this.typingIndicator.style.display="none")}scrollToBottom(){this.messagesContainer&&(this.messagesContainer.scrollTop=this.messagesContainer.scrollHeight)}toggle(e){void 0===e&&(e=!this.isOpen),this.isOpen=e,this.widgetElement&&(this.widgetElement.style.display=e?"flex":"none"),e&&this.inputElement&&setTimeout(()=>{var e;return null===(e=this.inputElement)||void 0===e?void 0:e.focus()},100)}destroy(){this.wsClient&&(this.wsClient.close(),this.wsClient=null),this.widgetElement&&this.widgetElement.parentNode&&(this.widgetElement.parentNode.removeChild(this.widgetElement),this.widgetElement=null),this.typingTimeout&&(window.clearTimeout(this.typingTimeout),this.typingTimeout=null),"function"==typeof this.config.onClose&&this.config.onClose()}}class i{constructor(t){this.widget=null,this.messages=[],this.onMessageCallbacks=[],this.onErrorCallbacks=[],this.onReadyCallbacks=[],this.onCloseCallbacks=[],this.config={...t,onMessage:this.handleMessage.bind(this),onError:this.handleError.bind(this),onReady:this.handleReady.bind(this),onClose:this.handleClose.bind(this)},this.apiClient=new e(this.config.apiKey,this.config.businessId,this.config.apiBaseUrl)}handleMessage(e){this.messages.push(e),this.onMessageCallbacks.forEach(t=>t(e))}handleError(e){this.onErrorCallbacks.forEach(t=>t(e))}handleReady(){this.onReadyCallbacks.forEach(e=>e())}handleClose(){this.onCloseCallbacks.forEach(e=>e())}async sendMessage(e,t={}){try{const s=await this.apiClient.sendMessage(e,t);return this.handleMessage({id:Date.now().toString(),content:s.content,sender:"bot",timestamp:new Date,metadata:s.metadata}),s}catch(e){throw this.handleError(e instanceof Error?e:new Error("Failed to send message")),e}}async startTraining(e,t={}){try{return await this.apiClient.startTraining(e,t)}catch(e){throw this.handleError(e instanceof Error?e:new Error("Training failed")),e}}getMessages(){return[...this.messages]}clearMessages(){this.messages=[]}updateConfig(t){this.config={...this.config,...t},(t.apiKey||t.businessId||t.apiBaseUrl)&&(this.apiClient=new e(t.apiKey||this.config.apiKey,t.businessId||this.config.businessId,t.apiBaseUrl||this.config.apiBaseUrl))}on(e,t){switch(e){case"message":this.onMessageCallbacks.push(t);break;case"error":this.onErrorCallbacks.push(t);break;case"ready":this.onReadyCallbacks.push(t);break;case"close":this.onCloseCallbacks.push(t)}}off(e,t){let s;switch(e){case"message":s=this.onMessageCallbacks;break;case"error":s=this.onErrorCallbacks;break;case"ready":s=this.onReadyCallbacks;break;case"close":s=this.onCloseCallbacks;break;default:return}const i=s.indexOf(t);-1!==i&&s.splice(i,1)}close(){this.destroy()}destroy(){this.widget&&(this.widget.destroy(),this.widget=null),this.messages=[],this.onMessageCallbacks=[],this.onErrorCallbacks=[],this.onReadyCallbacks=[],this.onCloseCallbacks=[]}}function n(e){const t=new i(e);return t.widget=new s(e),t}"undefined"!=typeof window&&(window.NeoChat={init:n});var o={init:n,NeoChat:i};export{i as NeoChat,o as default,n as init}; //# sourceMappingURL=neochat.esm.js.map