Vue3实现类ChatGPT聊天式流式输出(vue-sse实现)

1. 效果展示

流式输出
在这里插入图片描述
直接输出
在这里插入图片描述

2. 核心代码

找了一些示例与AI生成的代码,或多或少有些问题,搞了好久,郁闷~,在此记录下

2.1 依赖安装

npm install vue-sse

2.2 改写main.ts

import VueSSE from 'vue-sse'const app = Vue.createApp(App)// Use VueSSE, including a polyfill for older browsers
// @ts-ignore
app.use($).use(ElementPlus).use(store).use(router).use(VueSSE, {polyfill: true
})

2.3 Chat.vue完整代码

代码尚不完善,最新代码可参考Github, 见文末

<template><div class="chat"><el-form><el-row><div class="chat-container" style="margin-bottom: 40px">
<!--          <div v-for="message in messages" :key="message.id" class="message">-->
<!--            <el-avatar v-if="!message.isUser" shape="square" size="50" :src="botAvatar"></el-avatar>-->
<!--            <div :class="{'user-message': message.isUser, 'bot-message': !message.isUser}">-->
<!--              <div className="show-html" v-html=message.text></div>-->
<!--            </div>-->
<!--          </div>--><div class="messages" v-for="msg in messages" :key="msg.id"><div :class="msg.from === 'user' ? 'user-message' : 'ai-message'"><div v-if="msg.type === 'code'" class="code-block"><pre><code class="language-javascript">{{ msg.text }}</code></pre><button @click="copyToClipboard(msg.text)">复制</button></div><div v-else v-html="renderMessageContent(msg.text)"></div></div></div></div></el-row><el-row style="position: fixed; bottom: 45px; left: 5%; right: 5%; width: 90%;"><el-col :span="21"><el-input v-model="inputMessage" placeholder="请输入问题..." @keyup.enter="sendMessage" style="width: 100%;"></el-input></el-col><el-col :span="3"><el-button type="primary" @click="sendMessage" style="width: 100%;">发送</el-button></el-col></el-row></el-form></div>
</template><script>import {marked} from 'marked'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
import store from "@/store";
export default {name: "sseChat",data () {return {messages: [{id: 1, text: '我是您的私人智能助理,请问现在能帮您做什么?', isUser: false}],inputMessage: '',botAvatar: require('../../assets/images/robot.png'),handlers: [{event: 'message',color: '#60778e'},{event: 'time',color: '#778e60'}],client: null,// 无需跨域,否则无法接收消息, 这个原因浪费我好多时间url: 'http://127.0.0.1:8080/sse/subscribe?token=' + store.getters.token,}},mounted() {this.connect()},methods: {copyToClipboard(text) {navigator.clipboard.writeText(text).then(() => {alert('代码已复制到剪贴板!');});},connect () {// create the client with the user's configconst self = thislet client = this.$sse.create({url: this.url,includeCredentials: false})// add the user's handlersthis.handlers.forEach((h) => {client.on(h.event, (data) => { //if (data === '<SSE_START>') {self.messages.push( {text: '',from: 'ai',type: 'text',})console.log(data)} else if (data === '<SSE_END>') {console.log(data)} else {const isCode = data.startsWith('```');console.log(data)const msg = {text: data,from: 'ai',type: isCode ? 'code' : 'text',};self.messages[self.messages.length - 1].text += data;self.highlightCode();}})})client.on('error', () => { // eslint-disable-lineconsole.log('[error] disconnected, automatically re-attempting connection', 'system')})// and finally -- try to connect!client.connect() // eslint-disable-line.then(() => {console.log('[info] connected', 'system')}).catch(() => {console.log('[error] failed to connect', 'system')})},highlightCode() {this.$nextTick(() => {this.$el.querySelectorAll('pre code').forEach((block) => {hljs.highlightBlock(block);});});},// markdownrenderMessageContent(msg) {if (msg === '') {return '';}marked.setOptions({renderer: new marked.Renderer(),highlight: function(code, lang) {// If lang is provided, use it; otherwise, let hljs guessreturn hljs.highlight(code, { language: lang || '' }).value;},langPrefix: 'hljs language-',pedantic: false,gfm: true,  // GitHub Flavored Markdown for better code block support among other thingsbreaks: false,sanitize: true,  // For security, sanitize the HTML output unless you trust the sourcesmartypants: false,xhtml: false});let html = marked(msg)return html},sendMessage() {const self = thisif (self.inputMessage) {self.messages.push({id: self.messages.length + 1, text: self.inputMessage, isUser: true});// 一次性输出// self.$http.post('/chat/chat', {'content': self.inputMessage}, 'apiUrl').then(res => {//   self.messages.push({id: self.messages.length + 1, text: self.renderMessageContent(res), isUser: false});//   self.inputMessage = '';// })// 流式输出self.$http.post('/chat/sseChat', {'content': self.inputMessage}, 'apiUrl').then(res => {self.inputMessage = '';})}},}
}
</script><style scoped>
.chat{height: calc(100vh - 120px); /* Adjust based on your header/footer size */overflow-y: auto;
}.message {display: flex;align-items: flex-start;margin: 10px;
}.user-message {justify-content: flex-end;text-align: right;
}.bot-message {text-align: left;
}
chat-container {display: flex;flex-direction: column;max-width: 600px;margin: auto;
}.messages {flex: 1;overflow-y: auto;padding: 10px;
}.user-message {text-align: right;background-color: #d1e7dd;padding: 8px;border-radius: 5px;margin: 5px 0;
}.ai-message {text-align: left;background-color: #f6f8f8;padding: 8px;border-radius: 5px;margin: 5px 0;
}input {padding: 10px;border: 1px solid #ccc;border-radius: 5px;
}button {margin-left: 10px;padding: 5px 10px;cursor: pointer;
}
</style>

3. 后端改造

// 1.配置允许跨域与流式响应
@GetMapping(value = "/subscribe", produces = "text/event-stream")
@CrossOrigin
@Operation(summary = "SSE订阅", tags = "AI大模型")
public SseEmitter subscribe(String token, HttpServletResponse response) {SseEmitter sseEmitter = SseServer.subscribe(token);response.setHeader("Cache-Control", "no-cache");response.setHeader("Connection", "keep-alive");return sseEmitter;
}// 2.SecurityConfiguration.java中权限控制放开
.requestMatchers("/sse/**").permitAll()// 3.在订阅式发送了开始<SSE_START>标识,消息结束发送了<SSE_END>标识,其他内容直接返回大模型字符串

4. 开源地址

https://github.com/SJshenjian/cloud-web
https://github.com/SJshenjian/cloud

TODO

  1. 流式输出Markdown支持
  2. 代码高亮可复制

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

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

相关文章

饲料颗粒机全套设备有哪些机器组成

饲料颗粒机全套设备通常包括原料粉碎、混合机、制粒机、冷却器、筛分机、包装机以及配套的电气控制等多个部分组成&#xff1a;1、粉碎机&#xff1a;将各种饲料原料进行清理、去杂、破碎等预处理&#xff0c;确保原料的纯净度和适宜粒度&#xff0c;为后续加工做准备。2、混合…

撤销与恢复的奥秘:设计模式之备忘录模式详解

备忘录模式 &#x1f3af; 备忘录模式&#xff08;Memento Pattern&#xff09;简介 备忘录模式 是一种行为型设计模式&#xff0c;用于保存对象的某一时刻状态&#xff0c;以便稍后可以恢复到该状态&#xff0c;而不破坏对象的封装性。备忘录模式将对象的状态封装在一个独立的…

240922-Conda的在线下载与离线安装

A. 修改路径&#xff08;如果需要&#xff09; 在 conda 中无法直接通过命令指定下载路径。默认情况下&#xff0c;conda 将软件包下载到其缓存目录中&#xff0c;具体位置通常是 ~/miniconda/pkgs 或 ~/anaconda/pkgs&#xff0c;取决于你安装 conda 的路径。 如果你希望将下…

【机器学习】ROC曲线

【机器学习】ROC曲线 1、ROC曲线简介2、ROC曲线和AUC值2.1 ROC曲线2.2 AUC值 3、实验内容3.1 准备数据集3.2 特征提取3.3 数据集划分3.4 模型训练与预测3.5 计算和绘制ROC曲线3.6 绘制混淆矩阵3.7 三分类混淆矩阵 4 源代码4.1 实现ROC二分类4.2 三分类混淆例子 1、ROC曲线简介 …

Qt 注册表操作

一.操作环境 二.注册表查看 1. 搜索注册表打开 2. 注册表查看 例如我想操作 计算机\HKEY_CURRENT_USER\SOFTWARE\winzq\qwert下的内容 三.代码 1. H文件 #ifndef __REGISTER_H__ #define __REGISTER_H__#include <QString> #include <QSettings> #include <Q…

Kotlin 类和属性(五)

导读大纲 1.1 封装行为和数据: 类和属性1.1.1 将数据与类关联并使其可被访问: 属性1.1.2 计算属性,而不是存储其值: 自定义访问器1.1.3 Kotlin 源代码目录和包 1.1 封装行为和数据: 类和属性 与其他面向对象编程语言一样,Kotlin 也提供类的抽象 Kotlin 在这方面的概念您一定不…

UE学习篇ContentExample解读-----------Blueprint_Overview

文章目录 总览描述批次阅览1.1 Blueprint- Hello World1.2 Blueprint- Components1.3 Blueprint- Variables1.4 Blueprint- ConstructionScript1.5 Blueprint- Event Graph1.6 Blueprint- Simple Math1.7 Blueprint- Flow Control 概念总结致谢&#xff1a; 总览描述 打开关卡后…

Golang | Leetcode Golang题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; func dfs(node *Node) (last *Node) {cur : nodefor cur ! nil {next : cur.Next// 如果有子节点&#xff0c;那么首先处理子节点if cur.Child ! nil {childLast : dfs(cur.Child)next cur.Next// 将 node 与 child 相连cur.Next cur.Chi…

超越sora,最新文生视频CogVideoX-5b模型分享

CogVideoX-5B是由智谱 AI 开源的一款先进的文本到视频生成模型&#xff0c;它是 CogVideoX 系列中的更大尺寸版本&#xff0c;旨在提供更高质量的视频生成效果。 CogVideoX-5B 采用了 3D 因果变分自编码器&#xff08;3D causal VAE&#xff09;技术&#xff0c;通过在空间和时…

【变化检测】基于Superpoint+Lightglue+TinyCD建筑物(LEVIR-CD)变化检测实战及ONNX推理

后面再详细完善内容吧&#xff0c;先丢代码&#xff01; 1 创建文件与输入文件夹 注意&#xff1a;img中包括A期与B期文件夹&#xff0c;图片名要求一致对应。 1.1 运行代码 新建main.py文件&#xff0c;内容如下&#xff1a; import os import cv2 import time import a…

Kotlin while 和 for 循环(九)

导读大纲 1.1 while 和 for 循环1.1.1 while 循环1.1.2 范围和级数&#xff1a;for循环 1.1 while 和 for 循环 Kotlin 中的迭代与 Java、C# 或其他语言中的迭代非常相似 while 循环与其他语言中的传统形式相同, 只需简单了解一下即可还会发现 for 循环,其写法为 for ( in ) 是…

从0开始的linux(4)——权限

欢迎来到博主的专栏&#xff1a;从0开始的linux 博主ID&#xff1a;代码小豪 文章目录 用户和用户组文件权限更改文件权限目录文件的权限意义普通文件的权限意义 sudo命令 linux具有多用户的任务环境&#xff0c;为了让每个用户保护各自文件数据&#xff08;防止别的用户对其他…

【功能详解】IoTDB 与 ThingsBoard 成功集成!

可视化工具集成1 IoTDB 实现了 ThingsBoard 的无缝集成对接&#xff0c;IoTDB 构建的工业数据存储处理-可视化呈现链路又多了一种可用、易用的工具选择。 我们的代码已贡献到 ThingsBoard 社区&#xff08;待发版&#xff09;&#xff0c;用户手册也已发布&#xff08;可点击下…

Spring Boot框架:蜗牛兼职网实现

第3章 系统分析 3.1 需求分析 蜗牛兼职网主要是为了提高工作人员的工作效率和更方便快捷的满足用户和企业&#xff0c;更好存储所有数据信息及快速方便的检索功能&#xff0c;对系统的各个模块是通过许多今天的发达系统做出合理的分析来确定考虑用户和企业的可操作性&#xff0…

SpringCloud入门(六)Nacos注册中心(下)

一、Nacos环境隔离 Nacos提供了namespace来实现环境隔离功能。 nacos中可以有多个namespace。namespace下可以有group、service等。不同namespace之间相互隔离&#xff0c;例如不同namespace的服务互相不可见。 使用Nacos Namespace 环境隔离 步骤&#xff1a; 1.在Nacos控制…

【AI画图】stable-diffusion-webui学习之一《安装部署》

简介 Stable Diffusion是2022年发布的深度学习文本到图像生成模型&#xff0c;它是一种潜在扩散模型&#xff0c;它由创业公司Stability AI与多个学术研究者和非营利组织合作开发。目前的SD的源代码和模型都已经开源&#xff0c;在Github上由AUTOMATIC1111维护了一个完整的项目…

Python | Leetcode Python题解之第430题扁平化多级双向链表

题目&#xff1a; 题解&#xff1a; class Solution:def flatten(self, head: "Node") -> "Node":def dfs(node: "Node") -> "Node":cur node# 记录链表的最后一个节点last Nonewhile cur:nxt cur.next# 如果有子节点&#…

OpenCV特征检测(9)检测图像中直线的函数HoughLines()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在二值图像中使用标准 Hough 变换查找直线。 该函数实现了用于直线检测的标准 Hough 变换或标准多尺度 Hough 变换算法。详见 http://homepages…

WebLogic系列漏洞

后台弱⼝令GetShell 漏洞描述 通过弱⼝令进⼊后台界⾯ , 上传部署war包 , getshell 影响范围 全版本&#xff08;前提后台存在弱⼝令&#xff09; 环境搭建 cd vulhub/weblogic/weak_password docker-compose up -d 漏洞复现 默认账号密码&#xff1a;weblogic/Oracle123 (单…

哔哩哔哩自动批量删除抽奖动态解析篇(二)

通过前文我们已经获得账户下转发的动态列表&#xff0c;这一节我们要做的就是根据前一节获得的动态列表数据判断抽奖动态是否已开奖。 一、获取抽奖动态开奖状态信息 首先我们按F12健进入网页源代码&#xff0c;然后点开一条抽奖动态的按钮链接&#xff0c;找到API接口。流程…