diff --git a/src/app/MainArea/Antechamber/index.tsx b/src/app/MainArea/Antechamber/index.tsx index 9672d8b..72c5648 100644 --- a/src/app/MainArea/Antechamber/index.tsx +++ b/src/app/MainArea/Antechamber/index.tsx @@ -72,19 +72,20 @@ export default function Antechamber() { setIsLoading(false); }); }; + return (
- + toRoom({})} /> { - isLoading ?
- loading - 加载中 -
: <> + isLoading ?
+ loading + 加载中 +
: <> }
); diff --git a/src/components/AntechamberFile/index.tsx b/src/components/AntechamberFile/index.tsx index 4f53f6e..1a35c8c 100644 --- a/src/components/AntechamberFile/index.tsx +++ b/src/components/AntechamberFile/index.tsx @@ -20,7 +20,8 @@ export default function AntechamberFile({handleLoading}:Props) { const { setHasHandledReport,hasHandledReport } = useContext(ReportContext); const { handleConnect } = useContext(RealtimeClientContext); - const useFileFetch = async () => { + const useFileFetch = async () => { + handleLoading(true) const result = await fetchFile({ params: { id: fileId, location: locationCode }, options: { @@ -28,13 +29,14 @@ export default function AntechamberFile({handleLoading}:Props) { headers: { Authorization: `Bearer ${token}` }, }, }); + if (result.message) { toast({ title: result.message, }); } let resp = result.result as FileInfo; - handleLoading(true) + handleConnect({ fileInfo: {type: resp.type,url: resp.url,tableName: resp.tableName,provinceName: resp.provinceName,subjectClaim: resp.subjectClaim}, }); @@ -43,6 +45,7 @@ export default function AntechamberFile({handleLoading}:Props) { useEffect(() => { + if (fileId && locationCode && !hasHandledReport) { useFileFetch(); } diff --git a/src/components/AntechamberWishList/index.tsx b/src/components/AntechamberWishList/index.tsx index 297756e..0eb4189 100644 --- a/src/components/AntechamberWishList/index.tsx +++ b/src/components/AntechamberWishList/index.tsx @@ -19,8 +19,11 @@ import { useAbortController } from "@/hooks/useAbortController"; import { useSearchParams } from "react-router-dom"; import { toast } from "@/hooks/use-toast"; +type Props = { + handleLoading:(val:boolean) => void +} -export default function AntechamberWishList() { +export default function AntechamberWishList({handleLoading}:Props) { const { wishList } = useWishList(); const { handleConnect } = useContext(RealtimeClientContext); const { getSignal } = useAbortController(); @@ -28,10 +31,7 @@ export default function AntechamberWishList() { const token = searchParams.get("token") || ""; const handleNavigate = async (item: any) => { - const loadingId = toast({ - title:'文件生成中...', - duration: 10000, - }) + handleLoading(true) const result = await fetchFile({ params: { id: item.vId, location: item.personlocationCode }, options: { @@ -39,7 +39,7 @@ export default function AntechamberWishList() { headers: { Authorization: `Bearer ${token}` }, }, }); - loadingId.dismiss(); + if (result.message) { toast({ title: result.message, diff --git a/src/components/Provider/RealtimeClientProvider.tsx b/src/components/Provider/RealtimeClientProvider.tsx index 8afca4f..29416b1 100644 --- a/src/components/Provider/RealtimeClientProvider.tsx +++ b/src/components/Provider/RealtimeClientProvider.tsx @@ -3,7 +3,6 @@ import { EventNames, RealtimeAPIError, RealtimeClient, - RealtimeError, RealtimeUtils, } from "@coze/realtime-api"; import { @@ -15,6 +14,7 @@ import { useState, } from "react"; import { useToast } from "@/hooks/use-toast"; +import { FileParser, FileParseStatus } from "./FileParser"; type RoomInfo = { appId: string; @@ -23,7 +23,13 @@ type RoomInfo = { uid: string; }; -export type FileInfo = {type:string,url:string,tableName:string,provinceName:string,subjectClaim:string} +export type FileInfo = { + type: string; + url: string; + tableName: string; + provinceName: string; + subjectClaim: string; +}; export const RealtimeClientContext = createContext<{ client: RealtimeClient | null; @@ -31,91 +37,89 @@ export const RealtimeClientContext = createContext<{ isConnected: boolean; audioEnabled: boolean; isSupportVideo: boolean; - messageList: { content: string; role: RoleType }[]; + messageList: { + content: string; + role: RoleType; + event?: any; + fileInfo?: FileInfo; + fileParseStatus?: number; + }[]; isAiTalking: boolean; roomInfo: RoomInfo | null; - initClient: ({ - initMessage, - fileInfo, - }: { - initMessage?: string; - fileInfo?: FileInfo; - }) => void; - handleConnect: ({ - initMessage, - fileInfo, - }: { - initMessage?: string; - fileInfo?: FileInfo; - }) => Promise; + initClient: (opts: { initMessage?: string; fileInfo?: FileInfo }) => void; + handleConnect: (opts: { initMessage?: string; fileInfo?: FileInfo }) => Promise; handleInterrupt: () => void; handleDisconnect: () => void; toggleMicrophone: () => void; -}>({ - client: null, - isConnecting: false, - isConnected: false, - audioEnabled: true, - isSupportVideo: false, - messageList: [], - isAiTalking: false, - roomInfo: null, +}>(/* 默认值省略 */ null!); - initClient: () => {}, - handleConnect: () => Promise.resolve(), - handleInterrupt: () => {}, - handleDisconnect: () => {}, - toggleMicrophone: () => {}, -}); - -// 添加自定义hook export const useRealtimeClient = () => { - const context = useContext(RealtimeClientContext); - - if (context === undefined) { - throw new Error("useRealtimeClient必须在RealtimeClientProvider内部使用"); - } - - return { ...context }; + const ctx = useContext(RealtimeClientContext); + if (!ctx) throw new Error("useRealtimeClient 必须在 RealtimeClientProvider 内部使用"); + return ctx; }; -export const RealtimeClientProvider = ({ - children, -}: { - children: ReactNode; -}) => { +export const RealtimeClientProvider = ({ children }: { children: ReactNode }) => { const token = import.meta.env.VITE_COZE_TOKEN; const botId = import.meta.env.VITE_COZE_BOT_ID; const voiceId = import.meta.env.VITE_COZE_VOICE_ID; const connectorId = "1024"; const clientRef = useRef(null); - // 添加连接锁 - const connectingLockRef = useRef(false); - // 实时语音回复消息列表 - const [messageList, setMessageList] = useState< - { content: string; role: RoleType; event?: any }[] - >([]); - // 是否正在连接 - const [isConnecting, setIsConnecting] = useState(false); - // 是否已连接 - const [isConnected, setIsConnected] = useState(false); - // 是否开启麦克风 - const [audioEnabled, setAudioEnabled] = useState(true); - // 是否支持视频 - const [isSupportVideo] = useState(false); - // 是否正在说话 - const [isAiTalking, setIsAiTalking] = useState(false); + const connectingLockRef = useRef(false); + const [messageList, setMessageList] = useState<{ + content: string; + role: RoleType; + event?: any; + fileInfo?: FileInfo; + fileParseStatus?: number; + }[]>([]); + const [isConnecting, setIsConnecting] = useState(false); + const [isConnected, setIsConnected] = useState(false); + const [audioEnabled, setAudioEnabled] = useState(true); + const [isSupportVideo] = useState(false); + const [isAiTalking, setIsAiTalking] = useState(false); const [roomInfo, setRoomInfo] = useState(null); - // 记录文件解析是否完成 分为 没有解析 -1,未解析 0,解析中 1,解析完成 2 - - const fileParseStatusRef = useRef(-1); + // 引入状态机 + const fileParseStatusRef = useRef(-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(); + /** 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 ({ initMessage, fileInfo, @@ -123,36 +127,28 @@ export const RealtimeClientProvider = ({ initMessage?: string; fileInfo?: FileInfo; }) => { - const permission = await RealtimeUtils.checkDevicePermission(false); + const perm = await RealtimeUtils.checkDevicePermission(false); const device = await RealtimeUtils.getAudioDevices(); - if (!permission.audio) { - toast({ - title: "连接错误", - description: "需要麦克风访问权限", - }); + if (!perm.audio) { + toast({ title: "连接错误", description: "需要麦克风访问权限" }); throw new Error("需要麦克风访问权限"); } - if (device.audioInputs.length === 0) { - toast({ - title: "连接错误", - description: "没有麦克风设备", - }); + toast({ title: "连接错误", description: "没有麦克风设备" }); throw new Error("没有麦克风设备"); } const client = new RealtimeClient({ accessToken: token, - botId: botId, - voiceId: voiceId, - connectorId: connectorId, - allowPersonalAccessTokenInBrowser: true, // 可选:允许在浏览器中使用个人访问令牌 + botId, + voiceId, + connectorId, + allowPersonalAccessTokenInBrowser: true, suppressStationaryNoise: true, suppressNonStationaryNoise: true, - debug: false, + debug: true, }); - clientRef.current = client; setupEventListeners(client); @@ -160,6 +156,7 @@ export const RealtimeClientProvider = ({ setupInitMessageEventListener(client, { initMessage, fileInfo }); }; + /** 连接房间 */ const handleConnect = async ({ initMessage, fileInfo, @@ -167,313 +164,245 @@ export const RealtimeClientProvider = ({ initMessage?: string; fileInfo?: FileInfo; }) => { + if (connectingLockRef.current) return; + connectingLockRef.current = true; + + if (isConnected || isConnecting) { + connectingLockRef.current = false; + return; + } + try { - // 使用连接锁确保原子性 - if (connectingLockRef.current) { - return; - } - connectingLockRef.current = true; - - // 如果已经连接或正在连接中,直接返回 - if (isConnected || isConnecting) { - connectingLockRef.current = false; - return; - } - if (!clientRef.current) { await initClient({ initMessage, fileInfo }); } - await clientRef.current?.connect(); - await clientRef.current?.setAudioEnable(false); + await clientRef.current!.connect(); + await clientRef.current!.setAudioEnable(false); setAudioEnabled(false); - - } catch (error) { - // console.error(error); + } catch (error: any) { if (error instanceof RealtimeAPIError) { - switch (error.code) { - case RealtimeError.CREATE_ROOM_ERROR: - console.error(`创建房间失败: ${error.message}`); - break; - case RealtimeError.CONNECTION_ERROR: - console.error(`加入房间失败: ${error.message}`); - break; - case RealtimeError.DEVICE_ACCESS_ERROR: - console.error(`获取设备失败: ${error.message}`); - break; - default: - console.error(`连接错误: ${error.message}`); - } + console.error(`连接错误 (${error.code}): ${error.message}`); } else { console.error("连接错误:" + error); } } finally { - // 确保在任何情况下都释放连接锁 connectingLockRef.current = false; } }; const handleInterrupt = () => { - try { - clientRef.current?.interrupt(); - } catch (error) { - console.error("打断失败:" + error); - } + clientRef.current?.interrupt(); }; const handleDisconnect = async () => { - try { - // 关闭客户的时候清除一些信息 - setIsAiTalking(false); - setMessageList([]); - await clientRef.current?.setAudioEnable(false); - setAudioEnabled(false); - - await clientRef.current?.disconnect(); - clientRef.current?.clearEventHandlers(); - clientRef.current = null; - setIsConnected(false); - } catch (error) { - console.error("断开失败:" + error); - } + setIsAiTalking(false); + setMessageList([]); + await clientRef.current?.setAudioEnable(false); + setAudioEnabled(false); + await clientRef.current?.disconnect(); + clientRef.current?.clearEventHandlers(); + clientRef.current = null; + setIsConnected(false); }; const toggleMicrophone = async () => { - try { - await clientRef.current?.setAudioEnable(!audioEnabled); - setAudioEnabled(!audioEnabled); - } catch (error) { - console.error("切换麦克风状态失败:" + error); - } + await clientRef.current?.setAudioEnable(!audioEnabled); + setAudioEnabled(!audioEnabled); }; + /** 首条初始化消息(session.create & bot.join) */ const setupInitMessageEventListener = useCallback( ( client: RealtimeClient, { initMessage, fileInfo }: { initMessage?: string; fileInfo?: FileInfo } ) => { - client.on(EventNames.ALL_SERVER, async (eventName, _event: any) => { + client.on(EventNames.ALL_SERVER, async (eventName, event: any) => { if (eventName === "server.session.created") { await client.sendMessage({ id: "", event_type: "session.update", data: { - chat_config: { - allow_voice_interrupt: false, - }, - turn_detection: { - silence_duration_ms: 2000, - }, + chat_config: { allow_voice_interrupt: false }, + turn_detection: { silence_duration_ms: 2000 }, }, }); } - if (eventName === "server.bot.join" && initMessage) { - // 这里需要加个 server. 前缀 - await clientRef.current?.sendMessage({ - id: "", - event_type: "conversation.message.create", - data: { - role: "user", - content_type: "text", - content: initMessage, - }, - }); - } else if (eventName === "server.bot.join" && fileInfo) { - fileParseStatusRef.current = 0; - - await clientRef.current?.sendMessage({ - id: "", - event_type: "conversation.message.create", - data: { - role: "user", - content_type: "object_string", - content: JSON.stringify([ - { - type: "text", - text: "帮我解读这个文件,结合当下的专业行情以及对该专业未来的发展趋势,简介的给出志愿建议", - }, - { type: "image", file_url: fileInfo.url }, - ]), - }, - }); + if (eventName === "server.bot.join") { + if (initMessage) { + await clientRef.current!.sendMessage({ + id: "", + event_type: "conversation.message.create", + data: { + role: "user", + content_type: "text", + content: initMessage, + }, + }); + } else if (fileInfo) { + await clientRef.current!.sendMessage({ + id: "", + event_type: "conversation.message.create", + data: { + role: "user", + content_type: "object_string", + content: JSON.stringify([ + { type: "text", text: "帮我解读这个文件,结合当下的专业行情以及对该专业未来的发展趋势,简介的给出志愿建议" }, + { type: "image", file_url: fileInfo.url }, + ]), + }, + }); + } } }); }, - [clientRef.current] + [] ); + /** 消息及文件解析监听 */ const setupMessageEventListeners = ( client: RealtimeClient, - { initMessage, fileInfo }: { initMessage?: string; fileInfo?: FileInfo } + opts: { initMessage?: string; fileInfo?: FileInfo } ) => { client.on(EventNames.ALL, (_eventName, event: any) => { - // AI智能体设置 - - if(_eventName === 'server.error'){ - // 长期不活动,服务端终结了 - handleDisconnect(); - } - + // 交给状态机处理解析流程 + fileParserRef.current.handleEvent(_eventName, event); + // 普通消息流处理 if ( event.event_type !== ChatEventType.CONVERSATION_MESSAGE_DELTA && - event.event_type !== ChatEventType.CONVERSATION_MESSAGE_COMPLETED && - event.event_type !== "conversation.created" && - event.event_type !== "conversation.message.create" + 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 ) { return; } - const content = event.data.content; - if ( - fileParseStatusRef.current === 0 && - event.data.type === "function_call" && - JSON.parse(content).name === "doc_reader-PDF_reader" - ) { - fileParseStatusRef.current = 1; - } else if ( - event.data.type === "tool_response" && - fileParseStatusRef.current === 1 - ) { - fileParseStatusRef.current = 2; + // 如果没有fileInfo,过滤掉function_call和tool_response类型的消息 + if (!opts.fileInfo && (event.data.type === "function_call" || event.data.type === "tool_response")) { + return; } - setMessageList((prev) => { - // 如果上一个事件是增量更新,则附加到最后一条消息 + const content = event.data.content; + setMessageList(prev => { + // 合并增量 + console.log("合并增量",prev); + console.log("信息",content); + + // 处理工具回调结果 + try { + const parsedContent = JSON.parse(content); + if (parsedContent.msg_type === "time_capsule_recall" || + (parsedContent.name && parsedContent.arguments)) { + // 如果没有fileInfo,不创建假信息 + if (!opts.fileInfo) { + return prev; + } + + // 检查是否已存在工具回调消息 + const existingToolMessageIndex = prev.findIndex(msg => + msg.content === "正在处理您的请求..." && + msg.fileInfo === opts.fileInfo + ); + + if (existingToolMessageIndex !== -1) { + // 更新已存在的消息的fileParseStatus + const newStatus = parsedContent.msg_type === "time_capsule_recall" ? 0 : 2; + return [ + ...prev.slice(0, existingToolMessageIndex), + { + ...prev[existingToolMessageIndex], + fileParseStatus: newStatus + }, + ...prev.slice(existingToolMessageIndex + 1) + ]; + } else { + // 创建新的工具回调消息 + return [ + ...prev, + { + content: "正在处理您的请求...", + role: event.data.role, + event, + fileInfo: opts.fileInfo, + fileParseStatus: parsedContent.msg_type === "time_capsule_recall" ? 0 : 2 + }, + ]; + } + } + } catch (e) { + // 如果不是JSON格式,继续正常处理 + } + 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.type === event.data.type && - prev[prev.length - 1].event.data.answer_id === event.data.answer_id + 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: event, + event, }, ]; } - - // 添加AI的欢迎语 - if ( - typeof initMessage === "undefined" && - typeof fileInfo === "undefined" && - event.event_type === "conversation.created" - ) { - return [ - ...prev, - { - content: event.data.prologue, - role: RoleType.Assistant, - event: event, - }, - ]; - } - - // 否则添加新消息 - if ( - (content !== "" && - event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA) || - (event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && - (event.data.type === "answer" || event.data.type === "question") && - event.data.role !== RoleType.Assistant) - ) { - // lastEvent = event; - if(event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA && fileParseStatusRef.current === 2){ - fileParseStatusRef.current = -1; - } - return [ - ...prev, - { content: content, role: event.data.role, event: event }, - ]; - } - - // 添加一个文件解析的信息 - if ( - fileParseStatusRef.current === 0 && - event.event_type === "conversation.message.completed" - ) { - return [ - ...prev, - { - content: "AI正在解析您的文档...", - role: RoleType.Assistant, - event: event, - fileParseStatus: fileParseStatusRef.current, - fileInfo: fileInfo, - }, - ]; - } else if ( - fileParseStatusRef.current === 1 && - event.event_type === "conversation.message.completed" - ) { - return [ - ...prev.slice(0, -1), - { - content: "AI正在调用插件", - role: prev[prev.length - 1].role, - event: event, - fileParseStatus: fileParseStatusRef.current, - fileInfo: fileInfo, - }, - ]; - } else if ( - fileParseStatusRef.current === 2 && - event.event_type === "conversation.message.completed" - ) { - return [ - ...prev.slice(0, -1), - { - content: "文档解析完成", - role: RoleType.Assistant, - event: event, - fileParseStatus: fileParseStatusRef.current, - fileInfo: fileInfo, - }, - ]; - } - return prev; + // 新消息追加 + return [ + ...prev, + { content, role: event.data.role, event }, + ]; }); }); }; - // 设置事件监听器 + /** 基本连接状态 & 语音事件监听 */ const setupEventListeners = useCallback( (client: RealtimeClient) => { - // 监听 AI 开始说话事件 client.on(EventNames.AUDIO_AGENT_SPEECH_STARTED, async () => { - // console.log("AI开始说话"); setIsAiTalking(true); await clientRef.current?.setAudioEnable(false); setAudioEnabled(false); }); - - // 监听 AI 结束说话事件 client.on(EventNames.AUDIO_AGENT_SPEECH_STOPPED, async () => { - // console.log("AI结束说话"); setIsAiTalking(false); await clientRef.current?.setAudioEnable(true); setAudioEnabled(true); }); - - // 监听连接客户端 client.on(EventNames.CONNECTING, () => { setIsConnecting(true); setIsConnected(false); }); - - // 客户端连接成功 - client.on(EventNames.CONNECTED, (_eventName: string, event: any) => { - setRoomInfo(event); + client.on(EventNames.CONNECTED, (_name, evt) => { + setRoomInfo(evt as RoomInfo); setIsConnecting(false); setIsConnected(true); }); - + client.on(EventNames.ALL_SERVER, (name, evt) => { + // 其它全局服务端事件可在此处理 + }); }, - [clientRef.current] + [] ); return ( diff --git a/src/components/Provider/fileParser.ts b/src/components/Provider/fileParser.ts new file mode 100644 index 0000000..4c717b1 --- /dev/null +++ b/src/components/Provider/fileParser.ts @@ -0,0 +1,93 @@ +import { ChatEventType } from "@coze/api"; + +/** 文件解析状态枚举 */ +export type FileParseStatus = -1 | 0 | 1 | 2; + +/** 状态接口 */ +export interface IFileParseState { + status: FileParseStatus; + handleEvent(eventType: string, event: any): void; +} + +/** + * 文件解析器:状态模式 + 回调(观察者) + */ +export class FileParser { + private state: IFileParseState|null=null; + + constructor( + /** 状态变更时回调,更新 UI 或者其他逻辑 */ + private onStatusChange: (newStatus: FileParseStatus) => void + ) { + this.transitionTo(new UninitializedState(this)); + } + + get status() { + return this.state?.status; + } + + /** 外部事件统一入口,交由当前状态去处理 */ + handleEvent(eventType: string, event: any) { + this.state?.handleEvent(eventType, event); + } + + /** 切换状态并触发回调 */ + transitionTo(state: IFileParseState) { + this.state = state; + this.onStatusChange(state.status); + } +} + +/** 初始状态:-1,等待 bot.join 且带 fileInfo */ +class UninitializedState implements IFileParseState { + status: FileParseStatus = -1; + constructor(private ctx: FileParser) {} + + handleEvent(eventType: string, event: any) { + if (eventType === "server.bot.join" && event.fileInfo) { + this.ctx.transitionTo(new WaitingState(this.ctx)); + } + } +} + +/** 等待发送解析请求:0 */ +class WaitingState implements IFileParseState { + status: FileParseStatus = 0; + constructor(private ctx: FileParser) {} + + handleEvent(eventType: string, event: any) { + if ( + event.event_type === ChatEventType.CONVERSATION_MESSAGE_DELTA && + event.data.type === "function_call" && + JSON.parse(event.data.content).name === "doc_reader-PDF_reader" + ) { + this.ctx.transitionTo(new ParsingState(this.ctx)); + } + } +} + +/** 正在调用插件解析:1 */ +class ParsingState implements IFileParseState { + status: FileParseStatus = 1; + constructor(private ctx: FileParser) {} + + handleEvent(eventType: string, event: any) { + if ( + event.event_type === ChatEventType.CONVERSATION_MESSAGE_COMPLETED && + event.data.type === "tool_response" + ) { + this.ctx.transitionTo(new CompletedState(this.ctx)); + } + } +} + +/** 解析完成:2 */ +class CompletedState implements IFileParseState { + status: FileParseStatus = 2; + constructor(private ctx: FileParser) {} + + handleEvent(eventType: string, event: any) { + // 如需重置或其他后续逻辑,可在此处理 + } +} + diff --git a/src/components/RoomConversation/index.tsx b/src/components/RoomConversation/index.tsx index 64000bd..8ebc554 100644 --- a/src/components/RoomConversation/index.tsx +++ b/src/components/RoomConversation/index.tsx @@ -68,18 +68,18 @@ export default function RoomConversation() {
- {message.fileInfo.tableName} + {message?.fileInfo?.tableName}
- {message.fileInfo.type} + {message?.fileInfo?.type}
- {message.fileInfo.provinceName}·{message.fileInfo.score} + {message?.fileInfo?.provinceName}·{message?.fileInfo?.score} - {message.fileInfo.subjectClaim.split(",").join("/")} + {message?.fileInfo?.subjectClaim?.split(",").join("/")} { message.fileParseStatus < 2 && (