feat: 改造云智能体桌面编辑与试用工作区

This commit is contained in:
brother7 committed 2026-09-12 17:39:31 +08:00
1 parent 6b92591129
commit dca6deab29
16 files changed
+623 -222

No files matched your search

+23 -8
View File
@@ -4,14 +4,18 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { CloudKnowledgePanel } from './CloudKnowledge';
import { CloudResources } from './CloudResources';
import { CloudBudgetEditor } from './CloudCosts';
import type { CloudAgentConfiguration, CloudCatalog } from '../../../shared/cloud-agents';
export function ConfigurationFields({ slug, value, disabled, onChange }: {
export function ConfigurationFields({ slug, value, disabled, onChange, section = 'all' }: {
section?: 'all' | 'instructions' | 'capabilities' | 'knowledge' | 'limits';
slug: string; value: CloudAgentConfiguration; disabled: boolean; onChange: (value: CloudAgentConfiguration) => void;
}) {
const [catalog, setCatalog] = useState<CloudCatalog | null>(null);
const [error, setError] = useState('');
const [refresh, setRefresh] = useState(0);
const [search, setSearch] = useState('');
const [choosing, setChoosing] = useState<string | null>(null);
useEffect(() => {
let live = true;
cloudAgentsApi.call('catalog', {}).then(v => { if (live) { setCatalog(v); setError(''); } })
@@ -20,10 +24,10 @@ export function ConfigurationFields({ slug, value, disabled, onChange }: {
}, [refresh]);
const change = <K extends keyof CloudAgentConfiguration>(key: K, next: CloudAgentConfiguration[K]) => onChange({ ...value, [key]: next });
return <div className="space-y-6">
<div className="flex items-center justify-between"><h2 className="font-medium">能力与运行限制</h2>
<div hidden={section === 'instructions' || section === 'limits'} className="flex items-center justify-between"><h2 className="font-medium">{section === 'knowledge' ? '知识库' : '能力'}</h2>
<Button type="button" variant="ghost" disabled={disabled} onClick={() => setRefresh(v => v + 1)}>刷新目录</Button></div>
{error && <p role="alert" className="text-sm text-destructive">{error}</p>}
<label className="block space-y-2 text-sm"><span>模型</span>
<div hidden={section !== 'all' && section !== 'capabilities'} className="space-y-4"><label className="block space-y-2 text-sm"><span>模型</span>
<select aria-label="模型" className="h-10 w-full rounded-md border border-input bg-background px-3" disabled={disabled || !catalog}
value={value.model} onChange={e => change('model', e.target.value)}>
<option value="">选择可用模型</option>
@@ -33,17 +37,20 @@ export function ConfigurationFields({ slug, value, disabled, onChange }: {
{catalog?.pricing && <p className="text-xs leading-5 text-muted-foreground tabular-nums">
每 {catalog.pricing.unit_size.toLocaleString()} 个词元约 {catalog.pricing.points_per_unit} 词元点数,按实际模型调用结算。
</p>}
</div>
{(['tools', 'knowledges', 'mcps', 'skills', 'subagents'] as const).map(key => {
const labels = { tools: '工具', knowledges: '知识库', mcps: 'MCP 服务', skills: 'Skills', subagents: '子智能体' };
const options = catalog?.resources[key] ?? [];
const missing = value[key].filter(id => !options.some(option => option.key === id));
return <fieldset key={key} disabled={disabled} className="space-y-2">
return <fieldset key={key} disabled={disabled} hidden={section !== 'all' && section !== (key === 'knowledges' ? 'knowledge' : 'capabilities')} className="space-y-2">
<legend className="mb-2 text-sm font-medium">{labels[key]} <span className="font-normal text-muted-foreground">· 已选 {value[key].length}</span></legend>
{!options.length && !missing.length && <p className="text-xs text-muted-foreground">{catalog ? '当前没有可用资源' : '正在读取资源…'}</p>}
<div className="max-h-48 overflow-auto rounded-lg bg-muted/30">
{[...options, ...missing.map(id => ({ key: id, name: id + '(已不可用)', description: '取消选择后重新保存' }))].map(option =>
<Button variant="outline" size="sm" onClick={() => { setChoosing(choosing === key ? null : key); setSearch(''); }}>{choosing === key ? '完成选择' : '选择' + labels[key]}</Button>
{choosing === key && <Input aria-label={'搜索' + labels[key]} placeholder={'搜索' + labels[key]} value={search} onChange={e => setSearch(e.target.value)} />}
<div className="max-h-64 overflow-auto rounded-lg bg-muted/30">
{[...options, ...missing.map(id => ({ key: id, name: id + '(已不可用)', description: '取消选择后重新保存' }))].filter(option => choosing === key ? (option.name + ' ' + (option.description ?? '')).toLowerCase().includes(search.toLowerCase()) : value[key].includes(option.key)).map(option =>
<label key={option.key} className="flex min-h-10 cursor-pointer items-start gap-3 px-3 py-2 text-sm hover:bg-muted/50">
<input className="mt-1 h-4 w-4 accent-violet-600" type="checkbox" checked={value[key].includes(option.key)}
<input className="mt-1 h-4 w-4 accent-primary" type="checkbox" checked={value[key].includes(option.key)}
onChange={e => {
const selected = e.target.checked ? [...value[key], option.key] : value[key].filter(id => id !== option.key);
onChange({ ...value, [key]: selected, ...(key === 'skills' ? { preload_skills: value.preload_skills.filter(id => selected.includes(id)) } : {}) });
@@ -56,18 +63,25 @@ export function ConfigurationFields({ slug, value, disabled, onChange }: {
onChange={e => change('preload_skills', e.target.checked ? [...value.skills] : [])} />开始运行时预加载已选 Skills</label>}
</fieldset>;
})}
<div hidden={section !== 'all' && section !== 'capabilities'} className="space-y-4">
<p className="text-xs leading-5 text-muted-foreground">未选择的资源不会开放给此智能体。分享使用时仍按本配置限制能力。</p>
<CloudResources onChanged={() => setRefresh(v => v + 1)} />
</div>
<div hidden={section !== 'all' && section !== 'knowledge'}>
<CloudKnowledgePanel slug={slug} onDeleted={id => { change('knowledges', value.knowledges.filter(key => key !== id)); setRefresh(v => v + 1); }} onCreated={kb => {
change('knowledges', [...new Set([...value.knowledges, kb.kb_id])]);
setRefresh(v => v + 1);
}} />
</div>
<div hidden={section !== 'all' && section !== 'limits'} className="space-y-6">
<CloudBudgetEditor slug={slug} />
<h2 className="font-medium">运行限制</h2><p className="text-xs text-muted-foreground">以下设置随草稿保存;费用上限独立保存,对后续调用生效。</p>
<label className="block space-y-2 text-sm"><span>工具审批</span>
<select className="h-10 w-full rounded-md border border-input bg-background px-3" disabled={disabled}
value={value.tool_approval_mode} onChange={e => change('tool_approval_mode', e.target.value === 'always_trust' ? 'always_trust' : 'default')}>
<option value="default">按工具要求确认</option><option value="always_trust">信任已选工具,自动执行</option>
</select></label>
<div className="grid gap-3 sm:grid-cols-3">
<div className="grid gap-4">
{([{ key: 'max_execution_steps', label: '最多执行步数', min: 1, max: 300 },
{ key: 'max_output_tokens', label: '单次输出词元上限', min: 1, max: 32768 },
{ key: 'max_run_seconds', label: '单次运行时限(秒)', min: 10, max: 3600 }] as const).map(item =>
@@ -75,5 +89,6 @@ export function ConfigurationFields({ slug, value, disabled, onChange }: {
<Input type="number" min={item.min} max={item.max} value={value[item.key]} disabled={disabled}
onChange={e => change(item.key, Number(e.target.value))} /></label>)}
</div>
</div>
</div>;
}