feat: 改造云智能体桌面编辑与试用工作区
This commit is contained in:
1 parent
6b92591129
commit
dca6deab29
16 files changed
+623
-222
No files matched your search
@@ -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>;
|
||||
}
|
||||
Reference in new issue
Block a user