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

news2024/12/27 13:32:10

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 config
      const self = this
      let client = this.$sse.create({
        url: this.url,
        includeCredentials: false
      })
      // add the user's handlers
      this.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-line
        console.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);
        });
      });
    },
    // markdown
    renderMessageContent(msg) {
      if (msg === '') {
        return '';
      }
      marked.setOptions({
        renderer: new marked.Renderer(),
        highlight: function(code, lang) {
          // If lang is provided, use it; otherwise, let hljs guess
          return hljs.highlight(code, { language: lang || '' }).value;
        },
        langPrefix: 'hljs language-',
        pedantic: false,
        gfm: true,  // GitHub Flavored Markdown for better code block support among other things
        breaks: false,
        sanitize: true,  // For security, sanitize the HTML output unless you trust the source
        smartypants: false,
        xhtml: false
      });
      let html = marked(msg)
      return html
    },
    sendMessage() {
      const self = this
      if (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.coloradmin.cn/o/2157315.html

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

相关文章

ubuntu+MobaXterm+ssh+运行Qt(成功版)

点击上方"蓝字"关注我们 01、ubuntu连接SSH >>> 通过串口工具连接ubuntu 登录 解决连接不上的问题 检查 SSH 服务:确保目标机器上 SSH 服务已启动。你可以在目标机器上运行以下命令: sudo systemctl status ssh 如果没有运行,可以使用以下命令启动 SSH …

解锁2024年翻译在线Top4,让每一次交流都精准无误

现在世界就像个大家庭&#xff0c;交流多了&#xff0c;语言不通就成了问题。有道翻译在线就像桥梁&#xff0c;帮我们和全世界的朋友沟通。对企业来说&#xff0c;翻译准确太重要了&#xff0c;一句话翻错可能损失巨大。有道翻译在线技术强&#xff0c;各种语言都能搞定&#…

简述混沌神经网络

混沌神经网络是一种结合了神经网络与混沌理论的新型智能信息处理系统。以下是对混沌神经网络的详细解析&#xff1a; 一、定义与背景 混沌神经网络是由于神经网络具有高度非线性动力学系统的特性&#xff0c;而混沌又具有无规则性、遍历性、随机性等特点&#xff0c;因此神经网…

快递物流查询-快递查询-快递单号查询-快递物流单号查询-快递物流轨迹查询-快递物流查询接口

快递物流查询接口&#xff08;API&#xff09;是一种允许开发者通过编程方式实时查询快递物流信息的服务。这些接口通常集成了多家快递公司的物流数据&#xff0c;为电商平台、物流管理系统、个人用户等提供便捷的物流查询服务。以下是关于快递物流查询接口的一些详细介绍&…

【通讯协议】S32K142芯片——LIN通信的学习和配置

文章目录 前言1.LIN是什么&#xff1f;2. LIN连接结构及节点构成3. 帧的组成3.1 帧头3.1.1 同步间隔场&#xff08;Break&#xff09;3.1.2 同步场&#xff08;Synch&#xff09;3.1.3 标识符场&#xff08;PID&#xff09; 3.2 帧响应3.2.1 数据场3.2.2 校验和场 3. 代码配置总…

「C++系列」动态内存

【人工智能教程】&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站&#xff1a;【人工智能教程】 文章目录 一、动态内存1. 使用new和delete①分配单个对象②分配对象数组 2. …

深入理解MySQL InnoDB中的B+索引机制

目录 一、InnoDB中的B 树索引介绍 二、聚簇索引 &#xff08;一&#xff09;使用记录主键值的大小进行排序 页内记录排序 页之间的排序 目录项页的排序 &#xff08;二&#xff09;叶子节点存储完整的用户记录 数据即索引 自动创建 &#xff08;三&#xff09;聚簇索引…

[数据结构与算法·C++] 笔记 1.5 流

流 标准输入输出流 标准输入流 cin>>x 读入整型数时以第一个非数字为终结读入字符串时以第一个空格、tab 或换行符为终结 其它方法 标准输出流 cout<<y cout 输出到标准设备cerr 输出错误信息clog 输出错误日志 输出不同进制 hex -> 16 进制dec -> 10 …

windows cuda12.1 pytorch gpu环境配置

安装cuda12.1 nvcc -V conda创建pythong3.10环境 conda create -n llama3_env python3.10 conda activate llama3_env 安装pytorch conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidia gpu - Pytorch version for cuda 12.2 - Stack Ov…

Stable Diffusion WebUI Forge 支持 Flux 了!

大家好&#xff0c;我是每天分享AI应用的萤火君&#xff01; Flux横空出世有段时间了&#xff0c;模型效果也得到了广泛的认可&#xff0c;但是 Stable Diffusion WebUI 官方迟迟没有跟进&#xff0c;据说是因为要修改很多底层的处理机制&#xff0c;加之ComfyUI如火如荼&…

鸿蒙OpenHarmony【轻量系统内核扩展组件(CPU占用率)】子系统开发

基本概念 CPU&#xff08;中央处理器&#xff0c;Central Processing Unit&#xff09;占用率分为系统CPU占用率和任务CPU占用率。 系统CPU占用率&#xff1a;是指周期时间内系统的CPU占用率&#xff0c;用于表示系统一段时间内的闲忙程度&#xff0c;也表示CPU的负载情况。系…

iOS 中 KVC 与 KVO 底层原理

KVC 本质&#xff1a; [object setValue: forKey:];即使没有在.h 文件中有property 的属性声明&#xff0c;setValue:forKey依然会按照上图流程执行代码 KVC 如果成功改变了成员变量&#xff0c;是一定可以被 KVO 监听到成员变量的前后改变的 KVO runtime会生成中间类&…

EmguCV学习笔记 C# 12.3 OCR

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

ActiveMQ 的消息持久化策略

ActiveMQ 的消息持久化策略 消息持久化对于可靠消息传递来说是一种比较好的方法&#xff0c;即使发送者和接受者不是同时在线&#xff0c;或者消息中心在发送者发送消息后宕机了&#xff0c;消息中心重启后仍然可以将消息发送出去。 消息持久性的原理很简单&#xff0c;就是在发…

[Linux] 通透讲解 什么是进程

标题&#xff1a;[Linux] 通透讲解 什么是进程 个人主页&#xff1a;水墨不写bug &#xff08;图片来自网络&#xff09; 目录 一.深入进程基本概念 二.管理好进程 1.管理好进程的方法 2.描述进程-PCB 3.组织进程 正文开始&#xff1a; 本文按照对进程的先描述再组织进行…

C++之模版的进阶

1.非类型模版参数 模版参数分类类型与非类型形形参 类型形参&#xff1a;出现在模版参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff1a;用一个常亮作为类&#xff08;函数&#xff09;模版的一个参数&#xff0c;在类&#xff08;函数…

股指期货交割方式是什么?

说起股指期货&#xff0c;这可是个高大上的金融玩意儿。咱们平时买卖股票&#xff0c;那是看准了哪只股就下手&#xff0c;赚了就卖&#xff0c;赔了就扛&#xff0c;挺直接的。但股指期货呢&#xff0c;它玩的是未来的预期&#xff0c;就像是你跟人打赌明天天气好不好&#xf…

Fyne ( go跨平台GUI )中文文档-Fyne总览(二)

本文档注意参考官网(developer.fyne.io/) 编写, 只保留基本用法 go代码展示为Go 1.16 及更高版本, ide为goland2021.2​​​​​​​ 这是一个系列文章&#xff1a; Fyne ( go跨平台GUI )中文文档-入门(一)-CSDN博客 Fyne ( go跨平台GUI )中文文档-Fyne总览(二)-CSDN博客 Fyne…

《python语言程序设计》2018版第8章18题几何circle2D类(中部)

第一、重新分析 第一-1、我设计的第一模式第一-1-1、遇到的逻辑分析迷雾第一-1-2、无畏挣扎后的无奈 第二-1、我就把你们两个放到一起,第二-2、我的想法 当我看到了这个2个园并且比对. 第一-1、我设计的第一模式 设计一个最抽象的Circle2D类. 这个类只包含一个x,y和circle 这个…

Parallels Desktop 20 for Mac 推出:完美兼容 macOS Sequoia 与 Win11 24H2

Parallels Desktop 20 for Mac 近日正式发布&#xff0c;这一新版本不仅全面支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;还在企业版中引入了一个全新的管理门户。新版本针对 Windows、macOS 和 Linux 虚拟机进行了多项改进&#xff0c;其中最引人注目的当属 Parallels …