import { useEffect, useRef, useState } from 'react'; import { useBlocker } from 'react-router-dom'; import { ArrowLeft, Check, Loader2, Save } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/ui/dialog'; import { cloudAgentsApi } from '@/lib/cloud-agents-api'; import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentDraft, type CloudRecent, type CloudThread, type CloudScheduleProposal } from '../../../shared/cloud-agents'; import { ConfigurationFields } from './ConfigurationFields'; import { CloudChat } from './CloudChat'; import { CloudChannelConversations } from './CloudChannelConversations'; import { CloudAccessPanel } from './CloudAccess'; import { CloudSchedules } from './CloudSchedules'; import { CloudPendingContext, useCloudPendingState } from './CloudPending'; import { DesktopSplit } from './DesktopSplit'; type Tab = 'configuration' | 'chat' | 'tasks' | 'access'; type Category = 'instructions' | 'capabilities' | 'knowledge' | 'limits'; const hasDraftChanges = (draft: CloudAgentDraft, saved: CloudAgentDraft) => draft.name !== saved.name || draft.purpose !== saved.purpose || draft.system_prompt !== saved.system_prompt || JSON.stringify(draft.configuration) !== JSON.stringify(saved.configuration); const errorMessage = (error: unknown) => error instanceof Error ? error.message : '暂时无法完成操作'; export function DraftEditor({ initial, recent, onBack, onSaved }: { initial: CloudAgentDraft; recent: CloudRecent | null; onBack: () => void; onSaved: (agent: CloudAgentDraft) => void; }) { const [saved, setSaved] = useState(initial); const [draft, setDraft] = useState(initial); const [previewCreated, setPreviewCreated] = useState(Boolean(initial.configuration?.model)); const [remote, setRemote] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const [leaving, setLeaving] = useState(false); const initialTab = recent?.mode === 'preview' ? 'configuration' : initial.published_version ? 'chat' : 'configuration'; const [tab, setTab] = useState(initialTab); const [visited, setVisited] = useState>(() => new Set([initialTab])); const [category, setCategory] = useState('instructions'); const [openedThread, setOpenedThread] = useState(); const [conversationSource, setConversationSource] = useState<'desktop' | 'wechat'>('desktop'); const [wechatVisited, setWechatVisited] = useState(false); const [scheduleProposal, setScheduleProposal] = useState(); const pending = useCloudPendingState(); const formalPending = useCloudPendingState(); const hasPending = pending.hasPending || formalPending.hasPending; const alive = useRef(true); const saving = useRef(false); const dirty = hasDraftChanges(draft, saved); const canSave = dirty && !busy && !remote && Boolean(draft.name.trim() && draft.purpose.trim()); const navigate = (next: Tab) => { setVisited(current => new Set([...current, next])); setTab(next); }; const blocker = useBlocker(dirty || busy || hasPending); const finishLeaving = () => { if (blocker.state === 'blocked') blocker.proceed(); else onBack(); }; useEffect(() => { alive.current = true; return () => { alive.current = false; }; }, []); useEffect(() => { if (tab === 'configuration' && !saved.configuration?.model) { void cloudAgentsApi.remember({ slug: saved.slug, mode: 'preview', draft_revision: saved.draft_revision }) .catch(e => { if (alive.current) setError(errorMessage(e)); }); } }, [tab, saved.slug, saved.draft_revision, saved.configuration?.model]); useEffect(() => { const warn = (event: BeforeUnloadEvent) => { if (dirty || busy || hasPending) { event.preventDefault(); event.returnValue = ''; } }; window.addEventListener('beforeunload', warn); return () => window.removeEventListener('beforeunload', warn); }, [dirty, busy, hasPending]); const save = async (exit = false) => { if (!canSave || saving.current) return; saving.current = true; setBusy(true); setError(''); try { const result = await cloudAgentsApi.save(saved.slug, { expected_revision: saved.draft_revision, name: draft.name, purpose: draft.purpose, system_prompt: draft.system_prompt, configuration: draft.configuration ?? EMPTY_CLOUD_CONFIGURATION, }); if (!alive.current) return; setSaved(result); if (result.configuration?.model) setPreviewCreated(true); setDraft(current => !hasDraftChanges(current, draft) ? result : { ...result, name: current.name, purpose: current.purpose, system_prompt: current.system_prompt, configuration: current.configuration }); setRemote(null); onSaved(result); if (exit) finishLeaving(); } catch (failure) { if (!alive.current) return; setError(errorMessage(failure)); try { const latest = await cloudAgentsApi.get(saved.slug); if (alive.current && latest.draft_revision !== saved.draft_revision) setRemote(latest); } catch { /* Keep the failed save and local input available for retry. */ } } finally { saving.current = false; if (alive.current) setBusy(false); } }; useEffect(() => { const shortcut = (event: KeyboardEvent) => { if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') { event.preventDefault(); void save(); } }; window.addEventListener('keydown', shortcut); return () => window.removeEventListener('keydown', shortcut); }); return
setDraft({ ...draft, name: event.target.value })} />

{saved.published_version ? `已发布 v${saved.published_version}` : '私人草稿'} · 修订 {saved.draft_revision}

{busy ? '正在保存…' : dirty ? '有未保存修改' : '云端已保存'}
{error &&

{error}

} {remote &&

云端已有修订 {remote.draft_revision},本地输入仍保留

查看最新草稿

{remote.name} · {remote.purpose}

{remote.system_prompt}
}