原生js: AI聊天功能, 仿照chatGPT问答功能

news2024/12/23 23:42:19

问:

现在我们需要一个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 = '';
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1987331.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

stm32入门学习10-软件I2C和陀螺仪模块

&#xff08;一&#xff09;I2C通信 &#xff08;1&#xff09;通信方式 I2C是一种同步半双工的通信方式&#xff0c;同步指的是通信双方时钟为一个时钟&#xff0c;半双工指的是在同一时间只能进行接收数据或发送数据&#xff0c;其有一条时钟线&#xff08;SCL&#xff09;…

MyBatis补充

控制类和dao层接口以及mapper中的xml是怎样的关联的&#xff1f; 在Mybatis中&#xff0c;控制类和dao层接口是通过mapper的xml文件进行连接的。 控制类调用dao层接口中的方法&#xff0c;通过接口实现进行访问数据库操作。dao层接口定义数据库操作的方法&#xff0c;提供给控制…

第6章>>实验7:PS(ARM)端Linux RT与PL端FPGA之间(通过Memory存储器进行通信和交互)《LabVIEW ZYNQ FPGA宝典》

1、实验内容 上一节实验里面介绍的Reg寄存器通道比较适合在PS端和PL端之间传递标量数据&#xff0c;也就是单个元素&#xff0c;如果要传递多个元素的数组或者连续数据流的话&#xff0c;Reg寄存器通道就不是很合适了。 本节实验我们向大家讲解如何借助Memory存储器通道在PS&am…

【Mind+】掌控板入门教程06 多彩呼吸灯

呼吸灯是指模仿动物一呼一吸使灯光由亮到暗逐渐变化&#xff0c;给人以安静沉稳的感觉。电子产品中经常会使用不同色彩的呼吸灯&#xff0c;起到很好的视觉提醒效果。 这个项目中我们将带领大家用掌控板制作一个颜色和亮度一起变化的多彩呼吸灯&#xff01; 项目示例 …

编程深水区之并发④:Web多线程

Node的灵感来源于Chrome&#xff0c;更是移植了V8引擎。在Node中能够实现的多线程&#xff0c;在Web环境中自然也可以。 一、浏览器是多进程和多线程的复杂应用 在本系列的第二章节&#xff0c;有提到现代浏览器是一个多进程和多线程的复杂应用。浏览器主进程统管全局&#xf…

vue动态规则

vue动态规则 在Vue中&#xff0c;可以使用动态规则来实现灵活的表单验证和输入限制。动态规则允许你根据特定条件或动态数据来定义验证规则。 以下是一个示例&#xff0c;展示如何在Vue中使用动态规则&#xff1a; <template><div><input v-model"inputVa…

LVS--DR模式

目录 1 DR模式原理 2 DR模式请求回复过程 3 实验环境 4 开始实验 4.1 配置实验环境 4.2 Router 配置路由转发 4.3 LVS 设置转发规则 4.4 解决vip响应问题 4.5 Web1配置 4.6 Web2配置 5 测试效果 1 DR模式原理 当用户向负载均衡调度器&#xff08;Director Server&#xff09;发…

如何用数字便签管理工作任务?

在快节奏的工作环境中&#xff0c;我们每天都需要处理大量的工作任务。如果仅仅依靠个人的记忆力和精力&#xff0c;很容易导致任务遗漏或者延误。随着数字化技术的发展&#xff0c;选择一款功能强大的数字便签软件已经成为我们管理工作任务的更好选择。 在众多的数字便签软件…

Java零基础之多线程篇:讲解并发集合

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

c语言11天笔记

函数的概述 函数&#xff1a;实现一定功能的&#xff0c;独立的代码模块。我们的函数一定是先定义&#xff0c;后使用。 使用函数的优势&#xff1a; 1. 我们可以通过函数提供功能给别人使用。当然我们也可以使用别人提供的函数&#xff0c;减少代码量。 2. 借助函数可以减…

4.6.长短期记忆网络(LSTM)

长短期记忆网络(LSTM) ​ 长短期记忆网络的设计灵感来自于计算机的逻辑门。 长短期记忆网络引入了记忆元&#xff08;memory cell&#xff09;&#xff0c;或简称为单元&#xff08;cell&#xff09;。 有些文献认为记忆元是隐状态的一种特殊类型&#xff0c; 它们与隐状态具有…

萱仔求职系列——1.1 机器学习基础知识复习

由于我最近拿到offer还是想再找找更好的机会&#xff0c;目前有很多的面试&#xff0c;面试的时候很多面试官会问一些机器学习的基础知识&#xff0c;由于我上一段实习的时候主要是机器学习和部分深度学习的内容&#xff0c;为了避免在面试的时候想不起来自己学习的内容&#x…

MPU6050的STM32数据读取

目录 1. 概述2. STM32G030对MPU6050的读取3. STM32F1xx对MPU6050的读取 1. 概述 项目中&#xff0c;往往需要根据不同的环境使用不同的芯片处理某些数据&#xff0c;当使用不同的芯片对六轴陀螺仪芯片MPU6050进行数据处理中&#xff0c;硬件的连接、I/O口的设置往往需要根据相…

【HarmonyOS NEXT星河版开发学习】小型测试案例05-得物列表项

个人主页→VON 收录专栏→鸿蒙开发小型案例总结​​​​​ 基础语法部分会发布于github 和 gitee上面&#xff08;暂未发布&#xff09; 前言 鸿蒙操作系统通过其先进的分布式架构和开发工具&#xff0c;以及灵活的界面布局和样式控制&#xff0c;为开发者提供了丰富的开发资源…

设计模式- 数据源架构模式

活动记录&#xff08;Active Record&#xff09; 一个对象&#xff0c;它包装数据库表或视图中的某一行&#xff0c;封装数据库访问&#xff0c;并在这些数据上增加了领域逻辑 对象中既有数据又有行为。这些数据大多是持久数据、并且需要保存到数据库。 运行机制 活动记录的…

Iris for mac 好用的录屏软件

Iris 是一款高性能屏幕录像机&#xff0c;可录制到 h.264。Iris 在可用时利用板载 GPU 加速。它可以选择包括来自摄像头和最多两个麦克风的视频。 兼容性 所有功能在macOS 11.0-14上完全支持&#xff0c;包括macOS Sonoma。 简单编码 直接录制为h.264、h.265、ProRes或Motion…

WPF学习(10)-Label标签+TextBlock文字块+TextBox文本框+RichTextBox富文本框

Label标签 Label控件继承于ContentControl控件&#xff0c;它是一个文本标签&#xff0c;如果您想修改它的标签内容&#xff0c;请设置Content属性。我们曾提过ContentControl的Content属性是object类型&#xff0c;意味着Label的Content也是可以设置为任意的引用类型的。 案…

游戏ID统一管理器DEMO

一般游戏的角色ID、名字&#xff0c;工会ID、名字&#xff0c;等最好统一创建&#xff0c;方便合服处理&#xff0c;可以以此基础&#xff0c;动态配置生成ID 这个也可以用openresty 作个&#xff0c;可能更专业点&#xff0c; 1&#xff1a;go1.20 最后一版支持win7的 mongod…

微信小程序乡村医疗系统,源码、部署+讲解

目录 摘 要 Abstract 1 绪论 1.1 研究背景及意义 1.2 研究现状 1.3 研究内容 2 相关技术介绍 2.1 Java 语言 2.2 MySQL 数据库 2.3 Spring Boot 框架 2.4 B/S 结构 2.5 微信小程序 3 系统分析 3.1 可行性分析 3.1.1 经济可行性 3.1.2 技术可行性…

4.MySQL数据类型

目录 数据类型 ​编辑数值类型 tinyint类型 bit类型 float类型 decimal类型 字符串类型 char类型 varchar varchar和char的区别 日期和时间类型 数据类型 数值类型 说明一下&#xff1a;MySQL本身是不支持bool类型的&#xff0c;当把一个数据设置成bool类型时&#x…