import { useState, useCallback, useEffect, useRef } from 'react'; import agentService from '@/services/agent_service'; import type { IAgent, IGraph } from '@/interfaces/database/agent'; import type { IAgentPaginationParams, IAgentCreateRequestBody, IAgentSettingRequestBody } from '@/interfaces/request/agent'; import logger from '@/utils/logger'; import { useTranslation } from 'react-i18next'; import { useSnackbar } from '@/hooks/useSnackbar'; export interface UseAgentListState { agents: IAgent[]; total: number; loading: boolean; error: string | null; currentPage: number; pageSize: number; keywords: string; orderby?: string; desc?: boolean; } export interface UseAgentListReturn extends UseAgentListState { fetchAgents: (params?: IAgentPaginationParams) => Promise; setKeywords: (keywords: string) => void; setCurrentPage: (page: number) => void; setPageSize: (size: number) => void; setOrder: (orderby?: string, desc?: boolean) => void; refresh: () => Promise; } /** * 智能体列表钩子 * @param initialParams 初始参数 */ export const useAgentList = (initialParams?: IAgentPaginationParams) => { const [agents, setAgents] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(initialParams?.page || 1); const [pageSize, setPageSize] = useState(initialParams?.page_size || 10); const [keywords, setKeywords] = useState(initialParams?.keywords || ''); const fetchAgentList = useCallback(async (params?: IAgentPaginationParams) => { setLoading(true); setError(null); try { const envMode = import.meta.env.MODE; let response: any = null; if (envMode === 'flask') { response = await agentService.teamlistCanvas(params); } else { response = await agentService.listCanvas(params); } const res = response.data || {}; logger.info('useAgentList fetchAgentList', res); const data = res.data setAgents(data.canvas || []); setTotal(data.total || 0); } catch (err: any) { setError(err.message || 'Failed to fetch agent list'); } finally { setLoading(false); } }, []); const refresh = useCallback(() => fetchAgentList({ keywords, page: currentPage, page_size: pageSize, }), [keywords, currentPage, pageSize]); useEffect(() => { refresh(); }, [refresh]); return { agents, total, loading, error, currentPage, pageSize, keywords, fetchAgents: fetchAgentList, setKeywords, setCurrentPage, setPageSize, refresh, }; }; export function useAgentOperations() { const { t } = useTranslation(); const { showMessage } = useSnackbar(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const createAgent = useCallback(async (body: IAgentCreateRequestBody) => { try { setLoading(true); setError(null); const res = await agentService.setCanvas(body); const newId = res?.data?.data?.id || res?.data?.id; showMessage.success(t('agent.createAgentSuccess')); return { success: true, id: newId } as const; } catch (err: any) { const errorMessage = err?.response?.data?.message || err?.message || t('agent.createAgentFailed'); setError(errorMessage); showMessage.error(errorMessage); return { success: false, error: errorMessage } as const; } finally { setLoading(false); } }, [t, showMessage]); const editAgent = useCallback(async (data: Partial) => { try { setLoading(true); setError(null); const res = await agentService.settingAgent(data); const code = res?.data?.code; if (code === 0) { showMessage.success(t('message.updated')); return { success: true } as const; } const msg = res?.data?.message || t('common.operationFailed'); showMessage.error(msg); return { success: false, error: msg } as const; } catch (err: any) { const errorMessage = err?.response?.data?.message || err?.message || t('common.operationFailed'); setError(errorMessage); showMessage.error(errorMessage); return { success: false, error: errorMessage } as const; } finally { setLoading(false); } }, [t, showMessage]); const deleteAgents = useCallback(async (ids: string[]) => { try { setLoading(true); setError(null); const res = await agentService.removeCanvas(ids); const code = res?.data?.code; if (code === 0) { showMessage.success(t('message.deleted')); return { success: true } as const; } const msg = res?.data?.message || t('common.operationFailed'); showMessage.error(msg); return { success: false, error: msg } as const; } catch (err: any) { const errorMessage = err?.response?.data?.message || err?.message || t('common.operationFailed'); setError(errorMessage); showMessage.error(errorMessage); return { success: false, error: errorMessage } as const; } finally { setLoading(false); } }, [t, showMessage]); const clearError = useCallback(() => setError(null), []); return { loading, error, createAgent, editAgent, deleteAgents, clearError }; } /** * 智能体详情钩子:拉取 Canvas 详情并解析出图(nodes/edges) */ export function useAgentDetail(id?: string) { const [agent, setAgent] = useState(null); const [graph, setGraph] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchDetail = useCallback(async () => { if (!id) { setError('Invalid agent id'); return; } setLoading(true); setError(null); try { const res = await agentService.getCanvas(id); const body = res?.data ?? {}; // 服务端常见返回结构:{ code, data: { id, title, dsl } } const data = (body.data ?? body) as IAgent; const dsl = (data as any)?.dsl ?? {}; const g: IGraph | null = dsl?.graph; setAgent(data); setGraph(g); logger.info('useAgentDetail fetched', { id, title: data?.title, nodes: g?.nodes?.length, edges: g?.edges?.length }); } catch (err: any) { const msg = err?.response?.data?.message || err?.message || 'Failed to fetch agent detail'; setError(msg); logger.error('useAgentDetail error', err); } finally { setLoading(false); } }, [id]); useEffect(() => { fetchDetail(); }, [fetchDetail]); return { agent, graph, loading, error, refresh: fetchDetail } as const; }; export interface UseAgentCanvasOptions { agent?: IAgent | null; graph?: IGraph | null; onReady?: (api: { fitView: () => void }) => void; onAutoSaved?: (date: Date) => void; disableNoteClick?: boolean; } export function useAgentCanvas({ agent, graph, onReady, onAutoSaved, disableNoteClick = true }: UseAgentCanvasOptions) { const [selectedNode, setSelectedNode] = useState(null); const [hoverNode, setHoverNode] = useState(null); const [sanitized, setSanitized] = useState(null); const instRef = ({} as any) as { current?: any }; const sanitizeGraph = useCallback((g?: IGraph | null): IGraph | null => { if (!g) return null; const nodes = (g.nodes || []).map((n: any) => ({ ...n, draggable: false, selectable: true })); const edges = (g.edges || []).map((e: any) => { const { sourceHandle, targetHandle, ...rest } = e || {}; return { ...rest }; }); return { nodes, edges }; }, []); useEffect(() => { setSanitized(sanitizeGraph(graph)); }, [graph, sanitizeGraph]); const onInit = useCallback((inst: any) => { try { (instRef as any).current = inst; onReady?.({ fitView: () => inst?.fitView?.({ padding: 0.2 }) }); inst?.fitView?.({ padding: 0.2 }); } catch (err) { logger.error('useAgentCanvas onInit error', err); } }, [onReady]); const onNodeClick = useCallback((_: any, node: any) => { if (disableNoteClick && node?.type === 'noteNode') return; setSelectedNode(node); }, [disableNoteClick]); const onPaneClick = useCallback(() => setSelectedNode(null), []); const onNodeMouseEnter = useCallback((_: any, node: any) => setHoverNode(node), []); // 30秒自动保存(基于当前画布),确保在卸载与依赖变更时清理定时器 const autoSaveTimerRef = useRef(null); useEffect(() => { // 清理已有定时器,避免重复 if (autoSaveTimerRef.current) { clearInterval(autoSaveTimerRef.current); autoSaveTimerRef.current = null; } // 数据不完整时不创建定时器(Canvas 不可用或退出) if (!agent?.id || !agent?.title || !sanitized) { return; } autoSaveTimerRef.current = window.setInterval(async () => { try { const payload = { id: agent.id, title: agent.title, dsl: { ...(agent.dsl || {}), graph: sanitized }, }; await agentService.setAgentDSL(payload as any); onAutoSaved?.(new Date()); } catch (err) { logger.error('autoSave failed', err); } }, 30000); return () => { if (autoSaveTimerRef.current) { clearInterval(autoSaveTimerRef.current); autoSaveTimerRef.current = null; } }; }, [agent?.id, agent?.title, agent?.dsl, sanitized, onAutoSaved]); return { sanitized, selectedNode, hoverNode, onInit, onNodeClick, onPaneClick, onNodeMouseEnter, } as const; }