function doQuestionEventHandler(e) { const self = e.currentTarget; const question = self.dataset.doQuestion; const chat = document.querySelector(".chat-container"); chat.appendChild(createRenderBubble("user", true, question).message); const msgid = makeAssistantMsg(); sendMessages( msgid, prepareUserMessage(question), function(resp, msgid){ if(resp["status"] != "done") { return; } let content = marked.parse(resp["content"]).trim(); updateMsgContent(msgid, content); updateMsgHistory(); } ); } const specialViewMap = { product_card: data => { console.log("product_card", data); let html = `<div class="kb-card kb-product-card"> <h4 class="heading">${data.title}</h4> <div class="actions-group"> <div class="price"> <span class="now">${data.price}</span> </div> <div> <a href="${data.link}" class="kb-btn tricky go-to-product"> Купити <svg class="kb-icon" width="24" height="24"> <use href="#kb-arrow-right"></use> </svg> </a> </div> </div> </div>`; let container = document.createElement("div"); container.innerHTML = html; return container.querySelector(".kb-card"); }, identification_card: data => { console.log("identification_card", data); let html = ` <div class="kb-card kb-auth-card"> <div class="description">Введіть номер телефону для перевірки статусу замовлення</div> <div class="kb-input-form"> <label for="kb-phone" class="kb-input-label">Номер телефону</label> <div class="kb-input-container"> <div class="prefix"> <svg class="kb-icon" width="16" height="16"> <use href="#kb-ua-flag"></use> </svg> <span>+380</span> <div class="sep"></div> </div> <input type="phone" id="kb-phone" maxlength="9" placeholder="000000000" class="kb-input"> </div> </div> <button class="kb-btn large disabled kb-auth-btn">Відправити</button> </div> `; let container = document.createElement("div"); container.innerHTML = html; const authCard = container.querySelector(".kb-auth-card"); const kbAuthBtn = authCard.querySelector(".kb-auth-btn"); authCard.querySelector("#kb-phone").addEventListener("input", e => { e.currentTarget.value = e.currentTarget.value.replace(/\D/g, ''); let val = e.currentTarget.value; if(val.length < 9) { kbAuthBtn.classList.add("disabled"); } else { kbAuthBtn.classList.remove("disabled"); } }); return authCard; }, frequentlyQuestions: data => { let questions = ``; for(let q of data.questions) { questions += ` <button class="kb-btn tricky question" data-do-question="${q}">${q}</button> `; } const html = ` <div class="frequently-questions"> <h4 class="heading">Часті запитання:</h4> <div class="questions-container"> ${questions} </div> </div> `; let container = document.createElement("div"); container.innerHTML = html; const fquestions = container.querySelector(".frequently-questions"); fquestions.querySelectorAll("[data-do-question]").forEach(i => { i.addEventListener("click", e => { doQuestionEventHandler(e); }); }); return fquestions; }, frequentlyAsked: data => { let questions = ``; for(let q of data.questions) { questions += ` <button class="kb-btn tricky question" data-do-question="Мене цікавить ${q}"> <svg class="kb-icon" width="16" height="16"> <use href="#kb-star"></use> </svg> ${q} </button> `; } const html = ` <div class="frequently-asked"> <h4 class="heading">Поширені запити:</h4> <div class="questions-container"> ${questions} <button class="kb-btn tricky question" onclick="showFreqQuests()"> <svg class="kb-icon" width="16" height="16"> <use href="#kb-star"></use> </svg> Часті запитання </button> </div> </div> `; let container = document.createElement("div"); container.innerHTML = html; const fquestions = container.querySelector(".frequently-asked"); fquestions.querySelectorAll("[data-do-question]").forEach(i => { i.addEventListener("click", e => { doQuestionEventHandler(e); }); }); return fquestions; } }; function showFreqQuests() { const freqQuests = specialViewMap.frequentlyQuestions({ questions: [ "Як мені працевлаштуватися у вас", "Які умови співпраці?", "Де можна отримати консультацію?", ] }); document.querySelector(".chat-container").appendChild(freqQuests); scrollMsgsContainerDown(); updateMsgHistory(); } async function sendMessages(msgid, message, onChunk) { url = AISERV; hostMap = { "kitanbot.vmk-group.tech": "kitan-group", "localhost:3000": "kitan-group", "kitan-group.com": "kitan-group", "hbloom.com.ua": "hbloom", "nubea.com.ua": "nubea", "mediceuticals.com.ua": "mediceuticals", "bosleymd.com.ua": "bosleymd" }; message["site"] = hostMap[document.location.host] ?? document.location.host; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: "kitan", message }) }); if (!response.ok) { throw new Error(`Ошибка при отправке: ${response.status} ${response.statusText}`); } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); let lines = buffer.split("\n"); buffer = lines.pop(); // последний может быть неполной строкой for (const line of lines) { if (!line.trim()) continue; try { const data = JSON.parse(line); if (onChunk) onChunk(data, msgid); // вызвать обработчик } catch (e) { console.warn("Ошибка разбора JSON:", e, line); } } } if (buffer) { try { const final = JSON.parse(buffer); if (onChunk) onChunk(final); } catch (e) { console.warn("Ошибка разбора последнего чанка:", e, buffer); } } } catch (error) { console.error("Ошибка при отправке сообщений:", error); } } function prepareUserMessage(text) { return { message: { id: 1, date: getCurrentDateTime(), text: text, reply_to_message: null, entities: null, photo: null, document: null, voice: null, audio: null, video: null, sticker: null, caption: null }, author: { id: 1, username: '', first_name: '', last_name: '', title: null, type: 'user' }, is_forwarding: false, origin: { from: 'kitan_bot', chat_id: getChatId() } } } function getTimeView() { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); return time = `${hours}:${minutes}`; } function getCurrentDateTime() { const now = new Date(); const pad = n => n.toString().padStart(2, '0'); const year = now.getFullYear(); const month = pad(now.getMonth() + 1); // Месяцы от 0 до 11 const day = pad(now.getDate()); const hours = pad(now.getHours()); const minutes = pad(now.getMinutes()); const seconds = pad(now.getSeconds()); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } function updateMsgHistory() { const chat = document.querySelector(".chat-container"); localStorage.setItem("chat", chat.innerHTML); } function restoreMsgStorage() { const chat = document.querySelector(".chat-container"); let content = localStorage.getItem("chat"); if(content) { chat.innerHTML = content; } chat.querySelectorAll("[data-do-question]").forEach(i => { i.addEventListener("click", e => { doQuestionEventHandler(e); }); }); } function getChatId() { chatId = localStorage.getItem("chatId"); if(!chatId) { chatId = Math.floor(Math.random() * (99999999 - 1000000 + 1)) + 1000000; localStorage.setItem("chatId", chatId); } return chatId; } function resetChat() { localStorage.removeItem("chatId"); localStorage.removeItem("chat"); localStorage.removeItem("user_rating"); } function genMsgId() { return "msgid" + (new Date()).getTime(); } function createRenderBubble(type, isFirst, msgtext, mid, time) { time = time || getTimeView(); mid = mid || genMsgId(); waitingLoader = ` <span class="waiting"> <span></span> <span></span> <span></span> </span> `; msgtext = msgtext || waitingLoader; const additionalCls = []; let icon = ""; if(type == "user") { additionalCls.push("user"); icon = ` <svg class="kb-icon" width="16" height="16"> <use href="#kb-user"></use> </svg> `; } else { additionalCls.push("agent"); additionalCls.push("assistant"); additionalCls.push("state-waiting"); icon = ` <svg class="kb-icon" width="32" height="32"> <use href="#kb-redes-sm-avatar"></use> </svg> `; } const classes = additionalCls.join(" "); const msg = ` <div class="message ${classes}" id="${mid}"> <div class="leftside"> <div class="avatar"> ${icon} </div> </div> <div class="rightside"> <div class="kb-card bubble first"> ${msgtext} </div> <div class="foot"> <span class="timestamp">${time}</span> </div> </div> </div> `; const cont = document.createElement("DIV"); cont.innerHTML = msg; return { "id": mid, "message": cont.querySelector(".message") } } function updateMsgContent(mid, msgtext) { const msg = document.querySelector(`.assistant#${mid}`); msg.classList.remove("state-waiting"); msg.querySelector(".bubble").innerHTML = msgtext; updateMsgHistory(); scrollMsgsContainerDown(); } function doAction(action, params, callback) { url = BACKSERV; fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: action, params: params }) }) .then(response => response.json()) .then(data => { callback(data); }) .catch(error => { console.error("Err of request:", error); }); } function getSpecialViewFromMsg(msg) { const sep = "front_special_view"; if(msg.indexOf(sep) == -1) { return false; } let specialView = msg.split(sep)[1].trim(); let textarea = document.createElement('textarea'); textarea.innerHTML = specialView; specialView = textarea.value; if(!specialView) { return false; } specialView = JSON.parse(specialView); if(typeof specialViewMap[specialView.special_view] == "undefined") { return false; } return specialViewMap[specialView.special_view](specialView.data); } function initAuthCard() { } document.addEventListener("DOMContentLoaded", e => { console.log("App start"); });