问:
现在我们需要一个ai聊天功能, 接口已经给出:
只要是message就是我们的数据, 是message_end就是结束信息, 其他的我们不需要管.
回答:
我们不使用传统的fetch请求这个接口, 而是使用sse, eventSource去请求,
当我们输入框回车 或者 点击元素, 获取到输入框中用户输入的值, 然后创建用户问题标签,
同时, 检查当前有无eventSource , 没有的话创建eventSource, eventSource要设置,withCredentials:true携带cookie.
当请求接口的时候没有返回值之前, 我们需要创建一个 "正在加载..."的标签, 给用户提示.
在eventSource.onmessage外面创建一个newStr空字段,
注意: 要在eventSource.onmessage外面创建, 在里面创建会创建很多的newStr,
eventSource.onmessage处理返回的数据,
这里加了一个小功能: 接口返回文字我们的网页跟随, 返回文字增加一行我们就向下滑动,时刻是在最下面, 不需要用户手动下滑.
判断: 当时要是message_end, 就调用eventSource.close关闭连接.
问:
eventSource = new EventSource(fullUrl, { withCredentials: true });的{ withCredentials: true }什么呢意思?
回答:
在JavaScript中,`EventSource` 是用于接收服务器推送的一种技术,通常用于实现服务器发送事件(Server-Sent Events,SSE)。在你的代码片段中,`EventSource` 的初始化使用了一个配置对象 `{ withCredentials: true }`。
`withCredentials: true` 的意思是在跨域请求中携带凭据(credentials),比如说在请求中包括 cookies 和 HTTP认证信息(即使是跨域请求)。这对于需要在跨域请求中进行认证或会话管理的情况非常有用。具体来说:
- 如果 `withCredentials` 设置为 `true`,则 `EventSource` 在发送请求时会包括当前页面的 cookies 和 HTTP认证信息(如果存在的话)。
- 如果 `withCredentials` 设置为 `false` 或未指定,默认情况下,跨域请求中不会携带 cookies 和 HTTP认证信息。使用 `withCredentials: true` 的场景包括需要进行用户身份验证或授权的跨域服务器推送。这确保了在跨域情况下能够正确地传递身份验证信息,使得服务器能够基于用户的会话状态提供个性化的数据或服务。
需要注意的是,为了确保安全性,跨域请求中的 `withCredentials` 功能通常需要服务器端的支持,并且需要服务器配置允许接收包含凭据的请求
问:
我们调用ai聊天接口, 触发一次事件, 会不停的调用这个接口, 请问为什么?
回答:
function handleKeyPress(event) {if (event.keyCode === 13) {event.preventDefault();var input = document.getElementById('chat_input');var question = input.value.trim();if (question !== '') {var dialogueContainer = document.querySelector('.dialogue dl');let str = "";// 创建用户问题的对话框项var dt = document.createElement('dt');var dtParagraph = document.createElement('p');dtParagraph.textContent = '' + question + '';dt.appendChild(dtParagraph);dialogueContainer.appendChild(dt);const appinfo = getCookie('appinfo');var apiUrl = 'https://xxxx.xxxx.com/xxxx-xxxx/xxxx/xxxx';var params = {query: question,unicodeToChinese: 1,};if (appinfo) {params.appinfo = appinfo;}var queryString = new URLSearchParams(params).toString();var fullUrl = apiUrl + '?' + queryString;eventSource = new EventSource(fullUrl, { withCredentials: true });// 创建对话框和段落元素var dd = document.createElement('dd');var ddParagraph = document.createElement('p');dd.appendChild(ddParagraph);dialogueContainer.appendChild(dd);// 初始化段落元素的文本内容var newStr = '';// 处理消息事件eventSource.onmessage = function (event) {var data = JSON.parse(event.data);
这里我们触发handleKeyPress事件后, eventSource = new EventSoutce会一直调用, 所以就一直创建新的连接,
需要怎么改?
function handleKeyPress(event) {if (event.keyCode === 13) {event.preventDefault();var input = document.getElementById('chat_input');var question = input.value.trim();if (question !== '') {var dialogueContainer = document.querySelector('.dialogue dl');let str = "";// 创建用户问题的对话框项var dt = document.createElement('dt');var dtParagraph = document.createElement('p');dtParagraph.textContent = '' + question + '';dt.appendChild(dtParagraph);dialogueContainer.appendChild(dt);const appinfo = getCookie('appinfo');var apiUrl = 'https://xxxx.xxxx.com/xxxx-x/xxxx/xxxx';var params = {query: question,unicodeToChinese: 1,};if (appinfo) {params.appinfo = appinfo;}var queryString = new URLSearchParams(params).toString();var fullUrl = apiUrl + '?' + queryString;// 检查是否已有 EventSource 实例if (!eventSource || eventSource.readyState === EventSource.CLOSED) {eventSource = new EventSource(fullUrl, { withCredentials: true });
我们在创建eventSource = new EventSource之前先进入判断: 当前没有eventSource的情况采取创建连接,否则不创建连接.这样修改后触发一次handleKeyPress事件就创建一个eventSource连接.
其中遇到的问题:
接口返回得文字是一个字或者两个字或者三个字, 我们在页面展示的时候, 总是会按字数的增加递增或者一个字一行:
你
你好
你好啊
你好啊是
你好啊是啥
x
x
x
xxx
的一个
xx
xx
xx
,
由
《
x
x
xx
》
xx
x
xx
,
xx
深度
xx
和
xxx
xx
上面两种情况不是我们需要的,
这个情况的原因是:
我们每次循环遍历文字我们都创建了一个p标签, 导致接口返回一个字我们就会重新开启一行, 这是p标签导致的.
最终代码:
<!-- 聊天显示 -->
<div class="dialogue"><dl><!--<dt><p>“财新数据通”是什么?</p></dt><dd><p>“财新数据通是集金融数据、权威资讯、品质服务于一体的金融数据资讯产品,帮助读者完成资讯获取、背景调查、数据分析和决策制定</p></dd>--></dl></div>
<!-- 聊天输入框 --><div class="customer_service"><p>联系客服</p><!-- <input class="chat_input" type="text" placeholder="请输入您想咨询的问题…"> --><input id="chat_input" class="chat_input" type="text" placeholder="请输入您想咨询的问题…"onkeydown="handleKeyPress(event)"></div>// 聊天函数
var eventSource; // 在函数外部定义 eventSource 变量,以便在整个作用域中访问, 控制回车, 只调用一次接口function handleKeyPress(event) {console.log(event, '聊天函数event');if (event.keyCode === 13 || event.type === 'click') {if (event.keyCode === 13) {event.preventDefault();event.stopPropagation(); // 阻止事件继续传播}var input = document.getElementById('chat_input');var question = event.type === 'click' ? event.question : input.value.trim(); // 获取问题文本if (question !== '') {var dialogueContainer = document.querySelector('.dialogue dl');let str = "";// 创建用户问题的对话框项var dt = document.createElement('dt');var dtParagraph = document.createElement('p');dtParagraph.textContent = '' + question + '';dtParagraph.style.textAlign = 'left'; // 样式设置为text-align:left;dt.appendChild(dtParagraph);dialogueContainer.appendChild(dt);const appinfo = getCookie('appinfo');var apiUrl = 'https://xxxx.xxxx.com/xxxx-xxxx/xxxx/xxxx';var params = {query: question,unicodeToChinese: 1,};if (appinfo) {params.appinfo = appinfo;}var queryString = new URLSearchParams(params).toString();var fullUrl = apiUrl + '?' + queryString;// 检查是否已有 EventSource 实例if (!eventSource || eventSource.readyState === EventSource.CLOSED) {eventSource = new EventSource(fullUrl, { withCredentials: true });// 创建对话框和段落元素//var dd = document.createElement('dd');//var ddParagraph = document.createElement('p');//dd.appendChild(ddParagraph);//dialogueContainer.appendChild(dd);var loadingAnswer = '正在加载...'; // 定义 loading 回答// 创建对话框和段落元素 创建 loading 回答的对话框项var loadingDt = document.createElement('dd');var loadingDtParagraph = document.createElement('p');loadingDtParagraph.textContent = loadingAnswer;loadingDt.appendChild(loadingDtParagraph);dialogueContainer.appendChild(loadingDt);// 初始化段落元素的文本内容var newStr = '';// 处理消息事件eventSource.onmessage = function (event) {var data = JSON.parse(event.data);if (data.event === "message") {// 使用每个新字符更新段落元素的文本内容 更新 loading 回答为接口返回的数据newStr += data.answer;loadingDtParagraph.textContent = newStr;// 滚动页面到底部window.scrollTo(0, document.body.scrollHeight);// 如果是第一个字符,显示对话框if (newStr.length === 1) {showDialogue();}// 如果接收到完整的回复,关闭 EventSource 对象if (data.complete) {eventSource.close();}} else if (data.event === "message_end") {eventSource.close();}};function showDialogue() {// 显示对话框和段落元素样式loadingDt.style.display = 'block'; // 或者使用其他适当的样式来显示对话框}}// 清空输入框input.value = '';}}
}