From 86918a667e77be51670acf82db352906dd1600c5 Mon Sep 17 00:00:00 2001 From: xjs Date: Thu, 29 May 2025 17:50:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E8=AF=9D=E6=B5=81=E9=87=87?= =?UTF-8?q?=E7=94=A8=E7=AD=96=E7=95=A5=E8=AE=BE=E8=AE=A1=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Provider/RealtimeClientProvider.tsx | 99 +--------- src/hooks/useRealtimeClient.ts | 183 ++++++++++++++++-- 2 files changed, 178 insertions(+), 104 deletions(-) diff --git a/src/components/Provider/RealtimeClientProvider.tsx b/src/components/Provider/RealtimeClientProvider.tsx index 7f7d8ce..7b6cf74 100644 --- a/src/components/Provider/RealtimeClientProvider.tsx +++ b/src/components/Provider/RealtimeClientProvider.tsx @@ -14,6 +14,7 @@ import { useState, } from "react"; import { useToast } from "@/hooks/use-toast"; +import { MessageHandlerStrategy } from "@/hooks/useRealtimeClient"; type RoomInfo = { appId: string; @@ -94,6 +95,8 @@ export const RealtimeClientProvider = ({ const { toast } = useToast(); + const messageHandlerStrategy = useRef(new MessageHandlerStrategy()); + /** 初始化客户端并设置监听 */ const initClient = async ({ initMessage, @@ -244,101 +247,17 @@ export const RealtimeClientProvider = ({ client.on(EventNames.ALL, (_eventName, event: any) => { // 交给状态机处理解析流程 - // 普通消息流处理 if ( event.event_type !== ChatEventType.CONVERSATION_MESSAGE_DELTA && - event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED - ) { - // 处理conversation.created事件 - if ( - event.event_type === "conversation.created" && - !opts.initMessage && - !opts.fileInfo - ) { - setMessageList((prev) => [ - ...prev, - { - content: event.data.prologue, - role: RoleType.Assistant, - event, - }, - ]); - } - return; - } - - // 如果是assistant的completed消息或verbose类型,直接返回 - if ( - (event.data.role === "assistant" && - event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && - event.data.type === "verbose") || - (event.data.type === "answer" && - event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED) + event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED && + event.event_type !== "conversation.created" ) { return; } - - const content = event.data.content; - setMessageList((prev) => { - // 如果是工具调用相关的消息,不添加到消息列表 - if ( - event.data.type === "function_call" || - event.data.type === "tool_response" - ) { - const jsonContent = JSON.parse(event.data.content); - const lastMessage = prev[prev.length - 1]; - - if (jsonContent.name === "doc_reader-PDF_reader") { - return [ - ...prev, - { - content: "", - role: RoleType.Assistant, - fileInfo: opts.fileInfo, - fileParseStatus: 1, - event, - }, - ]; - } - else if ( - lastMessage.event.type === "function_call" && - event.data.type === "tool_response" - ) { - return [ - ...prev.slice(0, prev.length - 2), - { - content: "", - role: RoleType.Assistant, - fileInfo: opts.fileInfo, - fileParseStatus: 2, - event, - }, - ]; - }else{ - return [...prev] - } - } - - if ( - prev.length > 0 && - prev[prev.length - 1].event?.event_type === - ChatEventType.CONVERSATION_MESSAGE_DELTA && - event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA && - prev[prev.length - 1].event.data.answer_id === event.data.answer_id - ) { - return [ - ...prev.slice(0, -1), - { - content: prev[prev.length - 1].content + content, - role: prev[prev.length - 1].role, - event, - }, - ]; - } else { - // 新消息追加 - return [...prev, { content, role: event.data.role, event }]; - } - }); + + setMessageList(prev => + messageHandlerStrategy.current.process(event, prev, opts) + ); }); }; diff --git a/src/hooks/useRealtimeClient.ts b/src/hooks/useRealtimeClient.ts index 2200a05..9c2bd96 100644 --- a/src/hooks/useRealtimeClient.ts +++ b/src/hooks/useRealtimeClient.ts @@ -1,20 +1,175 @@ -class MessageHandler { - canHandle(_eventType: string, _dataType: string) { - throw new Error("Subclass must implement canHandle method"); - } +import { ChatEventType, RoleType } from "@coze/api"; - handle(_event: string) { - throw new Error("Subclass must implement handle method"); +interface MessageHandler { + canHandle(event: any, opts?: any, prevMessageList?: any[]): boolean; + handle(event: any, prevMessageList: any[], opts?: any): any[]; + } + + abstract class BaseMessageHandler implements MessageHandler { + abstract canHandle(event: any, opts: any, prevMessageList?: any[]): boolean; + abstract handle(event: any, prevMessageList: any[], opts: any): any[]; + + protected createMessage(content: string, role: RoleType, event: any, opts?: any) { + const fileParseStatus = opts && opts.fileInfo ? event.data.type === "function_call" ? 1 : + event.data.type === "tool_response" ? 2 : undefined:undefined + return { + content, + role, + fileInfo: (opts && opts.fileInfo) ? opts.fileInfo : undefined, + fileParseStatus: fileParseStatus, + event + }; + } } - setNextHandle(){} -} -class ToolResponseHandler extends MessageHandler{ - canHandle(eventType: string, dataType: string): void { - + // 普通消息处理 添加欢迎语 + class ConversationHelloHandler extends BaseMessageHandler { + canHandle(event: any, opts: any, _prevMessageList?: any[]): boolean { + return event.event_type === "conversation.created" && + !opts.initMessage && + !opts.fileInfo; } - handle(event: string): void { - + + handle(event: any, prevMessageList: any[], _opts: any): any[] { + return [ + ...prevMessageList, + this.createMessage(event.data.prologue, RoleType.Assistant, event,) + ]; } -} + } + + // 处理忽略的事件类型 + class IgnoredEventHandler extends BaseMessageHandler { + canHandle(event: any, _opts: any, _prevMessageList?: any[]): boolean { + return ( + (event.data.role === "assistant" && + event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && + event.data.type === "verbose") || + (event.data.type === "answer" && + event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED) + ); + } + + handle(_event: any, prevMessageList: any[], _opts: any): any[] { + // 直接返回原消息列表,不做任何更改 + return prevMessageList; + } + } + + class UserMessageEventHandle extends BaseMessageHandler{ + canHandle(event: any, _opts: any, _prevMessageList?: any[]): boolean { + return event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && event.data.role === RoleType.User + } + + handle(event: any, prevMessageList: any[], _opts: any): any[] { + return [...prevMessageList,this.createMessage(event.data.content,event.data.role,event)] + } + } + + class NormalMessageEventHandle extends BaseMessageHandler{ + canHandle(event: any, _opts: any, prevMessageList?: any[]): boolean { + if(typeof prevMessageList === 'undefined'){ + return false + } + let prevMessage = prevMessageList[prevMessageList.length - 1]; + // 当前信息为增量,并且上一条信息为完成 + if(prevMessage?.event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA){ + return true + }else{ + return false; + } + } + + handle(event: any, prevMessageList: any[], _opts: any): any[] { + return [...prevMessageList,this.createMessage(event.data.content,event.data.role,event)] + } + } + + class AppendMessageEventHandle extends BaseMessageHandler{ + canHandle(event: any, _opts: any, prevMessageList?: any[]): boolean { + if(typeof prevMessageList === 'undefined' || prevMessageList.length === 0){ + return false; + } + let prevMessage = prevMessageList[prevMessageList.length - 1]; + if(prevMessage.event?.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA && event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA){ + return true + }else{ + return false; + } + } + + handle(event: any, prevMessageList: any[], _opts: any): any[] { + let prevMessage = prevMessageList[prevMessageList.length - 1]; + return [...prevMessageList.slice(0,prevMessageList.length-1),{...prevMessage,content:prevMessage.content + event.data.content}] + } + } + + class BottomMessageEventHandle extends BaseMessageHandler{ + canHandle(_event: any, _opts: any, _prevMessageList?: any[]): boolean { + return true + } + handle(_event: any, _prevMessageList: any[], _opts: any): any[] { + return _prevMessageList + } + } + + class FileFunctionCallEventHandle extends BaseMessageHandler{ + canHandle(event: any, _opts: any, _prevMessageList?: any[]): boolean { + if(event.data.type === "function_call"){ + let jsonData = JSON.parse(event.data.content) + return jsonData.name==='doc_reader-PDF_reader' && event.event_type === "conversation.message.completed" + }else{ + return false + } + } + + handle(event: any, prevMessageList: any[], opts: any): any[] { + return [...prevMessageList,this.createMessage('',event.data.role,event,opts)] + } + } + + class FileFunctionResponseEventHandle extends BaseMessageHandler{ + canHandle(event: any, _opts: any, prevMessageList?: any[]): boolean { + if(typeof prevMessageList === 'undefined'){ + return false + } + let prevMessage = prevMessageList[prevMessageList.length - 1]; + + if( prevMessage?.event.data.type === "function_call" && event.data.type ==="tool_response"){ + return true + }else { + return false + } + } + + handle(event: any, prevMessageList: any[], opts: any): any[] { + return [...prevMessageList.slice(0,prevMessageList.length-1),this.createMessage('',event.data.role,event,opts)] + } + } + + export class MessageHandlerStrategy { + private handlers: MessageHandler[]; + + constructor() { + this.handlers = [ + new ConversationHelloHandler(), + new IgnoredEventHandler(), + new UserMessageEventHandle(), + new NormalMessageEventHandle(), + new AppendMessageEventHandle(), + new FileFunctionCallEventHandle(), + new FileFunctionResponseEventHandle(), + new BottomMessageEventHandle() + ]; + } + + process(event: any, prevMessageList: any[], opts: any): any[] { + for (const handler of this.handlers) { + if (handler.canHandle(event, opts, prevMessageList)) { + return handler.handle(event, prevMessageList, opts); + } + } + return prevMessageList; // 理论上不会执行到这里 + } + } \ No newline at end of file