详细解析用户提交咨询
上一篇文章中写到了使用Server-Sent Events (SSE),并获取message里面的内容。本篇文章主要是写,详细该如何实现的详细代码,代码见下方,可直接拿
async submitConsult() {
this.scrollToBottom();
if (!this.$checkLogin()) return;
let _consultInfo = JSON.parse(JSON.stringify(this.consultInfo));
if (this.btnLoading || !_consultInfo.user_input) return;
this.btnLoading = true;
let myInfo = {
action_id: "my",
question: _consultInfo.user_input.replace(/\\n/g, '\n'),
};
this.currentQuestion = _consultInfo.user_input;
this.historyList.push(myInfo);
this.addPopoverEventListeners();
this.consultInfo.user_input = "";
_consultInfo.select_param = this.label;
const url = BASEURL + "xxxx";
try {
const controller = new AbortController();
const payload = {
method: "POST",
body: JSON.stringify(_consultInfo),
signal: controller.signal,
headers: {
"Content-Type": "application/json",
},
};
const requestTimeoutId = setTimeout(() => controller.abort(), 50000);
let responseText = "";
let remainText = "";
let finished = false;
const animateResponseText = async () => {
if (finished || controller.signal.aborted) {
responseText += remainText;
return;
}
if (remainText.length > 0) {
const fetchCount = Math.max(1, Math.round(remainText.length / 5000));
const fetchText = remainText.slice(0, fetchCount);
responseText += fetchText;
remainText = remainText.slice(fetchCount);
if (responseText.trim()) {
this.readWriter(responseText);
}
}
requestAnimationFrame(animateResponseText);
};
const finish = () => {
if (finished) {
finished = true;
if (remainText.trim()) {
this.readWriter(remainText, true);
}
}
};
let that = this;
controller.signal.onabort = finish;
let lashIndex = 0;
await fetchEventSource(url, {
...payload,
async onopen(res) {
clearTimeout(requestTimeoutId);
const contentType = res.headers.get("content-type");
if (contentType && contentType.startsWith("text/plain")) {
responseText = await res.clone().text();
return finish();
}
if (!res.ok || res.status !== 200 || !res.headers.get("content-type").startsWith(EventStreamContentType)) {
try {
const errorText = await res.clone().text();
} catch (e) {
console.log("异常信息:", e);
}
return finish();
}
},
onmessage(msg) {
that.isReadText = true; // 开始发送请求相当于正在进行打印
const text = msg.data;
if (text) {
remainText = text
that.readWriter(text);
}
},
onclose() {
finished = true
finish();
},
onerror(e) {
console.log(e);
},
openWhenHidden: true,
});
} catch (e) {
console.log(e, "-------e");
} finally {
this.hasNewAction = false;
}
},
[*]async submitConsult() { … }: 定义了一个异步方法 submitConsult 用于提交用户的咨询。
[*]this.scrollToBottom();: 调用方法滚动到消息容器的底部。
[*]if (!this.$checkLogin()) return;: 如果用户未登录,则返回。
[*]let _consultInfo = JSON.parse(JSON.stringify(this.consultInfo));: 创建 consultInfo 的深拷贝,以避免直接修改原始数据。
[*]if (this.btnLoading || !_consultInfo.user_input) return;: 如果按钮处于加载状态或用户输入为空,则返回。
[*]this.btnLoading = true;: 设置按钮加载状态为真。
[*]let myInfo = { … };: 创建一个包含用户题目的对象。
[*]this.currentQuestion = _consultInfo.user_input;: 设置当前题目为用户输入。
[*]this.historyList.push(myInfo);: 将用户题目添加到历史列表。
[*]this.addPopoverEventListeners();: 添加弹出框变乱监听器。
[*]this.consultInfo.user_input = “”;: 清空用户输入。
[*]_consultInfo.select_param = this.label;: 设置选择参数为当前标签。
async submitConsult() {
this.scrollToBottom(); // 确保在发送新消息时滚动到消息列表的底部
if (!this.$checkLogin()) return; // 检查用户是否登录,如果未登录则返回,不执行后续操作
let _consultInfo = JSON.parse(JSON.stringify(this.consultInfo)); // 创建consultInfo对象的深拷贝,以避免直接修改原始数据
if (this.btnLoading || !_consultInfo.user_input) return; // 如果按钮处于加载状态或用户输入为空,则不执行后续操作
this.btnLoading = true; // 设置按钮的加载状态为true,表示正在处理中
let myInfo = {
action_id: "my",
question: _consultInfo.user_input.replace(/\\n/g, '\n'), // 将用户输入的字符串中的转义换行符替换为实际的换行符
};
this.currentQuestion = _consultInfo.user_input; // 将用户输入的问题保存到currentQuestion属性中
this.historyList.push(myInfo); // 将用户的问题添加到历史消息列表中
this.addPopoverEventListeners(); // 添加弹出框事件监听器
this.consultInfo.user_input = ""; // 清空用户输入框
_consultInfo.select_param = this.label; // 将当前选中的标签赋值给consultInfo对象的select_param属性
const url = BASEURL + "chat/v2"; // 定义请求的URL,BASEURL是一个常量,表示API的基础路径
// ...省略了部分代码...
}
try {
const controller = new AbortController(); // 创建一个控制器,用于取消fetch请求
const payload = {
method: "POST", // 设置请求方法为POST
body: JSON.stringify(_consultInfo), // 将用户咨询信息转换为JSON字符串作为请求体
signal: controller.signal, // 将控制器的signal属性传递给fetch,用于取消请求
headers: {
"Content-Type": "application/json", // 设置请求头,表明请求体是JSON格式
},
};
const requestTimeoutId = setTimeout(() => controller.abort(), 50000); // 设置一个50秒的超时定时器,如果请求超时则取消请求
let responseText = ""; // 初始化一个变量来存储响应文本
let remainText = ""; // 初始化一个变量来存储剩余的文本
let finished = false; // 初始化一个标志位,表示是否完成
const animateResponseText = async () => {
// ...省略了部分代码...
};
const finish = () => {
// ...省略了部分代码...
};
let that = this; // 由于在事件回调中this的指向可能会变化,这里用that来保存当前的this
controller.signal.onabort = finish; // 当请求被取消时,执行finish函数
let lashIndex = 0; // 初始化一个变量,用于记录上一次的索引位置
await fetchEventSource(url, {
// ...省略了部分代码...
});
} catch (e) {
console.log(e, "-------e"); // 如果请求过程中发生异常,打印异常信息
} finally {
this.hasNewAction = false; // 无论请求成功还是失败,将hasNewAction设置为false
}
}
fetchEventSource是一个用于处理服务器发送变乱(Server-Sent Events, SSE)的函数,它可以大概处理来自服务器的流式响应。这个方法中的逻辑主要是发送用户的题目到服务器,并处理返回的答案,将其表现在界面上。
animateResponseText 函数
animateResponseText函数的作用是动态地将服务器响应的文本内容逐渐表现到用户界面上,加强用户体验。这个函数可能会定期查抄是否有新的文本内容到来,如果有,就将新内容添加到界面上。这里是一个假设的实现:
const animateResponseText = async () => {
if (finished || controller.signal.aborted) {
responseText += remainText;
return;
}
if (remainText.length > 0) {
const fetchCount = Math.max(1, Math.round(remainText.length / 50));
const fetchText = remainText.slice(0, fetchCount);
responseText += fetchText;
remainText = remainText.slice(fetchCount);
if (responseText.trim()) {
this.readWriter(responseText);
}
}
requestAnimationFrame(animateResponseText);
};
[*]查抄finished或controller.signal.aborted,如果请求完成或被中止,则将剩余文本remainText追加到responseText并竣事函数实行。
[*]如果remainText包含未处理的文本,盘算需要获取的文本长度(fetchCount),并从remainText中获取这段文本追加到responseText。
[*]this.readWriter(responseText);调用readWriter方法,可能用于将文本表现到界面上。
[*]使用requestAnimationFrame(animateResponseText);递归调用自身,以动态更新内容。
fetchEventSource 函数
fetchEventSource是处理服务器发送变乱(Server-Sent Events, SSE)的API,用于接收服务器端的及时数据流。这个函数通常用于订阅服务器端的消息,然后将这些消息动态地展示给用户。一个简化的实现示例如下:
await fetchEventSource(url, {
...payload,
async onopen(res) {
clearTimeout(requestTimeoutId);
if (!res.ok || !res.headers.get("content-type").startsWith(EventStreamContentType)) {
finish();
}
},
onmessage(msg) {
that.isReadText = true;
const text = msg.data;
if (text) {
remainText = text;
that.readWriter(text);
}
},
onclose() {
finished = true;
finish();
},
onerror(e) {
console.log(e);
},
openWhenHidden: true,
});
[*]onopen(res): 当连接乐成打开时调用。取消之前设置的超时定时器。查抄响应状态,如果不是200或内容类型不匹配,则调用finish函数处理竣事逻辑。
[*]onmessage(msg): 当从服务器接收到消息时调用。将接收到的数据赋值给remainText,然后调用readWriter方法处理文本表现。
[*]onclose(): 当连接关闭时调用。设置finished = true,并调用finish函数处理竣事逻辑。
[*]onerror(e): 当发生错误时调用,打印错误信息。
这样的处理流程可以大概实现与服务器端的及时通信,适用于聊天应用、及时通知表现等场景。
使文本的表现看起来更加平滑和自然
const fetchCount = Math.max(1, Math.round(remainText.length / 50));
const fetchText = remainText.slice(0, fetchCount);
responseText += fetchText;
remainText = remainText.slice(fetchCount);
这
这段代码是处理从服务器接收到的文本数据,并将其逐步表现到用户界面上。下面是对这几行代码的详细表明:
const fetchCount = Math.max(1, Math.round(remainText.length / 50));
[*]remainText.length / 50: 这里将剩余文本remainText的长度除以50,目的是为了分批处理文本,每批大约处理文本长度的1/50。
[*]Math.round(...): 四舍五入盘算出的结果,确保fetchCount是一个整数。
[*]Math.max(1, ...): 确保即使盘算结果小于1,fetchCount的值至少也是1。这意味着,无论如何,每次至少会处理一个字符。
const fetchText = remainText.slice(0, fetchCount);
[*]remainText.slice(0, fetchCount): 从remainText中提取从索引0开始到fetchCount索引竣事的子字符串。这部分文本是将要追加到responseText的内容,而且是下一次动画帧要表现的文本。
responseText += fetchText;
[*]responseText += fetchText: 将提取出来的文本fetchText追加到responseText变量中。responseText变量用于累积已经处理并预备表现给用户的文本。
remainText = remainText.slice(fetchCount);
[*]remainText.slice(fetchCount): 更新remainText变量,移除已经追加到responseText的部分。remainText现在只包含尚未处理的文本,这部分文本将在下一次动画帧中被处理。
团体来看,这段代码的作用是将从服务器接收到的文本分批次动态地表现到界面上,而不是一次性表现全部文本,从而进步用户体验,使文本的表现看起来更加平滑和自然。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]