重命名前端项目目录:web-flowith -> web
- 将前端项目目录从 web-flowith 重命名为 web,使目录结构更简洁 - 保持所有前端代码和配置文件不变 - 统一项目目录命名规范
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
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(() => {
|
||||
addMessage({
|
||||
content: segment.trim(),
|
||||
type: 'ai',
|
||||
sessionId: currentSession.value?.id
|
||||
})
|
||||
|
||||
// 最后一条消息后停止输入状态
|
||||
if (index === segments.length - 1) {
|
||||
isTyping.value = false
|
||||
}
|
||||
}, index * delay)
|
||||
})
|
||||
}
|
||||
|
||||
// WebSocket消息处理
|
||||
const handleWebSocketMessage = (wsMessage: WebSocketMessage) => {
|
||||
console.log('处理WebSocket消息:', wsMessage)
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user