问:
现在我们需要一个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关闭连接.
问:
我们调用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 = '';
}
}
}