feat: 对话流采用策略设计模式重构
parent
97d2ccc154
commit
86918a667e
|
|
@ -14,6 +14,7 @@ import {
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import { MessageHandlerStrategy } from "@/hooks/useRealtimeClient";
|
||||||
|
|
||||||
type RoomInfo = {
|
type RoomInfo = {
|
||||||
appId: string;
|
appId: string;
|
||||||
|
|
@ -94,6 +95,8 @@ export const RealtimeClientProvider = ({
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const messageHandlerStrategy = useRef(new MessageHandlerStrategy());
|
||||||
|
|
||||||
/** 初始化客户端并设置监听 */
|
/** 初始化客户端并设置监听 */
|
||||||
const initClient = async ({
|
const initClient = async ({
|
||||||
initMessage,
|
initMessage,
|
||||||
|
|
@ -244,101 +247,17 @@ export const RealtimeClientProvider = ({
|
||||||
client.on(EventNames.ALL, (_eventName, event: any) => {
|
client.on(EventNames.ALL, (_eventName, event: any) => {
|
||||||
// 交给状态机处理解析流程
|
// 交给状态机处理解析流程
|
||||||
|
|
||||||
// 普通消息流处理
|
|
||||||
if (
|
if (
|
||||||
event.event_type !== ChatEventType.CONVERSATION_MESSAGE_DELTA &&
|
event.event_type !== ChatEventType.CONVERSATION_MESSAGE_DELTA &&
|
||||||
event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED
|
event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED &&
|
||||||
) {
|
event.event_type !== "conversation.created"
|
||||||
// 处理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)
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = event.data.content;
|
setMessageList(prev =>
|
||||||
setMessageList((prev) => {
|
messageHandlerStrategy.current.process(event, prev, opts)
|
||||||
// 如果是工具调用相关的消息,不添加到消息列表
|
);
|
||||||
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 }];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,175 @@
|
||||||
class MessageHandler {
|
import { ChatEventType, RoleType } from "@coze/api";
|
||||||
canHandle(_eventType: string, _dataType: string) {
|
|
||||||
throw new Error("Subclass must implement canHandle method");
|
interface MessageHandler {
|
||||||
|
canHandle(event: any, opts?: any, prevMessageList?: any[]): boolean;
|
||||||
|
handle(event: any, prevMessageList: any[], opts?: any): any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
handle(_event: string) {
|
abstract class BaseMessageHandler implements MessageHandler {
|
||||||
throw new Error("Subclass must implement handle method");
|
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; // 理论上不会执行到这里
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue