详细解析用户提交咨询
上一篇文章中写到了使用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 = textthat.readWriter(text);}},onclose() {finished = truefinish();},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", // 设置请求方法为POSTbody: 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来保存当前的thiscontroller.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
现在只包含尚未处理的文本,这部分文本将在下一次动画帧中被处理。
整体来看,这段代码的作用是将从服务器接收到的文本分批次动态地显示到界面上,而不是一次性显示全部文本,从而提高用户体验,使文本的显示看起来更加平滑和自然。
相关文章:
详细解析用户提交咨询
上一篇文章中写到了使用Server-Sent Events (SSE),并获取message里面的内容。 本篇文章主要是写,具体该如何实现的具体代码,代码见下方,可直接拿 async submitConsult() {this.scrollToBottom();if (!this.$checkLogin()) return;…...

UDP/TCP协议解析
我最近开了几个专栏,诚信互三! > |||《算法专栏》::刷题教程来自网站《代码随想录》。||| > |||《C专栏》::记录我学习C的经历,看完你一定会有收获。||| > |||《Linux专栏》࿱…...

力扣94题(java语言)
题目 思路 使用一个栈来模拟递归的过程,以非递归的方式完成中序遍历(使用栈可以避免递归调用的空间消耗)。 遍历顺序步骤: 遍历左子树访问根节点遍历右子树 package algorithm_leetcode;import java.util.ArrayList; import java.util.List; import…...
JavaScript基础入门:构建动态Web世界的基石
简要介绍JavaScript作为互联网上最流行的编程语言之一,它在构建交互式网页、动态Web应用及服务器后端(通过Node.js)中的重要性。强调学习JS对于任何想要进入Web开发领域的人来说是不可或缺的。 1. JavaScript是什么? 定义JavaSc…...
01-client-go
想学习K8S源码,可以加 :mkjnnm 1、介绍 client-go 是用来和 k8s 集群交互的go语言客户端库,地址为:https://github.com/kubernetes/client-go client-go 的版本有两种标识方式: v0.x.y (For each v1.x.y Kubernetes…...

WebRTC QoS方法十三.2(Jitter延时的计算)
一、背景介绍 一些报文在网络传输中,会存在丢包重传和延时的情况。渲染时需要进行适当缓存,等待丢失被重传的报文或者正在路上传输的报文。 jitter延时计算是确认需要缓存的时间 另外,在检测到帧有重传情况时,也可适当在渲染时…...
PHP进阶:前后端交互、cookie验证、sql与php
单词:construct 构造 destruct 摧毁 empty 空的 trim 修剪 strip 清除 slash 斜线 special 特殊 char 字符 query 询问 构造方法(魔术方法) 构造方法是一种特殊的函数࿰…...

优思学院|ANOVA方差分析是什么?如何用EXCEL进行计算?
在数据分析、六西格玛管理领域中,ANOVA(方差分析)是一种基本的统计工具,广泛用于确定三组或三组以上的独立群体之间的平均值是否存在统计学上的显着差异。ANOVA的主要目的在于评估一个或多个因素的影响,通过比较不同样…...

Mindspore框架循环神经网络RNN模型实现情感分类|(三)RNN模型构建
Mindspore框架循环神经网络RNN模型实现情感分类 Mindspore框架循环神经网络RNN模型实现情感分类|(一)IMDB影评数据集准备 Mindspore框架循环神经网络RNN模型实现情感分类|(二)预训练词向量 Mindspore框架循环神经网络RNN模型实现…...

深度解读大语言模型中的Transformer架构
一、Transformer的诞生背景 传统的循环神经网络(RNN)和长短期记忆网络(LSTM)在处理自然语言时存在诸多局限性。RNN 由于其递归的结构,在处理长序列时容易出现梯度消失和梯度爆炸的问题。这导致模型难以捕捉长距离的依…...

安装好anaconda,打开jupyter notebook,新建 报500错
解决办法: 打开anaconda prompt 输入 jupyter --version 重新进入jupyter notebook: 可以成功进入进行代码编辑...
C++20之设计模式:状态模式
状态模式 状态模式状态驱动的状态机手工状态机Boost.MSM 中的状态机总结 状态模式 我必须承认:我的行为是由我的状态支配的。如果我没有足够的睡眠,我会有点累。如果我喝了酒,我就不会开车了。所有这些都是状态(states),它们支配着我的行为:…...

数据库安全综合治理方案(可编辑54页PPT)
引言:数据库安全综合治理方案是一个系统性的工作,需要从多个方面入手,综合运用各种技术和管理手段,确保数据库系统的安全稳定运行。 方案介绍: 数据库安全综合治理方案是一个综合性的策略,旨在确保数据库系…...

人工智能:大语言模型提示注入攻击安全风险分析报告下载
大语言模型提示注入攻击安全风险分析报告下载 今天分享的是人工智能AI研究报告:《大语言模型提示注入攻击安全风险分析报告》。(报告出品方:大数据协同安全技术国家工程研究中心安全大脑国家新一代人工智能开放创新平台) 研究报告…...
【购买源码时有许多需要注意的坑】
购买源码时有许多需要注意的“坑”,这些坑可能会对项目的后续开发和使用造成严重影响。以下是一些需要特别注意的方面: 源码的完整性 编译测试:确保到手的源码能够从头至尾编译、打包、部署和功能测试无误。这一步非常关键,因为只…...
CAS的三大问题和解决方案
一、ABA问题的解决方案 变量第一次读取的值是1,后来其他线程改成了3,然后又被其他线程修改成了1,原来期望的值是第一个1才会设置新值,第二个1跟期望不符合,但是,可以设置新值。 解决方案: &a…...
EDA和统计分析有什么区别
EDA(Electronic Design Automation)和统计分析在多个方面存在显著的区别,这些区别主要体现在它们的应用领域、目的、方法以及所使用的工具上。 EDA(电子设计自动化) 定义与目的: EDA是电子设计自动化&…...
CentOS 7 修改DNS
1、nmcli connection show 命令找到设备名称 # nmcli connection show NAME UUID TYPE DEVICE enp4s0 99559edf-4e0a-4bae-a528-6d75065261e9 ethernet enp4s0 2、nmcli connection modify 命令修改dns nmcli connection modif…...
PHP基础语法-Part2
if-else语句、switch语句 与其他语言相同 循环结构 for循环while循环do-while循环foreach循环,搭配数组使用 foreach ($age as $avlue) //只输出值 {xxx; } foreach ($age as $key > $avlue) //键和值都输出 {xxx; }foreach ($age as $key >…...

数据结构门槛-顺序表
顺序表 1. 线性表2. 顺序表2.1 静态顺序表2.2 动态顺序表2.2.1 动态数据表初始化和销毁2.2.2 动态数据表的尾插尾删2.2.3 动态数据表的头插头删2.2.4 动态数据表的中间部分插入删除2.2.5 动态数据表的查询数据位置 3. 总结 1. 线性表 线性表(linear list࿰…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...