vue+java实现简易AI问答组件(基于百度文心大模型)

本文主要是介绍vue+java实现简易AI问答组件(基于百度文心大模型),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、需求

公司想要在页面中加入AI智能对话功能,故查找免费gpt接口,最终决定百度千帆大模型(进入官网、官方文档中心);

二、主要功能列举

  • AI智能对话;
  • 记录上下文回答环境;
  • 折叠/展开窗口;
  • 可提前中止回答;
  • 回答内容逐字展示并语音播报;

三、效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、技术选型

1、前端环境

  • node(14.21.3)
  • VueCli 2
  • element-ui(^2.15.14)
  • axios
  • node-sass(^4.14.1)
  • sass-loader(^7.3.1)
  • js-md5(^0.8.3)

2、后端环境

  • JDK8
  • springboot

五、声明

  • 本文章以及源码纯粹自己写着玩,等于是个demo,有许多需要完善和优化的地方,仅供大家参考,有错误的地方欢迎大家批评指正~~
  • 由于作者其实是java,所以前端代码中如果看到神奇的地方,希望大家包涵,哈哈哈哈

四、百度千帆大模型应用创建

1、访问官网,注册账号并登录;

2、选择“应用接入”-“创建应用”

在这里插入图片描述
进去填一个应用名及描述即可,服务默认全勾选上;

3、保存后返回应用列表,获取api key和secret key

在这里插入图片描述

PS:

百度提供的大模型服务有好多种,我此处是白嫖的其中一个免费的,如下图:
其中ERNIE开头的是百度自己的,文心一言用的就是这种,其他有些是三方大模型;
在这里插入图片描述

具体不同服务之间有什么区别可以看官方介绍,个人觉得免费的几个主要在于轻量等级、响应速度、回答内容复杂程度、可保存的上下文大小等几个方面;

五、部分后台代码

1、官网下载java sdk或者引入百度千帆pom

官放文档地址:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/7ltgucw50
java SDK地址:https://github.com/baidubce/bce-qianfan-sdk/tree/main/java
maven仓库地址:https://mvnrepository.com/artifact/com.baidubce/qianfan

POM:

<!-- https://mvnrepository.com/artifact/com.baidubce/qianfan -->
<dependency><groupId>com.baidubce</groupId><artifactId>qianfan</artifactId><version>0.0.4</version>
</dependency>

2、创建springboot项目,并导入sdk或引入千帆pom

此处是把sdk导入到工程中;

3、项目代码结构如下

在这里插入图片描述

pom.xml :

<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.baidu</groupId><artifactId>aichat</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><name>aichat</name><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.9</version></parent><dependencies><!-- SpringBoot的依赖配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.2.13.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.10.1</version></dependency><dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifactId><version>5.3.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.1.1.RELEASE</version><configuration><fork>true</fork> <!-- 如果没有该配置,devtools不会生效 --></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><version>3.1.0</version><configuration><failOnMissingWebXml>false</failOnMissingWebXml><warName>${project.artifactId}</warName></configuration></plugin></plugins><finalName>${project.artifactId}</finalName></build></project>

AiChatController.java:

package com.baidubce.controller;import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import com.baidubce.qianfan.Qianfan;
import com.baidubce.qianfan.core.builder.ChatBuilder;
import com.baidubce.qianfan.model.chat.ChatResponse;
import com.baidubce.qianfan.model.chat.Message;
import com.baidubce.utils.JsonUtils;
import com.baidubce.utils.SecUtils;@RestController
public class AiChatController {private static final String accessKey = "你创建的应用的API Key";private static final String secretKey = "你创建的应用的Secret Key";private static Qianfan qianfan = new Qianfan("OAuth", accessKey, secretKey);/*** 	参数:* 		messages: 对话记录,role:user是用户,assistant是AI,如:[{"role":"user","content":"1"},{"role":"assistant","content":"“1”是一个数字。"},{"role":"user","content":"你是"},{"role":"assistant","content":""}]* 		timestamp:请求毫秒值,1717742825695* 		signature: 签名,4bc9c3b8dbe4de5bc924b6fa0506c606* @author x轩* @version 2024年6月7日 下午2:46:32*/@PostMapping("/sendMsg")public String sendMsg(@RequestBody Map<String, Object> params) {// 验签,我自己加的,防止恶意调用,作用不大,提高门槛而已if(!checkSign(params)) {return "签名不正确!";}String result = null;try {result = chat(String.valueOf(params.get("messages")));} catch (Exception e) {e.printStackTrace();return "接口繁忙,请稍后再试!";}return result;}/*** 	参数:* 		messages: 业务参数* 		timestamp:请求毫秒值* 		signature: 签名* *	加签规则:*		要求1:timestamp和当前系统时间不能超过5秒钟*		要求2:MYCHAT|timestamp|messages拼接后MD53次加密** @author x轩* @version 2024年6月6日 下午4:13:03*/private boolean checkSign(Map<String, Object> params) {String timestamp = String.valueOf(params.get("timestamp"));String messages = String.valueOf(params.get("messages"));String signature = String.valueOf(params.get("signature"));if(StringUtils.isAnyBlank(timestamp, messages, signature)) {return false;}// 1.判断时间if((System.currentTimeMillis()- Long.valueOf(timestamp))>5000) {// 过期return false;}// 2.验签String p = "MYCHAT|"+timestamp+"|"+messages;String md5of3 = SecUtils.encoderByMd5With32Bit(SecUtils.encoderByMd5With32Bit(SecUtils.encoderByMd5With32Bit(p)));if(!signature.equalsIgnoreCase(md5of3)) {return false;}return true;}public static void main(String[] args) {
//        chat("对于调休你怎么看");chatStream("介绍一下自己");}private static String chat(String messages) {ChatBuilder bulder = qianfan.chatCompletion()
//        		.model("ERNIE-Speed-128K")
//        		.model("ERNIE-Speed-8K").model("ERNIE-Tiny-8K");List<Message> messageList = JsonUtils.readValues(messages, Message.class);// 过滤一下,去除空内容对象messageList = messageList.stream().filter(m->{return StringUtils.isNotBlank(m.getContent());}).collect(Collectors.toList());for(Message m : messageList) {bulder.addMessage(m);}ChatResponse response = bulder.execute();return response.getResult();}private static void chatStream(String message) {Iterator<ChatResponse> stream = qianfan.chatCompletion()
//		.model("ERNIE-Speed-128K")
//		.model("ERNIE-Speed-8K").model("ERNIE-Tiny-8K").addMessage("user", message).executeStream();while(stream.hasNext()) {System.out.println(stream.next().getResult());}}
}

六、 Vue部分代码

1、vue.config.js

const port = process.env.port || process.env.npm_config_port || 80 // 端口module.exports = {lintOnSave: false,publicPath: "/aichat-front",assetsDir: 'static',// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。productionSourceMap: false,devServer: {host: '0.0.0.0',port: port,open: true,proxy: {['/aichat']: {target: `http://127.0.0.1:8080/aichat`,changeOrigin: true,pathRewrite: {['^/aichat']: ''}},},},
}

2、App.vue

<template><div id="app"><qian-fan-chat/></div>
</template><script>
import QianFanChat from './components/QianFanChat'export default {name: 'App',components: {QianFanChat}
}
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>

3、@/components/QianFanChat组件

<template><div class="chat-div"><div v-show="showChatBox" class="chat-main"><div id="messagediv" class="messagediv"><div class="item"><img class="avatar" :src="aiAvatar" ><div class="answerDiv"><p>我是AI智能小助手,有问题请咨询我吧!</p></div></div><div v-for="(item, index) in messageList" class="item"><img v-if="item.role=='assistant'" class="avatar" :src="aiAvatar" ><img v-if="item.role=='user'" class="avatar" :src="userAvatar" ><div class="answerDiv"><p v-if="!item.loading" v-html="item.content"></p><p v-else>思考中 <i class="el-icon-loading"></i></p><a v-if="index==messageList.length-1 &&item.role=='assistant' && (loading || speaking)" href="#" @click="stopAnswer">停止回答</a></div></div></div><div class="sendDiv"><el-input @keyup.enter.native="getAnswer" v-model="question" placeholder="输入中医药相关内容搜一搜"></el-input><el-button @click="getAnswer"><i class="el-icon-s-promotion"></i></el-button></div></div><div v-show="showChatBox" class="close-btn" @click="showChatBox=false"><i class="el-icon-close"></i></div><div v-show="!showChatBox" class="small-window" @click="showChatBox=true">AI问答</div></div>
</template>
<script>
const msg = new SpeechSynthesisUtterance();import { sendMsg } from '@/api/aichat';
import { sign } from '@/utils/securityUtil'
import aiAvatar from '@/assets/images/ai-avatar.jpg';
import userAvatar from '@/assets/images/user-avatar.jpg';export default {name: 'QianFanChat',data(){return {showChatBox: false,loading: false,speaking: false,aiAvatar,userAvatar,question:'',messageList:[],// 逐字输出 STARTtimer: null,length: 0,index: 0,// 逐字输出 END}},mounted(){},methods: {getAnswer(){if(this.loading){this.$message({type: 'warning', message:'正在回答,请耐心等待!'});return;}if(!this.question.trim()){this.$message({type: 'warning', message:'请输入内容!'});return;}this.loading = true;let tmpQustion = this.question;this.question = '';this.messageList.push({ role:'user', content: tmpQustion, loading: false });this.messageList.push({ role:'assistant', content: '' , loading: true});this.$nextTick(()=>{this.scroll();})let params = {messages: JSON.stringify(this.messageList)};params.timestamp = new Date().getTime();params.signature = sign(params);sendMsg(params).then(res=>{// 判断loading是否被中断(停止回答可中断)if(!this.loading){// 点击了“停止回答”return;}// 接口请求完毕,替换最后一条内容this.messageList[this.messageList.length-1].loading = false;this.index = 0;this.length = res.length;this.handleSpeak(res);// 一个字一个字给我蹦this.timer = setInterval(()=>{if(this.index<=this.length-1){let word = res.charAt(this.index);if(word=='\n'){word = '<br>'}this.messageList[this.messageList.length-1].content += word;this.index++;}else{// 结束this.loading = false;clearInterval(this.timer);}this.scroll();}, 30);})},scroll(){messagediv.scrollTo({top: messagediv.scrollHeight,})},stopAnswer(){this.handleStop();clearInterval(this.timer);this.length = 0;this.index = 0;this.loading = false;// 判断最后一条内容是不是空,是则给上默认输出let lastMsg = this.messageList[this.messageList.length-1];if(!lastMsg.content){lastMsg.loading = false;lastMsg.content = '请继续向我提问吧!';}},// 语音播报的函数handleSpeak(text) {this.handleStop();this.speaking = true;// 处理多音字msg.text = text;     // 朗读内容msg.lang = "zh-CN";  // 使用的语言:中文 msg.volume = 0.5;      // 声音音量:1  设置将在其中发言的音量。区间范围是0到1,默认是1msg.rate = 1.6;        // 语速:1  设置说话的速度。默认值是1,范围是0.1到10,表示语速的倍数,例如2表示正常语速的两倍msg.pitch = 1.5;       // 音高:2  设置说话的音调(音高)。范围从0(最小)到2(最大)。默认值为1// msg.voiceURI = 'Google 普通话(中国大陆)';msg.onstart = (e)=>{};msg.onend = (e)=>{this.speaking = false;};msg.onboundary = (e) => {}speechSynthesis.speak(msg);    // 播放},// 语音停止handleStop(e) {this.speaking = false;msg.text = e;msg.lang = "zh-CN";speechSynthesis.cancel(msg);},}
}
</script>
<style lang="scss" scoped>::v-deep {.el-input {width: 270px;input {background-color: rgba(0,0,0,.5);border: none;color: white;}}.el-button {background-color: rgba(0,0,0,.5);border: none;margin-left: 10px;padding: 0 20px;i {font-size: 20px;color: white;}&:focus {background-color: rgba(0,0,0,.5);}&:hover {background-color: white;i {color: black;}}}}.chat-div {position: absolute;top: 0;left: 0;display: flex;z-index: 99;cursor: pointer;.close-btn {margin-top: 18px;color: white;background-color: rgba(0, 0, 0, .6);height: 30px;width: 30px;text-align: center;line-height: 30px;border-radius: 50%;}.small-window {color: white;margin-top: 18px;padding: 10px;background-color: rgba(16, 168, 129, .8);width: 26px;font-size: 20px;border-top-right-radius: 10px;border-bottom-right-radius: 10px;}}.chat-main {border-radius: 4px;margin: 10px 0;padding: .1rem .1rem;width: 374px;.messagediv {overflow: auto;max-height: 60vh;.item {display: flex;margin-bottom: .1rem;.avatar {width: 40px;height: 40px;}.answerDiv {p {color: white;background-color: rgba(0, 0, 0, .6);padding: 10px;margin: 0 10px;font-size: 16px;border-radius: 6px;text-align: left;}a {cursor: pointer;font-size: 15px;color: red;text-decoration: underline;margin-left: 10px;font-weight: bold;}}}&::-webkit-scrollbar {width: 0;}}.sendDiv {display: flex;justify-content: end;margin: 10px 10px 0 0;}}
</style>

4、@/utils/request.js

import axios from 'axios'
import { Notification, MessageBox, Message } from 'element-ui'axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: process.env.VUE_APP_BASE_API,// 超时timeout: 50000
})
// request拦截器
service.interceptors.request.use(config => {// get请求映射params参数if (config.method === 'get' && config.params) {let url = config.url + '?';for (const propName of Object.keys(config.params)) {const value = config.params[propName];var part = encodeURIComponent(propName) + "=";if (value !== null && typeof (value) !== "undefined") {if (typeof value === 'object') {for (const key of Object.keys(value)) {if (value[key] !== null && typeof (value[key]) !== 'undefined') {let params = propName + '[' + key + ']';let subPart = encodeURIComponent(params) + '=';url += subPart + encodeURIComponent(value[key]) + '&';}}} else {url += part + encodeURIComponent(value) + "&";}}}url = url.slice(0, -1);config.params = {};config.url = url;}return config
}, error => {console.log(error)Promise.reject(error)
})// 响应拦截器
service.interceptors.response.use(res => {return res.data;},error => {console.log('err' + error)let { message } = error;if (message == "Network Error") {message = "后端接口连接异常";}else if (message.includes("timeout")) {message = "系统接口请求超时";}else if (message.includes("Request failed with status code")) {message = "系统接口" + message.substr(message.length - 3) + "异常";}Message({message: message,type: 'error',duration: 5 * 1000})return Promise.reject(error)}
)export default service

5、@/utils/securityUtil.js

PS: 由于这个项目不需要登录,我又怕接口泄露导致别人恶意调用,所以给接口加了个签名,具体策略大家可以自定义(讲真的,没什么用,前台加签别人打开调试模式照样可以看到加签策略。。。为了应对这个情况,我把前台加签JS给做了个混淆,算是增加一下门槛吧;还可以禁止用户点击F12和右键事件【具体代码见此篇文章】)

// 加签方法,方法接收两个参数: timestamp和messages(消息JSON字符串),返回签名;已混淆,以下代码具体签名策略如下:固定字符串"MYCHAT"、时间戳、消息体用|拼接后进行3次MD5加密:如MYCHAT|1718181053994|[{"role":"user","content":"1"},{"role":"assistant","content":"“1”是一个数字。"}]
const _0x4e66=['MYCHAT','timestamp'];const _0x3524=function(_0x4e6602,_0x35247b){_0x4e6602=_0x4e6602-0x0;let _0x2ee47d=_0x4e66[_0x4e6602];return _0x2ee47d;};import _0x50d0de from'js-md5';export function sign(_0x5c789a){let _0x1bf721='|'+_0x5c789a[_0x3524('0x1')];let _0x202f97='|'+_0x5c789a['messages'];let _0x74f806=_0x3524('0x0')+_0x1bf721+_0x202f97;_0x74f806=_0x50d0de(_0x74f806);_0x74f806=_0x50d0de(_0x74f806);_0x74f806=_0x50d0de(_0x74f806);return _0x74f806;}

6、@/api/aichat.js

import request from '@/utils/request'export function sendMsg(data) {return request({url: '/sendMsg',method: 'post',data: data})}

有问题或者需要完整项目源码的话私聊吧,关机下班底薪到手~

这篇关于vue+java实现简易AI问答组件(基于百度文心大模型)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1056316

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

揭秘未来艺术:AI绘画工具全面介绍

📑前言 随着科技的飞速发展,人工智能(AI)已经逐渐渗透到我们生活的方方面面。在艺术创作领域,AI技术同样展现出了其独特的魅力。今天,我们就来一起探索这个神秘而引人入胜的领域,深入了解AI绘画工具的奥秘及其为艺术创作带来的革命性变革。 一、AI绘画工具的崛起 1.1 颠覆传统绘画模式 在过去,绘画是艺术家们通过手中的画笔,蘸取颜料,在画布上自由挥洒的创造性过程。然而,随着AI绘画工

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

AI儿童绘本创作

之前分享过AI儿童绘画的项目,但是主要问题是角色一致要花费很长的时间! 今天发现了这款,非常奈斯! 只需输入故事主题、风格、模板,软件就会自动创作故事内容,自动生成插画配图,自动根据模板生成成品,测试效果如下图。 变现方式:生成儿童绘本发布到各平台,吸引宝妈群体进私域。  百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前