import { useEffect, useRef, useState } from 'react'; import { FormInstance } from 'antd'; import { Agent, AgentAPI, Team, TeamAPI, AiModel, ModelAPI, ImageAPI } from '../../../api'; import { DEFAULT_AVATAR } from '../constants'; interface UseAgentEditorOptions { id?: string; isNew: boolean; form: FormInstance; message: any; navigate: any; } export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentEditorOptions) { const [agent, setAgent] = useState(null); const [saving, setSaving] = useState(false); const [teams, setTeams] = useState([]); const [models, setModels] = useState([]); const [autoSaveStatus, setAutoSaveStatus] = useState<'saved' | 'dirty' | 'saving' | 'error'>('saved'); const [initModalOpen, setInitModalOpen] = useState(isNew); const [selectedAvatar, setSelectedAvatar] = useState(DEFAULT_AVATAR); const [avatarSelectorOpen, setAvatarSelectorOpen] = useState(false); const [avatarUploading, setAvatarUploading] = useState(false); const [agentName, setAgentName] = useState(''); const [skillEditorOpen, setSkillEditorOpen] = useState(false); const [editingSkillId, setEditingSkillId] = useState(null); const pollTimer = useRef(null); const pollCount = useRef(0); const hydratingRef = useRef(false); const MAX_POLL_COUNT = 30; const stopPolling = () => { if (pollTimer.current) { window.clearInterval(pollTimer.current); pollTimer.current = null; } pollCount.current = 0; }; const startPolling = () => { pollCount.current = 0; if (pollTimer.current) { window.clearInterval(pollTimer.current); } pollTimer.current = window.setInterval(pollKnowledgeStatus, 4000); pollKnowledgeStatus(); }; const pollKnowledgeStatus = async () => { if (!id) return; try { const result = await AgentAPI.getKnowledgeStatus(id); const files = result.files || []; setAgent((prev) => { if (!prev) return prev; return { ...prev, knowledge: files }; }); const indexing = files.some((k) => k.status === 'pending' || k.status === 'indexing'); pollCount.current++; if (!indexing || pollCount.current >= MAX_POLL_COUNT) { stopPolling(); if (!indexing) { refresh(false); } else { message.warning('部分文件处理超时,请稍后刷新页面查看结果'); } } } catch (e) { pollCount.current++; if (pollCount.current >= MAX_POLL_COUNT) { stopPolling(); message.warning('知识库状态查询超时,请稍后刷新页面查看结果'); } } }; const refresh = async (force = false) => { if (!id) return; const data = await AgentAPI.detail(id); setAgent(data); if (force || autoSaveStatus !== 'dirty') { hydratingRef.current = true; form.setFieldsValue(data); window.setTimeout(() => { hydratingRef.current = false; }, 0); setAutoSaveStatus('saved'); setAgentName(data.name); setSelectedAvatar(data.avatar || DEFAULT_AVATAR); } const indexing = data.knowledge?.some((k) => k.status === 'pending' || k.status === 'indexing'); if (indexing && !pollTimer.current) { startPolling(); } }; useEffect(() => { TeamAPI.list() .then(setTeams) .catch(() => setTeams([])); ModelAPI.list() .then(setModels) .catch(() => setModels([])); if (isNew) { setInitModalOpen(true); setSelectedAvatar(DEFAULT_AVATAR); setAgentName(''); form.setFieldsValue({ name: '', description: '', prompt: 'You are a helpful AI assistant.', model: '', temperature: 0.7, visibility: 'private', teamId: null, }); } else { setInitModalOpen(false); refresh(true); } return () => { if (pollTimer.current) { window.clearInterval(pollTimer.current); pollTimer.current = null; } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); const handleInitConfirm = async (values: any) => { setSaving(true); try { const created = await AgentAPI.create({ ...values, avatar: selectedAvatar, prompt: 'You are a helpful AI assistant.', temperature: 0.7, visibility: 'private', }); message.success('初始化成功'); setInitModalOpen(false); navigate(`/agents/${created.id}`, { replace: true }); } catch (e) { message.error('创建失败'); } finally { setSaving(false); } }; const handleSave = async (silent = false) => { if (isNew) return; const values = await form.validateFields(); if (!silent) setSaving(true); setAutoSaveStatus('saving'); try { const changedFields: Record = {}; Object.keys(values).forEach((key) => { const formValue = (values as any)[key]; const originalValue = (agent as any)?.[key]; if (formValue !== originalValue) { changedFields[key] = formValue; } }); if (Object.keys(changedFields).length === 0) { if (!silent) message.info('无变化'); setAutoSaveStatus('saved'); return; } const updatedAgent = await AgentAPI.update(id!, changedFields); setAgent(updatedAgent); form.setFieldsValue(updatedAgent); if (!silent) message.success('已保存'); setAutoSaveStatus('saved'); } catch (e) { setAutoSaveStatus('error'); if (!silent) message.error('保存失败'); } finally { if (!silent) setSaving(false); } }; const uploadAvatar = async (file: File) => { setAvatarUploading(true); try { const res = await ImageAPI.upload([file]); const url = res.files?.[0]?.url; if (!url) { throw new Error('未获取到图片地址'); } return url; } finally { setAvatarUploading(false); } }; const handleDeleteKnowledge = async (fileId: string) => { if (!id) return; try { await AgentAPI.deleteKnowledge(id, fileId); setAgent((prev) => { if (!prev) return prev; return { ...prev, knowledge: (prev.knowledge || []).filter((k: any) => k.id !== fileId), }; }); message.success('已删除'); } catch (e: any) { message.error('删除失败:' + (e?.message ?? e)); } }; const beforeUploadKnowledge = async (file: any) => { if (!id) { message.warning('请先保存智能体基础信息后再上传'); return false; } try { const result = await AgentAPI.uploadKnowledge(id, [file as File]); setAgent((prev) => { if (!prev) return prev; const existingIds = new Set((prev.knowledge || []).map((k: any) => k.id)); const uniqueNewFiles = (result.files || []).filter((f: any) => !existingIds.has(f.id)); return { ...prev, knowledge: [...uniqueNewFiles, ...(prev.knowledge || [])], }; }); startPolling(); message.success(`${file.name} 已上传,正在建索引…`); } catch (e: any) { message.error('上传失败:' + (e?.message ?? e)); } return false; }; const beforeUploadInitAvatar = async (file: any) => { try { const url = await uploadAvatar(file as File); setSelectedAvatar(url); message.success('头像上传成功'); } catch (e: any) { message.error('头像上传失败:' + (e?.message ?? e)); } return false; }; const handleAvatarSelect = async (avatarUrl: string) => { if (!id) return; try { await AgentAPI.updateAvatar(id, avatarUrl); setAgent((prev) => { if (!prev) return prev; return { ...prev, avatar: avatarUrl }; }); setSelectedAvatar(avatarUrl); message.success('头像已更新'); setAvatarSelectorOpen(false); } catch (e: any) { message.error('头像更新失败:' + (e?.message ?? e)); } }; const beforeUploadEditAvatar = async (file: any) => { if (!id) return false; try { const url = await uploadAvatar(file as File); await AgentAPI.updateAvatar(id, url); setAgent((prev) => { if (!prev) return prev; return { ...prev, avatar: url }; }); setSelectedAvatar(url); message.success('头像已更新'); setAvatarSelectorOpen(false); } catch (e: any) { message.error('头像上传失败:' + (e?.message ?? e)); } return false; }; const liveAgent = { ...(agent ?? {}), ...(form.getFieldsValue() as Agent) } as Agent; const currentName = agent?.name || liveAgent?.name || agentName || undefined; const markDirty = () => { if (hydratingRef.current) return; setAutoSaveStatus('dirty'); }; return { agent, saving, teams, models, autoSaveStatus, initModalOpen, setInitModalOpen, selectedAvatar, setSelectedAvatar, avatarSelectorOpen, setAvatarSelectorOpen, avatarUploading, agentName, setAgentName, skillEditorOpen, setSkillEditorOpen, editingSkillId, setEditingSkillId, hydratingRef, refresh, handleInitConfirm, handleSave, beforeUploadKnowledge, beforeUploadInitAvatar, beforeUploadEditAvatar, handleAvatarSelect, handleDeleteKnowledge, liveAgent, currentName, markDirty, }; }