优化外部工具 API 卡片折叠交互
parent
be0dae0719
commit
e33a1c3741
|
|
@ -9,5 +9,6 @@ alwaysApply: true
|
||||||
3. **代码修改** - 任何时候,当存在字段格式不对,变量名不对,表使用不对等,禁止做兼容修改,必须按唯一性修改。比如约定字段是string,正确:只能传string;错误:可以传int。 比如约定字段名是data,正确:只能传data;错误:可以传sourceData或者data。
|
3. **代码修改** - 任何时候,当存在字段格式不对,变量名不对,表使用不对等,禁止做兼容修改,必须按唯一性修改。比如约定字段是string,正确:只能传string;错误:可以传int。 比如约定字段名是data,正确:只能传data;错误:可以传sourceData或者data。
|
||||||
4. **语言** - 永远使用中文跟用户沟通,专有名词可以用英文。
|
4. **语言** - 永远使用中文跟用户沟通,专有名词可以用英文。
|
||||||
5. **文档** - 在用户没有明确要求先提供文档时,禁止创建文档。避免生产垃圾文件。用户明确要求提供文档时,才创建文档。
|
5. **文档** - 在用户没有明确要求先提供文档时,禁止创建文档。避免生产垃圾文件。用户明确要求提供文档时,才创建文档。
|
||||||
|
6. **注释** - 你生成的代码,尽可能完善中文注释。注释的格式需要按照Go语言的注释规范。要描述清楚代码的功能,参数,返回值,异常等。
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { CopyOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { CopyOutlined, DownOutlined, MinusCircleOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
import { App as AntApp, Button, Card, Form, Input, Modal, Select, Space } from 'antd';
|
import { App as AntApp, Button, Card, Form, Input, Modal, Select, Space } from 'antd';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { AgentAPI, ExternalToolApi, ExternalToolApiRouting, ExternalToolPlugin, ExternalToolPluginPayload } from '../api';
|
import { AgentAPI, ExternalToolApi, ExternalToolApiRouting, ExternalToolPlugin, ExternalToolPluginPayload } from '../api';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -137,6 +138,43 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS
|
||||||
const { message } = AntApp.useApp();
|
const { message } = AntApp.useApp();
|
||||||
const [form] = Form.useForm<ToolPluginFormValue>();
|
const [form] = Form.useForm<ToolPluginFormValue>();
|
||||||
const isEditing = Boolean(plugin);
|
const isEditing = Boolean(plugin);
|
||||||
|
const apis = Form.useWatch('apis', form) || [];
|
||||||
|
const [expandedApiIndexes, setExpandedApiIndexes] = useState<number[]>([]);
|
||||||
|
const previousApiCountRef = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) {
|
||||||
|
previousApiCountRef.current = 0;
|
||||||
|
setExpandedApiIndexes([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiCount = apis.length;
|
||||||
|
if (apiCount < 1) {
|
||||||
|
previousApiCountRef.current = 0;
|
||||||
|
setExpandedApiIndexes([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousApiCount = previousApiCountRef.current;
|
||||||
|
if (previousApiCount === 0) {
|
||||||
|
setExpandedApiIndexes(apiCount === 1 ? [0] : []);
|
||||||
|
} else if (previousApiCount === 1 && apiCount > 1) {
|
||||||
|
setExpandedApiIndexes([]);
|
||||||
|
} else if (apiCount === 1) {
|
||||||
|
setExpandedApiIndexes([0]);
|
||||||
|
} else {
|
||||||
|
setExpandedApiIndexes((current) => current.filter((index) => index < apiCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
previousApiCountRef.current = apiCount;
|
||||||
|
}, [apis.length, open]);
|
||||||
|
|
||||||
|
const toggleApiCard = (index: number) => {
|
||||||
|
setExpandedApiIndexes((current) =>
|
||||||
|
current.includes(index) ? current.filter((item) => item !== index) : [...current, index],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -264,149 +302,167 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => {
|
||||||
<Card
|
const isExpanded = expandedApiIndexes.includes(index);
|
||||||
key={field.key}
|
|
||||||
size="small"
|
return (
|
||||||
title={`API ${index + 1}`}
|
<Card
|
||||||
className="agent-editor-tool-card"
|
key={field.key}
|
||||||
extra={
|
size="small"
|
||||||
<Space>
|
title={
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<CopyOutlined />}
|
onClick={() => toggleApiCard(index)}
|
||||||
onClick={() => {
|
style={{ padding: 0, fontWeight: 500 }}
|
||||||
const currentApis = form.getFieldValue('apis') || [];
|
icon={isExpanded ? <DownOutlined /> : <RightOutlined />}
|
||||||
const apiToCopy = currentApis[field.name];
|
|
||||||
if (apiToCopy) {
|
|
||||||
add({
|
|
||||||
...apiToCopy,
|
|
||||||
name: `${apiToCopy.name}-copy`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
复制
|
{`API ${index + 1}`}
|
||||||
</Button>
|
</Button>
|
||||||
{fields.length > 1 ? (
|
}
|
||||||
<Button type="text" danger size="small" icon={<MinusCircleOutlined />} onClick={() => remove(field.name)}>
|
className="agent-editor-tool-card"
|
||||||
删除
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
const currentApis = form.getFieldValue('apis') || [];
|
||||||
|
const apiToCopy = currentApis[field.name];
|
||||||
|
if (apiToCopy) {
|
||||||
|
add({
|
||||||
|
...apiToCopy,
|
||||||
|
name: `${apiToCopy.name}-copy`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
复制
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
{fields.length > 1 ? (
|
||||||
</Space>
|
<Button type="text" danger size="small" icon={<MinusCircleOutlined />} onClick={() => remove(field.name)}>
|
||||||
}
|
删除
|
||||||
>
|
</Button>
|
||||||
<div className="agent-editor-tool-grid">
|
) : null}
|
||||||
<Form.Item
|
</Space>
|
||||||
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={[{ validator: validateTrimmedText('请输入描述') }]}
|
|
||||||
>
|
>
|
||||||
<Input.TextArea rows={2} placeholder="描述调用时机和工具能力" />
|
{isExpanded ? (
|
||||||
</Form.Item>
|
<>
|
||||||
<Form.Item
|
<div className="agent-editor-tool-grid">
|
||||||
label="path(API 地址)"
|
<Form.Item
|
||||||
name={[field.name, 'path']}
|
label="name(工具名)"
|
||||||
rules={[{ validator: validateTrimmedText('请输入 API 地址') }]}
|
name={[field.name, 'name']}
|
||||||
>
|
rules={[
|
||||||
<Input placeholder="/v1/products/hot-selling" />
|
{ required: true, message: '请输入工具名' },
|
||||||
</Form.Item>
|
{ pattern: /^[A-Za-z0-9_]+$/, message: '仅支持字母、数字和下划线' },
|
||||||
<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}'} />
|
<Input placeholder="query_hot_selling_products" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item label="method(调用方法)" name={[field.name, 'method']} rules={[{ required: true }]}>
|
||||||
label="parametersSchema(依赖参数 JSON 或对象)"
|
<Select options={['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].map((value) => ({ value, label: value }))} />
|
||||||
name={[field.name, 'parametersSchema']}
|
</Form.Item>
|
||||||
rules={[{ required: true, message: '请输入依赖参数 Schema' }]}
|
</div>
|
||||||
>
|
<Form.Item
|
||||||
<Input.TextArea rows={7} className="agent-editor-code-input" />
|
label="description(描述)"
|
||||||
</Form.Item>
|
name={[field.name, 'description']}
|
||||||
</div>
|
rules={[{ validator: validateTrimmedText('请输入描述') }]}
|
||||||
<Card size="small" title="routing(路由规则)">
|
>
|
||||||
<Form.Item label="summary" name={[field.name, 'routing', 'summary']}>
|
<Input.TextArea rows={2} placeholder="描述调用时机和工具能力" />
|
||||||
<Input placeholder="查询商品维度数据" />
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Form.List name={[field.name, 'routing', 'useWhen']} rules={[{ validator: validateRoutingList('至少添加一条 useWhen', 1) }]}>
|
label="path(API 地址)"
|
||||||
{(routingFields, { add: addUseWhen, remove: removeUseWhen }, { errors }) => (
|
name={[field.name, 'path']}
|
||||||
<Space direction="vertical" size={8} className="agent-editor-tool-list">
|
rules={[{ validator: validateTrimmedText('请输入 API 地址') }]}
|
||||||
<div className="agent-editor-tool-list-header">
|
>
|
||||||
<div>
|
<Input placeholder="/v1/products/hot-selling" />
|
||||||
<strong>useWhen</strong>
|
</Form.Item>
|
||||||
<div className="agent-editor-tool-help">至少一项,描述什么情况下应该调用这个 API。</div>
|
<div className="agent-editor-tool-grid">
|
||||||
</div>
|
<Form.Item label="headers(请求头 JSON 或对象)" name={[field.name, 'headers']}>
|
||||||
<Button type="dashed" icon={<PlusOutlined />} onClick={() => addUseWhen('')}>
|
<Input.TextArea rows={7} className="agent-editor-code-input" placeholder={'{\n "X-Custom-Source": "aura-agent"\n}'} />
|
||||||
添加条件
|
</Form.Item>
|
||||||
</Button>
|
<Form.Item
|
||||||
</div>
|
label="parametersSchema(依赖参数 JSON 或对象)"
|
||||||
{routingFields.map((routingField) => (
|
name={[field.name, 'parametersSchema']}
|
||||||
<Space key={routingField.key} align="start" className="agent-editor-tool-list">
|
rules={[{ required: true, message: '请输入依赖参数 Schema' }]}
|
||||||
<Form.Item
|
>
|
||||||
name={routingField.name}
|
<Input.TextArea rows={7} className="agent-editor-code-input" />
|
||||||
className="flex-1 mb-0"
|
</Form.Item>
|
||||||
rules={[{ validator: validateTrimmedText('条件不能为空') }]}
|
</div>
|
||||||
>
|
<Card size="small" title="routing(路由规则)">
|
||||||
<Input placeholder="用户明确要求商品数据" style={{ width: 600 }} />
|
<Form.Item label="summary" name={[field.name, 'routing', 'summary']}>
|
||||||
</Form.Item>
|
<Input placeholder="查询商品维度数据" />
|
||||||
<Button
|
</Form.Item>
|
||||||
danger
|
<Form.List name={[field.name, 'routing', 'useWhen']} rules={[{ validator: validateRoutingList('至少添加一条 useWhen', 1) }]}>
|
||||||
type="text"
|
{(routingFields, { add: addUseWhen, remove: removeUseWhen }, { errors }) => (
|
||||||
icon={<MinusCircleOutlined />}
|
<Space direction="vertical" size={8} className="agent-editor-tool-list">
|
||||||
onClick={() => removeUseWhen(routingField.name)}
|
<div className="agent-editor-tool-list-header">
|
||||||
disabled={routingFields.length <= 1}
|
<div>
|
||||||
/>
|
<strong>useWhen</strong>
|
||||||
</Space>
|
<div className="agent-editor-tool-help">至少一项,描述什么情况下应该调用这个 API。</div>
|
||||||
))}
|
</div>
|
||||||
<Form.ErrorList errors={errors} />
|
<Button type="dashed" icon={<PlusOutlined />} onClick={() => addUseWhen('')}>
|
||||||
</Space>
|
添加条件
|
||||||
)}
|
</Button>
|
||||||
</Form.List>
|
</div>
|
||||||
<Form.List name={[field.name, 'routing', 'doNotUseWhen']} rules={[{ validator: validateRoutingList('列表项不能为空') }]}>
|
{routingFields.map((routingField) => (
|
||||||
{(routingFields, { add: addDoNotUseWhen, remove: removeDoNotUseWhen }, { errors }) => (
|
<Space key={routingField.key} align="start" className="agent-editor-tool-list">
|
||||||
<Space direction="vertical" size={8} className="agent-editor-tool-list" style={{ marginTop: 16 }}>
|
<Form.Item
|
||||||
<div className="agent-editor-tool-list-header">
|
name={routingField.name}
|
||||||
<div>
|
className="flex-1 mb-0"
|
||||||
<strong>doNotUseWhen</strong>
|
rules={[{ validator: validateTrimmedText('条件不能为空') }]}
|
||||||
<div className="agent-editor-tool-help">可为空,描述什么情况下不要调用这个 API。</div>
|
>
|
||||||
</div>
|
<Input placeholder="用户明确要求商品数据" style={{ width: 600 }} />
|
||||||
<Button type="dashed" icon={<PlusOutlined />} onClick={() => addDoNotUseWhen('')}>
|
</Form.Item>
|
||||||
添加条件
|
<Button
|
||||||
</Button>
|
danger
|
||||||
</div>
|
type="text"
|
||||||
{routingFields.map((routingField) => (
|
icon={<MinusCircleOutlined />}
|
||||||
<Space key={routingField.key} align="start" className="agent-editor-tool-list">
|
onClick={() => removeUseWhen(routingField.name)}
|
||||||
<Form.Item
|
disabled={routingFields.length <= 1}
|
||||||
name={routingField.name}
|
/>
|
||||||
className="flex-1 mb-0"
|
</Space>
|
||||||
rules={[{ validator: validateTrimmedText('条件不能为空') }]}
|
))}
|
||||||
>
|
<Form.ErrorList errors={errors} />
|
||||||
<Input placeholder="当前问题只需要其他维度数据" style={{ width: 600 }} />
|
</Space>
|
||||||
</Form.Item>
|
)}
|
||||||
<Button danger type="text" icon={<MinusCircleOutlined />} onClick={() => removeDoNotUseWhen(routingField.name)} />
|
</Form.List>
|
||||||
</Space>
|
<Form.List name={[field.name, 'routing', 'doNotUseWhen']} rules={[{ validator: validateRoutingList('列表项不能为空') }]}>
|
||||||
))}
|
{(routingFields, { add: addDoNotUseWhen, remove: removeDoNotUseWhen }, { errors }) => (
|
||||||
<Form.ErrorList errors={errors} />
|
<Space direction="vertical" size={8} className="agent-editor-tool-list" style={{ marginTop: 16 }}>
|
||||||
</Space>
|
<div className="agent-editor-tool-list-header">
|
||||||
)}
|
<div>
|
||||||
</Form.List>
|
<strong>doNotUseWhen</strong>
|
||||||
|
<div className="agent-editor-tool-help">可为空,描述什么情况下不要调用这个 API。</div>
|
||||||
|
</div>
|
||||||
|
<Button type="dashed" icon={<PlusOutlined />} onClick={() => addDoNotUseWhen('')}>
|
||||||
|
添加条件
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{routingFields.map((routingField) => (
|
||||||
|
<Space key={routingField.key} align="start" className="agent-editor-tool-list">
|
||||||
|
<Form.Item
|
||||||
|
name={routingField.name}
|
||||||
|
className="flex-1 mb-0"
|
||||||
|
rules={[{ validator: validateTrimmedText('条件不能为空') }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="当前问题只需要其他维度数据" style={{ width: 600 }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Button danger type="text" icon={<MinusCircleOutlined />} onClick={() => removeDoNotUseWhen(routingField.name)} />
|
||||||
|
</Space>
|
||||||
|
))}
|
||||||
|
<Form.ErrorList errors={errors} />
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</Card>
|
</Card>
|
||||||
</Card>
|
);
|
||||||
))}
|
})}
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
</Form.List>
|
</Form.List>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue