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


开源

日星月云 / AI对话完善版

jsss-1/aichat

推荐

百度智能云+SpringBoot=AI对话【人工智能】

对话Chat-千帆大模型平台

AI 对话

以下版本除了最简单的AI对话,还完善了一下功能。

以下是部分代码,完整代码请移步GIT。

v0版本:基础

聊天

v1版本:对话

新建新对话

可以置顶(取消置顶)、删除、修改对应的对话

AI 对话完善【人工智能】_redis

数据表

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版本:回复中

用户发送问题之后,显示回复中,得到回复后显示。

前端发送请求之后,先会得到“回复中”;
之后,去轮询获取最新回复。

后端接受请求之后,先存入到数据库中一个未回复请求。
然后异步得到回复之后,再去更新数据库。

AI 对话完善【人工智能】_User_02

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版本:流式输出

流式输出,终止生成。

前端实现,让消息一个字符一个字符显示

AI 对话完善【人工智能】_ajax_03

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()

AI 对话完善【人工智能】_人工智能_04

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:其他修改

AI 对话完善【人工智能】_User_05

前端样式:跳转到最后一个消息

在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

迎着日光月光星光,直面风霜雨霜雪霜。