chore: initialize qiming workspace repository
This commit is contained in:
429
qiming-mobile/utils/chatDataAdapter.uts
Normal file
429
qiming-mobile/utils/chatDataAdapter.uts
Normal file
@@ -0,0 +1,429 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user