fix(agents): make editor settings peer tabs
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user