Files
qiming/qiming-mobile/utils/chatDataAdapter.uts

430 lines
11 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
*/