fix(agents): make editor settings peer tabs

This commit is contained in:
2026-09-15 14:50:06 +08:00
parent 80e52496d2
commit 868ab54be6
5 changed files with 163 additions and 18 deletions

View File

@@ -4,6 +4,7 @@ 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 { 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, type CloudThread, type CloudScheduleProposal } from '../../../shared/cloud-agents';
@@ -18,7 +19,7 @@ import { AgentRequirements } from './AgentRequirements';
import { agentExamples } from './agent-guidance';
type Tab = 'configuration' | 'chat' | 'tasks' | 'access';
type Category = 'instructions' | 'capabilities' | 'knowledge' | 'limits';
type AdvancedCategory = '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 : '暂时无法完成操作';
@@ -29,7 +30,8 @@ export function DraftEditor({ initial, recent, onBack, onSaved, startingPrompt }
}) {
const [saved, setSaved] = useState(initial);
const [draft, setDraft] = useState(() => startingPrompt === undefined ? initial : { ...initial, system_prompt: startingPrompt });
const [advanced, setAdvanced] = useState(false);
const [editorTab, setEditorTab] = useState('instructions');
const [advancedCategory, setAdvancedCategory] = useState<AdvancedCategory>('capabilities');
const [previewActivation, setPreviewActivation] = useState(0);
const [previewCreated, setPreviewCreated] = useState(Boolean(initial.configuration?.model));
const [remote, setRemote] = useState<CloudAgentDraft | null>(null);
@@ -39,7 +41,7 @@ export function DraftEditor({ initial, recent, onBack, onSaved, startingPrompt }
const initialTab = recent?.mode === 'preview' ? 'configuration' : initial.published_version ? 'chat' : 'configuration';
const [tab, setTab] = useState<Tab>(initialTab);
const [visited, setVisited] = useState<Set<Tab>>(() => new Set([initialTab]));
const [category, setCategory] = useState<Category>('instructions');
const category = editorTab === 'instructions' ? 'instructions' : advancedCategory;
const [openedThread, setOpenedThread] = useState<CloudThread>();
const [conversationSource, setConversationSource] = useState<'desktop' | 'wechat'>('desktop');
const [wechatVisited, setWechatVisited] = useState(false);
@@ -49,6 +51,7 @@ export function DraftEditor({ initial, recent, onBack, onSaved, startingPrompt }
const hasPending = pending.hasPending || formalPending.hasPending;
const alive = useRef(true);
const saving = useRef(false);
const configurationScroll = useRef<HTMLDivElement>(null);
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); };
@@ -128,14 +131,21 @@ export function DraftEditor({ initial, recent, onBack, onSaved, startingPrompt }
<Button variant="outline" onClick={() => { setSaved(remote); if (remote.configuration?.model) setPreviewCreated(true); setRemote(null); setError(''); }}></Button></div>
</div>}
<div hidden={tab !== 'configuration'} className="agent-workspace-view">
{visited.has('configuration') && <DesktopSplit previewActivation={previewActivation} configuration={<>
{visited.has('configuration') && <DesktopSplit previewActivation={previewActivation} configuration={<Tabs value={editorTab} onValueChange={next => {
setEditorTab(next);
if (configurationScroll.current) configurationScroll.current.scrollTop = 0;
}} className="flex min-h-0 flex-1 flex-col">
<div className="agent-config-tabs">
<Button variant={category === 'instructions' ? 'secondary' : 'ghost'} onClick={() => setCategory('instructions')}></Button>
<Button variant="ghost" aria-expanded={advanced} onClick={() => { setAdvanced(!advanced); setCategory('instructions'); }}></Button>
<TabsList aria-label="编辑设置" className="h-auto w-full justify-start">
<TabsTrigger value="instructions" className="flex-1 data-[state=active]:text-primary"></TabsTrigger>
<TabsTrigger value="advanced" className="flex-1 data-[state=active]:text-primary"></TabsTrigger>
</TabsList>
</div>
{advanced && <nav aria-label="配置分类" className="agent-config-tabs">{([['capabilities', '能力'], ['knowledge', '知识'], ['limits', '限制']] as const).map(([key, label]) =>
<Button key={key} variant={category === key ? 'secondary' : 'ghost'} aria-current={category === key ? 'page' : undefined} onClick={() => setCategory(key)}>{label}</Button>)}</nav>}
<div className="agent-config-scroll"><fieldset disabled={busy} className="min-w-0">
{/* Keep the shared form mounted so switching tabs preserves unfinished resource and budget edits. */}
<TabsContent value={editorTab} className="m-0 flex min-h-0 flex-1 flex-col">
{editorTab === 'advanced' && <nav aria-label="配置分类" className="agent-config-tabs pt-2">{([['capabilities', '能力'], ['knowledge', '知识'], ['limits', '限制']] as const).map(([key, label]) =>
<Button key={key} variant={advancedCategory === key ? 'secondary' : 'ghost'} aria-current={advancedCategory === key ? 'page' : undefined} onClick={() => setAdvancedCategory(key)}>{label}</Button>)}</nav>}
<div ref={configurationScroll} className="agent-config-scroll"><fieldset disabled={busy} className="min-w-0">
<div hidden={category !== 'instructions'} className="mb-6 space-y-6">
<label className="block space-y-2 text-sm"><span className="font-medium"></span><Textarea aria-label="用途" className="min-h-20 resize-y" maxLength={2000} value={draft.purpose}
onChange={event => setDraft({ ...draft, purpose: event.target.value })} placeholder="一句话介绍,它可以帮你做什么。" /></label>
@@ -144,7 +154,8 @@ export function DraftEditor({ initial, recent, onBack, onSaved, startingPrompt }
<ConfigurationFields slug={saved.slug} section={category} chooseInitialModel={startingPrompt !== undefined} value={draft.configuration ?? EMPTY_CLOUD_CONFIGURATION} disabled={busy}
onChange={configuration => setDraft({ ...draft, configuration })} />
</fieldset></div>
</>} preview={expandAction => <>
</TabsContent>
</Tabs>} preview={expandAction => <>
{!saved.configuration?.model && !previewCreated ? <div className="my-auto p-6 text-sm leading-7">{expandAction}<h2 className="font-medium"></h2>
<p className="mt-2 text-muted-foreground"></p>
<Button className="mt-4 min-h-10" disabled={!canSave || !draft.configuration?.model} onClick={async () => { if (await save() !== undefined) setPreviewActivation(v => v + 1); }}></Button>