430 lines
11 KiB
Plaintext
430 lines
11 KiB
Plaintext
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<string, MsgItem>()
|
||
|
||
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. 在模板中使用
|
||
<template v-for="item in msgItemsForRender" :key="item._id">
|
||
<uni-ai-x-msg v-if="item.from_uid == 'uni-ai'" :msg="item" />
|
||
<text v-else class="user-msg">{{item.body}}</text>
|
||
</template>
|
||
|
||
// 4. 处理流式响应
|
||
const handleStreamResponse = (chunk: string) => {
|
||
try {
|
||
const sseChunk: SSEChunk = JSON.parse(chunk)
|
||
handleSSEChunk(sseChunk)
|
||
} catch (error) {
|
||
console.error('解析SSE数据失败:', error)
|
||
}
|
||
}
|
||
*/
|