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

  • 前言
  • 版权
  • 推荐
  • 百度智能云+SpringBoot=AI对话【人工智能】
  • 效果演示
  • 登录
  • AI对话
  • 项目结构
  • 后端开发
  • pom和properties
  • sql_table和entity
  • dao和mapper
  • service和impl
  • config和util
  • LoginController和ChatController
  • 前端开发
  • css和js
  • login.html和chat.html
  • 后言
  • 最后


前言

2024-3-20 19:41:47

使用百度千帆平台的大模型,完成一个简单的AI对话聊天


推荐

Gitee项目地址: 日星月云 / AI对话

GitHub项目地址:jsss-1/qianfan

百度千帆模型初次体验【人工智能】

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

通过上篇,我们成功地完成了初次对大模型的使用
本篇,我将带大家开发一个AI对话聊天框

效果演示

登录

输入用户名,点击登录

返回“登录成功”

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


查询状态

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

AI对话

页面

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

输入内容,点击回车即可提问

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

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


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

项目结构

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

后端开发

pom和properties

pom.xml

SpringBoot2.4.2+JDK8

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>qianfan</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>qianfan</name>
    <description>qianfan</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--        千帆大模型平台-->
        <dependency>
            <groupId>com.baidubce</groupId>
            <artifactId>qianfan</artifactId>
            <version>0.0.1</version>
        </dependency>

        <!--        thymeleaf 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--        lombok 依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--        mysql 依赖-->

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--        mybatis 依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

spring.application.name=qianfan

# Qianfan
QIANFAN_ACCESS_KEY=你的AK
QIANFAN_SECRET_KEY=你的SK

# ServerProperties
server.port=8080
server.servlet.context-path=

# ThymeleafProperties
spring.thymeleaf.cache=false

# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/ai?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.jsss.entity
# 生成主键
mybatis.configuration.useGeneratedKeys=true
# 驼峰命名
mybatis.configuration.mapUnderscoreToCamelCase=true

sql_table和entity

ai.sql

create table conversation
(
    id           int auto_increment
        primary key,
    username     varchar(16) null,
    user_message text        null,
    bot_message  text        null,
    create_time  varchar(32) null
);

**enntity.Conversation **

package com.jsss.qianfan.entity;



import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Conversation {

    private Integer id;

    private String username;

    private String userMessage;

    private String botMessage;

    private String createTime;

}

dao和mapper

**dao.ChatMapper **

package com.jsss.qianfan.dao;

import com.jsss.qianfan.entity.Conversation;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface ChatMapper {

    List<Conversation> getByUsername(String username);

    void insert(Conversation conversation);


}

**mapper/ChatMapper.xml **

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.jsss.qianfan.dao.ChatMapper">

    <!-- 定义SQL映射关系 -->


    <!-- 根据username查询conversation记录 -->
    <select id="getByUsername" resultType="com.jsss.qianfan.entity.Conversation" parameterType="string">
        SELECT * FROM conversation WHERE username = #{username}
    </select>



    <!-- 插入新的conversation记录 -->
    <insert id="insert" parameterType="com.jsss.qianfan.entity.Conversation">
        INSERT INTO conversation     (
            username, user_message, bot_message, create_time
        ) VALUES (
                     #{username},
                     #{userMessage},
                     #{botMessage},
                     #{createTime}
                 )
    </insert>




</mapper>

service和impl

service.ChatService

package com.jsss.qianfan.service;

import com.jsss.qianfan.entity.Conversation;

import java.util.List;

public interface ChatService {

    void addChat(Conversation conversation);

    List<Conversation> searchByUsername(String username);

}

impl.ChatServiceImpl

package com.jsss.qianfan.service.impl;

import com.jsss.qianfan.dao.ChatMapper;
import com.jsss.qianfan.entity.Conversation;
import com.jsss.qianfan.service.ChatService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ChatServiceImpl implements ChatService {

    @Autowired
    ChatMapper chatMapper;

    @Override
    public void addChat(Conversation conversation) {
        chatMapper.insert(conversation);
    }

    @Override
    public List<Conversation> searchByUsername(String username) {
        return chatMapper.getByUsername(username);
    }
}

config和util

configuration.QianfanConfig

package com.jsss.qianfan.configuration;


import com.baidubce.qianfan.Qianfan;
import com.baidubce.qianfan.core.auth.Auth;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QianfanConfig {

    @Value("${QIANFAN_ACCESS_KEY}")
    String ak;

    @Value("${QIANFAN_SECRET_KEY}")
    String sk;

    @Bean
    public Qianfan qianFan() {
        return new Qianfan(Auth.TYPE_OAUTH, ak, sk);
    }
}

util.QianfanUtil

package com.jsss.qianfan.util;

import com.baidubce.qianfan.Qianfan;
import com.baidubce.qianfan.model.chat.ChatResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class QianfanUtil {

    @Autowired
    Qianfan qianfan;

    public String addMessage(String content) {

        ChatResponse response = qianfan.chatCompletion()
                //.model("ERNIE-Bot-4")  //使用model指定预置模型 默认模型是ERNIE-Bot-turbo
                .addMessage("user", content) // 添加用户消息 (此方法可以调用多次,以实现多轮对话的消息传递)
                .temperature(0.7) // 自定义超参数
                .execute(); // 发起请求


        return response.getResult();

    }
    public void executeStream(String content) {

        qianfan.chatCompletion()
                .addMessage("user", content)
                .executeStream() // 发起流式请求
                .forEachRemaining(chunk -> System.out.print(chunk.getResult())); // 流式迭代,并打印消息


    }



}

LoginController和ChatController

controller.LoginController

package com.jsss.qianfan.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login(){
        return "login";
    }

    @PostMapping("/login")
    @ResponseBody
    public String login(HttpServletRequest request,String username){
        HttpSession session = request.getSession();
        session.setAttribute("username",username);
        return "登录成功";
    }

    @GetMapping("/logout")
    @ResponseBody
    public String logout(HttpServletRequest request,String username){
        HttpSession session = request.getSession();
        session.removeAttribute("username");
        return "登出成功";
    }


    @GetMapping("/status")
    @ResponseBody
    public String status(HttpServletRequest request){
        HttpSession session = request.getSession();
        String username =(String)session.getAttribute("username");
        if (username!=null&&!username.isEmpty()){
            return username;
        }else {
            return "没有登录";
        }
    }
}

controller.ChatController

package com.jsss.qianfan.controller;

import com.jsss.qianfan.entity.Conversation;
import com.jsss.qianfan.service.ChatService;
import com.jsss.qianfan.util.QianfanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;


import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;


@Controller
@RequestMapping("chat")
public class ChatController {




//    List<Conversation> conversations=new ArrayList<>();
//
//    static int id=1;
//
//    {
//        conversations.add(new Conversation(id++,"1","你好","抱歉,网络出现异常,请你重试或联系客服!TooManyRequests", format(new Date())));
//        conversations.add(new Conversation(id++,"1","你好","抱歉,网络出现异常,请你重试或联系客服!TooManyRequests", format(new Date())));
//    }

    @Autowired
    QianfanUtil qianfanUtil;

    @Autowired
    ChatService chatService;

    private String format(Date date){
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
    }



    @GetMapping("/list")
    public String getChat(HttpServletRequest request,Model model){
        String username= (String) request.getSession().getAttribute("username");
        List<Conversation> conversations=chatService.searchByUsername(username);
        model.addAttribute("conversations",conversations);
        return "chat";
    }



    @PostMapping("/chat")
    public String chat(HttpServletRequest httpServletRequest,@RequestBody Map<String, String> request){
        String username= (String) httpServletRequest.getSession().getAttribute("username");

        String content = request.get("content");

        System.out.println(content);

//        String res="回复";
//        Conversation conversation = new Conversation(id++,username, content, res, format(new Date());
//        conversations.add(conversation);

         String res = qianfanUtil.addMessage(content);
        Conversation conversation = new Conversation(null, username, content, res, format(new Date()));
        chatService.addChat(conversation);

        return "redirect:list";
    }


}

前端开发

css和js

css/style.css

/* 设置用户发送消息的样式 */
.user-message {
    background-color: #4CAF50; /* 绿色背景 */
    color: white;
    padding: 10px;
    margin: 10px;
    border-radius: 10px;
    white-space: pre;
}

/* 设置ChatGPT发送消息的样式 */
.bot-message {
    background-color: #f2f2f2; /* 灰色背景 */
    padding: 10px;
    margin: 10px;
    border-radius: 10px;
    white-space: pre;
}

.question-container {
    display: flex;
    justify-content: flex-end;
}

.question {
    display: flex;
    flex-direction: row;
    align-items: center;
}

.question td:first-child {
    margin-left: auto;
}

.answer-container {
    display: flex;
    justify-content: flex-start;
}

.answer {
    display: flex;
    flex-direction: row;
    align-items: center;
}

.answer td:last-child {
    margin-left: auto;
}

/* 设置提问和回复消息的表格样式 */
.question, .answer {
    display: flex;
    align-items: center;
    padding: 10px;
    border-radius: 10px;
}

/* 设置输入框和发送按钮的样式 */
.form-container {
    display: flex;
    justify-content: center;
    align-items: center;
    position: fixed;
    bottom: 0;
    width: 100%;
    padding: 10px;
    background-color: #f9f9f9;
    border-top: 1px solid #ccc;
}

.form-row {
    display: flex;
    flex: 1;
}

.form-group {
    flex: 1;
    margin-right: 10px;
}

.form-group input {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    outline: none;
}


.message-container {
    max-height: 700px; /* 设置最大高度,超出部分可滚动 */
    overflow-y: auto; /* 竖直方向溢出部分可滚动 */
}

.send-message-container {
    flex: 1; /* 占据剩余空间 */
    display: flex;
    align-items: center;
    background-color: #f5f5f5;
}

textarea {
    width: 1800px; /* 设置输入框的宽度为300像素,您可以根据需要调整这个值 */
    height: 100px; /* 设置输入框的高度为200像素,您可以根据需要调整这个值 */
    font-size: 16px; /* 设置输入框中文字的字体大小为16像素,您可以根据需要调整这个值 */
    resize: none; /* 禁止用户调整输入框的尺寸 */
}

js/onload.js

window.onload = function() {
    // 找到消息容器
    var messageContainer = document.querySelector(".message-container");

    // 找到消息容器中最后一个子元素
    var lastMessage = messageContainer.lastElementChild;

    // 将最后一个子元素滚动到可见区域
    lastMessage.scrollIntoView();
};

js/textarea.js

var textarea = document.getElementById("messageInput");

textarea.addEventListener("keydown", function(event) {
    if (event.key === "Enter" && !event.shiftKey) {
        event.preventDefault();

        var message = textarea.value.trim();
        textarea.value = "";

        // 发送 POST 请求
        fetch('/chat/chat', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ content: message })
        }).then(function(response) {
            // 刷新页面
            location.reload();
        });
    }
});

textarea.addEventListener("keydown", function(event) {
    if (event.key === "Enter" && event.shiftKey) {
        // 在 Shift+Enter 情况下允许换行
        textarea.value += "\n";
        event.preventDefault();
    }
});

login.html和chat.html

html/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
	<head>
		<title>AI对话</title>
	</head>

	<body>
		<div class="chat-container">
			<h1 class="title">登录</h1>

			<div class="login-container">
				<form th:action="@{/login}" method="post">
					<div class="form-container">
						<div class="form-row">
                             <span class="form-group no-border">
                                <input id="username" name="username" placeholder="输入用户名">
                             </span>
							<button>登录</button>
						</div>
					</div>
				</form>
			</div>


		</div>
	</body>


</html>

html/chat.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
	<head>
		<link rel="stylesheet" th:href="@{/css/style.css}">
		<title>AI对话</title>
	</head>

	<body>
		<div class="chat-container">
			<h1 class="title">AI 对话</h1>

			<div class="message-container">
				<!-- 用户消息和ChatGPT消息显示部分 -->
				<div th:each="conversation:${conversations}">

					<div class="question-container">
						<table class="question">
							<td>
								<span th:utext="${conversation.createTime}"></span>
								<div class="user-message" th:utext="${conversation.userMessage}"></div>
							</td>
							<td type="text" th:text="${conversation.username}">提问</td>
						</table>
					</div>

					<div class="answer-container">
						<table class="answer">
							<td type="text">AI</td>
							<td>
								<span th:utext="${conversation.createTime}"></span>
								<div class="bot-message" th:utext="${conversation.botMessage}"></div>
							</td>
						</table>
					</div>
				</div>
			</div>


			<div class="send-message-container">
				<!-- 发送消息部分 -->
				<form th:action="@{/chat/chat}" method="post">
					<div class="form-container">
						<div class="form-row">
                             <span class="form-group no-border">
                                <textarea id="messageInput" placeholder="问我任何问题...(Shift + Enter 换行,按下Enter 发送)"></textarea>
                             </span>
						</div>
					</div>
				</form>
			</div>


		</div>
	</body>


	<script th:src="@{/js/onload.js}"></script>

	<script th:src="@{/js/textarea.js}"></script>

</html>

后言

待完善的功能:

  1. 用户对话之后,需要等待回复,才能弹出对话内容
  2. 等待期间,还能输入聊天框
  3. 并且,没有终止生成
  4. 没有左边框-新建对话
  5. 没有md格式的复制

最后

2024-3-20 21:06:39

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