/** * 历史消息适配器 * 将PC端历史会话消息中的自定义HTML标签转换为移动端支持的Markdown容器语法 * * PC端格式:
* 移动端格式: :::container executeId="xxx" type="xxx" status="xxx" name="xxx"\n:::\n */ import type { MessageInfo, ProcessingInfo, ExecuteResultInfo } from '@/types/interfaces/conversationInfo' import { ProcessingEnum } from '@/types/enums/common' import { AssistantRoleEnum, MessageTypeEnum, AgentComponentTypeEnum } from '@/types/enums/agent' /** * 组件执行列表项接口(来自API返回) */ export interface ComponentExecutedItem { name: string result: { executeId: string id: number name: string type: string // "Plan", "ToolCall", "Skill", "Workflow", "Page", etc. startTime: number endTime?: number success?: boolean data?: any input?: any } status: string // "FINISHED" | "FAILED" | "EXECUTING" targetId: number type: string // "Plan", "ToolCall", "Skill", "Workflow", "Page", etc. } /** * 解析的自定义标签信息 */ interface ParsedCustomTag { executeId: string type: string status: string name: string fullMatch: string // 完整的匹配字符串,用于替换 } /** * 解析PC端的markdown-custom-process HTML标签 * @param text 包含HTML标签的文本 * @returns 解析出的标签信息数组 */ export function parseMarkdownCustomProcess(text: string): ParsedCustomTag[] { if (!text) return [] const results: ParsedCustomTag[] = [] // 匹配
格式 // 以及 格式(无div包裹) const regex = /
[\s\n]*]*)><\/markdown-custom-process>[\s\n]*<\/div>|]*)><\/markdown-custom-process>/g let match: RegExpExecArray | null while ((match = regex.exec(text)) !== null) { const attrString = match[1] || match[2] || '' const fullMatch = match[0] // 解析属性 const executeId = extractAttribute(attrString, 'executeId') const type = extractAttribute(attrString, 'type') const status = extractAttribute(attrString, 'status') const name = extractAttribute(attrString, 'name') if (executeId) { results.push({ executeId, type: type || '', status: status || '', name: name || '', fullMatch }) } } return results } /** * 从属性字符串中提取指定属性的值 * @param attrString 属性字符串,如 ' executeId="xxx" type="yyy"' * @param attrName 属性名 * @returns 属性值 */ function extractAttribute(attrString: string, attrName: string): string { const regex = new RegExp(`${attrName}="([^"]*)"`) const match = attrString.match(regex) return match ? match[1] : '' } /** * 将PC端HTML标签转换为移动端Markdown容器语法 * @param tag 解析的标签信息 * @returns Markdown容器语法字符串 */ export function convertToMobileContainerSyntax(tag: ParsedCustomTag): string { const attrs = [ `executeId="${tag.executeId}"`, tag.type ? `type="${tag.type}"` : '', tag.status ? `status="${tag.status}"` : '', tag.name ? `name="${tag.name}"` : '' ].filter(Boolean).join(' ') return `\n\n:::container ${attrs}\n:::\n\n` } /** * 解析状态字符串为ProcessingEnum * @param status 状态字符串 * @returns ProcessingEnum值 */ function parseStatus(status: string): ProcessingEnum { switch (status) { case 'FINISHED': return ProcessingEnum.FINISHED case 'FAILED': return ProcessingEnum.FAILED case 'EXECUTING': default: return ProcessingEnum.EXECUTING } } /** * 解析组件类型字符串为AgentComponentTypeEnum * @param type 类型字符串 * @returns AgentComponentTypeEnum值 */ function parseComponentType(type: string): AgentComponentTypeEnum { switch (type) { case 'Plan': return AgentComponentTypeEnum.Plan case 'ToolCall': return AgentComponentTypeEnum.ToolCall case 'Skill': return AgentComponentTypeEnum.Skill case 'Workflow': return AgentComponentTypeEnum.Workflow case 'Knowledge': return AgentComponentTypeEnum.Knowledge case 'Page': return AgentComponentTypeEnum.Page case 'Plugin': return AgentComponentTypeEnum.Plugin case 'Event': return AgentComponentTypeEnum.Event case 'Agent': return AgentComponentTypeEnum.Agent case 'Mcp': case 'MCP': return AgentComponentTypeEnum.MCP default: // 默认返回 ToolCall,因为这是最常见的类型 return AgentComponentTypeEnum.ToolCall } } /** * 将componentExecutedList转换为processingList * @param componentExecutedList 组件执行列表 * @returns ProcessingInfo数组 */ export function convertComponentExecutedListToProcessingList( componentExecutedList: ComponentExecutedItem[] | null | undefined ): ProcessingInfo[] { if (!componentExecutedList || !Array.isArray(componentExecutedList)) { return [] } return componentExecutedList.map((item): ProcessingInfo => { const result = item.result || {} const status = parseStatus(item.status) const componentType = parseComponentType(item.type) // 构建ExecuteResultInfo const executeResult: ExecuteResultInfo = { kind: result?.kind || '', id: result.id || -1, icon: '', name: result.name || item.name || '', type: componentType, success: result.success ?? (item.status === 'FINISHED'), error: '', data: result.data || null, startTime: result.startTime || 0, endTime: result.endTime || 0, input: result.input || null } return { executeId: result.executeId || '', name: item.name || result.name || '', type: componentType, status: status, targetId: item.targetId || result.id || -1, result: executeResult, cardBindConfig: null as any } as ProcessingInfo }) } /** * 适配单条历史消息 * 将PC端格式的自定义标签转换为移动端支持的格式 * @param message 原始消息 * @returns 适配后的消息 */ export function adaptHistoryMessage(message: MessageInfo): MessageInfo { if (!message) return message // 只处理ASSISTANT消息,非ASSISTANT消息确保text有值后直接返回 if (message.role !== AssistantRoleEnum.ASSISTANT && message.messageType !== MessageTypeEnum.ASSISTANT) { // 确保 text 字段有值 if (message.text === undefined || message.text === null) { return { ...message, text: '' } } return message } let adaptedText = message.text || '' let processingList = message.processingList || [] // 1. 从componentExecutedList转换processingList const componentExecutedList = (message as any).componentExecutedList as ComponentExecutedItem[] | null if (componentExecutedList && componentExecutedList.length > 0) { const convertedProcessingList = convertComponentExecutedListToProcessingList(componentExecutedList) // 合并已有的processingList(如果有的话) processingList = [...processingList, ...convertedProcessingList] } // 2. 解析并转换PC端HTML标签为移动端Markdown容器语法 const parsedTags = parseMarkdownCustomProcess(adaptedText) for (const tag of parsedTags) { const containerSyntax = convertToMobileContainerSyntax(tag) // 替换HTML标签为Markdown容器语法 adaptedText = adaptedText.replace(tag.fullMatch, containerSyntax) } // 3. 清理多余的空行(超过2个连续换行符的情况) adaptedText = adaptedText.replace(/\n{3,}/g, '\n\n') return { ...message, text: adaptedText || '', // 确保 text 始终有值 processingList } } /** * 批量适配历史消息列表 * @param messageList 原始消息列表 * @returns 适配后的消息列表 */ export function adaptHistoryMessageList(messageList: MessageInfo[]): MessageInfo[] { if (!messageList || !Array.isArray(messageList)) { return [] } return messageList.map(message => adaptHistoryMessage(message)) }