233 lines
6.3 KiB
TypeScript
233 lines
6.3 KiB
TypeScript
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,
|
||
}
|
||
}
|
||
}
|