import { useEffect, useRef, useState } from 'react'; import { useBlocker } from 'react-router-dom'; import { ArrowLeft, Check, ImagePlus, Loader2, MessageSquareText, Pencil, Save, SlidersHorizontal, Sparkles } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/ui/dialog'; import { cloudAgentsApi } from '@/lib/cloud-agents-api'; import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentDraft, type CloudRecent } from '../../../shared/cloud-agents'; import { ConfigurationFields } from './ConfigurationFields'; import { CloudChat } from './CloudChat'; import { CloudPendingContext, useCloudPendingState } from './CloudPending'; import { AgentRequirements } from './AgentRequirements'; import { CloudAvatar, CloudAvatarEditor } from './CloudAvatar'; import { useCloudPublication } from './useCloudPublication'; import { AgentConfigHeading } from './AgentConfigHeading'; import './agent-config.css'; type Tab = 'configuration' | 'chat'; const hasDraftChanges = (draft: CloudAgentDraft, saved: CloudAgentDraft) => draft.name !== saved.name || (draft.icon ?? null) !== (saved.icon ?? null) || 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, startingPrompt, entryView }: { startingPrompt?: string; entryView?: 'configuration' | 'chat'; initial: CloudAgentDraft; recent: CloudRecent | null; onBack: () => void; onSaved: (agent: CloudAgentDraft) => void; }) { const [saved, setSaved] = useState(initial); const [draft, setDraft] = useState(() => startingPrompt === undefined ? initial : { ...initial, system_prompt: startingPrompt }); const [editorTab, setEditorTab] = useState('instructions'); const [uploading, setUploading] = useState(false); const [editingAvatar, setEditingAvatar] = useState(false); const [remote, setRemote] = useState(null); const [savingDraft, setBusy] = useState(false); const [error, setError] = useState(''); const [leaving, setLeaving] = useState(false); const initialTab = entryView ?? (recent?.mode === 'preview' ? 'configuration' : initial.published_version ? 'chat' : 'configuration'); const [tab, setTab] = useState(initialTab); const [visited, setVisited] = useState>(() => new Set([initialTab])); const [attachmentHeader, setAttachmentHeader] = useState(null); const pending = useCloudPendingState(); const formalPending = useCloudPendingState(); const alive = useRef(true); const saving = useRef(false); const configurationScroll = useRef(null); const nameInput = useRef(null); const dirty = hasDraftChanges(draft, saved); const navigate = (next: Tab) => { setVisited(current => new Set([...current, next])); setTab(next); }; const publication = useCloudPublication(saved.slug, saved.draft_revision, result => { const next = { ...saved, published_version: result.version }; setSaved(current => ({ ...current, published_version: result.version })); setDraft(current => ({ ...current, published_version: result.version })); onSaved(next); if (!saved.published_version) navigate('chat'); }); const busy = savingDraft || publication.busy || uploading; const hasPending = pending.hasPending || formalPending.hasPending || Boolean(publication.pending); const canSave = dirty && !busy && !remote && Boolean(draft.name.trim()); const needsApply = Boolean(saved.published_version) && publication.appliedRevision !== undefined && publication.appliedRevision !== saved.draft_revision; const applicationStatus = publication.busy ? '正在应用…' : publication.pending ? '应用结果待确认' : publication.appliedRevision === undefined ? '应用状态待确认' : needsApply ? '修改已保存,尚未应用' : '已应用最新修改'; 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') { 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, icon: draft.icon ?? null, configuration: draft.configuration ?? EMPTY_CLOUD_CONFIGURATION, }); if (!alive.current) return; setSaved(result); setDraft(current => !hasDraftChanges(current, draft) ? result : { ...result, name: current.name, icon: current.icon, icon_url: current.icon_url, purpose: current.purpose, system_prompt: current.system_prompt, configuration: current.configuration }); setRemote(null); onSaved(result); if (exit) finishLeaving(); return result.draft_revision; } 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); } }; const startUsing = () => { if (busy || saving.current) return; if (remote) { navigate('configuration'); setError('云端已有新修改,请先处理保存冲突。'); return; } if (!draft.configuration.model) { navigate('configuration'); setEditorTab('advanced'); if (configurationScroll.current) configurationScroll.current.scrollTop = 0; setError('请先在高级设置中选择模型。'); return; } if (dirty) { navigate('configuration'); setError('请先保存草稿,再开始使用。'); return; } if (!publication.access) { setError('暂时无法确认智能体状态,请刷新后重试。'); return; } setError(''); void publication.publish(); }; useEffect(() => { const shortcut = (event: KeyboardEvent) => { if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') { event.preventDefault(); if (tab === 'configuration') void save(); } }; window.addEventListener('keydown', shortcut); return () => window.removeEventListener('keydown', shortcut); }); return
{tab === 'chat' ? <>

{saved.name}

: <>
setDraft({ ...draft, name: event.target.value })} />

{saved.published_version ? '我的智能体' : '配置智能体'}

{savingDraft ? '正在保存…' : dirty ? '有未保存修改' : saved.published_version ? applicationStatus : '云端已保存'} {saved.published_version && } {!saved.published_version && } }
{tab === 'configuration' && saved.published_version && (needsApply || dirty) &&

{dirty ? '修改尚未保存和应用' : '这次修改还没有应用到正在使用的助手'}

保存只更新草稿。请点击顶部“应用最新修改”,再到微信等渠道使用。已开始的任务保持原配置。

} {tab === 'configuration' && publication.error &&

{publication.error}

} {error &&

{error}

} {remote &&

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

查看最新草稿

{remote.name} · {remote.purpose}

{remote.system_prompt}
} { if (!uploading) setEditingAvatar(open); }}> 修改头像选择这个智能体的头像。 setDraft(current => ({ ...current, ...avatar }))} /> { if (!open && !busy) { setLeaving(false); if (blocker.state === 'blocked') blocker.reset(); } }}> 未保存的修改 {hasPending ? '还有未发送的内容或未确认的操作。离开会丢弃当前输入;已被云端接受的任务仍会继续,可在活动中查看。' : '保存后离开,或放弃这次修改。'}
{!hasPending && }
; }