import { MsgItem, markdownElItem } from '@/uni_modules/uni-ai-x/types' import { MessageInfo } from '@/types/interfaces/conversationInfo' import { AssistantRoleEnum, MessageModeEnum, MessageTypeEnum } from '@/types/enums/agent' import { MessageStatusEnum } from '@/types/enums/common' /** * SSE数据块接口 - 匹配实际返回的数据结构 */ export interface SSEChunk { requestId: string messageType?: string message_type?: string subType?: string sub_type?: string eventType: string error: string | null data: { id: string role: string type: string text: string time: string | null attachments: any[] | null think: string | null quotedText: string | null ext: any[] | null finished: boolean executeId: string | null messageType: string } completed: boolean } /** * 聊天数据适配器 * 用于将MessageInfo格式转换为uni-ai-x期望的MsgItem格式 */ export class ChatDataAdapter { /** * 判断SSE数据块是否可以按聊天文本消息渲染 */ static isRenderableSSEChunk(chunk: SSEChunk): boolean { const messageType = (chunk as any).messageType || (chunk as any).message_type if (messageType === 'acpRequestPermission') { return false } const data = (chunk as any).data if (!data || typeof data !== 'object') { return false } return typeof data.id === 'string' && data.id.length > 0 } /** * 将SSE数据块转换为MsgItem * @param chunk SSE数据块 * @param chatId 聊天ID * @returns uni-ai-x的MsgItem格式 */ static convertSSEChunkToMsgItem(chunk: SSEChunk, chatId: string): MsgItem { console.log(`Converting SSE chunk to MsgItem:`, chunk) const data = (chunk as any).data || {} const msgItem: MsgItem = { _id: data.id || this.generateId(), from_uid: this.getFromUidFromRole(data.role || 'ASSISTANT'), body: data.text || '', create_time: this.parseTime(data.time) || Date.now(), state: data.finished ? 3 : 2, // 完成状态为3,未完成为2 chat_id: chatId, markdownElList: [], rendered: false } console.log(`Converted MsgItem:`, msgItem) return msgItem } /** * 将MessageInfo转换为MsgItem * @param messageInfo 消息信息 * @returns uni-ai-x的MsgItem格式 */ static convertToMsgItem(messageInfo: MessageInfo): MsgItem { const msgItem: MsgItem = { _id: messageInfo.id?.toString() || '', from_uid: this.getFromUid(messageInfo.role), body: messageInfo.text || '', create_time: this.parseTime(messageInfo.time), state: this.getMsgState(messageInfo.status), chat_id: '', // 需要从外部传入 markdownElList: (messageInfo as any).markdownElList || [], rendered: false, // 传递processingList用于自定义组件渲染 processingList: messageInfo.processingList || [] } return msgItem } /** * 将MsgItem转换为MessageInfo * @param msgItem uni-ai-x的MsgItem格式 * @returns MessageInfo格式 */ static convertToMessageInfo(msgItem: MsgItem): MessageInfo { const messageInfo: MessageInfo = { id: msgItem._id, role: this.getAssistantRole(msgItem.from_uid), type: this.getMessageMode(msgItem.from_uid), text: msgItem.body, think: (msgItem as any).thinkContent || '', time: new Date(msgItem.create_time).toISOString(), attachments: [], ext: [], finished: false, metadata: null, messageType: MessageTypeEnum.USER, status: this.getMessageStatus(msgItem.state), markdownElList: msgItem.markdownElList || [], processingList: [], finalResult: null, requestId: '' } return messageInfo } /** * 批量转换MessageInfo数组为MsgItem数组 * @param messageList 消息列表 * @param chatId 聊天ID * @returns MsgItem数组 */ static convertMessageListToMsgItems(messageList: MessageInfo[], chatId: string = ''): MsgItem[] { return messageList.map(message => { const msgItem = this.convertToMsgItem(message) msgItem.chat_id = chatId return msgItem }) } /** * 批量转换MsgItem数组为MessageInfo数组 * @param msgItems 消息项数组 * @returns MessageInfo数组 */ static convertMsgItemsToMessageList(msgItems: MsgItem[]): MessageInfo[] { return msgItems.map(msgItem => this.convertToMessageInfo(msgItem)) } /** * 处理SSE流式数据,累积文本内容 * @param chunks SSE数据块数组 * @param chatId 聊天ID * @returns 处理后的MsgItem数组 */ static processSSEChunks(chunks: SSEChunk[], chatId: string): MsgItem[] { const messageMap = new Map() chunks.forEach(chunk => { if (!this.isRenderableSSEChunk(chunk)) { return } const messageId = chunk.data.id if (!messageMap.has(messageId)) { // 创建新消息 const msgItem = this.convertSSEChunkToMsgItem(chunk, chatId) messageMap.set(messageId, msgItem) } else { // 累积文本内容 const existingMsg = messageMap.get(messageId)! existingMsg.body += chunk.data.text || '' // 更新状态 if (chunk.data.finished) { existingMsg.state = 3 // 完成状态 } } }) // 兼容性处理:将Map值转换为数组 const result: MsgItem[] = [] messageMap.forEach((value) => { result.push(value) }) return result } /** * 根据角色获取from_uid * @param role 角色 * @returns from_uid */ private static getFromUid(role: AssistantRoleEnum): string { switch (role) { case AssistantRoleEnum.USER: return 'user' case AssistantRoleEnum.ASSISTANT: return 'uni-ai' case AssistantRoleEnum.SYSTEM: return 'system' case AssistantRoleEnum.FUNCTION: return 'tool' default: return 'user' } } /** * 根据角色字符串获取from_uid * @param role 角色字符串 * @returns from_uid */ private static getFromUidFromRole(role: string): string { switch (role) { case 'USER': return 'user' case 'ASSISTANT': return 'uni-ai' case 'SYSTEM': return 'system' case 'FUNCTION': return 'tool' default: return 'user' } } /** * 根据from_uid获取角色 * @param fromUid from_uid * @returns 角色 */ private static getAssistantRole(fromUid: string): AssistantRoleEnum { switch (fromUid) { case 'user': return AssistantRoleEnum.USER case 'uni-ai': return AssistantRoleEnum.ASSISTANT case 'system': return AssistantRoleEnum.SYSTEM case 'tool': return AssistantRoleEnum.FUNCTION default: return AssistantRoleEnum.USER } } /** * 根据from_uid获取消息模式 * @param fromUid from_uid * @returns 消息模式 */ private static getMessageMode(fromUid: string): MessageModeEnum { switch (fromUid) { case 'user': return MessageModeEnum.CHAT case 'uni-ai': return MessageModeEnum.ANSWER case 'system': return MessageModeEnum.GUID case 'tool': return MessageModeEnum.CHAT default: return MessageModeEnum.CHAT } } /** * 根据消息状态获取MsgItem状态 * @param status 消息状态 * @returns MsgItem状态 */ private static getMsgState(status?: MessageStatusEnum | null): number { switch (status) { case MessageStatusEnum.Loading: return 1 // 加载中 case MessageStatusEnum.Incomplete: return 2 // 未完成 case MessageStatusEnum.Complete: return 3 // 完成 case MessageStatusEnum.Error: return 4 // 错误 default: return 3 // 默认完成 } } /** * 根据MsgItem状态获取消息状态 * @param state MsgItem状态 * @returns 消息状态 */ private static getMessageStatus(state: number): MessageStatusEnum | null { switch (state) { case 1: return MessageStatusEnum.Loading case 2: return MessageStatusEnum.Incomplete case 3: return MessageStatusEnum.Complete case 4: return MessageStatusEnum.Error default: return null } } /** * 解析时间字符串为时间戳 * @param timeStr 时间字符串 * @returns 时间戳 */ private static parseTime(timeStr: string | null): number { if (!timeStr) return Date.now() try { return new Date(timeStr).getTime() } catch (error) { return Date.now() } } /** * 创建新的用户消息 * @param content 消息内容 * @param chatId 聊天ID * @param attachments 附件 * @returns 新的MsgItem */ static createUserMessage(content: string, chatId: string, attachments: any[] = []): MsgItem { return { _id: this.generateId(), from_uid: 'user', body: content, create_time: Date.now(), state: 3, // 完成状态 chat_id: chatId, markdownElList: [], rendered: false } } /** * 创建新的AI消息 * @param content 消息内容 * @param chatId 聊天ID * @param markdownElements markdown元素 * @returns 新的MsgItem */ static createAIMessage(content: string, chatId: string, markdownElements: markdownElItem[] = []): MsgItem { return { _id: this.generateId(), from_uid: 'uni-ai', body: content, create_time: Date.now(), state: 2, // 未完成状态 chat_id: chatId, markdownElList: markdownElements, rendered: false } } /** * 生成唯一ID * @returns 唯一ID */ private static generateId(): string { // #ifdef MP-WEIXIN // 微信小程序中,uni.getRandomValues返回Promise,这里简化处理 return Date.now().toString() + Math.random().toString(36).substr(2, 9) // #endif // #ifdef H5 // 这里需要导入uuid库 return Date.now().toString() + Math.random().toString(36).substr(2, 9) // #endif // 默认返回值 return Date.now().toString() + Math.random().toString(36).substr(2, 9) } } // 导出适配器实例 export const chatDataAdapter = new ChatDataAdapter() /* 使用示例: // 1. 在聊天页面中导入适配器 import { chatDataAdapter, SSEChunk } from '@/utils/chatDataAdapter' // 2. 处理SSE流式数据 const handleSSEChunk = (chunk: SSEChunk) => { // 解析SSE数据 const sseData = JSON.parse(chunk) // 累积数据块 sseChunks.value.push(sseData) // 处理并转换为MsgItem const msgItems = chatDataAdapter.processSSEChunks(sseChunks.value, 'chat-' + conversationId.value) // 更新渲染列表 msgItemsForRender.value = msgItems } // 3. 在模板中使用 // 4. 处理流式响应 const handleStreamResponse = (chunk: string) => { try { const sseChunk: SSEChunk = JSON.parse(chunk) handleSSEChunk(sseChunk) } catch (error) { console.error('解析SSE数据失败:', error) } } */