前言
最近在做一个项目,项目里面有个AI聊天模块,然后我在网上查了一些文章,没有我想要手把手从头到尾教的文章,所以现在我把我学到的分享给同学们🤓,在本篇本章中你可以学到如何使用SpringAI
调用大模型以及添加记忆上下文,以及Vue
的自定义指令和组合式函数,还有SSE
消息实时推送,实现打字机效果
最后我们会实现这样一个案例:
准备
本案例后端使用Java JDK 17
,Spring AI
前端使用Vue3
、AntDesignVue
API-Key
用的是智谱免费的AI
大模型GLM-4-Flash
你也可以替换成自己需要的
Spring AI的基本使用
介绍
SpringAI
是Spring
框架的一个扩展,用于方便开发者集成AI
调用AI
接口,有点像Langchain
这种人工智能框架,SpringAI
是基于Java
语言的框架,对Java
开发者更友好,类似还有Langchain4j
,Langchain4j
是Langchain
的Java
版本
新建项目
我们新建一个SpringBoot
项目
初始pom.xml
文件内容如下:
<?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>3.3.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ai</groupId>
<artifactId>code</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>code</name>
<description>code</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
运行mvn install
之后再在dependencies
标签同级下添加如下内容注意是**dependencies**
:
<repositories>
<!-- 因为Spring AI还没添加到中央仓库所以要在里程碑仓库下载 -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
之后再运行mvn install
,为什么要这样子一个一个添加呢?因为不这样运行mvn install
就会报错
后面再添加srping-ai
和 spring-ai-zhipuai
的依赖,在dependencies
下添加:
<!-- spring ai起步依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!--智谱ai SDK-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
在dependencies
同级添加:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
运行mvn install
进行安装
添加配置
我们将application
文件改为application.yml
添加以下配置:
server:
port: 8080
spring:
ai:
openai:
api-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 这里替换为你的api-key
base-url: https://open.bigmodel.cn/api/paas/
zhipuai: # 智谱ai的配置
api-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 这里替换为你的api-key
base-url: https://open.bigmodel.cn/api/paas/ # 这里是你调用ai的路径 这里使用的是智谱的
chat:
enabled: true
options:
model: GLM-4-FLASH # 模型 这里使用的是智谱的免费模型
我们点击进入智谱官网注册一个账号,然后获取一下api-key
,添加到配置文件上
之后我们新建一个Controller
,内容如下:
package com.ai.code;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/ai")
@Slf4j
@RequiredArgsConstructor
public class AIController {
private final ZhiPuAiChatModel chatModel;
@GetMapping("/message")
public String getAIMessage(String message) {
// 直接使用call调用
return chatModel.call(message);
}
}
运行项目,我们访问:http://localhost:8080/ai/message?message=hello
然后我们就获取到下面的内容
如果我们想要stream流式返回呢?几个字几个字那样打印出来,我们在controller
中添加如下代码
@GetMapping(value = "/message/stream", params = "message", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getZhiPuAIMessage(@RequestParam String message) {
// 流式返回
return chatModel.stream(message);
}
然后我们打开链接http://localhost:8080/ai/message/stream?message=hello
格式到时候我们处理一下就好了,好了到这你们应该就知道如何使用SpringAI
调用AI
大模型了吧?
挺简单的,这只是基本使用,接下来我们来做案例
后端实现
1.新建项目
我们新建一个SpringBoot
项目并添加spring-ai
和spring-ai-zhipu
的依赖,跟上面介绍SrpingAI
时一样同学们复制过来就可以了,配置也是一样的
2.新建Controller
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@RestController
@RequestMapping("/ai")
@Slf4j
@RequiredArgsConstructor
public class ChatController {
private final ZhiPuAiChatModel chatModel;
// 基于内存的聊天记忆
private final ChatMemory chatMemory = new InMemoryChatMemory();
@GetMapping(value = "/chat", params = "question", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> getZhiPuAIMessage(String question,String sessionId) {
// 使用InMemoryChatMemory进行内存存储记忆 sessionId根据id找对应的记录,只需要最近30条
MessageChatMemoryAdvisor messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, sessionId, 30);
return ChatClient.create(chatModel).prompt().user(question)
.advisors(messageChatMemoryAdvisor) // 查找记录一起发送给大模型
.stream().content().map(
chatResponse -> ServerSentEvent.builder(chatResponse).event("message").build());
}
}
重新启动一下,访问一下http://localhost:8080/ai/message/stream?message=hello
如果有数据则是成功
前端实现
1.新建项目
打开控制台输入pnpm create vue@latest
创建一个最简单的TypeScript
项目
使用cd chat-demo
切换到该项目,然后使用pnpm i
安装依赖,使用code .
命名使用VSCode
打开该项目
使用pnpm dev
运行项目
2.安装Ant-Design-Vue
我们安装一下ant-design-vue
还有@ant-design/icons-vue
和unplugin-vue-components
pnpm add ant-design-vue@4.x
pnpm add @ant-design/icons-vue
3.聊天页面基本样式
App.vue
代码
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import { Button, Flex, Input, InputGroup, Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider, message, Spin } from 'ant-design-vue';
import { ref, useId } from 'vue';
type UserRole = 'system' | 'human';
const [messageApi, contextHolder] = message.useMessage();
// 聊天信息列表
const chatMessages = ref([
{
id: useId(),
role: 'system',
content: "Hi!你好,我是一个爱学习的AI,有什么问题可以问我哦",
},
]);
// 添加聊天信息
const addChatMessage = (role: UserRole, content: string) => {
chatMessages.value.push({ id: useId(), role, content });
};
// 用户输入内容
const userInputContent = ref('');
// 加载状态
const isLoading = ref(false);
// 发送信息
const sendChatMessage = () => {
const inputContent = userInputContent.value.trim();
if (!inputContent) return messageApi.error('请输入要发送的消息');
isLoading.value = true;
addChatMessage('human', inputContent);
userInputContent.value = '';
setTimeout(() => {
addChatMessage('system', '你好,请问有什么需要帮助的?');
isLoading.value = false;
}, 2000);
};
</script>
<template>
<context-holder />
<Layout style="height: 100%;">
<!-- 侧边栏 -->
<LayoutSider class="sider">
<Flex gap="middle" vertical>
<Button type="primary" size="large" style="width: 100%;">
<template #icon>
<PlusOutlined />
</template>
新建对话
</Button>
<Button style="width: 100%;">当前对话</Button>
</Flex>
</LayoutSider>
<Layout>
<!-- 头部 -->
<LayoutHeader class="header">与小Q的对话</LayoutHeader>
<!-- 聊天内容区域 -->
<LayoutContent class="content">
<!-- 聊天列表 -->
<ul class="chat-list">
<li class="chat-item" :class="`chat-item--${msg.role}`" v-for="msg in chatMessages" :key="msg.id">
{{ msg.content }}
</li>
<li class="chat-item chat-item--system" :class="{ hidden: !isLoading }">
<Spin />
</li>
</ul>
</LayoutContent>
<!-- 底部 -->
<LayoutFooter class="footer">
<!-- 聊天输入框 -->
<InputGroup>
<Input v-model:value="userInputContent" @press-enter="sendChatMessage" />
<Button type="primary" @click="sendChatMessage">发送</Button>
</InputGroup>
</LayoutFooter>
</Layout>
</Layout>
</template>
<style lang="scss" scoped>
:deep(.md-editor-preview-wrapper) {
padding: 0;
background-color: transparent;
}
.header {
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
}
.content {
display: flex;
background-color: #fff;
overflow: hidden;
}
.footer {
padding: 16px;
background-color: #fff;
}
.sider {
display: flex;
justify-content: stretch;
padding: 10px;
border-right: 1px solid #f0f0f0;
background-color: #fff;
}
.hidden {
display: none;
}
:deep(.ant-layout-sider-children) {
width: 100%;
}
:deep(.ant-input-group) {
display: flex !important;
}
:deep(.ant-input) {
height: 45px;
}
:deep(.ant-btn) {
height: 45px;
}
.chat-list {
display: flex;
flex-direction: column;
list-style-type: none;
flex: 1;
width: 100%;
padding: 30px !important;
gap: 30px;
margin: 0;
padding: 0;
overflow-y: auto;
.chat-item {
line-height: 30px;
padding: 3px 12px;
border-radius: 10px;
&--human {
background-color: #1677ff;
align-self: flex-end;
:deep(p) {
color: #fff;
}
}
&--system {
align-self: flex-start;
border: 1px solid #f0f0f0;
}
}
}
</style>
我们运行,就会得到这个页面,测试一下
4.请求后端API
如果我们要实现打字机的效果,我们就要使用Server-Sent Events(SSE)协议
它提供了一种从服务器单向推送消息到客户端的机制。这使得服务器能够在有新的数据可用时,自动将数据发送给客户端,而不需要客户端不断地发起请求来获取更新。从而实现打字机效果
我们先安装需要用到的依赖
pnpm add @microsoft/fetch-event-source
相较于EventSource API
,@microsoft/fetch-event-source
更有优势
自带的EventSource
有许多局限:
- 不能传递请求体
body
- 不能传递自定义请求头
- 只能发送
GET
请求方式,无法指定其它方法 - 如果连接被切断,无法进行重试
接着我们在代码中使用
/**会话id*/
const sessionId = useId()
// 发送信息
const sendChatMessage = () => {
const inputContent = userInputContent.value.trim();
if (!inputContent) return messageApi.error('请输入要发送的消息');
// 加载状态
isLoading.value = true;
// 添加用户消息
addChatMessage('human', inputContent);
userInputContent.value = '';
setTimeout(async () => {
// 添加ai消息
addChatMessage('system', '你好,请问有什么需要帮助的?');
// 请求获取ai回复数据
await fetchChatAIData(inputContent)
isLoading.value = false;
}, 2000);
};
/**
* 获取ai消息数据
* @param question 问题
*/
const fetchChatAIData = async (question: string) => {
await fetchEventSource(`http://localhost:8080/ai/chat?question=${question}&sessionId=${sessionId}`, {
headers: {
"Content-Type": "application/json" // 设置请求头
},
// 监听消息
onmessage(event) {
console.log(event);
},
// 关闭
onclose() {
console.log("连接关闭");
},
// 错误
onerror(err) {
console.log('连接错误');
ctrl.abort()
},
// 控制请求取消的信号
signal: ctrl.signal
})
}
我们运行项目看看,我们发送一条消息试试
结果发生了跨域的错误
那我们在SpringBoot
项目中配置一下跨域,添加WebMvcConfig
的配置
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@RequiredArgsConstructor
@Slf4j
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加映射路径,允许所有路径跨域
registry.addMapping("/**")
// 允许所有来源跨域
.allowedOriginPatterns("*")
// 允许所有请求方法跨域
.allowedMethods("*")
// 允许所有请求头跨域
.allowedHeaders("*")
// 是否允许携带凭证(cookies)
.allowCredentials(true);
}
}
我们重启,再看一下前端项目
如我所愿返回了数据
5.处理渲染数据
那我们处理一下吧数据渲染到页面上,因为我们要实现的是打字机的效果,所以不能直接使用addMessage
添加消息,我们先写一个占位符显示后端返回的stream
数据流
先写一个消息占位符
/**占位符 */
const messagePlaceholder = ref('')
接着我们fetchChatAIData
这个方法里,将接收到的消息拼接占位符
const fetchChatAIData = async (question: string) => {
await fetchEventSource(`http://localhost:8080/ai/chat?question=${question}`, {
// ...
// 监听消息
onmessage(event) {
console.log(event);
// 拼接占位符
messagePlaceholder.value += event.data
},
// ...
})
}
然后我们加到信息列表的加载动画前面
<li class="chat-item chat-item--system" :class="{ hidden: !isLoading }">
{{ messagePlaceholder }}
<Spin />
</li>
为了测试方便我将sendMessage
发送消息的方法改成了这样
const sendChatMessage = async () => {
const inputContent = userInputContent.value.trim();
if (!inputContent) return messageApi.error('请输入要发送的消息');
// 加载状态
isLoading.value = true;
// 添加用户消息
addChatMessage('human', inputContent);
userInputContent.value = '';
// 添加ai消息
// addChatMessage('system', '你好,请问有什么需要帮助的?');
// 请求获取ai回复数据
await fetchChatAIData(inputContent)
// isLoading.value = false;
};
接着我们点击进入http://localhost:5173/ 测试看一下(记得启动后端)
嗯是我想要的效果,然后接下来我们添加到消息列表上
const fetchChatAIData = async (question: string) => {
await fetchEventSource(`http://localhost:8080/ai/chat?question=${question}`, {
headers: {
"Content-Type": "application/json" // 设置请求头
},
// 监听消息
onmessage(event) {
console.log(event);
messagePlaceholder.value += event.data
},
// 关闭
onclose() {
console.log("连接关闭");
//添加消息到消息列表上
addChatMessage('system', messagePlaceholder.value);
// 清空占位符
messagePlaceholder.value = ''
isLoading.value = false;
},
// 错误
onerror(err) {
console.log('连接错误');
ctrl.abort()
isLoading.value = false;
},
// 控制请求取消的信号
signal: ctrl.signal
})
}
我们看看效果
是我想要的呢😉,不过有个问题,就是超出聊天框时不会自动滚动到底部
那我们做一下这部分的逻辑吧,在添加消息和接收消息那里添加一行代码就可以
// 发送信息
const sendChatMessage = async () => {
// ...
// 添加用户消息
addChatMessage('human', inputContent);
// 滚动到最后一个元素
messageListRef.value?.lastElementChild?.scrollIntoView(false)
// ...
};
const fetchChatAIData = async (question: string) => {
await fetchEventSource(`http://localhost:8080/ai/chat?question=${question}`, {
// ...
// 监听消息
onmessage(event) {
console.log(event);
messagePlaceholder.value += event.data
// 滚动到最后一个元素
messageListRef.value?.lastElementChild?.scrollIntoView(false)
},
// ...
})
}
不过渲染的格式不对,返回的是markdown
格式的,所以我们要使用markdown
渲染,我们安装一下md-editor-v3
用来渲染markdown
pnpm add md-editor-v3
我们导入使用
import { MdPreview } from 'md-editor-v3';
import 'md-editor-v3/lib/preview.css';
渲染
<li class="chat-item" :class="`chat-item--${msg.role}`" v-for="msg in chatMessages" :key="msg.id">
<MdPreview style="padding: 0; background-color: transparent;" :model-value="msg.content" />
</li>
<li class="chat-item chat-item--system" :class="{ hidden: !isLoading }">
<MdPreview :model-value="messagePlaceholder" />
<Spin />
</li>
在监听消息那里修改一下
// 监听消息
onmessage(event) {
// 在一次或多次出现# 后面添加一个空格
messagePlaceholder.value += event.data.replace(/#+/g, event.data + " ")
// 滚动到最后一个元素
messageListRef.value?.lastElementChild?.scrollIntoView(false)
},
测试一下
不错可以了,但是还是有些问题
6.优化
现在页面有点乱我们优化一下
封装自动滚动指令
我们封装一个自动滚动指令,只要有消息发送就自动滚动到聊天框底部,可以多次复用,只要用到的地方就加一个v-auto-scroll
就可以,不了解Vue3
的自定义指令可以点击进入官方文档进行了解
我们新建一个directive.ts
文件
import type { Directive } from "vue";
/**
* 自动滚动指令
*/
export const autoScrollDirective: Directive = {
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el: HTMLElement, binding) {
if (!binding.arg) throw Error("请传入arg参数")
// 获取arg参数执行对应操作 方便后续扩展
switch (binding.arg) {
case 'bottom':
// 判断是否能滚动
if (!isVerticalScrollable) throw Error('该元素不能纵向滚动 请设置style')
if (!el.lastElementChild) return
// 滚动到最后一个元素
el.lastElementChild.scrollIntoView(false)
break
default:
throw Error("当前该指令仅支持 bottom")
}
}
}
arg
参数是v-auto-scroll:bottom
中的bottom
其中isVerticalScrollable
是一个检查元素是否能纵向滚动的工具函数
/**
* 检查当前元素是否可纵向滚动
* @param element 检查的元素
* @returns
*/
export const isVerticalScrollable = (element: HTMLElement) => {
const style = window.getComputedStyle(element);
return (
style.overflow === 'scroll' ||
style.overflow === 'auto' ||
style.overflowY === 'auto' ||
style.overflowY === 'scroll'
)
}
我们在main.ts
中注册全局指令
const app = createApp(App)
// 注册全局指令
app.directive("auto-scroll", autoScrollDirective)
app.mount('#app')
注册全局指令一定要在app
挂在之前注册,要不然会报错的
我们在模板中使用
<ul class="chat-list" ref="messageListRef" v-auto-scroll:bottom>
<!-- ... -->
</ul>
然后把添加消息之后滚动的代码删掉
messageListRef.value?.lastElementChild?.scrollIntoView(false)
测试一下http://localhost:5173/,可以的,这样就少了两行代码了,这样做的好处就是将来要在其他地方使用的使用,添加以下v-auto-scroll
指令就可以了,比如聊天室页面,还学到了Vue3
的自定义指令
封装请求fetchChatAIData
我们封装fetchChatAIData
组合式函数,新建一个useFetchChatAIData.ts
组合式函数,代码如下:
import { fetchEventSource } from "@microsoft/fetch-event-source"
import { ref, useId } from "vue"
/**
* 抓取聊天ai消息
* @returns
*/
export const useFetchChatAIData = () => {
/** 消息加载状态 */
const isLoading = ref(false)
/** 返回的数据 */
const value = ref('')
const ctrl = new AbortController()
/** 会话id */
const sessionId = useId()
/** 获取函数 */
const fetchChatAIData = async (message: string | number) => {
try {
isLoading.value = true
await fetchEventSource(`http://localhost:8080/ai/chat?question=${message}&sessionId=${sessionId}`, {
headers: {
"Content-Type": "application/json"
},
onmessage(event) {
console.log("message:" + event.data);
value.value = event.data.replace(/#+/g, event.data + " ")
},
onclose() {
console.log("连接关闭");
},
onerror(err) {
ctrl.abort()
},
signal: ctrl.signal
})
} catch (error) {
throw Error('获取信息失败:' + error)
} finally {
isLoading.value = false
}
}
return {
value,
isLoading,
fetchChatAIData
}
}
isLoading
是消息的加载状态,value
是请求的返回消息,fetchChatAIData
是请求数据的方法
我们在App.vue
中使用
const { fetchChatAIData, value, isLoading } = useFetchChatAIData()
然后将App.vue
的fetchChatAIData
和isLoading
删了,我们将onSendMessage
的代码修改:
/**发送信息 */
const sendChatMessage = async () => {
const inputContent = userInputContent.value.trim();
if (!inputContent) return messageApi.error('请输入要发送的消息');
// 添加用户消息
addChatMessage('human', inputContent);
// 清空用户输入框
userInputContent.value = '';
// 请求获取ai回复数据
await fetchChatAIData(inputContent)
// 请求完毕之后追加到消息列表
addChatMessage('system', messagePlaceholder.value)
// 清空占位符
messagePlaceholder.value = ''
};
我们添加一个事件监
// 监听消息变化 修改占位符的值
watch(value, value => {
messagePlaceholder.value = value
})
如果返回的消息发生了变化就修改占位符的值
结果也是可以的,我们又学习了如何封装一个组合式函数,在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。有点像React
的Hook
这样我们实现了一个Web
端的聊天AI
了
总结
总结一下吧
- 在本文章中我们实现了一个网页
AI
聊天机器人 - 学习了如何使用
SpringAI
调用大模型,基于内存的存储上下文记忆 - 学习了前端如何使用
SSE
接收后端返回的stream
流实现打字机效果 - 学习了使用
md-editor-v3
渲染markdown
格式的数据 - 学习了
Vue
的自定义指令 - 学习了如何封装组合式函数
如果有问题可以在评论区分享一下