Files
test1/components/nova-sdk/hooks/useNovaChatLogic.ts
2026-03-20 07:33:46 +00:00

233 lines
6.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string | undefined>()
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,
}
}
}