Skip to content

Commit 4fe3b99

Browse files
committed
[frontend] 增加工具市场MCP批量添加功能
1 parent 60618f2 commit 4fe3b99

File tree

5 files changed

+214
-14
lines changed

5 files changed

+214
-14
lines changed

frontend/src/pages/addFlow/components/elsa-stage.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,32 @@ const Stage = (props) => {
393393
}
394394
}
395395

396+
// 批量添加技能回调
397+
const confirmCallBack = (workFlowList, fitList) => {
398+
if (!Array.isArray(workFlowList) || !Array.isArray(fitList)) {
399+
return;
400+
}
401+
402+
const toolList = fitList.concat(workFlowList);
403+
404+
const uniqueNameList = toolList.map((item) => {
405+
return item.uniqueName;
406+
}).filter(name => name);
407+
408+
setSkillList(uniqueNameList);
409+
410+
// 为每个工具调用 pluginCallback.current,将工具添加到画布
411+
if (pluginCallback.current && typeof pluginCallback.current === 'function') {
412+
toolList.forEach((tool) => {
413+
try {
414+
pluginCallback.current(tool);
415+
} catch (error) {
416+
// 静默处理错误
417+
}
418+
});
419+
}
420+
}
421+
396422
// 自定义表单选中
397423
const formConfirm = (item) => {
398424
formCallback.current(item);
@@ -556,6 +582,7 @@ const Stage = (props) => {
556582
checkData={skillList}
557583
modalType={modalTypes}
558584
type='addSkill'
585+
confirmCallBack={confirmCallBack}
559586
/>
560587
{/* 创建测试集 */}
561588
<CreateTestSet

frontend/src/pages/addFlow/components/tool-modal.tsx

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,57 @@ const ToolDrawer = (props) => {
169169
const workflowAdd = () => {
170170
const workFlowList: any = [];
171171
const fitList: any = [];
172-
checkedList.current.forEach((item) => {
173-
if (item.tags.includes('WATERFLOW')) {
172+
173+
if (!checkedList.current || checkedList.current.length === 0) {
174+
return;
175+
}
176+
177+
// 过滤掉字符串类型的项,只处理对象类型的工具
178+
// 在 addSkill 模式下,checkedList.current 可能包含字符串(uniqueName)和对象(工具对象)
179+
// 我们只处理对象类型的工具,字符串类型的项应该已经被处理过了
180+
const validTools = checkedList.current.filter((item) => {
181+
const isString = typeof item === 'string';
182+
const isObject = typeof item === 'object' && item !== null && !Array.isArray(item);
183+
184+
if (isString) {
185+
return false;
186+
}
187+
188+
if (!isObject) {
189+
return false;
190+
}
191+
192+
// 确保对象有 uniqueName 属性
193+
if (!item.uniqueName) {
194+
return false;
195+
}
196+
197+
return true;
198+
});
199+
200+
validTools.forEach((item) => {
201+
// 安全地检查 tags,处理 tags 可能为 undefined、null 或不是数组的情况
202+
const tags = item?.tags || [];
203+
const isArray = Array.isArray(tags);
204+
205+
if (isArray && tags.includes('WATERFLOW')) {
174206
workFlowList.push({ ...item, type: 'waterflow' });
175207
} else {
176208
fitList.push({ ...item, type: 'tool' });
177209
}
178210
});
211+
179212
const workFlowId = workFlowList.map((item) => item.uniqueName);
180213
const fitId = fitList.map((item) => item.uniqueName);
214+
181215
if (type === 'addSkill') {
182-
confirmCallBack(workFlowList, fitList);
216+
if (typeof confirmCallBack === 'function') {
217+
confirmCallBack(workFlowList, fitList);
218+
}
183219
} else {
184-
confirmCallBack(workFlowId, fitId);
220+
if (typeof confirmCallBack === 'function') {
221+
confirmCallBack(workFlowId, fitId);
222+
}
185223
}
186224
};
187225

frontend/src/pages/addFlow/components/tool-table.tsx

Lines changed: 133 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
66

7-
import React, { useState } from 'react';
7+
import React, { useState, useRef } from 'react';
88
import { Collapse, Button, Divider, Tag } from 'antd';
99
import { getPluginDetail, getChatbotPluginDetail } from '@/shared/http/plugin';
1010
import { useTranslation } from 'react-i18next';
@@ -43,8 +43,11 @@ const ToolTable = (props: any) => {
4343
searchName,
4444
} = props;
4545
const [getPluginData, setGetPluginData] = useState<any>([]);
46+
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
4647
const modalTypes = ['pluginButtonTool', 'loop'];
4748
let checkedToolList: any = [];
49+
// 用于标记是否正在进行批量添加操作
50+
const isBatchAddingRef = useRef(false);
4851

4952
const confirm = (item: any) => {
5053
const pluginInfo = pluginData?.mapType ? [pluginData] : getPluginData;
@@ -83,8 +86,94 @@ const ToolTable = (props: any) => {
8386
}
8487
};
8588

89+
// 批量添加处理函数
90+
const handleBatchAdd = () => {
91+
// 标记正在进行批量添加操作
92+
isBatchAddingRef.current = true;
93+
94+
const unselectedTools = getPluginData.filter((item: any) => !item.isChecked);
95+
96+
if (unselectedTools.length === 0) {
97+
isBatchAddingRef.current = false;
98+
return;
99+
}
100+
101+
// 获取当前插件的 pluginId,用于过滤工具
102+
const currentPluginId = pluginData?.pluginId;
103+
104+
// 在 addSkill 模式下,我们需要确保只处理当前插件的工具
105+
// 先过滤掉字符串类型的项,然后只添加当前插件的工具
106+
if (type === 'addSkill') {
107+
// 过滤掉字符串类型的项(这些是之前添加的其他插件的工具)
108+
const objectTools = checkedList.current.filter((item: any) => {
109+
return typeof item === 'object' && item !== null && !Array.isArray(item);
110+
});
111+
112+
// 进一步过滤,只保留当前插件的工具
113+
// 工具对象的 pluginId 可能直接存在,也可能需要从父插件对象中获取
114+
const currentPluginTools = objectTools.filter((item: any) => {
115+
const itemPluginId = item?.pluginId || item?.pluginData?.pluginId;
116+
return itemPluginId === currentPluginId;
117+
});
118+
119+
// 重置 checkedList.current,只保留当前插件的工具
120+
checkedList.current = [...currentPluginTools];
121+
}
122+
123+
// 添加当前未选中的工具
124+
unselectedTools.forEach((item: any) => {
125+
// 确保工具属于当前插件
126+
// 如果工具对象没有 pluginId,则从父插件对象中获取
127+
const itemPluginId = item?.pluginId || pluginData?.pluginId;
128+
129+
if (itemPluginId === currentPluginId) {
130+
// 确保工具对象有 pluginId 属性
131+
if (!item.pluginId) {
132+
item.pluginId = currentPluginId;
133+
}
134+
item.isChecked = true;
135+
checkedList.current.push(item);
136+
}
137+
});
138+
139+
// 保存当前的 panelKey,确保面板保持展开
140+
const currentPanelKey = pluginData.appCategory ? pluginData.uniqueName : pluginData.pluginId;
141+
142+
// 更新 UI 状态
143+
let newData = deepClone(getPluginData);
144+
newData.forEach((ite: any) => {
145+
if (!ite.isChecked) {
146+
ite.isChecked = true;
147+
}
148+
});
149+
setGetPluginData(newData);
150+
151+
// 确保面板保持展开状态
152+
// 如果当前 panelKey 不在 expandedKeys 中,则添加它
153+
if (!expandedKeys.includes(currentPanelKey)) {
154+
setExpandedKeys([...expandedKeys, currentPanelKey]);
155+
}
156+
157+
// 调用添加方法
158+
if (type === 'addSkill') {
159+
workflowAdd();
160+
}
161+
162+
// 延迟重置批量添加标志,确保父组件重新渲染完成后再重置
163+
// 这样可以避免父组件重新渲染时触发的 onChange 关闭面板
164+
setTimeout(() => {
165+
isBatchAddingRef.current = false;
166+
}, 500);
167+
};
168+
86169
// 通用HTML
87170
const fncHTML = (item: any, toolType: string) => {
171+
const panelKey = pluginData.appCategory ? pluginData.uniqueName : pluginData.pluginId;
172+
const isMcpPlugin = pluginData.extension?.type === 'mcp';
173+
const isExpanded = expandedKeys.includes(panelKey);
174+
const hasUnselectedTools = getPluginData.some((tool: any) => !tool.isChecked);
175+
const showBatchAdd = isMcpPlugin && isExpanded && toolType === 'panel' && type === 'addSkill' && getPluginData.length > 0 && hasUnselectedTools;
176+
88177
return (
89178
<div className='tool-table'>
90179
<div className='tool-table-header'>
@@ -127,8 +216,17 @@ const ToolTable = (props: any) => {
127216
{toolType === 'panel' ? item.extension.description : item.description}
128217
</div>
129218
</div>
130-
{(toolType === ToolType.TOOL || toolType === ToolType.WATERFLOW) && (
131-
<div>
219+
<div className='tool-table-actions'>
220+
{showBatchAdd && (
221+
<Button
222+
type='primary'
223+
onClick={handleBatchAdd}
224+
className='batch-add-button'
225+
>
226+
{t('批量添加') || 'batchAdd'}
227+
</Button>
228+
)}
229+
{(toolType === ToolType.TOOL || toolType === ToolType.WATERFLOW) && (
132230
<Button
133231
disabled={type === 'addSkill' ? item.isChecked : false}
134232
onClick={() => confirm(item.uniqueName)}
@@ -148,16 +246,34 @@ const ToolTable = (props: any) => {
148246
t('additions')
149247
)}
150248
</Button>
151-
</div>
152-
)}
249+
)}
250+
</div>
153251
</div>
154252
</div>
155253
</div>
156254
);
157255
};
158256

159257
const onChange = async (e: any) => {
160-
let param = e.toString();
258+
const panelKey = pluginData.appCategory ? pluginData.uniqueName : pluginData.pluginId;
259+
260+
// 更新展开状态 - Collapse 的 onChange 接收的是展开 key 的数组
261+
const expandedArray = Array.isArray(e) ? e : (e ? [e] : []);
262+
const isCurrentlyExpanded = expandedArray.includes(panelKey);
263+
const isTryingToClose = !isCurrentlyExpanded && expandedKeys.includes(panelKey);
264+
265+
// 如果正在进行批量添加操作,且尝试关闭当前面板,则阻止关闭
266+
if (isBatchAddingRef.current && isTryingToClose) {
267+
// 保持面板展开状态
268+
setExpandedKeys([...expandedKeys.filter(key => key !== panelKey), panelKey]);
269+
return;
270+
}
271+
272+
// 更新展开状态
273+
setExpandedKeys(expandedArray);
274+
275+
let param = expandedArray.length > 0 ? panelKey : '';
276+
161277
if (type === 'addSkill') {
162278
checkData.forEach((item: any) => {
163279
checkedToolList.push(item.name);
@@ -181,15 +297,23 @@ const ToolTable = (props: any) => {
181297
setGetPluginData(newRes);
182298
}
183299
}
184-
} catch {}
300+
} catch (error) {
301+
// 静默处理错误
302+
}
185303
};
186304

305+
const panelKey = pluginData.appCategory ? pluginData.uniqueName : pluginData.pluginId;
306+
187307
return (
188308
<>
189-
<Collapse expandIconPosition='end' onChange={onChange}>
309+
<Collapse
310+
expandIconPosition='end'
311+
onChange={onChange}
312+
activeKey={expandedKeys}
313+
>
190314
<Collapse.Panel
191315
header={fncHTML(pluginData, 'panel')}
192-
key={pluginData.appCategory ? pluginData.uniqueName : pluginData.pluginId}
316+
key={panelKey}
193317
>
194318
{getPluginData.map((item: any, index: any) => {
195319
return (

frontend/src/pages/addFlow/styles/tool-modal.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
.tool-modal{
187187
.ant-btn {
188188
margin: 0 !important;
189-
margin-bottom: 20px !important;
189+
margin-bottom: 0 !important;
190190
}
191191
.ant-tabs-nav-wrap {
192192
justify-content: start !important;

frontend/src/pages/addFlow/styles/tool-table.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,16 @@
7474
color: white;
7575
}
7676
}
77+
78+
.tool-table-actions {
79+
display: flex;
80+
align-items: center;
81+
flex-shrink: 0;
82+
margin-left: 16px;
83+
84+
.batch-add-button {
85+
white-space: nowrap;
86+
}
87+
}
7788
}
7889
}

0 commit comments

Comments
 (0)