feat: 开放并完善外部工具配置管理功能
parent
b7c4159f1a
commit
86c923cc40
|
|
@ -33,6 +33,32 @@ export interface SkillDetail extends SkillBrief {
|
|||
config: string;
|
||||
}
|
||||
|
||||
export interface ExternalToolApi {
|
||||
id?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
path: string;
|
||||
headers?: Record<string, string> | null;
|
||||
parametersSchema: Record<string, unknown>;
|
||||
createdAt?: number;
|
||||
}
|
||||
|
||||
export interface ExternalToolPluginPayload {
|
||||
name: string;
|
||||
description?: string;
|
||||
baseUrl: string;
|
||||
authType: 'none' | 'bearer' | 'basic' | 'apiKey';
|
||||
authConfig: Record<string, unknown>;
|
||||
apis: ExternalToolApi[];
|
||||
}
|
||||
|
||||
export interface ExternalToolPlugin extends ExternalToolPluginPayload {
|
||||
id: string;
|
||||
enabled: number;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -50,6 +76,7 @@ export interface Agent {
|
|||
updated_at: number;
|
||||
knowledge?: KnowledgeFile[];
|
||||
skills?: SkillBrief[];
|
||||
plugins?: ExternalToolPlugin[];
|
||||
_access?: 'owner' | 'team' | 'view' | 'none';
|
||||
}
|
||||
|
||||
|
|
@ -83,6 +110,13 @@ export const AgentAPI = {
|
|||
getSkill: (agentId: string, skillId: string) => api.get<SkillDetail>(`/agents/${agentId}/skills/${skillId}`).then((r) => r.data),
|
||||
updateSkill: (agentId: string, skillId: string, payload: { content?: string; enabled?: boolean }) =>
|
||||
api.put(`/agents/${agentId}/skills/${skillId}`, payload).then((r) => r.data),
|
||||
deleteSkill: (agentId: string, skillId: string) => api.delete(`/agents/${agentId}/skills/${skillId}`).then((r) => r.data)
|
||||
deleteSkill: (agentId: string, skillId: string) => api.delete(`/agents/${agentId}/skills/${skillId}`).then((r) => r.data),
|
||||
|
||||
bindPlugin: (agentId: string, payload: ExternalToolPluginPayload) =>
|
||||
api.post(`/agents/${agentId}/plugins`, payload).then((r) => r.data),
|
||||
updatePlugin: (agentId: string, pluginId: string, payload: ExternalToolPluginPayload) =>
|
||||
api.put(`/agents/${agentId}/plugins/${pluginId}`, payload).then((r) => r.data),
|
||||
deletePlugin: (agentId: string, pluginId: string) =>
|
||||
api.delete(`/agents/${agentId}/plugins/${pluginId}`).then((r) => r.data)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { App as AntApp, Button, Card, Form, Input, Modal, Select, Space } from 'antd';
|
||||
import { AgentAPI, ExternalToolApi, ExternalToolPlugin, ExternalToolPluginPayload } from '../api';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
agentId: string;
|
||||
plugin?: ExternalToolPlugin | null;
|
||||
onClose: () => void;
|
||||
onSaved?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ToolApiFormValue extends Omit<ExternalToolApi, 'headers' | 'parametersSchema'> {
|
||||
headers?: string;
|
||||
parametersSchema: string;
|
||||
}
|
||||
|
||||
interface ToolPluginFormValue extends Omit<ExternalToolPluginPayload, 'authConfig' | 'apis'> {
|
||||
authConfig?: string;
|
||||
apis: ToolApiFormValue[];
|
||||
}
|
||||
|
||||
const EMPTY_API: ToolApiFormValue = {
|
||||
name: '',
|
||||
description: '',
|
||||
method: 'GET',
|
||||
path: '',
|
||||
headers: '{}',
|
||||
parametersSchema: JSON.stringify({ type: 'object', properties: {} }, null, 2),
|
||||
};
|
||||
|
||||
function parseJsonObject(value: string | undefined, fieldName: string, optional = false) {
|
||||
if (!value?.trim()) {
|
||||
if (optional) return undefined;
|
||||
throw new Error(`${fieldName}不能为空`);
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(value);
|
||||
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
|
||||
throw new Error(`${fieldName}必须是 JSON 对象`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export default function ExternalToolEditor({ open, agentId, plugin, onClose, onSaved }: Props) {
|
||||
const { message } = AntApp.useApp();
|
||||
const [form] = Form.useForm<ToolPluginFormValue>();
|
||||
const isEditing = Boolean(plugin);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
const payload: ExternalToolPluginPayload = {
|
||||
name: values.name.trim(),
|
||||
description: values.description?.trim(),
|
||||
baseUrl: values.baseUrl.trim(),
|
||||
authType: values.authType,
|
||||
authConfig: parseJsonObject(values.authConfig, '认证配置', values.authType === 'none') || {},
|
||||
apis: values.apis.map((item) => ({
|
||||
name: item.name.trim(),
|
||||
description: item.description.trim(),
|
||||
method: item.method,
|
||||
path: item.path.trim(),
|
||||
headers: parseJsonObject(item.headers, `API ${item.name} 的请求头`, true),
|
||||
parametersSchema: parseJsonObject(item.parametersSchema, `API ${item.name} 的依赖参数`),
|
||||
})),
|
||||
};
|
||||
|
||||
if (plugin) {
|
||||
await AgentAPI.updatePlugin(agentId, plugin.id, payload);
|
||||
message.success('外部工具更新成功');
|
||||
} else {
|
||||
await AgentAPI.bindPlugin(agentId, payload);
|
||||
message.success('外部工具绑定成功');
|
||||
}
|
||||
await onSaved?.();
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
if (error?.errorFields) return;
|
||||
if (error instanceof SyntaxError) {
|
||||
message.error('JSON 格式不正确,请检查认证配置、请求头或依赖参数');
|
||||
return;
|
||||
}
|
||||
message.error(error?.message || '外部工具绑定失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title={isEditing ? '编辑外部工具集' : '配置外部工具集'}
|
||||
width={920}
|
||||
okText={isEditing ? '保存修改' : '绑定工具'}
|
||||
cancelText="取消"
|
||||
onCancel={onClose}
|
||||
onOk={handleSubmit}
|
||||
destroyOnHidden
|
||||
afterOpenChange={(visible) => {
|
||||
if (visible) {
|
||||
form.setFieldsValue(
|
||||
plugin
|
||||
? {
|
||||
name: plugin.name,
|
||||
description: plugin.description,
|
||||
baseUrl: plugin.baseUrl,
|
||||
authType: plugin.authType,
|
||||
authConfig: JSON.stringify(plugin.authConfig || {}, null, 2),
|
||||
apis: plugin.apis.map((item) => ({
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
method: item.method,
|
||||
path: item.path,
|
||||
headers: JSON.stringify(item.headers || {}, null, 2),
|
||||
parametersSchema: JSON.stringify(item.parametersSchema || { type: 'object', properties: {} }, null, 2),
|
||||
})),
|
||||
}
|
||||
: {
|
||||
name: '',
|
||||
description: '',
|
||||
baseUrl: '',
|
||||
authType: 'bearer',
|
||||
authConfig: JSON.stringify({ token: '' }, null, 2),
|
||||
apis: [{ ...EMPTY_API }],
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form form={form} layout="vertical" requiredMark="optional">
|
||||
<div className="agent-editor-tool-grid">
|
||||
<Form.Item label="工具集名称" name="name" rules={[{ required: true, message: '请输入工具集名称' }]}>
|
||||
<Input placeholder="例如:Hoyidata 工具集" />
|
||||
</Form.Item>
|
||||
<Form.Item label="API 基础地址" name="baseUrl" rules={[{ required: true, message: '请输入 API 基础地址' }]}>
|
||||
<Input placeholder="https://api.hoyidata.com" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item label="工具集描述" name="description">
|
||||
<Input.TextArea rows={2} placeholder="说明该工具集提供的能力" />
|
||||
</Form.Item>
|
||||
<div className="agent-editor-tool-grid">
|
||||
<Form.Item label="认证方式" name="authType" rules={[{ required: true }]}>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'none', label: '无需认证' },
|
||||
{ value: 'bearer', label: 'Bearer Token' },
|
||||
{ value: 'basic', label: 'Basic Auth' },
|
||||
{ value: 'apiKey', label: 'API Key' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="认证配置(JSON)" name="authConfig">
|
||||
<Input.TextArea rows={3} className="agent-editor-code-input" placeholder={'{\n "token": "YOUR_API_KEY"\n}'} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<Form.List name="apis">
|
||||
{(fields, { add, remove }) => (
|
||||
<Space direction="vertical" size={12} className="agent-editor-tool-list">
|
||||
<div className="agent-editor-tool-list-header">
|
||||
<div>
|
||||
<strong>API 功能</strong>
|
||||
<div className="agent-editor-tool-help">一个工具集可同时绑定多个 API 功能。</div>
|
||||
</div>
|
||||
<Button type="dashed" icon={<PlusOutlined />} onClick={() => add({ ...EMPTY_API })}>
|
||||
添加 API
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{fields.map((field, index) => (
|
||||
<Card
|
||||
key={field.key}
|
||||
size="small"
|
||||
title={`API ${index + 1}`}
|
||||
className="agent-editor-tool-card"
|
||||
extra={
|
||||
fields.length > 1 ? (
|
||||
<Button type="text" danger icon={<MinusCircleOutlined />} onClick={() => remove(field.name)}>
|
||||
删除
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<div className="agent-editor-tool-grid">
|
||||
<Form.Item
|
||||
label="name(工具名)"
|
||||
name={[field.name, 'name']}
|
||||
rules={[
|
||||
{ required: true, message: '请输入工具名' },
|
||||
{ pattern: /^[A-Za-z0-9_]+$/, message: '仅支持字母、数字和下划线' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="query_hot_selling_products" />
|
||||
</Form.Item>
|
||||
<Form.Item label="method(调用方法)" name={[field.name, 'method']} rules={[{ required: true }]}>
|
||||
<Select options={['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].map((value) => ({ value, label: value }))} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="description(描述)"
|
||||
name={[field.name, 'description']}
|
||||
rules={[{ required: true, message: '请输入描述' }]}
|
||||
>
|
||||
<Input.TextArea rows={2} placeholder="描述调用时机和工具能力" />
|
||||
</Form.Item>
|
||||
<Form.Item label="path(API 地址)" name={[field.name, 'path']} rules={[{ required: true, message: '请输入 API 地址' }]}>
|
||||
<Input placeholder="/v1/products/hot-selling" />
|
||||
</Form.Item>
|
||||
<div className="agent-editor-tool-grid">
|
||||
<Form.Item label="headers(请求头 JSON)" name={[field.name, 'headers']}>
|
||||
<Input.TextArea rows={7} className="agent-editor-code-input" placeholder={'{\n "X-Custom-Source": "aura-agent"\n}'} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="parametersSchema(依赖参数 JSON Schema)"
|
||||
name={[field.name, 'parametersSchema']}
|
||||
rules={[{ required: true, message: '请输入依赖参数 Schema' }]}
|
||||
>
|
||||
<Input.TextArea rows={7} className="agent-editor-code-input" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,6 +17,12 @@ interface CapabilitySettingsProps {
|
|||
setAvatarSelectorOpen: (open: boolean) => void;
|
||||
beforeUploadKnowledge: (file: any) => Promise<boolean>;
|
||||
onDeleteKnowledge: (fileId: string) => Promise<void>;
|
||||
onCreateSkill: () => void;
|
||||
onEditSkill: (skillId: string) => void;
|
||||
onDeleteSkill: (skillId: string) => Promise<void>;
|
||||
onCreateExternalTool: () => void;
|
||||
onEditExternalTool: (pluginId: string) => void;
|
||||
onDeleteExternalTool: (pluginId: string) => Promise<void>;
|
||||
markDirty: () => void;
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
|
@ -32,6 +38,12 @@ export default function CapabilitySettings({
|
|||
setAvatarSelectorOpen,
|
||||
beforeUploadKnowledge,
|
||||
onDeleteKnowledge,
|
||||
onCreateSkill,
|
||||
onEditSkill,
|
||||
onDeleteSkill,
|
||||
onCreateExternalTool,
|
||||
onEditExternalTool,
|
||||
onDeleteExternalTool,
|
||||
markDirty,
|
||||
isMobile = false,
|
||||
}: CapabilitySettingsProps) {
|
||||
|
|
@ -62,6 +74,12 @@ export default function CapabilitySettings({
|
|||
agent={agent}
|
||||
beforeUploadKnowledge={beforeUploadKnowledge}
|
||||
onDeleteKnowledge={onDeleteKnowledge}
|
||||
onCreateSkill={onCreateSkill}
|
||||
onEditSkill={onEditSkill}
|
||||
onDeleteSkill={onDeleteSkill}
|
||||
onCreateExternalTool={onCreateExternalTool}
|
||||
onEditExternalTool={onEditExternalTool}
|
||||
onDeleteExternalTool={onDeleteExternalTool}
|
||||
/>
|
||||
<WebSearchCard />
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button, Collapse, Form, Input, List, Popconfirm, Tag } from 'antd';
|
||||
import { DatabaseOutlined, ToolOutlined } from '@ant-design/icons';
|
||||
import { ApiOutlined, DatabaseOutlined, DeleteOutlined, EditOutlined, PlusOutlined, ToolOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Collapse, Input, List, Popconfirm, Space, Tag } from 'antd';
|
||||
import { Agent } from '../../../../api';
|
||||
import { STATUS_TAG } from '../../constants';
|
||||
|
||||
|
|
@ -7,13 +7,28 @@ interface KnowledgeSettingsPanelProps {
|
|||
agent: Agent | null;
|
||||
beforeUploadKnowledge: (file: any) => Promise<boolean>;
|
||||
onDeleteKnowledge: (fileId: string) => Promise<void>;
|
||||
onCreateSkill: () => void;
|
||||
onEditSkill: (skillId: string) => void;
|
||||
onDeleteSkill: (skillId: string) => Promise<void>;
|
||||
onCreateExternalTool: () => void;
|
||||
onEditExternalTool: (pluginId: string) => void;
|
||||
onDeleteExternalTool: (pluginId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export default function KnowledgeSettingsPanel({
|
||||
agent,
|
||||
beforeUploadKnowledge,
|
||||
onDeleteKnowledge,
|
||||
onCreateSkill,
|
||||
onEditSkill,
|
||||
onDeleteSkill,
|
||||
onCreateExternalTool,
|
||||
onEditExternalTool,
|
||||
onDeleteExternalTool,
|
||||
}: KnowledgeSettingsPanelProps) {
|
||||
const skills = agent?.skills ?? [];
|
||||
const plugins = agent?.plugins ?? [];
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
ghost
|
||||
|
|
@ -37,12 +52,13 @@ export default function KnowledgeSettingsPanel({
|
|||
multiple
|
||||
className="agent-editor-file-input"
|
||||
id="knowledge-upload"
|
||||
onChange={async (e) => {
|
||||
const files = e.target.files;
|
||||
onChange={async (event) => {
|
||||
const files = event.target.files;
|
||||
if (!files) return;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
await beforeUploadKnowledge(files[i]);
|
||||
for (let index = 0; index < files.length; index++) {
|
||||
await beforeUploadKnowledge(files[index]);
|
||||
}
|
||||
event.target.value = '';
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
|
@ -79,8 +95,8 @@ export default function KnowledgeSettingsPanel({
|
|||
<span className="agent-editor-indexing-label">索引中…</span>
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag color={STATUS_TAG[(item.status || 'ready')].color} className="m-0 text-[10px] px-1">
|
||||
{STATUS_TAG[(item.status || 'ready')].text}
|
||||
<Tag color={STATUS_TAG[item.status || 'ready'].color} className="m-0 text-[10px] px-1">
|
||||
{STATUS_TAG[item.status || 'ready'].text}
|
||||
</Tag>
|
||||
)}
|
||||
</span>
|
||||
|
|
@ -93,14 +109,144 @@ export default function KnowledgeSettingsPanel({
|
|||
},
|
||||
{
|
||||
key: 'skills',
|
||||
collapsible: 'disabled',
|
||||
label: (
|
||||
<div className="agent-editor-disabled-label" title="技能功能开发中">
|
||||
<ToolOutlined />
|
||||
技能 & 工具 (开发中)
|
||||
<div className="agent-editor-collapse-label">
|
||||
<ToolOutlined className="agent-editor-label-icon" />
|
||||
技能 & 工具 ({skills.length + plugins.length})
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<div className="px-1">
|
||||
<div className="agent-editor-skill-actions">
|
||||
<span className="text-xs text-gray-500">Markdown 技能与外部 API 工具集分别管理</span>
|
||||
<Space wrap>
|
||||
<Button size="small" icon={<PlusOutlined />} onClick={onCreateSkill}>
|
||||
新建 Skill
|
||||
</Button>
|
||||
<Button type="primary" ghost size="small" icon={<ApiOutlined />} onClick={onCreateExternalTool}>
|
||||
添加外部工具
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div className="agent-editor-tool-section">
|
||||
<div className="agent-editor-tool-section-title">
|
||||
<span>Skills</span>
|
||||
<Tag>{skills.length}</Tag>
|
||||
</div>
|
||||
<List
|
||||
size="small"
|
||||
locale={{ emptyText: '暂无 Markdown Skill' }}
|
||||
dataSource={skills}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
className="agent-editor-skill-item"
|
||||
actions={[
|
||||
<Button key="edit" type="text" size="small" icon={<EditOutlined />} onClick={() => onEditSkill(item.id)}>
|
||||
编辑
|
||||
</Button>,
|
||||
<Popconfirm key="delete" title="确认删除该 Skill?" onConfirm={() => onDeleteSkill(item.id)}>
|
||||
<Button type="text" danger size="small" icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>,
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={<span className="agent-editor-skill-name">{item.filename || item.name}</span>}
|
||||
description={
|
||||
<div>
|
||||
<div>{item.description || '暂无描述'}</div>
|
||||
{item.filename && item.filename !== item.name && <div className="agent-editor-tool-help">{item.name}</div>}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Space size={6} wrap>
|
||||
<Tag color="blue">{item.type}</Tag>
|
||||
<Tag color={item.enabled ? 'success' : 'default'}>{item.enabled ? '已启用' : '未启用'}</Tag>
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="agent-editor-tool-section">
|
||||
<div className="agent-editor-tool-section-title">
|
||||
<span>外部工具集</span>
|
||||
<Tag>{plugins.length}</Tag>
|
||||
</div>
|
||||
{plugins.length === 0 ? (
|
||||
<div className="agent-editor-tool-empty">暂无外部工具集</div>
|
||||
) : (
|
||||
<Space direction="vertical" size={10} className="agent-editor-tool-list">
|
||||
{plugins.map((plugin) => (
|
||||
<Card
|
||||
key={plugin.id}
|
||||
size="small"
|
||||
className="agent-editor-plugin-card"
|
||||
title={
|
||||
<Space wrap>
|
||||
<ApiOutlined className="agent-editor-label-icon" />
|
||||
<span>{plugin.name}</span>
|
||||
<Tag color={plugin.enabled ? 'success' : 'default'}>{plugin.enabled ? '已启用' : '未启用'}</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Space>
|
||||
<Button type="text" size="small" icon={<EditOutlined />} onClick={() => onEditExternalTool(plugin.id)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Popconfirm title="确认删除该工具集及其全部 API?" onConfirm={() => onDeleteExternalTool(plugin.id)}>
|
||||
<Button type="text" danger size="small" icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<div className="agent-editor-plugin-description">{plugin.description || '暂无描述'}</div>
|
||||
<Space size={6} wrap className="agent-editor-plugin-meta">
|
||||
<Tag>{plugin.authType || 'none'}</Tag>
|
||||
<span>{plugin.baseUrl}</span>
|
||||
<span>{plugin.apis.length} 个 API</span>
|
||||
</Space>
|
||||
<Collapse
|
||||
ghost
|
||||
size="small"
|
||||
className="agent-editor-plugin-apis"
|
||||
items={plugin.apis.map((api) => ({
|
||||
key: api.id || api.name,
|
||||
label: (
|
||||
<Space wrap>
|
||||
<Tag color="geekblue">{api.method}</Tag>
|
||||
<strong>{api.name}</strong>
|
||||
<span className="agent-editor-tool-help">{api.path}</span>
|
||||
</Space>
|
||||
),
|
||||
children: (
|
||||
<div className="agent-editor-api-detail">
|
||||
<div>{api.description || '暂无描述'}</div>
|
||||
<div className="agent-editor-api-config-grid">
|
||||
<div>
|
||||
<strong>headers</strong>
|
||||
<pre>{JSON.stringify(api.headers || {}, null, 2)}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<strong>parametersSchema</strong>
|
||||
<pre>{JSON.stringify(api.parametersSchema || {}, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
children: null,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -223,6 +223,28 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
|
|||
}
|
||||
};
|
||||
|
||||
const handleDeleteSkill = async (skillId: string) => {
|
||||
if (!id) return;
|
||||
try {
|
||||
await AgentAPI.deleteSkill(id, skillId);
|
||||
setAgent((prev) => prev ? { ...prev, skills: (prev.skills || []).filter((skill) => skill.id !== skillId) } : prev);
|
||||
message.success('Skill 已删除');
|
||||
} catch (e: any) {
|
||||
message.error('Skill 删除失败:' + (e?.message ?? e));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeletePlugin = async (pluginId: string) => {
|
||||
if (!id) return;
|
||||
try {
|
||||
await AgentAPI.deletePlugin(id, pluginId);
|
||||
setAgent((prev) => prev ? { ...prev, plugins: (prev.plugins || []).filter((plugin) => plugin.id !== pluginId) } : prev);
|
||||
message.success('外部工具已删除');
|
||||
} catch (e: any) {
|
||||
message.error('外部工具删除失败:' + (e?.message ?? e));
|
||||
}
|
||||
};
|
||||
|
||||
const beforeUploadKnowledge = async (file: any) => {
|
||||
if (!id) {
|
||||
message.warning('请先保存智能体基础信息后再上传');
|
||||
|
|
@ -328,6 +350,8 @@ export function useAgentEditor({ id, isNew, form, message, navigate }: UseAgentE
|
|||
beforeUploadEditAvatar,
|
||||
handleAvatarSelect,
|
||||
handleDeleteKnowledge,
|
||||
handleDeleteSkill,
|
||||
handleDeletePlugin,
|
||||
liveAgent,
|
||||
currentName,
|
||||
markDirty,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||
import { App as AntApp } from 'antd';
|
||||
import { FileTextOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
import SkillEditor from '../../components/SkillEditor';
|
||||
import ExternalToolEditor from '../../components/ExternalToolEditor';
|
||||
import { useAgentEditor } from './hooks/useAgentEditor';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile';
|
||||
import Header from './components/Header';
|
||||
|
|
@ -24,6 +25,8 @@ export default function AgentEditor() {
|
|||
const navigate = useNavigate();
|
||||
const { message } = AntApp.useApp();
|
||||
const [form] = Form.useForm();
|
||||
const [externalToolEditorOpen, setExternalToolEditorOpen] = useState(false);
|
||||
const [editingPluginId, setEditingPluginId] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
agent,
|
||||
|
|
@ -52,6 +55,8 @@ export default function AgentEditor() {
|
|||
beforeUploadEditAvatar,
|
||||
handleAvatarSelect,
|
||||
handleDeleteKnowledge,
|
||||
handleDeleteSkill,
|
||||
handleDeletePlugin,
|
||||
liveAgent,
|
||||
currentName,
|
||||
markDirty,
|
||||
|
|
@ -85,6 +90,24 @@ export default function AgentEditor() {
|
|||
setAvatarSelectorOpen={setAvatarSelectorOpen}
|
||||
beforeUploadKnowledge={beforeUploadKnowledge}
|
||||
onDeleteKnowledge={handleDeleteKnowledge}
|
||||
onCreateSkill={() => {
|
||||
setEditingSkillId(null);
|
||||
setSkillEditorOpen(true);
|
||||
}}
|
||||
onEditSkill={(skillId) => {
|
||||
setEditingSkillId(skillId);
|
||||
setSkillEditorOpen(true);
|
||||
}}
|
||||
onDeleteSkill={handleDeleteSkill}
|
||||
onCreateExternalTool={() => {
|
||||
setEditingPluginId(null);
|
||||
setExternalToolEditorOpen(true);
|
||||
}}
|
||||
onEditExternalTool={(pluginId) => {
|
||||
setEditingPluginId(pluginId);
|
||||
setExternalToolEditorOpen(true);
|
||||
}}
|
||||
onDeleteExternalTool={handleDeletePlugin}
|
||||
markDirty={markDirty}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
|
|
@ -120,13 +143,28 @@ export default function AgentEditor() {
|
|||
)}
|
||||
|
||||
{!isNew && (
|
||||
<SkillEditor
|
||||
open={skillEditorOpen}
|
||||
agentId={id!}
|
||||
skillId={editingSkillId}
|
||||
onClose={() => setSkillEditorOpen(false)}
|
||||
onSaved={refresh}
|
||||
/>
|
||||
<>
|
||||
<SkillEditor
|
||||
open={skillEditorOpen}
|
||||
agentId={id!}
|
||||
skillId={editingSkillId}
|
||||
onClose={() => {
|
||||
setSkillEditorOpen(false);
|
||||
setEditingSkillId(null);
|
||||
}}
|
||||
onSaved={refresh}
|
||||
/>
|
||||
<ExternalToolEditor
|
||||
open={externalToolEditorOpen}
|
||||
agentId={id!}
|
||||
plugin={agent?.plugins?.find((plugin) => plugin.id === editingPluginId)}
|
||||
onClose={() => {
|
||||
setExternalToolEditorOpen(false);
|
||||
setEditingPluginId(null);
|
||||
}}
|
||||
onSaved={refresh}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,138 @@
|
|||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.agent-editor-skill-actions,
|
||||
.agent-editor-tool-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.875rem;
|
||||
}
|
||||
|
||||
.agent-editor-skill-item {
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.625rem 0.75rem !important;
|
||||
border: 1px solid #edf1f7;
|
||||
border-radius: 0.75rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.agent-editor-skill-name {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.agent-editor-tool-section + .agent-editor-tool-section {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.agent-editor-tool-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
margin-bottom: 0.625rem;
|
||||
color: var(--color-text);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.agent-editor-tool-empty {
|
||||
padding: 1.25rem;
|
||||
border: 1px dashed #d9e1ec;
|
||||
border-radius: 0.75rem;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agent-editor-plugin-card {
|
||||
border-color: #e7edf6;
|
||||
border-radius: 0.875rem;
|
||||
}
|
||||
|
||||
.agent-editor-plugin-description {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.agent-editor-plugin-meta {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.agent-editor-plugin-apis {
|
||||
margin-top: 0.625rem;
|
||||
border-top: 1px solid #edf1f7;
|
||||
}
|
||||
|
||||
.agent-editor-api-detail {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.78125rem;
|
||||
}
|
||||
|
||||
.agent-editor-api-config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.625rem;
|
||||
}
|
||||
|
||||
.agent-editor-api-config-grid pre {
|
||||
max-height: 12rem;
|
||||
margin: 0.25rem 0 0;
|
||||
padding: 0.625rem;
|
||||
overflow: auto;
|
||||
border-radius: 0.5rem;
|
||||
background: #f6f8fb;
|
||||
color: #334155;
|
||||
font-size: 0.6875rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.agent-editor-tool-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 0.875rem;
|
||||
}
|
||||
|
||||
.agent-editor-tool-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.agent-editor-tool-list-header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.agent-editor-tool-help {
|
||||
margin-top: 0.125rem;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.agent-editor-tool-card {
|
||||
border-radius: 0.875rem;
|
||||
}
|
||||
|
||||
.agent-editor-code-input {
|
||||
font-family: Consolas, Menlo, Monaco, monospace;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.agent-editor-tool-grid,
|
||||
.agent-editor-api-config-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.agent-editor-skill-actions,
|
||||
.agent-editor-tool-list-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.agent-editor-field-input,
|
||||
.agent-editor-number-input {
|
||||
height: 2.625rem;
|
||||
|
|
|
|||
Loading…
Reference in New Issue