初始化模版工程
This commit is contained in:
232
components/nova-sdk/hooks/useNovaChatLogic.ts
Normal file
232
components/nova-sdk/hooks/useNovaChatLogic.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user