【AI】⭐️搭建一个简单的个人问答网页

目录

🍸前言

🍻一、环境配置

🍹二、具体实现

[2.1 接口改动](#2.1 接口改动)

[2.2 静态页面](#2.2 静态页面)

🍸三、测试

[3.1 页面如下](#3.1 页面如下)

[3.2 问答测试](#3.2 问答测试)

[​🍹 四、章末](#🍹 四、章末)


🍸前言

小伙伴们大家好,上次在本地测试了智普大模型提供的免费 api ,并且在控台实现了结果的流式输出,文章链接如下:

【AI】✈️实现一个简单的问答服务-CSDN博客

但是只看控台还是不太行,费眼,鄙人对 html、css、js 这些也只是略懂一点,但是足以在已有接口的基础上改动下,搭配一个简易的前端页面,实现一个问答服务

🍻一、环境配置

因为目前后端的接口环境已经有了,所以直接将静态页面跟后端接口放在一起,属于前后端不分离的项目

后端环境:jdk8、springBoot

前端使用:html、css、js

大模型使用的依然是智普,可以看下之前的文章,申请个账号即可,比较简单

🍹二、具体实现

2.1 接口改动

接口会返回一个 SseEmitter(Server-Sent Events,服务器推送事件)用于在服务器与客户端之间进行流式数据传输;

这里使用的 client 实例,可以参考上篇文章,为智普提供的操作api的实例;

请求进来后,会打印下请求参数,然后新创建一个线程去异步执行,提高响应效率;

java 复制代码
    @GetMapping("/testAi2")
    public SseEmitter testAi2(@RequestParam("question") String question) {
        // 创建 SseEmitter 对象,用于流式推送
        SseEmitter emitter = new SseEmitter();
        System.out.println("请求进来了,对应的问题是: " + question);

        // 创建新的线程来处理流式数据
        new Thread(() -> {
            try {
                // 获取流式数据
                List<ChatMessage> messages = new ArrayList<>();
                ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), question);
                messages.add(chatMessage);
                String requestId = UUID.randomUUID().toString();
                ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                        .model(Constants.ModelChatGLM4)
                        .stream(Boolean.TRUE)
                        .messages(messages)
                        .requestId(requestId)
                        .build();

                ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);

                if (sseModelApiResp.isSuccess()) {
                    AtomicBoolean isFirst = new AtomicBoolean(true);

                    // 将流数据映射到累加器并推送到前端
                    mapStreamToAccumulator(sseModelApiResp.getFlowable(), chatMessage)
                            .doOnNext(accumulator -> {

                                if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
                                    // 通过 SSE 推送数据到前端
                                    try {
                                        emitter.send(accumulator.getDelta().getContent(), MediaType.TEXT_PLAIN);
                                    } catch (IOException e) {
                                        System.err.println("发送数据失败: " + e.getMessage());
                                        emitter.completeWithError(e);  // 发生错误时,关闭连接
                                    }
                                }
                            })
                            .doOnComplete(() -> {
                                try {
                                    // 在流完成时,发送 'end' 事件
                                    emitter.send("end", MediaType.TEXT_PLAIN); // 发送 'end' 标志
                                    emitter.complete(); // 完成 SSE 流
                                } catch (IOException e) {
                                    System.err.println("完成流时发送数据失败: " + e.getMessage());
                                    emitter.completeWithError(e);  // 发生错误时,关闭连接
                                }
                            })
                            .blockingLast();
                }
            } catch (Exception e) {
                System.err.println("处理过程中发生错误: " + e.getMessage());
                emitter.completeWithError(e);
            }
        }).start();

        return emitter;
    }
2.2 静态页面

css,js 跟 html 代码放到一起的,具体位置要在 resources/static 下面,这样项目启动后可以访问到这些静态资源;代码具体如下:因为对具体的使用不是很清楚,整体的搭建也是在 chatGpt 的帮助下,一步一步调试完善的

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AI Response</title>
  <style>
    body {
      font-family: 'Arial', sans-serif;
      background-color: #f4f7fc;
      color: #333;
      margin: 0;
      padding: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      flex-direction: column;
    }

    h1 {
      font-size: 2.5rem;
      color: #5c6bc0;
      margin-bottom: 20px;
    }

    .container {
      background-color: white;
      border-radius: 8px;
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
      padding: 30px;
      width: 100%;
      max-width: 600px;
      text-align: center;
    }

    input[type="text"] {
      width: 80%;
      padding: 12px;
      font-size: 1rem;
      border: 2px solid #ccc;
      border-radius: 4px;
      margin-bottom: 15px;
      transition: border-color 0.3s;
    }

    input[type="text"]:focus {
      border-color: #5c6bc0;
      outline: none;
    }

    button {
      padding: 12px 20px;
      font-size: 1rem;
      color: white;
      background-color: #5c6bc0;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      transition: background-color 0.3s;
    }

    button:hover {
      background-color: #4f5a92;
    }

    #response {
      display: none;
      background-color: #e3f2fd;
      border: 1px solid #90caf9;
      padding: 15px;
      border-radius: 4px;
      margin-top: 20px;
      text-align: left;
      max-height: 400px;
      overflow-y: auto;
      white-space: pre-line; /* 保持换行 */
      font-size: 1rem;
      color: #1e88e5;
    }

    /* 头像样式 */
    #wechat-avatar {
      position: fixed;
      bottom: 20px;
      right: 20px;
      width: 150px;  /* 调整头像大小 */
      height: 150px;
      border-radius: 50%;
      border: 3px solid #fff; /* 给头像加白色边框 */
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 轻微阴影 */
      cursor: pointer;
    }

  </style>
</head>
<body>
<h1>Ask the AI</h1>
<div class="container">
  <input type="text" id="question" placeholder="请输入问题" />
  <button id="askButton">提问</button>
  <div id="response"></div>
</div>

<!-- 微信头像,假设头像文件存放在 'images' 文件夹下 -->
<img id="wechat-avatar" src="uploads/weixinPic.jpg" alt="微信头像" title="点击联系我" onclick="window.open('https://blog.csdn.net/TM007_?spm=1000.2115.3001.5343');" />

<script>
  document.getElementById('askButton').addEventListener('click', function () {
    const question = document.getElementById('question').value.trim();
    if (!question) {
      alert("请输入一个问题!");
      return;
    }

    const responseDiv = document.getElementById('response');
    responseDiv.style.display = 'none'; // 隐藏答案区域,等待新问题的响应

    // 清空之前的回答
    responseDiv.innerHTML = '';

    // 创建 EventSource 对象
    const eventSource = new EventSource(`http://localhost:8888/test/testAi2?question=${encodeURIComponent(question)}`);

    // 监听 SSE 数据流
    eventSource.onmessage = function(event) {
      const data = event.data;
      if (data) {
        responseDiv.textContent += data; // 用 textContent 代替 innerHTML 以避免注入
        responseDiv.style.display = 'block'; // 显示答案区域
      }
    };

    // 监听 SSE 流的结束
    eventSource.addEventListener('end', function(event) {
      responseDiv.textContent += "\n回答完毕";  // 使用 \n 来保证换行
      eventSource.close(); // 关闭 SSE 连接
    });

    // 监听错误事件
    eventSource.onerror = function(event) {
      // 检查是否是正常关闭的连接
      if (eventSource.readyState === EventSource.CLOSED) {
        return;  // 如果连接已经被关闭,就不做任何处理
      }

      console.error("SSE 错误:", event);  // 输出错误日志以便调试
      eventSource.close(); // 关闭 SSE 连接,避免触发重复的错误
    };
  });
</script>
</body>
</html>

🍸三、测试

启动后端项目,浏览器访问端口号+静态资源,本地地址如下:

http://localhost:8888/index2.html

3.1 页面如下

一个问题输入框,可点击的按钮,一个横幅,加上 右下角的图片可以点击跳转,具体跳转到哪的,不明说了!

3.2 问答测试

输入问题,点击提问,流式输出的整体速度还好,后端记录也存在

🍹 四、章末

本地址是简单实现了页面上展示的功能,其中包含的问题还有很多,但是也能使用,留给后面在做更新,文章到这里既结束了~

相关推荐
努力往上爬de蜗牛3 分钟前
el-table ToggleRowSelection实现取消选中没效果(virtual-scroll)
前端·javascript·elementui
云空27 分钟前
《Python WEB安全 库全解析》
前端·python·安全·web安全
Jane - UTS 数据传输系统29 分钟前
处理VUE框架中,ElementUI控件u-table空值排序问题
前端·vue.js·elementui·字符串 空值处理
hshpy37 分钟前
why spring boot not load NacosConfigBootstrapConfiguration class
java·spring boot·后端
lauo39 分钟前
【智体OS】官方上新发布智体机器人:使用rtrobot智体应用远程控制平衡车机器人
前端·javascript·机器人·开源
风度前端39 分钟前
Mac键指如飞攻略之终端alias配置
前端
sanguine__1 小时前
javaScript学习
前端·javascript·学习
niech_cn1 小时前
前端项目初始化搭建(二)
前端
码猩1 小时前
pyhton 批量往PDF文件指定位置里面填写数据
前端·python·pdf
一枚前端小姐姐1 小时前
获取地址栏参数并重定向
前端·javascript·vue.js