feat(agents): 完成云智能体工作台与交互流程

This commit is contained in:
brother7 committed 2026-09-10 19:25:12 +08:00
1 parent 46fbae9dec
commit 64a3cf4934
26 files changed
+1955 -49

No files matched your search

@@ -0,0 +1,77 @@
import { useEffect, useState } from 'react';
import { cloudAgentsApi } from '@/lib/cloud-agents-api';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { CloudKnowledgePanel } from './CloudKnowledge';
import type { CloudAgentConfiguration, CloudCatalog } from '../../../shared/cloud-agents';
export function ConfigurationFields({ slug, value, disabled, onChange }: {
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);
useEffect(() => {
let live = true;
cloudAgentsApi.call('catalog', {}).then(v => { if (live) { setCatalog(v); setError(''); } })
.catch(e => { if (live) setError(e instanceof Error ? e.message : '配置目录暂不可用'); });
return () => { live = false; };
}, [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>
<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>
<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>
{value.model && !catalog?.models.some(m => m.id === value.model) && <option value={value.model}>{value.model}(目录中不可用)</option>}
{catalog?.models.map(m => <option key={m.id} value={m.id}>{m.name}</option>)}
</select></label>
{catalog?.pricing && <p className="text-xs leading-5 text-muted-foreground tabular-nums">
每 {catalog.pricing.unit_size.toLocaleString()} 个词元约 {catalog.pricing.points_per_unit} 词元点数,按实际模型调用结算。
</p>}
{(['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">
<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 =>
<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)}
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)) } : {}) });
}} />
<span><span>{option.name}</span>{option.description && <span className="mt-1 block text-xs leading-5 text-muted-foreground">{option.description}</span>}</span>
</label>)}
</div>
{key === 'skills' && value.skills.length > 0 && <label className="flex min-h-10 items-center gap-2 text-xs">
<input type="checkbox" checked={value.preload_skills.length === value.skills.length}
onChange={e => change('preload_skills', e.target.checked ? [...value.skills] : [])} />开始运行时预加载已选 Skills</label>}
</fieldset>;
})}
<p className="text-xs leading-5 text-muted-foreground">未选择的资源不会开放给此智能体。分享使用时仍按本配置限制能力。</p>
<CloudKnowledgePanel slug={slug} onCreated={kb => {
change('knowledges', [...new Set([...value.knowledges, kb.kb_id])]);
setRefresh(v => v + 1);
}} />
<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">
{([{ 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 =>
<label key={item.key} className="space-y-2 text-xs"><span>{item.label}</span>
<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>;
}