前端实现
在学习了之前的SpringAI基础功能SSE流、内存历史消息基础上,整合前端界面和数据库版本的历史消息。
前端的聊天面板组件是以
chat-view.vue
为核心,聊天面板的结构如下
- 聊天面板 - 会话面板(左) - 标题(上)- 会话列表(中),进入页面的时候获取用户的会话列表使用
session-item
组件,该组件有删除按钮点击触发handleDeleteSession
- 创建会话按钮(下), 触发点击事件调用handleCreateSession
- 消息面板(右) - 会话详情(上),可编辑会话名称调用handleUpdateSession
- 消息列表(中),for循环遍历activeSession
当前会话中的消息使用message-row
组件展示文本消息、图片消息、语音消息- 输入框使用(下),message-input
组件
会话列表
for循环遍历会话列表用会话组件显示,并监听点击事件和删除事件。点击时切换到被点击的会话,删除时从会话列表中提出被删除的会话。
<!-- 左侧的会话列表 --><divclass="session-panel"><divclass="title">AI助手</div><divclass="session-list"v-if="activeSession"><!-- 遍历会话列表 --><session-itemv-for="session in sessionList":key="session.id":active="session.id === activeSession.id":session="session"class="session"@click="activeSession = session"@delete="handleDeleteSession"></session-item></div><divclass="button-wrapper"><el-buttonstyle="margin-right: 20px":icon="ChatRound"size="small"@click="handleSessionCreate">创建会话</el-button></div></div>
会话的创建、删除、加载逻辑。
const activeSession =ref<AiSession>()const sessionList =ref<AiSession[]>([])consthandleCreateSession=async(session: AiSessionInput)=>{const res =await api.aiSessionController.save({ body: session })const sessionRes =await api.aiSessionController.findById({ id: res })
sessionList.value.unshift(sessionRes)
activeSession.value = sessionList.value[0]}// 从会话列表中删除会话consthandleDeleteSession=async(session: AiSession)=>{await api.aiSessionController.delete({ body:[session.id]})const index = sessionList.value.findIndex((value)=>{return value.id === session.id
})
sessionList.value.splice(index,1)if(index == sessionList.value.length){
activeSession.value = sessionList.value[index -1]}else{
activeSession.value = sessionList.value[index]}}onMounted(async()=>{// 查询自己的聊天会话
api.aiSessionController.findByUser().then((res)=>{// 讲会话添加到列表中
sessionList.value = res.map((row)=>{return{...row, checked:false}})// 默认选中的聊天会话是第一个if(sessionList.value.length >0){
activeSession.value = sessionList.value[0]}else{handleSessionCreate()}
loading.value =false})})
消息发送和展示
<!-- 监听发送事件 --><message-input@send="handleSendMessage"v-if="activeSession"></message-input>
用户发送的逻辑,使用
sse.js
发送消息,并监听sse消息。
consthandleSendMessage=async(message:{ text:string; image:string})=>{if(!activeSession.value){
ElMessage.warning('请创建会话')return}// 图片/语音const medias: AiMessage['medias']=[]if(message.image){
medias.push({ type:'image', data: message.image })}// 用户的提问const chatMessage ={
id:newDate().getTime().toString(),
sessionId: activeSession.value.id,
medias,
textContent: message.text,
type:'USER'} satisfies AiMessage
// 新建一个ChatGPT回复对象,不能重复使用同一个对象。
responseMessage.value ={
id:newDate().getTime().toString(),
type:'ASSISTANT',
textContent:'',
sessionId: activeSession.value.id
}const evtSource =newSSE(import.meta.env.VITE_API_PREFIX+'/message/chat',{
withCredentials:true,// 禁用自动启动,需要调用stream()方法才能发起请求
start:false,
headers:{'Content-Type':'application/json'},
payload:JSON.stringify(chatMessage),
method:'POST'})
evtSource.addEventListener('message',async(event:any)=>{
responseMessage.value.textContent = event.data
// 非通义千问使用下面的消息拼接逻辑// responseMessage.value.textContent += event.data})// 调用stream,发起请求。
evtSource.stream()// 将两条消息显示在页面中
activeSession.value.messages.push(...[chatMessage, responseMessage.value])awaitnextTick(()=>{
messageListRef.value?.scrollTo(0, messageListRef.value.scrollHeight)})}
遍历会话中的消息并展示
<divref="messageListRef"class="message-list"><!-- 过渡效果 --><transition-groupname="list"v-if="activeSession"><message-rowv-for="message in activeSession.messages":key="message.id":message="message"></message-row></transition-group></div>
版权归原作者 起凡7 所有, 如有侵权,请联系我们删除。