fix: 修复插件调用的处理

master
xjs 2025-05-21 11:33:36 +08:00
parent e1acc4973a
commit 207f28c728
1 changed files with 106 additions and 140 deletions

View File

@ -14,7 +14,6 @@ import {
useState, useState,
} from "react"; } from "react";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { FileParser, FileParseStatus } from "./fileParser";
type RoomInfo = { type RoomInfo = {
appId: string; appId: string;
@ -37,8 +36,8 @@ export const RealtimeClientContext = createContext<{
isConnected: boolean; isConnected: boolean;
audioEnabled: boolean; audioEnabled: boolean;
isSupportVideo: boolean; isSupportVideo: boolean;
messageList: { messageList: {
content: string; content: string;
role: RoleType; role: RoleType;
event?: any; event?: any;
fileInfo?: FileInfo; fileInfo?: FileInfo;
@ -48,7 +47,10 @@ export const RealtimeClientContext = createContext<{
roomInfo: RoomInfo | null; roomInfo: RoomInfo | null;
initClient: (opts: { initMessage?: string; fileInfo?: FileInfo }) => void; initClient: (opts: { initMessage?: string; fileInfo?: FileInfo }) => void;
handleConnect: (opts: { initMessage?: string; fileInfo?: FileInfo }) => Promise<void>; handleConnect: (opts: {
initMessage?: string;
fileInfo?: FileInfo;
}) => Promise<void>;
handleInterrupt: () => void; handleInterrupt: () => void;
handleDisconnect: () => void; handleDisconnect: () => void;
toggleMicrophone: () => void; toggleMicrophone: () => void;
@ -56,11 +58,16 @@ export const RealtimeClientContext = createContext<{
export const useRealtimeClient = () => { export const useRealtimeClient = () => {
const ctx = useContext(RealtimeClientContext); const ctx = useContext(RealtimeClientContext);
if (!ctx) throw new Error("useRealtimeClient 必须在 RealtimeClientProvider 内部使用"); if (!ctx)
throw new Error("useRealtimeClient 必须在 RealtimeClientProvider 内部使用");
return ctx; return ctx;
}; };
export const RealtimeClientProvider = ({ children }: { children: ReactNode }) => { export const RealtimeClientProvider = ({
children,
}: {
children: ReactNode;
}) => {
const token = import.meta.env.VITE_COZE_TOKEN; const token = import.meta.env.VITE_COZE_TOKEN;
const botId = import.meta.env.VITE_COZE_BOT_ID; const botId = import.meta.env.VITE_COZE_BOT_ID;
const voiceId = import.meta.env.VITE_COZE_VOICE_ID; const voiceId = import.meta.env.VITE_COZE_VOICE_ID;
@ -69,13 +76,15 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
const clientRef = useRef<RealtimeClient | null>(null); const clientRef = useRef<RealtimeClient | null>(null);
const connectingLockRef = useRef(false); const connectingLockRef = useRef(false);
const [messageList, setMessageList] = useState<{ const [messageList, setMessageList] = useState<
content: string; {
role: RoleType; content: string;
event?: any; role: RoleType;
fileInfo?: FileInfo; event?: any;
fileParseStatus?: number; fileInfo?: FileInfo;
}[]>([]); fileParseStatus?: number;
}[]
>([]);
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
const [audioEnabled, setAudioEnabled] = useState(true); const [audioEnabled, setAudioEnabled] = useState(true);
@ -83,42 +92,8 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
const [isAiTalking, setIsAiTalking] = useState(false); const [isAiTalking, setIsAiTalking] = useState(false);
const [roomInfo, setRoomInfo] = useState<RoomInfo | null>(null); const [roomInfo, setRoomInfo] = useState<RoomInfo | null>(null);
// 引入状态机
const fileParseStatusRef = useRef<FileParseStatus>(-1);
const fileParserRef = useRef(
new FileParser((newStatus:any) => {
fileParseStatusRef.current = newStatus;
// 根据不同状态更新 messageList
if (newStatus === 0) {
appendAssistantMessage("AI正在解析您的文档...");
} else if (newStatus === 1) {
replaceLastAssistantMessage("AI正在调用插件");
} else if (newStatus === 2) {
replaceLastAssistantMessage("文档解析完成");
}
})
);
const { toast } = useToast(); const { toast } = useToast();
/** Helpers */
const appendAssistantMessage = (content: string) => {
setMessageList(prev => {
return [
...prev,
{ content, role: RoleType.Assistant }
]
});
};
const replaceLastAssistantMessage = (content: string) => {
setMessageList(prev => {
return [
...prev.slice(0, -1),
{ content, role: RoleType.Assistant }
]
});
};
/** 初始化客户端并设置监听 */ /** 初始化客户端并设置监听 */
const initClient = async ({ const initClient = async ({
initMessage, initMessage,
@ -147,7 +122,7 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
allowPersonalAccessTokenInBrowser: true, allowPersonalAccessTokenInBrowser: true,
suppressStationaryNoise: true, suppressStationaryNoise: true,
suppressNonStationaryNoise: true, suppressNonStationaryNoise: true,
debug: true, debug: false,
}); });
clientRef.current = client; clientRef.current = client;
@ -227,7 +202,7 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
}, },
}); });
} }
if (eventName === "server.bot.join") { if (eventName === "server.bot.join") {
if (initMessage) { if (initMessage) {
await clientRef.current!.sendMessage({ await clientRef.current!.sendMessage({
id: "", id: "",
@ -246,7 +221,10 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
role: "user", role: "user",
content_type: "object_string", content_type: "object_string",
content: JSON.stringify([ content: JSON.stringify([
{ type: "text", text: "帮我解读这个文件,结合当下的专业行情以及对该专业未来的发展趋势,简介的给出志愿建议" }, {
type: "text",
text: "帮我解读这个文件,结合当下的专业行情以及对该专业未来的发展趋势,简介的给出志愿建议",
},
{ type: "image", file_url: fileInfo.url }, { type: "image", file_url: fileInfo.url },
]), ]),
}, },
@ -265,7 +243,6 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
) => { ) => {
client.on(EventNames.ALL, (_eventName, event: any) => { client.on(EventNames.ALL, (_eventName, event: any) => {
// 交给状态机处理解析流程 // 交给状态机处理解析流程
fileParserRef.current.handleEvent(_eventName, event);
// 普通消息流处理 // 普通消息流处理
if ( if (
@ -273,14 +250,18 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED
) { ) {
// 处理conversation.created事件 // 处理conversation.created事件
if (event.event_type === "conversation.created" && !opts.initMessage && !opts.fileInfo) { if (
setMessageList(prev => [ event.event_type === "conversation.created" &&
!opts.initMessage &&
!opts.fileInfo
) {
setMessageList((prev) => [
...prev, ...prev,
{ {
content: event.data.prologue, content: event.data.prologue,
role: RoleType.Assistant, role: RoleType.Assistant,
event event,
} },
]); ]);
} }
return; return;
@ -288,72 +269,62 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
// 如果是assistant的completed消息或verbose类型直接返回 // 如果是assistant的completed消息或verbose类型直接返回
if ( if (
(event.data.role === "assistant" && event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED &&event.data.type === "verbose" ) || (event.data.role === "assistant" &&
event.data.type === "answer" && event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED &&
event.data.type === "verbose") ||
(event.data.type === "answer" &&
event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED)
) { ) {
return; return;
} }
// 如果没有fileInfo过滤掉function_call和tool_response类型的消息
if (!opts.fileInfo && (event.data.type === "function_call" || event.data.type === "tool_response")) {
return;
}
const content = event.data.content; const content = event.data.content;
setMessageList(prev => { setMessageList((prev) => {
// 合并增量 // 如果是工具调用相关的消息,不添加到消息列表
// 处理工具回调结果 if (
try { event.data.type === "function_call" ||
const parsedContent = JSON.parse(content); event.data.type === "tool_response"
if (parsedContent.msg_type === "time_capsule_recall" || ) {
(parsedContent.name && parsedContent.arguments)) { const jsonContent = JSON.parse(event.data.content);
// 如果没有fileInfo不创建假信息 const lastMessage = prev[prev.length - 1];
if (!opts.fileInfo) {
return prev;
}
// 检查是否已存在工具回调消息
const existingToolMessageIndex = prev.findIndex(msg =>
msg.content === "正在处理您的请求..." &&
msg.fileInfo === opts.fileInfo
);
if (existingToolMessageIndex !== -1) { if (jsonContent.name === "doc_reader-PDF_reader") {
// 更新已存在的消息的fileParseStatus return [
const newStatus = parsedContent.msg_type === "time_capsule_recall" ? 0 : 2; ...prev,
return [ {
...prev.slice(0, existingToolMessageIndex), content: "",
{ role: RoleType.Assistant,
...prev[existingToolMessageIndex], fileInfo: opts.fileInfo,
fileParseStatus: newStatus fileParseStatus: 1,
}, event,
...prev.slice(existingToolMessageIndex + 1) },
]; ];
} else { }
// 创建新的工具回调消息 else if (
return [ lastMessage.event.type === "function_call" &&
...prev, event.data.type === "tool_response"
{ ) {
content: "正在处理您的请求...", return [
role: event.data.role, ...prev.slice(0, prev.length - 2),
event, {
fileInfo: opts.fileInfo, content: "",
fileParseStatus: parsedContent.msg_type === "time_capsule_recall" ? 0 : 2 role: RoleType.Assistant,
}, fileInfo: opts.fileInfo,
]; fileParseStatus: 2,
} event,
},
];
}else{
return [...prev]
} }
} catch (e) {
// 如果不是JSON格式继续正常处理
} }
if ( if (
prev.length > 0 && prev.length > 0 &&
prev[prev.length - 1].event?.event_type === prev[prev.length - 1].event?.event_type ===
ChatEventType.CONVERSATION_MESSAGE_DELTA && ChatEventType.CONVERSATION_MESSAGE_DELTA &&
event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA && event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA &&
prev[prev.length - 1].event.data.answer_id === prev[prev.length - 1].event.data.answer_id === event.data.answer_id
event.data.answer_id
) { ) {
return [ return [
...prev.slice(0, -1), ...prev.slice(0, -1),
@ -363,44 +334,39 @@ export const RealtimeClientProvider = ({ children }: { children: ReactNode }) =>
event, event,
}, },
]; ];
} else {
// 新消息追加
return [...prev, { content, role: event.data.role, event }];
} }
// 新消息追加
return [
...prev,
{ content, role: event.data.role, event },
];
}); });
}); });
}; };
/** 基本连接状态 & 语音事件监听 */ /** 基本连接状态 & 语音事件监听 */
const setupEventListeners = useCallback( const setupEventListeners = useCallback((client: RealtimeClient) => {
(client: RealtimeClient) => { client.on(EventNames.AUDIO_AGENT_SPEECH_STARTED, async () => {
client.on(EventNames.AUDIO_AGENT_SPEECH_STARTED, async () => { setIsAiTalking(true);
setIsAiTalking(true); await clientRef.current?.setAudioEnable(false);
await clientRef.current?.setAudioEnable(false); setAudioEnabled(false);
setAudioEnabled(false); });
}); client.on(EventNames.AUDIO_AGENT_SPEECH_STOPPED, async () => {
client.on(EventNames.AUDIO_AGENT_SPEECH_STOPPED, async () => { setIsAiTalking(false);
setIsAiTalking(false); await clientRef.current?.setAudioEnable(true);
await clientRef.current?.setAudioEnable(true); setAudioEnabled(true);
setAudioEnabled(true); });
}); client.on(EventNames.CONNECTING, () => {
client.on(EventNames.CONNECTING, () => { setIsConnecting(true);
setIsConnecting(true); setIsConnected(false);
setIsConnected(false); });
}); client.on(EventNames.CONNECTED, (_name, evt) => {
client.on(EventNames.CONNECTED, (_name, evt) => { setRoomInfo(evt as RoomInfo);
setRoomInfo(evt as RoomInfo); setIsConnecting(false);
setIsConnecting(false); setIsConnected(true);
setIsConnected(true); });
}); client.on(EventNames.ALL_SERVER, (_name, _evt) => {
client.on(EventNames.ALL_SERVER, (_name, _evt) => { // 其它全局服务端事件可在此处理
// 其它全局服务端事件可在此处理 });
}); }, []);
},
[]
);
return ( return (
<RealtimeClientContext.Provider <RealtimeClientContext.Provider