From c19e8d3e8bcc096ba76d692d1f6a7cb8c405ac7b Mon Sep 17 00:00:00 2001 From: dorren Date: Wed, 18 Dec 2024 20:08:33 +0800 Subject: [PATCH] doc assistant use remote script --- docs/assets/css/assistant_style.css | 38 ++- docs/assets/js/assistant.js | 456 ++------------------------- docs/assets/js/chatAssistant.js | 472 ++++++++++++++++++++++++++++ docs/mkdocs.template.yml | 3 - 4 files changed, 515 insertions(+), 454 deletions(-) create mode 100644 docs/assets/js/chatAssistant.js diff --git a/docs/assets/css/assistant_style.css b/docs/assets/css/assistant_style.css index 057bab79..835e87a1 100644 --- a/docs/assets/css/assistant_style.css +++ b/docs/assets/css/assistant_style.css @@ -65,7 +65,7 @@ height: 34px; width: 34px; cursor: pointer; - background: url(../icon_dialog.png) 0 -78px; + background: url(assets/icon_dialog.png) 0 -78px; background-size: 34px 240px; } @@ -82,7 +82,7 @@ height: 30px; width: 34px; cursor: pointer; - background: url(../icon_dialog.png) 0 -60px; + background: url(assets/icon_dialog.png) 0 -60px; background-size: 34px 260px; } @@ -103,7 +103,7 @@ height: 24px; width: 24px; cursor: pointer; - background: url(../icon_dialog.png) 0 -109px; + background: url(assets/icon_dialog.png) 0 -109px; background-size: 34px 160px; } @@ -217,7 +217,7 @@ } .content pre { - position: relative; + position: relative; margin: 0 10px; padding: 10px; overflow: auto; @@ -237,7 +237,7 @@ padding: 0.5em; height: 24px; width: 24px; - background: url(../icon_dialog.png) 0 -25px; + background: url(assets/icon_dialog.png) 0 -25px; background-size: 34px 240px; pointer-events: auto; cursor: pointer; @@ -305,7 +305,7 @@ } .useful-normal { - background: url(../icon_dialog.png) 0 -101px; + background: url(assets/icon_dialog.png) 0 -101px; background-size: 34px 230px; opacity: 0.4; } @@ -315,7 +315,7 @@ } .notuseful-normal { - background: url(../icon_dialog.png) 0 -153px; + background: url(assets/icon_dialog.png) 0 -153px; background-size: 34px 230px; opacity: 0.4; } @@ -325,22 +325,32 @@ } .useful { - background: url(../icon_dialog.png) 0 -125px; + background: url(assets/icon_dialog.png) 0 -125px; background-size: 34px 230px; opacity: 1; } .notuseful { - background: url(../icon_dialog.png) 0 -177px; + background: url(assets/icon_dialog.png) 0 -177px; background-size: 34px 230px; opacity: 1; } +.state-word { + animation: flash 2s infinite; +} + @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } +@keyframes flash { + 0% { opacity: 1; } + 50% { opacity: 0; } + 100% { opacity: 1; } +} + #captcha-box { position: fixed; bottom: 50%; @@ -359,7 +369,7 @@ width: 310px; margin: 20px 0; } - + .block { position: absolute; left: 0; @@ -478,7 +488,7 @@ right: 11px; width: 15px; height: 15px; - background: url(../icon_slider.png) 0 -38px; + background: url(assets/icon_slider.png) 0 -38px; background-size: 34px 471px; } @@ -488,7 +498,7 @@ left: 13px; width: 14px; height: 12px; - background: url(../icon_slider.png) 0 -25px; + background: url(assets/icon_slider.png) 0 -25px; background-size: 34px 471px; } @@ -499,6 +509,6 @@ width: 34px; height: 34px; cursor: pointer; - background: url(../icon_slider.png) 0 -400px; + background: url(assets/icon_slider.png) 0 -400px; background-size: 34px 471px; -} \ No newline at end of file +} diff --git a/docs/assets/js/assistant.js b/docs/assets/js/assistant.js index b5fc0e7e..48a628b8 100644 --- a/docs/assets/js/assistant.js +++ b/docs/assets/js/assistant.js @@ -1,443 +1,25 @@ document.addEventListener('DOMContentLoaded', function() { var script = document.createElement('script'); - script.src = 'https://cdn.jsdelivr.net/npm/showdown/dist/showdown.min.js' - document.head.appendChild(script); - - var logopath = "../../assets/logo.png"; - let pathName = window.document.location.pathname; - let paths = pathName.replace(/^\/|\/$/g, '').split('/'); - var versionInfo = '/' + paths.slice(0, 2).join('/'); - if (paths.length == 2){ - logopath = "assets/logo.png"; - } - // 对话助手入口 - var floatingIcon = document.createElement('div'); - floatingIcon.id = 'floating-icon'; - floatingIcon.innerHTML = `icon`; - document.body.appendChild(floatingIcon); - - var language; - var userQuery = ''; - var botReply = ''; - var sessionid = ''; - var isWaiting = false; - var firstHello = true; - var dialogInit = true; - var converter; - var placeholdervalue; - - var sendMessageButton; - var resetDialogButton; - var userInput; - var messages; - var dialogBox; - var captchaBox; - // 对话框 - async function initDialogDom() { - let curVersion = 'stable'; - language = paths[0]; - curVersion = paths[1]; - - try { - // 发送请求到后端接口 - const response = await fetch('https://api.lazyllm.top/checkVersion', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ version: curVersion }), - }); - - // 处理响应 - const data = await response.json(); - curVersion = data.version; - placeholdervalue = data.suggestedQuestion; - } catch (error) { - curVersion = 'stable'; - placeholdervalue = 'What is LazyLLM ?'; - } finally { - let sendbtn = language=='zh-cn' ? '发送' : 'Send'; - dialogBox = document.createElement('div'); - dialogBox.id = 'dialog-box'; - dialogBox.style = 'display: block' - dialogBox.innerHTML = ` -
-
- -
-
-
-
- - -
-
` - - document.body.appendChild(dialogBox); - dialogInit = false; - sendMessageButton = document.getElementById('send-message'); - resetDialogButton = document.getElementById('reset-dialog') - userInput = document.getElementById('user-input'); - messages = document.getElementById('messages'); - bindEvents(); - setTimeout(popHelloMessage, 1000); - userInput.focus(); - userInput.addEventListener('input', (e) => { - userInput.style.height = '40px'; - userInput.style.height = e.target.scrollHeight + 'px'; - }); - } - } - - // 人机验证框 - function initCaptchaDom(){ - captchaBox = document.createElement('div'); - captchaBox.id = 'captcha-box'; - captchaBox.style = "display: none"; - captchaBox.innerHTML = ` -
-
-
-
- ` - document.body.appendChild(captchaBox); - let closeCaptchaButton = document.getElementById('close-captcha'); - closeCaptchaButton.addEventListener('click', closeCaptcha); - } - - async function sendFeedback(e, feedback, state) { - let message_id = e.target.dataset.id; - let like = state==0 ? feedback.slice(0, -7) : feedback; - try { - // 发送请求到后端接口 - const response = await fetch('https://api.lazyllm.top/feedback', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ type: like, state: state, message_id: message_id }), - }); - - data = await response.json(); - if (state==1) { - let feedback_box = e.target.parentNode; - feedback_box.children[0].className = data.stateu; - feedback_box.children[1].className = data.staten; - } else { - e.target.className = like == 'useful' ? 'useful-normal' : 'notuseful-normal'; - } - } catch (error) { - - } - } - - function clickFeedback(e) { - let state = e.target.classList[0]; - if (state.includes('normal')) { - if (state.includes('notuseful')) { - sendFeedback(e, 'notuseful', 1); - } else { - sendFeedback(e, 'useful', 1); - } - } else { - if (state.includes('notuseful')) { - sendFeedback(e, 'notuseful-normal', 0); - } else { - sendFeedback(e, 'useful-normal', 0); - } - } - } - - function initFeedbackDom(message_id, domElement) { - var util_box = document.createElement('div'); - util_box.classList.add('util-box'); - - var likeButton = document.createElement('button'); - likeButton.className = 'useful-normal'; - likeButton.setAttribute('data-id', message_id); - likeButton.addEventListener('click', clickFeedback); - util_box.appendChild(likeButton); - - var dislikeButton = document.createElement('button'); - dislikeButton.className = 'notuseful-normal'; - dislikeButton.setAttribute('data-id', message_id); - dislikeButton.addEventListener('click', clickFeedback); - util_box.appendChild(dislikeButton); - - domElement.children[1].appendChild(util_box); - } - - function setMessageState(state) { - isWaiting = state; - sendMessageButton.disabled = state; - } - - function popHelloMessage() { - if (!isWaiting && firstHello){ - let hello = document.createElement('div'); - hello.innerHTML = ` -
- -

Hello, how can I assist you today ?

-
` - messages.appendChild(hello) - firstHello = false; - } - } - - function handleClickOutside(event) { - if (event.target === dialogBox) { - dialogBox.style.display = 'none'; - window.removeEventListener('click', handleClickOutside); - } - } - - function switchDialobBoxDisplay() { - if (dialogInit) { - dialogInit = false; - initDialogDom(); - initCaptchaDom(); - converter = new showdown.Converter(); - return; + script.src = 'https://api.lazyllm.top/static/scripts/chatAssistant.js' + // 确保在外部脚本加载完成后执行后续逻辑 + script.onload = function() { + var logopath = "../../assets/logo.png"; + let pathName = window.document.location.pathname; + let paths = pathName.replace(/^\/|\/$/g, '').split('/'); + if (paths.length == 2){ + logopath = "assets/logo.png"; } - - if (dialogBox.style.display === 'none') { - dialogBox.style.display = 'block'; - window.addEventListener('click', handleClickOutside); - setTimeout(popHelloMessage, 100); - userInput.focus(); - } else { - dialogBox.style.display = 'none'; - captchaBox.style.display = 'none'; - window.removeEventListener('click', handleClickOutside); - } - } - - function resetUserInput() { - userInput.value = ''; - userInput.style.height = '40px'; - } - - function resetDialog(){ - messages.innerHTML = ''; - setMessageState(false); - resetUserInput() - sessionid = ''; - firstHello = true; - setTimeout(popHelloMessage, 100); - } - - async function Verify() { - let seed = Math.ceil(Math.random()*10000); - try { - const response = await fetch('https://api.lazyllm.top/authorize', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ key: seed }), - }); - - const data = await response.json(); - sessionStorage.setItem('jigsaw_token', data.token); - getChatContent(); - } catch (error) { - - } - } - - function showCaptcha() { - captchaBox.style.display = 'block'; - if (document.getElementById('captcha').childElementCount==0){ - converter = new showdown.Converter(); - window.jigsaw.init({ - el: document.getElementById('captcha'), - onSuccess: function() { - captchaBox.style.display = 'none'; - Verify(); - this.reset(); - }, - }) - } - } - - function closeCaptcha() { - captchaBox.style.display = 'none'; - setMessageState(false); - userInput.value = userQuery; - messages.removeChild(messages.lastChild); - } - - function fetchWithTimeout(url, options = {}, timeout = 120000) { - // 创建一个新的 Promise 来处理超时逻辑 - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Request timed out')), timeout) - ); - - // 执行 fetch 请求 - const fetchPromise = fetch(url, options); - - // 使用 Promise.race() 来处理 fetch 请求和超时逻辑 - return Promise.race([fetchPromise, timeoutPromise]); - } - - function stream_response_handler(data, domElement) { - data = data.data[0]; - if (data.state == 'success') { - if (data.errmsg == 200) { - if (botReply==''){ - domElement.children[1].innerHTML = converter.makeHtml(data.message.response); - } - var source_box = document.createElement('div'); - source_box.classList.add('source-box'); - source_box.style = `height: ${data.message.sources.length>2 ? 80 : 40}px` - data.message.sources.forEach(source => { - const link = document.createElement('a'); - link.textContent = source.text; - link.href = versionInfo + source.href; - link.target = '_blank' - source_box.appendChild(link); - }); - domElement.children[1].appendChild(source_box); - sessionid = data.session_id; - initFeedbackDom(data.stable_id, domElement); - } else if (data.errmsg == 102 || data.errmsg==103){ - // 触发敏感词或无关问题, 不计入某段session - domElement.children[1].innerHTML = converter.makeHtml(data.message.response); - setMessageState(false); - } else if (data.errmsg == 101) { - // token 过期 - messages.removeChild(messages.lastChild); - showCaptcha(); - } else { - domElement.children[1].innerHTML = converter.makeHtml("Something went wrong, please try again later."); - setMessageState(false); - } - messages.scrollTop = messages.scrollHeight; - return true; - } - - botReply += data.response; - if (data.state == 'codeblock'){ - domElement.children[1].innerHTML = converter.makeHtml(botReply + '\n```'); - } else { - domElement.children[1].innerHTML = converter.makeHtml(botReply); - } - messages.scrollTop = messages.scrollHeight; - return false; - } - - async function getChatContent() { - const botMessage = document.createElement('div'); - botMessage.classList.add('message'); - botMessage.classList.add('received'); - botMessage.innerHTML = `机器人头像 -
-
-
- `; - botMessage.children[1].addEventListener('click', function(e) { - navigator.clipboard.writeText(e.target.innerText); + // 对话助手入口 + const chatAssistant = new ChatAssistant({ + logopath: logopath, + apiBaseUrl: 'https://api.lazyllm.top', + language: paths[0], + version: paths[1], + placeholder: 'Type your question here...', }); - messages.appendChild(botMessage); - messages.scrollTop = messages.scrollHeight; // 滚动到最新消息 + }; - try { - fetchWithTimeout('https://api.lazyllm.top/chatStream', { - method: 'POST', - headers: { - 'Accept': 'text/event-stream', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ query: userQuery, token: sessionStorage.getItem('jigsaw_token'), sessionid: sessionid }),}) - .then(response => { - const reader = response.body.getReader(); - const decoder = new TextDecoder('utf-8'); - var resFin = false; - // 启动递归读取流 - function readStream() { - // 读取流中的一段数据 - return reader.read().then(result => { - // 解码接收到的数据块 - const chunk = decoder.decode(result.value, { stream: true }); - - // 逐行解析 JSON 数据并显示 - chunk.split('\n').forEach(line => { - if (line.trim()) { // 跳过空行 - // console.log(line) - resFin = stream_response_handler(JSON.parse(line), botMessage) - } - }); - if (resFin) { - return; - } - // 递归调用以继续读取下一段数据 - return readStream(); - }); - } - readStream(); - setMessageState(false); - }) - .catch(error => { - // 500 404等 - botMessage.children[1].innerHTML = converter.makeHtml("Request failed, please try again later."); - setMessageState(false); - }); - } catch (error) { - botMessage.children[1].innerHTML = converter.makeHtml("Something went wrong, please try again later"); - setMessageState(false); - } - } - - function sendMessage() { - if (isWaiting){ - return; - } - const text = userInput.value.trim(); - if (!text) { userInput.value = placeholdervalue; } - else { - const userMessage = document.createElement('div'); - userMessage.classList.add('message'); - userMessage.classList.add('sent'); - userMessage.innerHTML = ` -
-

${text}

- `; - messages.appendChild(userMessage); - userQuery = text; - botReply = ''; - resetUserInput(); - setMessageState(true); - getChatContent(); - } - } - - floatingIcon.addEventListener('click', switchDialobBoxDisplay); - - function bindEvents() { - window.addEventListener('click', handleClickOutside); - sendMessageButton.addEventListener('click', sendMessage); - resetDialogButton.addEventListener('click', resetDialog); - userInput.addEventListener('keypress', function(e) { - // shift + enter 换行 - if (e.shiftKey && e.key === 'Enter') { - // 阻止默认事件 - e.preventDefault(); - // 将光标移动到换行后的正确位置 - const cursorPos = userInput.selectionStart; - userInput.value = userInput.value.substring(0, cursorPos) + "\n" + userInput.value.substring(cursorPos); - userInput.selectionStart = userInput.selectionEnd = cursorPos + 1; - // 调整高度 - userInput.style.height = 40 + "px"; - userInput.style.height = userInput.scrollHeight + "px"; - } else if (e.key === 'Enter') { - e.preventDefault(); - sendMessage(); - } - }); - } -}); + // 将脚本添加到文档中 + document.head.appendChild(script); +}); \ No newline at end of file diff --git a/docs/assets/js/chatAssistant.js b/docs/assets/js/chatAssistant.js new file mode 100644 index 00000000..c224c8c9 --- /dev/null +++ b/docs/assets/js/chatAssistant.js @@ -0,0 +1,472 @@ +(function () { + class ChatAssistant { + constructor(options) { + // 参数处理 + this.logopath = options.logopath || 'default-logo.png'; + this.apiBaseUrl = options.apiBaseUrl || 'https://api.lazyllm.top'; + this.language = options.language || 'en'; + this.placeholder = options.placeholder || 'How can I help you?'; + this.curVersion = options.version || 'stable'; + + // 初始化状态变量 + this.isWaiting = false; + this.firstHello = true; + this.dialogInit = true; + this.userQuery = ''; + this.botReply = ''; + this.sessionId = ''; + + this.loadCSS(`${this.apiBaseUrl}/static/scripts/style.css`) + this.loadJS(`${this.apiBaseUrl}/static/scripts/jigsaw.js`) + this.loadJS("http://cdn.jsdelivr.net/npm/showdown/dist/showdown.min.js") + + // 初始化 DOM 和事件绑定 + this.initFloatingIcon(); + this.clickFeedback = this.clickFeedback.bind(this); + } + + loadCSS(cssUrl) { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = cssUrl; + document.head.appendChild(link); + } + + loadJS(jsUrl) { + const script = document.createElement('script'); + script.src = jsUrl; + script.async = true; // 异步加载 + document.head.appendChild(script); + } + + initFloatingIcon() { + const floatingIcon = document.createElement('div'); + floatingIcon.id = 'floating-icon'; + floatingIcon.innerHTML = `icon`; + document.body.appendChild(floatingIcon); + + floatingIcon.addEventListener('click', () => this.switchDialogBoxDisplay()); + } + + async initDialogDom() { + try { + const response = await fetch(`${this.apiBaseUrl}/checkVersion`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ version: this.curVersion }), + }); + + const data = await response.json(); + this.curVersion = data.version; + this.placeholder = data.suggestedQuestion; + } catch (error) { + this.curVersion = 'stable'; + this.placeholder = 'What is LazyLLM?'; + } finally { + this.renderDialogBox(); + } + } + + initCaptchaDom(){ + const captchaBox = document.createElement('div'); + captchaBox.id = 'captcha-box'; + captchaBox.style = "display: none"; + captchaBox.innerHTML = ` +
+
+
+
+ ` + document.body.appendChild(captchaBox); + this.captchaBox = captchaBox; + let closeCaptchaButton = document.getElementById('close-captcha'); + closeCaptchaButton.addEventListener('click', () => this.closeCaptcha()); + } + + initFeedbackDom(message_id, domElement) { + var util_box = document.createElement('div'); + util_box.classList.add('util-box'); + + var likeButton = document.createElement('button'); + likeButton.className = 'useful-normal'; + likeButton.setAttribute('data-id', message_id); + likeButton.addEventListener('click', this.clickFeedback); + util_box.appendChild(likeButton); + + var dislikeButton = document.createElement('button'); + dislikeButton.className = 'notuseful-normal'; + dislikeButton.setAttribute('data-id', message_id); + dislikeButton.addEventListener('click', this.clickFeedback); + util_box.appendChild(dislikeButton); + + domElement.children[1].appendChild(util_box); + } + + renderDialogBox() { + var dialogBox = document.createElement('div'); + dialogBox.id = 'dialog-box'; + dialogBox.style.display = 'block'; + dialogBox.innerHTML = ` +
+
+ +
+
+
+
+ + +
+
`; + document.body.appendChild(dialogBox); + + this.bindEvents(dialogBox); + this.popHelloMessage(); + } + + bindEvents(dialogBox) { + this.sendMessageButton = dialogBox.querySelector('#send-message'); + this.resetDialogButton = dialogBox.querySelector('#reset-dialog'); + this.userInput = dialogBox.querySelector('#user-input'); + this.messages = dialogBox.querySelector('#messages'); + + this.sendMessageButton.addEventListener('click', () => this.sendMessage()); + this.resetDialogButton.addEventListener('click', () => this.resetDialog()); + this.userInput.focus(); + + let that = this; + let userInput = this.userInput; + userInput.addEventListener('input', (e) => { + userInput.style.height = '40px'; + userInput.style.height = e.target.scrollHeight + 'px'; + }); + userInput.addEventListener('keypress', function(e) { + // shift + enter 换行 + if (e.shiftKey && e.key === 'Enter') { + // 阻止默认事件 + e.preventDefault(); + // 将光标移动到换行后的正确位置 + const cursorPos = userInput.selectionStart; + userInput.value = userInput.value.substring(0, cursorPos) + "\n" + userInput.value.substring(cursorPos); + userInput.selectionStart = userInput.selectionEnd = cursorPos + 1; + // 调整高度 + userInput.style.height = 40 + "px"; + userInput.style.height = userInput.scrollHeight + "px"; + } else if (e.key === 'Enter') { + e.preventDefault(); + that.sendMessage(); + } + }); + window.addEventListener('click', this.handleClickOutside); + } + + fetchWithTimeout(url, options = {}, timeout = 120000) { + // 创建一个新的 Promise 来处理超时逻辑 + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Request timed out')), timeout) + ); + + // 执行 fetch 请求 + const fetchPromise = fetch(url, options); + + // 使用 Promise.race() 来处理 fetch 请求和超时逻辑 + return Promise.race([fetchPromise, timeoutPromise]); + } + + async sendFeedback(e, feedback, state) { + let message_id = e.target.dataset.id; + let like = state==0 ? feedback.slice(0, -7) : feedback; + try { + // 发送请求到后端接口 + const response = await fetch(`${this.apiBaseUrl}/feedback`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ type: like, state: state, message_id: message_id }), + }); + + let data = await response.json(); + if (state==1) { + let feedback_box = e.target.parentNode; + feedback_box.children[0].className = data.stateu; + feedback_box.children[1].className = data.staten; + } else { + e.target.className = like == 'useful' ? 'useful-normal' : 'notuseful-normal'; + } + } catch (error) { + + } + } + + clickFeedback(e) { + let state = e.target.classList[0]; + if (state.includes('normal')) { + if (state.includes('notuseful')) { + this.sendFeedback(e, 'notuseful', 1); + } else { + this.sendFeedback(e, 'useful', 1); + } + } else { + if (state.includes('notuseful')) { + this.sendFeedback(e, 'notuseful-normal', 0); + } else { + this.sendFeedback(e, 'useful-normal', 0); + } + } + } + + stream_response_handler(res, domElement) { + let converter = this.converter; + let data = res.data[0]; + if (data.state=='success'){ + this.updateSourceInfo(domElement, data) + this.messages.scrollTop = this.messages.scrollHeight; + return true; + } else if (["thinking", "planning", "solving"].some(sub => sub == data.state)){ + domElement.children[1].innerHTML = `

${data.state} ...

` + } else if (data.state == 'codeblock'){ + this.botReply += data.response; + domElement.children[1].innerHTML = converter.makeHtml(this.botReply + '\n```'); + } else{ + this.botReply += data.response; + domElement.children[1].innerHTML = converter.makeHtml(this.botReply); + } + this.messages.scrollTop = this.messages.scrollHeight; + return false; + } + + updateSourceInfo(domElement, data) { + let converter = this.converter; + let error_code = data.errmsg; + if (error_code == 200){ + if (this.botReply==''){ + domElement.children[1].innerHTML = converter.makeHtml(data.message.response); + } + let source_box = document.createElement('div'); + source_box.classList.add('source-box'); + source_box.style = `height: ${data.message.sources.length>2 ? 80 : 40}px` + data.message.sources.forEach(source => { + const link = document.createElement('a'); + link.textContent = source.text; + if (source.href.startsWith("/")) { + link.href = this.curVersion + source.href; + } else { link.href = source.href; } + link.target = '_blank' + source_box.appendChild(link); + }); + domElement.children[1].appendChild(source_box); + this.sessionid = data.session_id; + this.initFeedbackDom(data.stable_id, domElement); + }else if (error_code==102 || error_code==103){ + // 触发敏感词或无关问题, 不计入某段session + domElement.children[1].innerHTML = converter.makeHtml(data.message.response); + this.setMessageState(false); + }else if (error_code==101){ + // token 过期 + this.messages.removeChild(this.messages.lastChild); + this.showCaptcha(); + }else{ + domElement.children[1].innerHTML = converter.makeHtml("Something went wrong, please try again later."); + this.setMessageState(false); + } + } + + async getChatContent() { + let that = this; + let messages = that.messages; + let botMessage = document.createElement('div'); + botMessage.classList.add('message', 'received'); + botMessage.innerHTML = `机器人头像 +
+
+
`; + botMessage.children[1].addEventListener('click', function(e) { + navigator.clipboard.writeText(e.target.innerText); + }); + messages.appendChild(botMessage); + messages.scrollTop = messages.scrollHeight; + + try { + that.fetchWithTimeout(`${this.apiBaseUrl}/chatStream`, { + method: 'POST', + headers: { + 'Accept': 'text/event-stream', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ query: that.userQuery, token: sessionStorage.getItem('jigsaw_token'), sessionid: that.sessionid, version: that.curVersion }),}) + .then(response => { + const reader = response.body.getReader(); + const decoder = new TextDecoder('utf-8'); + var resFin = false; + // 启动递归读取流 + function readStream() { + // 读取流中的一段数据 + return reader.read().then(result => { + // 解码接收到的数据块 + const chunk = decoder.decode(result.value, { stream: true }); + + // 逐行解析 JSON 数据并显示 + chunk.split('\n').forEach(line => { + if (line.trim()) { // 跳过空行 + resFin = that.stream_response_handler(JSON.parse(line), botMessage) + } + }); + if (resFin) { + return; + } + // 递归调用以继续读取下一段数据 + return readStream(); + }); + } + readStream(); + that.setMessageState(false); + }) + .catch(error => { + // 500 404等 + botMessage.children[1].innerHTML = that.converter.makeHtml("Request failed, please try again later!"); + that.setMessageState(false); + }); + } catch (error) { + botMessage.children[1].innerHTML = that.converter.makeHtml("Something went wrong, please try again later!"); + that.setMessageState(false); + } + } + + sendMessage() { + if (this.isWaiting) return; + + let userInput = this.userInput; + const text = userInput.value.trim(); + if (!text) { userInput.value = this.placeholder; return;} + + const userMessage = document.createElement('div'); + userMessage.classList.add('message', 'sent'); + userMessage.innerHTML = `
+

${text}

`; + this.messages.appendChild(userMessage); + + this.userQuery = text; + this.botReply = ''; + + this.resetUserInput(); + this.setMessageState(true); + this.getChatContent(); + } + + popHelloMessage() { + if (!this.isWaiting && this.firstHello){ + let hello = document.createElement('div'); + hello.innerHTML = ` +
+ +

Hello, how can I assist you today ?

+
` + this.messages.appendChild(hello) + this.firstHello = false; + } + } + + handleClickOutside(event) { + if (event.target.id == 'dialog-box') { + let dialogBox = document.getElementById("dialog-box"); + let captchaBox = document.getElementById("captcha-box"); + dialogBox.style.display = 'none'; + captchaBox.style.display = 'none'; + window.removeEventListener('click', this.handleClickOutside); + } + } + + setMessageState(state) { + this.isWaiting = state; + this.sendMessageButton.disabled = state; + } + + resetUserInput() { + this.userInput.value = ''; + this.userInput.style.height = '40px'; + } + + resetDialog() { + this.messages.innerHTML = ''; + this.userInput.value = ''; + this.sessionId = ''; + this.isWaiting = false; + this.firstHello = true; + this.captchaBox.style.display = 'none'; + setTimeout(() => this.popHelloMessage(), 500); + console.log("!23") + } + + switchDialogBoxDisplay() { + if (this.dialogInit) { + this.dialogInit = false; + this.initDialogDom(); + this.initCaptchaDom(); + this.converter = new showdown.Converter(); + return; + } + + let dialogBox = document.querySelector("#dialog-box"); + if (dialogBox.style.display === 'none') { + dialogBox.style.display = 'block'; + window.addEventListener('click', this.handleClickOutside); + setTimeout(() => this.popHelloMessage(), 500); + this.userInput.focus(); + } else { + dialogBox.style.display = 'none'; + this.captchaBox.style.display = 'none'; + window.removeEventListener('click', this.handleClickOutside); + } + } + + async Verify() { + let that = this; + let seed = Math.ceil(Math.random()*10000); + try { + const response = await fetch(`${this.apiBaseUrl}/authorize`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ key: seed }), + }); + + const data = await response.json(); + sessionStorage.setItem('jigsaw_token', data.token); + that.getChatContent(); + } catch (error) { + + } + } + + showCaptcha() { + let that = this; + that.captchaBox.style.display = 'block'; + if (document.getElementById('captcha').childElementCount==0){ + that.converter = new showdown.Converter(); + window.jigsaw.init({ + el: document.getElementById('captcha'), + onSuccess: function() { + that.captchaBox.style.display = 'none'; + that.Verify(); + this.reset(); + }, + }) + } + } + + closeCaptcha() { + this.captchaBox.style.display = 'none'; + this.setMessageState(false); + this.userInput.value = this.userQuery; + this.messages.removeChild(this.messages.lastChild); + } + } + + // 暴露到全局 + window.ChatAssistant = ChatAssistant; + })(); diff --git a/docs/mkdocs.template.yml b/docs/mkdocs.template.yml index 11885c73..d8327057 100644 --- a/docs/mkdocs.template.yml +++ b/docs/mkdocs.template.yml @@ -84,11 +84,8 @@ extra: social: - icon: fontawesome/brands/github link: https://github.com/LazyAGI/LazyLLM -extra_css: - - 'assets/css/assistant_style.css' extra_javascript: - 'assets/js/assistant.js' - - 'assets/js/jigsaw.js' plugins: - search: - section-index: