AI 对话【人工智能】
- 前言
- 版权
- 开源
- 推荐
- AI 对话
- v0版本:基础
- v1版本:对话
- 数据表
- tag.js
- TagController
- v2版本:回复中
- textarea.js
- ChatController
- v3版本:流式输出
- chatLast.js
- ChatController
- v4版本:多轮对话
- QianfanUtil
- ChatController
- v5:其他修改
- 前端样式:跳转到最后一个消息
- 前端样式:Message保留空白符
- 前端样式:最新回复保留空白符
- 最后
前言
2024-4-7 15:04:07
以下内容源自《【人工智能】》
仅供学习交流使用
版权
禁止其他平台发布时删除以下此话
本文首次发布于CSDN平台
作者是CSDN@日星月云
博客主页是https://jsss-1.blog.csdn.net
禁止其他平台发布时删除以上此话
开源
日星月云 / AI对话完善版
jsss-1/aichat
推荐
百度智能云+SpringBoot=AI对话【人工智能】
对话Chat-千帆大模型平台
AI 对话
以下版本除了最简单的AI对话,还完善了一下功能。
以下是部分代码,完整代码请移步GIT。
v0版本:基础
聊天
v1版本:对话
新建新对话
可以置顶(取消置顶)、删除、修改对应的对话
数据表
create table tag
(
id int auto_increment
primary key,
user_id int not null,
tag_name varchar(16) not null,
top int default 0 null
);
create table conversation
(
id int auto_increment
primary key,
tag_id int null,
user_message text null,
bot_message text null,
create_time varchar(32) null,
username varchar(16) null
);
tag.js
$(document).ready(function () {
tagList();
$("#editBlock").hide();
$(".add-button").on("click", function() {
addTag();
});
});
function tagSearch(data) {
var data=$("#search-input").val();
if(!data){
//没有数据搜索全部
tagList();
return false;
}
$.ajax({
type: "GET",
url: SERVER_PATH + "/tag/search",
data: {
data: data
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
set_tags(result.data);
}
});
}
function addTag() {
$.ajax({
type: "POST",
url: SERVER_PATH + "/tag/addTag",
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
tagList();
}
});
}
function tagList() {
$.ajax({
type: "GET",
url: SERVER_PATH + "/tag/tagList",
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
set_tags(result.data);
}
});
}
function set_tags(tags) {
if (!tags) {
return false;
}
$(".tag-list").empty();
$.each(tags, function (i, tag) {
var btnClass = tag.top === 0 ? "top-btn" : "notop-btn";
var topClass = tag.top === 0 ? "ptTag" : "topTag";
var tagDiv = `
<div class="tag">
<img class="tag-btn ${topClass}"></img>
<span class="show-btn" data-id="${tag.id}">${tag.tagName}</span>
<div class="button-group">
<img class="icon-btn ${btnClass}" data-id="${tag.id}"></img>
<img class="icon-btn modify-btn" data-id="${tag.id}" data-name="${tag.tagName}"></img>
<img class="icon-btn delete-btn" data-id="${tag.id}"></img>
</div>
</div>`;
$(".tag-list").append(tagDiv);
});
$(".show-btn").on("click", function() {
var tagId = $(this).data('id');
window.location.href="aichat.html?tagId="+tagId;
});
$(".notop-btn").on("click", function() {
var tagId = $(this).data('id');
var newTop=0;
topTag(tagId,newTop);
});
$(".top-btn").on("click", function() {
var tagId = $(this).data('id');
var newTop=top=1;
topTag(tagId,newTop);
});
$(".modify-btn").on("click", function() {
var tagId = $(this).data('id');
var tagName = $(this).data('name'); // 获取标签名称
// 将标签名称填充到输入框中
$("#newName").val(tagName);
// 显示编辑界面块
$("#editBlock").show();
// 保存按钮点击事件
$("#saveBtn").off("click").on("click", function() {
var newName = $("#newName").val();
if(!newName){
alertBox("请输入新名字");
return false;
}
modify(tagId,newName);
// 关闭编辑界面块
$("#editBlock").hide();
});
$("#cancelBtn").on("click", function() {
$("#editBlock").hide();
});
});
$(".delete-btn").on("click", function() {
var tagId = $(this).data('id');
// 弹出确认删除的提示框
var confirmDelete = confirm("确定要删除这个标签吗?");
// 如果用户点击确定删除,则执行删除操作
if (confirmDelete) {
deleteTag(tagId);
}
});
}
function topTag(tagId,newTop){
$.ajax({
type: "POST",
url: SERVER_PATH + "/tag/top",
data: {
tagId: tagId,
top: newTop
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
tagList();
}
});
}
function modify(tagId,newName){
$.ajax({
type: "POST",
url: SERVER_PATH + "/tag/modify",
data: {
tagId: tagId,
tagName: newName
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
tagList();
}
});
}
function deleteTag(tagId){
$.ajax({
type: "GET",
url: SERVER_PATH + "/tag/delete",
data: {
tagId: tagId
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
tagList();
}
});
}
TagController
package com.jsss.qianfan.controller;
import com.jsss.common.BusinessException;
import com.jsss.common.ErrorCode;
import com.jsss.common.ResponseModel;
import com.jsss.entity.User;
import com.jsss.qianfan.entity.Tag;
import com.jsss.qianfan.service.TagService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("tag")
@CrossOrigin(origins = "${jsss.web.path}", allowedHeaders = "*", allowCredentials = "true")
public class TagController implements ErrorCode {
@Autowired
RedisTemplate redisTemplate;
@Autowired
TagService tagService;
@GetMapping("/tagList")
public ResponseModel getTags(String token) {
User user = null;
if (StringUtils.isNotEmpty(token)) {
user = (User) redisTemplate.opsForValue().get(token);
}
if (user == null) {
throw new BusinessException(USER_NOT_LOGIN, "用户未登录");
}
List<Tag> tags = tagService.searchByUserId(user.getUserId());
return new ResponseModel(tags);
}
@PostMapping("/addTag")
public ResponseModel addTag(String token) {
User user = null;
if (StringUtils.isNotEmpty(token)) {
user = (User) redisTemplate.opsForValue().get(token);
}
if (user == null) {
throw new BusinessException(USER_NOT_LOGIN, "用户未登录");
}
String tagName = "新对话";
Tag tag = new Tag(null, user.getUserId(), tagName, 0);
tagService.addTag(tag);
return new ResponseModel("添加成功");
}
@PostMapping("/modify")
public ResponseModel modifyTag(Integer tagId, String tagName) {
if (StringUtils.isEmpty(tagName)){
throw new BusinessException(PARAMETER_ERROR, "缺失新的tag名");
}
tagService.updateTagName(tagId, tagName);
return new ResponseModel("修改成功");
}
@PostMapping("/top")
public ResponseModel topTag(Integer tagId, Integer top) {
tagService.updateTagTop(tagId, top);
String res = top == 1 ? "置顶成功" : "取消置顶成功";
return new ResponseModel(res);
}
@GetMapping("/delete")
public ResponseModel deleteTag(Integer tagId) {
tagService.deleteTag(tagId);
return new ResponseModel("删除成功");
}
@GetMapping("/search")
public ResponseModel searchTag(String token, String data) {
User user = null;
if (StringUtils.isNotEmpty(token)) {
user = (User) redisTemplate.opsForValue().get(token);
}
if (user == null) {
throw new BusinessException(USER_NOT_LOGIN, "用户未登录");
}
List<Tag> tags = tagService.searchTag(user.getUserId(),data);
return new ResponseModel(tags);
}
}
v2版本:回复中
用户发送问题之后,显示回复中,得到回复后显示。
前端发送请求之后,先会得到“回复中”;
之后,去轮询获取最新回复。
后端接受请求之后,先存入到数据库中一个未回复请求。
然后异步得到回复之后,再去更新数据库。
textarea.js
var textarea = document.getElementById("messageInput");
var isSendingMessage = false; // 添加一个变量用于标识是否正在发送消息
textarea.addEventListener("keydown", function(event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
if (isSendingMessage) {
// 如果正在发送消息,则在文本框中添加换行符
textarea.value += "\n";
} else{
var message = textarea.value.trim();
textarea.value = "";
if(!message){
alertBox("输入内容不能为空!");
return false;
}
var tagId=$.getUrlParam("tagId");;
if(!tagId){
alertBox("没有对应的参数");
return false;
}
isSendingMessage = true; // 设置为true表示正在发送消息
$.ajax({
type: "POST",
url: SERVER_PATH+"/chat/chat",
data:{
"tagId": tagId,
"content":message
},
xhrFields: {withCredentials: true},
success:function(result){
isSendingMessage = false; // 发送完成后设置为false
if (result.status) {
alertBox(result.data.message);
return false;
}
//请求成功之后
list(tagId);
getChat(result.data.id);
}
});
}
}
});
textarea.addEventListener("keydown", function(event) {
if (event.key === "Enter" && event.shiftKey) {
// 在 Shift+Enter 情况下允许换行
textarea.value += "\n";
event.preventDefault();
}
});
function getChat(chatId){
var tagId=$.getUrlParam("tagId");;
$.ajax({
type: "GET",
url: SERVER_PATH+"/chat/getChat",
data:{
"id": chatId,
},
xhrFields: {withCredentials: true},
success:function(result){
isSendingMessage = false; // 发送完成后设置为false
if (result.status) {
alertBox(result.data.message);
return false;
}
if (result.data.botMessage == "回复中...") {
// 继续轮询,100ms 一次
setTimeout(function() {
getChat(chatId);
}, 100);
} else {
// 获取到最终回复
// 处理回复逻辑
list(tagId);
}
}
});
}
ChatController
@PostMapping("/chat")
public ResponseModel chat(Integer tagId,String content){
if (tagId==null){
throw new BusinessException(PARAMETER_ERROR,"没有指定响应的tag");
}
if (StringUtils.isEmpty(content)){
throw new BusinessException(PARAMETER_ERROR,"输入内容不能为空");
}
Tag tag = tagService.searchById(tagId);
if (tag==null){
throw new BusinessException(NOT_FIND,"没有找到对应的对话");
}
String username=userService.selectUserById(tag.getUserId()).getUsername();
Conversation conversation = new Conversation(null, tagId,username, content, "回复中...", format(new Date()));
chatService.addChat(conversation);
// 异步处理AI回复
CompletableFuture.runAsync(() -> {
Integer id=conversation.getId();
String res = null;
try {
res = qianfanUtil.addMessage(content);
} catch (Exception e) {
res = "回复失败";
}
Conversation aiConversation = new Conversation();
aiConversation.setId(id);
aiConversation.setBotMessage(res);
chatService.updateChat(aiConversation);
});
return new ResponseModel(conversation);
}
v3版本:流式输出
流式输出,终止生成。
前端实现,让消息一个字符一个字符显示
chatLast.js
var lastId;
var interval;
$(document).ready(function () {
$("#stopButton").on("click", function() {
var latestReply = $(".latest-reply");
var latestReplyText = latestReply.text();
clearInterval(interval); // 停止字符流输出
$("#stopButton").hide();
updateStop(lastId,latestReplyText);
});
});
function listLastReply(tagId) {
$.ajax({
type: "GET",
url: SERVER_PATH + "/chat/list",
data: {
tagId: tagId
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
set_conversations_last(result.data);
}
});
}
function set_conversations_last(conversations) {
if (!conversations) {
return false;
}
$(".conversation-list").empty();
var len=conversations.length;
$.each(conversations, function (i, conversation) {
var questionDiv = '<div class="question-container">' +
'<table class="question">' +
'<td>' +
'<span>' + conversation.createTime + '</span>' +
'<div class="user-message">' + conversation.userMessage + '</div>' +
'</td>' +
'<td type="text">' + conversation.username + '</td>' +
'</table>' +
'</div>';
var answerDiv = '<div class="answer-container">' +
'<table class="answer">' +
'<td type="text">AI</td>' +
'<td>' +
'<span>' + conversation.createTime + '</span>' +
'<div class="bot-message">' + conversation.botMessage + '</div>' +
'</td>' +
'</table>' +
'</div>';
if(i!=len-1){
$(".conversation-list").append(questionDiv);
$(".conversation-list").append(answerDiv);
}
});
// 获取最新对话的回复
var lastConversation = conversations[len - 1];
lastId=lastConversation.id;
var questionDiv =
'<div class="question-container">' +
'<table class="question">' +
'<td>' +
'<span>' + lastConversation.createTime + '</span>' +
'<div class="user-message">' + lastConversation.userMessage + '</div>' +
'</td>' +
'<td type="text">' + lastConversation.username + '</td>' +
'</table>' +
'</div>';
$(".conversation-list").append(questionDiv);
var answerDiv =
'<div class="answer-container">' +
'<table class="answer">' +
'<td type="text">AI</td>' +
'<td>' +
'<span>' + lastConversation.createTime + '</span>' +
'<div class="bot-message latest-reply">' + lastConversation.botMessage + '</div>' +
'</td>' +
'</table>' +
'</div>';
$(".conversation-list").append(answerDiv);
// 逐字显示最新回复
var latestReply = $(".latest-reply");
var latestReplyText = latestReply.text();
latestReply.empty();
var index = 0;
interval = setInterval(function() {
if (index < latestReplyText.length) {
$("#stopButton").show();
latestReply.append(latestReplyText.charAt(index));
index++;
} else {
clearInterval(interval);
$("#stopButton").hide();
}
}, 10); // 逐字显示的速度,您可以根据需要调整
}
function updateStop(tagId,message){
$.ajax({
type: "POST",
url: SERVER_PATH + "/chat/updateStop",
data: {
id: tagId,
botMessage: message
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
}
});
}
ChatController
@PostMapping("/updateStop")
public ResponseModel updateStop(Integer id,String botMessage){
Conversation conversation=new Conversation();
conversation.setId(id);
conversation.setBotMessage(botMessage);
chatService.updateChat(conversation);
return new ResponseModel();
}
v4版本:多轮对话
实现上下文有关的对话
多次调用qianfan.addMessage().addMessage()
QianfanUtil
public String addMessagePlus(List<Message> messages, String content) {
ChatResponse response = qianfan.chatCompletion()
.messages(messages)
.addMessage("user", content)
.temperature(0.7)
.execute();
return response.getResult();
}
ChatController
@PostMapping("/chat")
public ResponseModel chat(Integer tagId, String content) {
if (tagId == null) {
throw new BusinessException(PARAMETER_ERROR, "没有指定响应的tag");
}
if (StringUtils.isEmpty(content)) {
throw new BusinessException(PARAMETER_ERROR, "输入内容不能为空");
}
Tag tag = tagService.searchById(tagId);
if (tag == null) {
throw new BusinessException(NOT_FIND, "没有找到对应的对话");
}
String username = userService.selectUserById(tag.getUserId()).getUsername();
Conversation conversation = new Conversation(null, tagId, username, content, "回复中...", format(new Date()));
chatService.addChat(conversation);
// 异步处理AI回复
CompletableFuture.runAsync(() -> {
Integer id = conversation.getId();
String res = null;
try {
// res = qianfanUtil.addMessage(content);
res = qianfanUtil.addMessagePlus(getMessages(tagId), content);
} catch (Exception e) {
res = "回复失败";
}
Conversation aiConversation = new Conversation();
aiConversation.setId(id);
aiConversation.setBotMessage(res);
chatService.updateChat(aiConversation);
});
return new ResponseModel(conversation);
}
public List<Message> getMessages(Integer tagId) {
List<Message> messages = new ArrayList<>();
List<Conversation> conversations = chatService.searchByTagId(tagId);
int size = conversations.size() - 1;//最新的不需要
for (int i = 0; i < size; i++) {
Conversation conversation = conversations.get(i);
Message userMessage = new Message();
userMessage.setRole("user");
userMessage.setContent(conversation.getUserMessage());
messages.add(userMessage);
Message botMessage = new Message();
botMessage.setRole("assistant");
botMessage.setContent(conversation.getBotMessage());
messages.add(botMessage);
}
return messages;
}
v5:其他修改
前端样式:跳转到最后一个消息
在aichat.html中
<script>
function scrollToLastMessage() {
// 找到消息容器
var messageContainer = document.querySelector(".message-container");
// 找到消息容器中最后一个子元素
var lastMessage = messageContainer.lastElementChild;
// 将最后一个消息元素滚动到可见区域
lastMessage.scrollIntoView({ behavior: 'auto', block: 'end' });
}
function onLoad() {
setTimeout(scrollToLastMessage, 100); // 添加100毫秒的延迟
}
window.addEventListener('load', onLoad);
</script>
前端样式:Message保留空白符
savePre(conversation.userMessage)
savePre(conversation.botMessage)
function savePre(content){
return content.replace(/\n/g, "<br>").replace(/ /g, "<span> </span>");
}
前端样式:最新回复保留空白符
var botMessageWithBrAndSpace = lastConversation.botMessage.replace(/\n/g, "\\n");
var answerDiv =
'<div class="answer-container">' +
'<table class="answer">' +
'<td type="text">AI</td>' +
'<td>' +
'<span>' + lastConversation.createTime + '</span>' +
'<div class="bot-message latest-reply">' + botMessageWithBrAndSpace + '</div>' +
'</td>' +
'</table>' +
'</div>';
$(".conversation-list").append(answerDiv);
// 逐字显示最新回复
var latestReply = $(".latest-reply");
var latestReplyText = botMessageWithBrAndSpace;
latestReply.empty();
var index = 0;
interval = setInterval(function() {
if (index < latestReplyText.length) {
$("#stopButton").show();
if (latestReplyText.charAt(index) === "\\") {
if (latestReplyText.charAt(index + 1) === "n") {
latestReply.append("<br>");
index++; // 跳过"n"
} else {
latestReply.append(latestReplyText.charAt(index));
}
} else if(latestReplyText.charAt(index)===' '){
latestReply.append(" ");
}else {
latestReply.append(latestReplyText.charAt(index));
}
index++;
} else {
clearInterval(interval);
$("#stopButton").hide();
}
}, 25); // 逐字显示的速度,您可以根据需要调整
}
最后
2024-4-10 17:02:49
迎着日光月光星光,直面风霜雨霜雪霜。