【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 问答测试

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

🍹 四、章末

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

相关推荐
Asort16 分钟前
JavaScript 从零开始(六):控制流语句详解——让代码拥有决策与重复能力
前端·javascript
无双_Joney35 分钟前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥36 分钟前
前端必学的 CSS Grid 布局体系
前端·css
ccnocare38 分钟前
选择文件夹路径
前端
艾小码38 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月39 分钟前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁43 分钟前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅43 分钟前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸44 分钟前
Prompt结构化输出:从入门到精通的系统指南
前端