Files
happy-life-star/web/src/stores/chat.ts
T

358 lines
9.9 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import type { ChatMessage, ChatSession } from '@/types'
import webSocketService, { type WebSocketMessage, type ConnectionStatus } from '@/services/websocket'
import { useUserStore } from './user'
import { chatApi } from '@/services/chat'
export const useChatStore = defineStore('chat', () => {
const userStore = useUserStore()
// 聊天状态
const currentSession = ref<ChatSession | null>(null)
const messages = ref<ChatMessage[]>([])
const sessions = ref<ChatSession[]>([])
const isTyping = ref(false)
const isConnected = ref(false)
const connectionStatus = ref<ConnectionStatus>('DISCONNECTED')
const wsConnected = ref(false)
// 方法
const addMessage = (message: Omit<ChatMessage, 'id' | 'timestamp'>) => {
const newMessage: ChatMessage = {
...message,
id: Date.now().toString(),
timestamp: new Date().toISOString(),
status: message.type === 'user' ? 'sending' : 'sent'
}
messages.value.push(newMessage)
return newMessage
}
// 更新消息状态
const updateMessageStatus = (messageId: string, status: ChatMessage['status'], error?: string) => {
const message = messages.value.find(m => m.id === messageId)
if (message) {
message.status = status
if (error) {
message.error = error
}
}
}
// 发送消息:WebSocket推送+数据库保存
const sendMessage = async (content: string) => {
if (!wsConnected.value) {
console.error('WebSocket未连接,无法发送消息')
addMessage({
content: '连接已断开,请刷新页面重试。',
type: 'ai',
sessionId: currentSession.value?.id
})
return
}
// 添加用户消息
const userMessage = addMessage({
content,
type: 'user',
sessionId: currentSession.value?.id
})
try {
// WebSocket推送
webSocketService.sendChatMessage(content, currentSession.value?.id)
// 更新消息状态为已发送
updateMessageStatus(userMessage.id, 'sent')
// 数据库保存
if (currentSession.value?.id && userStore.user?.id) {
await chatApi.createMessage({
conversationId: currentSession.value.id,
userId: userStore.user.id,
content,
contentType: 'TEXT',
senderType: 'USER',
senderId: userStore.user.id
})
// 更新消息状态为已送达
updateMessageStatus(userMessage.id, 'delivered')
}
} catch (error) {
console.error('消息发送或保存失败:', error)
// 更新消息状态为失败
updateMessageStatus(userMessage.id, 'failed', '发送失败')
addMessage({
content: '抱歉,消息发送失败,请稍后重试。',
type: 'ai',
sessionId: currentSession.value?.id
})
}
return userMessage
}
// 创建会话:同步后端
const createSession = async (title?: string) => {
let newSession: ChatSession
if (userStore.user?.id) {
newSession = await chatApi.createSession(userStore.user.id, title || `对话${sessions.value.length + 1}`)
} else {
newSession = {
id: Date.now().toString(),
title: title || `对话${sessions.value.length + 1}`,
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
messageCount: 0
}
}
sessions.value.unshift(newSession)
currentSession.value = newSession
messages.value = []
// 如果WebSocket已连接,设置新的会话ID
if (wsConnected.value) {
webSocketService.setConversationId(newSession.id)
}
return newSession
}
// 切换会话:加载消息
const switchSession = async (sessionId: string) => {
const session = sessions.value.find(s => s.id === sessionId)
if (session) {
currentSession.value = session
await loadSessionMessages(sessionId)
// 如果WebSocket已连接,更新会话ID
if (wsConnected.value) {
webSocketService.setConversationId(sessionId)
}
}
}
// 加载会话消息:从后端获取
const loadSessionMessages = async (sessionId: string) => {
try {
const msgs = await chatApi.getAllSessionMessages(sessionId)
messages.value = msgs
} catch (error) {
console.error('Failed to load session messages:', error)
messages.value = []
}
}
// 删除会话:同步后端
const deleteSession = async (sessionId: string) => {
try {
await chatApi.deleteSession(sessionId)
const index = sessions.value.findIndex(s => s.id === sessionId)
if (index > -1) {
sessions.value.splice(index, 1)
if (currentSession.value?.id === sessionId) {
currentSession.value = sessions.value[0] || null
if (currentSession.value) {
await loadSessionMessages(currentSession.value.id)
} else {
messages.value = []
}
}
}
} catch (error) {
console.error('删除会话失败:', error)
}
}
const clearMessages = () => {
messages.value = []
}
const searchMessages = (keyword: string) => {
return messages.value.filter(message =>
message.content.toLowerCase().includes(keyword.toLowerCase())
)
}
// 分割AI回复为多条消息
const splitAiReply = (content: string): string[] => {
// 先按 \n\n 分割,再按 \n 分割
const segments = content.split(/\n\n|\n/).filter(segment => segment.trim().length > 0)
return segments
}
// 添加AI回复消息(支持分段显示)
const addAiReplyMessages = (content: string, delay: number = 1000) => {
const segments = splitAiReply(content)
segments.forEach((segment, index) => {
setTimeout(() => {
const aiMessage = addMessage({
content: segment.trim(),
type: 'ai',
sessionId: currentSession.value?.id
})
// 强制触发响应式更新
console.log('AI消息已添加,当前消息总数:', messages.value.length)
console.log('最新AI消息:', aiMessage)
// 最后一条消息后停止输入状态
if (index === segments.length - 1) {
isTyping.value = false
}
}, index * delay)
})
}
// WebSocket消息处理
const handleWebSocketMessage = (wsMessage: WebSocketMessage) => {
console.log('收到WebSocket消息:', wsMessage.type, wsMessage.senderType)
switch (wsMessage.type) {
case 'TEXT':
if (wsMessage.senderType === 'AI') {
// AI回复消息 - 支持分段显示
addAiReplyMessages(wsMessage.content)
}
break
case 'AI_THINKING':
// AI正在思考
isTyping.value = true
break
case 'CONNECTION':
// 连接状态消息
console.log('WebSocket连接状态:', wsMessage.content)
break
case 'ERROR':
// 错误消息
addMessage({
content: wsMessage.content,
type: 'ai',
sessionId: currentSession.value?.id
})
isTyping.value = false
break
case 'SYSTEM':
// 系统消息
addMessage({
content: wsMessage.content,
type: 'ai',
sessionId: currentSession.value?.id
})
break
default:
console.log('未处理的消息类型:', wsMessage.type)
}
}
// WebSocket连接管理
const connectWebSocket = async () => {
try {
// 优先使用userInfo中的用户ID,如果没有则使用user中的ID
const userId = userStore.userInfo?.id || userStore.user?.id || undefined
await webSocketService.connect(userId, {
onMessage: handleWebSocketMessage,
onConnect: () => {
console.log('WebSocket连接成功')
wsConnected.value = true
isConnected.value = true
// 设置会话ID
if (currentSession.value?.id) {
webSocketService.setConversationId(currentSession.value.id)
}
},
onDisconnect: () => {
console.log('WebSocket连接断开')
wsConnected.value = false
isConnected.value = false
isTyping.value = false
},
onError: (error) => {
console.error('WebSocket错误:', error)
wsConnected.value = false
isConnected.value = false
isTyping.value = false
// 显示用户友好的错误信息
if (error.userMessage) {
addMessage({
content: error.userMessage,
type: 'ai',
sessionId: currentSession.value?.id
})
}
},
onStatusChange: (status) => {
connectionStatus.value = status
isConnected.value = status === 'CONNECTED'
}
})
} catch (error) {
console.error('WebSocket连接失败:', error)
wsConnected.value = false
isConnected.value = false
}
}
const disconnectWebSocket = () => {
webSocketService.disconnect()
wsConnected.value = false
isConnected.value = false
isTyping.value = false
}
// 初始化
const initChat = async () => {
// 如果没有会话,创建一个默认会话
if (sessions.value.length === 0) {
await createSession('与开开的对话')
}
// 连接WebSocket
await connectWebSocket()
}
// 监听会话变化,更新WebSocket会话ID
watch(currentSession, (newSession) => {
if (newSession?.id && wsConnected.value) {
webSocketService.setConversationId(newSession.id)
}
})
return {
// 状态
currentSession,
messages,
sessions,
isTyping,
isConnected,
connectionStatus,
wsConnected,
// 方法
addMessage,
sendMessage,
createSession,
switchSession,
loadSessionMessages,
deleteSession,
clearMessages,
searchMessages,
initChat,
connectWebSocket,
disconnectWebSocket,
handleWebSocketMessage
}
})