java接入AI大模型个人实践(二)
大家好,你的月亮我的心,我是博主小阿金,欢迎各位工友。 今天浅浅的讲一下前端如何接收流式接口,由于本人是个前端小白,以下功能只实现了基础的对话交流,具体的交互和样式暂且不考虑。
直接上代码
<template>
<div class="app-container">
<!-- Welcome Message -->
<div class="welcome-message">
<p>您好!我是您的数字助理</p>
<p>我可以为你解答各类问题、生成图片、总结文档等。</p>
<p>有任何需要,请随时对我说~~😉</p>
</div>
<!-- Conversation Container -->
<div class="conversation-container">
<!-- Messages Loop -->
<div v-for="(msg, index) in messages" :key="index" :class="msg.type">
<div class="avatar" v-if="msg.type === 'user-message'">
<!-- User Message Content -->
<div class="message-content user-message-content">
<div v-html="md.render(msg.content)"></div>
</div>
<!-- User Avatar -->
<div class="avatar-icon user-avatar">
:<svg-icon icon-class="user" />
</div>
</div>
<div class="avatar" v-else>
<!-- System Avatar -->
<div class="avatar-icon system-avatar">
<svg-icon icon-class="assistant" />:
</div>
<!-- System Message Content -->
<div class="message-content system-message-content">
<div v-html="md.render(msg.content)"></div>
</div>
</div>
</div>
</div>
<!-- Input Field for User's Question with Welcome Message -->
<div class="question-container">
<div class="welcome-message-input">
<el-input v-model="question" type="textarea" placeholder="请输入您的问题" />
</div>
</div>
<!-- Send Button -->
<el-button @click="sendChatMessage" type="primary">发送</el-button>
</div>
</template>
<script setup>
import { historyList } from "@/api/system/chat";
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { getToken } from '@/utils/auth';
import { ref, reactive, onUnmounted } from 'vue';
import { onMounted } from 'vue';
import { md, initClipboard } from '@/utils/markdownSetup';
const baseURL = import.meta.env.VITE_APP_BASE_API;
const question = ref('');
const messages = ref([]);
const controller = new AbortController();
const toolCallId = ref(''); // 存储 toolCallId
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 3,
}
});
// 获取历史对话
async function gethistoryList() {
const response = await historyList(data.queryParams);
if (response.code === 200) {
response.rows.forEach(item => {
// 添加用户提问到messages中
messages.value.push({
type: 'user-message',
content: item.question,
timestamp: new Date(item.createTime).toLocaleString(),
isHistory: true // 标记为历史记录
});
// 添加系统回复到messages中
messages.value.push({
type: 'system-message',
content: item.answer,
timestamp: new Date(item.createTime).toLocaleString(),
isHistory: true // 标记为历史记录
});
});
}
}
function sendChatMessage() {
const signal = controller.signal;
// 添加用户提问到messages中
messages.value.push({
type: 'user-message',
content: question.value,
timestamp: getCurrentTime()
});
let systemReply = '';
fetchEventSource(`${baseURL}/system/chat/getChat`, {
method: 'POST',
body: JSON.stringify({ question: question.value, sessionId: toolCallId.value }), // 使用 toolCallId
headers: {
'Authorization': 'Bearer ' + getToken(),
'Content-Type': 'application/json'
},
signal,
onopen() { },
onerror(event) { },
onmessage(event) {
const data = JSON.parse(event.data);
const content = data.output.choices[0].message.content;
systemReply += content;
// 实时更新系统消息
const lastSystemMessageIndex = messages.value.findIndex(msg => msg.type === 'system-message' && !msg.complete && !msg.isHistory);
if (lastSystemMessageIndex !== -1) {
messages.value[lastSystemMessageIndex].content = systemReply;
messages.value[lastSystemMessageIndex].timestamp = getCurrentTime();
} else {
messages.value.push({
type: 'system-message',
content: systemReply,
complete: false,
timestamp: getCurrentTime()
});
}
// 更新 toolCallId
toolCallId.value = data.output.choices[0].message.toolCallId;
// 自动滚动到底部
scrollToBottom();
},
onclose() {
// 标记最后一条系统消息为完成
const lastSystemMessageIndex = messages.value.findIndex(msg => msg.type === 'system-message' && !msg.complete && !msg.isHistory);
if (lastSystemMessageIndex !== -1) {
messages.value[lastSystemMessageIndex].complete = true;
}
}
});
// 清空输入框
question.value = '';
};
// 渲染Markdown内容
const renderMarkdown = (content) => {
return md.render(content, 'java');
};
// 组件销毁前关闭EventSource连接
onUnmounted(() => {
controller.abort();
});
// 滚动到底部函数
function scrollToBottom() {
const container = document.querySelector('.conversation-container');
if (container) {
container.scrollTop = container.scrollHeight;
}
};
// 获取当前时间
function getCurrentTime() {
const now = new Date();
return `${now.getHours()}:${now.getMinutes()} ${now.getHours() >= 12 ? 'PM' : 'AM'}`;
};
onMounted(() => {
initClipboard(); // 初始化Clipboard.js
});
gethistoryList()
</script>
<style scoped>
.welcome-message {
margin: 20px 0;
text-align: center;
}
.question-container {
text-align: center;
}
.welcome-message-input {
margin-bottom: 10px;
}
.user-message,
.system-message {
display: flex;
}
.user-message .avatar,
.system-message .avatar {
display: flex;
}
.user-message .message-content,
.system-message .message-content {
padding: 1px 5px;
/* 调整内边距以提升视觉效果 */
border-radius: 5px;
/* 圆角边框 */
display: inline-flex;
/* 使用inline-flex以支持内容自适应并保持内部元素的对齐 */
justify-content: center;
/* 水平居中显示内部元素 */
align-items: center;
/* 垂直居中显示内部元素 */
background-color: #f2f2f2;
/* 用户消息背景色 */
min-width: 70px;
/* 最小宽度设置为70px,确保短消息不会显示得太窄 */
max-width: 60%;
}
.system-message .message-content {
background-color: #e6f7ff;
}
.user-message {
justify-content: flex-end;
/* 用户消息右对齐 */
}
.system-message {
justify-content: flex-start;
/* 系统消息左对齐 */
}
.copy-btn:hover {
background-color: #303f9f;
/* 鼠标悬停时颜色变化 */
}
.hljs {
position: relative;
}
.hljs code {
display: block;
padding: 1em;
}
.code-block {
position: relative;
}
.copy-btn {
display: flex;
align-items: center;
cursor: pointer;
}
.copy-btn svg {
fill: currentColor;
width: 1em;
height: 1em;
}
</style>
fetchEventSource函数释义:这段代码是一个事件源的实现,用于向指定URL发送POST请求获取聊天内容
- fetchEventSource函数:调用fetchEventSource函数来发送POST请求到
${baseURL}/system/chat/getChat
,并传递必要的参数。在请求中包括问题(question)和会话ID(sessionId)。 - Headers:设置请求头部,包括Authorization和Content-Type。Authorization使用Bearer加上getToken()函数返回的令牌,Content-Type设置为application/json。
- Signal:传递信号(signal)以便控制请求的终止。
- onopen()、onerror(event)、onmessage(event)、onclose():定义了不同事件发生时的处理函数。其中,onmessage处理从服务端返回的数据,解析JSON数据中的内容(content),更新系统回复(systemReply),更新消息列表(messages),更新工具调用ID(toolCallId),并自动滚动到底部。onclose用于标记最后一条系统消息为已完成。
- 清空输入框:在请求发送后,将输入框中的内容清空,以便用户继续输入新问题。
通过不断向服务器发送请求获取系统回复,并将回复展示在页面上。
总结
至此个人项目的简单接入AI大模型就告一段落,其中对于流式的输出与响应让博主花了一点时间,归根到底还是技术不够扎实,等博主在深耕一下前端技术领域,在回来更新样式和交互。
转载自:https://juejin.cn/post/7396933497947193396