feat: ExternalToolEditor 支持 JSON 全量编辑与双向同步

main
sp mac bookpro 2605 2026-07-27 23:28:06 +08:00
parent f1553be178
commit 49b48df182
1 changed files with 315 additions and 257 deletions

View File

@ -1,5 +1,5 @@
import { CopyOutlined, DownOutlined, MinusCircleOutlined, PlusOutlined, RightOutlined } 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, Tabs } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { AgentAPI, ExternalToolApi, ExternalToolApiRouting, ExternalToolPlugin, ExternalToolPluginPayload } from '../api'; import { AgentAPI, ExternalToolApi, ExternalToolApiRouting, ExternalToolPlugin, ExternalToolPluginPayload } from '../api';
@ -150,9 +150,19 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS
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 apis = Form.useWatch('apis', form) || [];
const allValues = Form.useWatch([], form);
const [expandedApiIndexes, setExpandedApiIndexes] = useState<number[]>([]); const [expandedApiIndexes, setExpandedApiIndexes] = useState<number[]>([]);
const [jsonContent, setJsonContent] = useState('');
const [activeTab, setActiveTab] = useState('visual');
const [isSyncing, setIsSyncing] = useState(false);
const previousApiCountRef = useRef(0); const previousApiCountRef = useRef(0);
useEffect(() => {
if (allValues && !isSyncing) {
setJsonContent(JSON.stringify(allValues, null, 2));
}
}, [allValues, isSyncing]);
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
previousApiCountRef.current = 0; previousApiCountRef.current = 0;
@ -181,6 +191,21 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS
previousApiCountRef.current = apiCount; previousApiCountRef.current = apiCount;
}, [apis.length, open]); }, [apis.length, open]);
const handleJsonChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const val = e.target.value;
setJsonContent(val);
try {
const parsed = JSON.parse(val);
if (parsed && typeof parsed === 'object') {
setIsSyncing(true);
form.setFieldsValue(parsed);
setTimeout(() => setIsSyncing(false), 0);
}
} catch (e) {
// 格式不正确时不更新表单
}
};
const toggleApiCard = (index: number) => { const toggleApiCard = (index: number) => {
setExpandedApiIndexes((current) => setExpandedApiIndexes((current) =>
current.includes(index) ? current.filter((item) => item !== index) : [...current, index], current.includes(index) ? current.filter((item) => item !== index) : [...current, index],
@ -280,7 +305,16 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS
} }
}} }}
> >
<Form form={form} layout="vertical" requiredMark="optional"> <Form form={form} layout="vertical" requiredMark="optional" preserve={true}>
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
items={[
{
key: 'visual',
label: '可视化编辑',
children: (
<>
<div className="agent-editor-tool-grid"> <div className="agent-editor-tool-grid">
<Form.Item label="工具集名称" name="name" rules={[{ required: true, message: '请输入工具集名称' }]}> <Form.Item label="工具集名称" name="name" rules={[{ required: true, message: '请输入工具集名称' }]}>
<Input placeholder="例如Hoyidata 工具集" /> <Input placeholder="例如Hoyidata 工具集" />
@ -539,6 +573,30 @@ export default function ExternalToolEditor({ open, agentId, plugin, onClose, onS
</Space> </Space>
)} )}
</Form.List> </Form.List>
</>
),
},
{
key: 'json',
label: 'JSON 编辑',
children: (
<div style={{ padding: '4px 0 16px' }}>
<div style={{ marginBottom: 8, color: '#666', fontSize: 12 }}>
JSON
</div>
<Input.TextArea
value={jsonContent}
onChange={handleJsonChange}
rows={25}
className="agent-editor-code-input"
placeholder="请输入完整的工具集配置 JSON"
style={{ fontFamily: 'monospace' }}
/>
</div>
),
},
]}
/>
</Form> </Form>
</Modal> </Modal>
); );