import { useEffect, useMemo, useState } from 'react' import { useRequest } from 'ahooks' import { useNovaEvents } from './useNovaEvents' import { useEventStore } from '../store/useEventStore' import { useArtifactsExtractor } from './useArtifactsExtractor' import { useEventProcessor } from './useEventProcessor' import { usePanelState } from './usePanelState' import { useAttachmentHandlers } from './useAttachmentHandlers' import { useMessageSender } from './useMessageSender' import { useNovaService } from './useNovaService' import type { ConversationInfo } from './useNovaService' import { TERMINAL_TASK_STATUS, TaskStatus } from '../types' import type { PlatformConfig, ApiEvent } from './useNovaEvents' export interface UseNovaChatLogicProps { mode: 'chat' | 'share' conversationId?: string platformConfig?: PlatformConfig reconnectLimit?: number reconnectInterval?: number agentId?: string getToken?: () => string | undefined getTenantId?: () => string | undefined onEvent?: (event: ApiEvent) => void onConnectionChange?: (connected: boolean) => void onError?: (error: Error) => void } /** * Nova Chat 主业务逻辑 Hook * 采用原子化模块组合模式,提高代码鲁棒性和可维护性 */ export function useNovaChatLogic({ mode, conversationId, platformConfig, reconnectLimit = 3, reconnectInterval = 3000, getToken, getTenantId, onEvent, onConnectionChange, onError, agentId, }: UseNovaChatLogicProps) { // 1. 核心事件与连接管理 const novaEvents = useNovaEvents({ mode, conversationId, platformConfig: platformConfig || { wssUrl: '', apiBaseUrl: '', agentId: '', agentName: '' }, reconnectLimit, reconnectInterval, getToken, getTenantId, onEvent, onConnectionChange, onError, }) const { rawEvents } = novaEvents // 2. 数据处理与提取 const { artifacts, taskStatus: eventTaskStatus } = useArtifactsExtractor(rawEvents) const [polledTaskStatus, setPolledTaskStatus] = useState() const [initialTaskStatusLoading, setInitialTaskStatusLoading] = useState(false) const taskStatus = useMemo( () => (polledTaskStatus as typeof eventTaskStatus | undefined) || eventTaskStatus, [polledTaskStatus, eventTaskStatus], ) const processedMessages = useEventProcessor(rawEvents) // 3. 面板与附件状态管理 const { panelVisible, selectedAttachment, togglePanel, closePanel, selectAttachment } = usePanelState() // 4. 附件操作处理器 const { handleAttachmentClick, handleImageAttachmentClick, handleToolCallClick } = useAttachmentHandlers(selectAttachment) // 5. 消息发送逻辑 const { sendingMessage, handleSend, setSendingMessage } = useMessageSender({ conversationId, platformConfig, sendMessage: novaEvents.sendMessage, agentId, }) // 6. 工件服务(URL 获取等) const service = useNovaService({ platformConfig, getToken, getTenantId, conversationId }) const { getConversationInfoList } = service // 7. Store 同步(副作用管理) const setEvents = useEventStore((state) => state.setEvents) const setArtifacts = useEventStore((state) => state.setArtifacts) // 8. WS 阶段轮询会话信息(ahooks useRequest):遇到终止状态时关闭 loading const terminalSet = new Set(TERMINAL_TASK_STATUS.map((s) => String(s).toLowerCase())) const getConversationTaskStatus = ( list: ConversationInfo[] | undefined, currentConversationId?: string, ) => { if (!currentConversationId) return undefined return list?.find( item => item.conversation_id === currentConversationId, )?.task_status } const isTerminalTaskStatus = (status?: string) => !!status && terminalSet.has(String(status).toLowerCase()) const shouldPollTaskStatus = !!conversationId && (sendingMessage || taskStatus === TaskStatus.IN_PROGRESS) const stopChat = async () => { await service.stopChat() setSendingMessage(false) setPolledTaskStatus(TaskStatus.STOPPED) cancelPoll() } const { run: runPoll, cancel: cancelPoll } = useRequest( (cid: string) => getConversationInfoList([cid]).then((res) => res.data ?? []), { manual: true, pollingInterval: shouldPollTaskStatus ? 5000 : undefined, ready: shouldPollTaskStatus, onSuccess: (list) => { const nextTaskStatus = getConversationTaskStatus(list, conversationId) if (nextTaskStatus) { setPolledTaskStatus(nextTaskStatus) } if (isTerminalTaskStatus(nextTaskStatus)) { setSendingMessage(false) cancelPoll() } }, } ) useEffect(() => { if (shouldPollTaskStatus && conversationId) { runPoll(conversationId) } else { cancelPoll() } }, [shouldPollTaskStatus, conversationId, runPoll, cancelPoll]) useEffect(() => { let cancelled = false setPolledTaskStatus(undefined) if (!conversationId) { setInitialTaskStatusLoading(false) return } setInitialTaskStatusLoading(true) void getConversationInfoList([conversationId]) .then((res) => { if (cancelled) return const nextTaskStatus = getConversationTaskStatus(res.data ?? [], conversationId) if (nextTaskStatus) { setPolledTaskStatus(nextTaskStatus) } }) .catch(() => {}) .finally(() => { if (!cancelled) { setInitialTaskStatusLoading(false) } }) return () => { cancelled = true } }, [conversationId, getConversationInfoList]) useEffect(() => { setEvents(rawEvents) }, [rawEvents, setEvents]) useEffect(() => { setArtifacts(artifacts) }, [artifacts, setArtifacts]) // 9. 统一对外接口 return { // 状态数据 messages: processedMessages, loading: sendingMessage || initialTaskStatusLoading, taskStatus, artifacts, hasArtifacts: artifacts.length > 0, panelVisible, selectedAttachment, // 操作方法 handleSend, handlePanelToggle: togglePanel, handlePanelClose: closePanel, handleAttachmentClick, handleImageAttachmentClick, handleToolCallClick, setLoading: setSendingMessage, agentId, // 统一的 API 命名空间 api: { ...service, stopChat, } } }