358 lines
9.9 KiB
TypeScript
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
|
|
}
|
|
})
|