Skip to content

Commit 7e08dda

Browse files
authored
[elsa] Add MCPServerConfig configuration to the large model node. (#206)
1 parent 1ed4486 commit 7e08dda

File tree

10 files changed

+167
-15
lines changed

10 files changed

+167
-15
lines changed

framework/elsa/fit-elsa-react/src/common/Consts.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,12 @@ export const DEFAULT_KNOWLEDGE_RETRIEVAL_NODE_KNOWLEDGE_CONFIG_ID = {
214214
type: DATA_TYPES.STRING,
215215
from: FROM_TYPE.INPUT,
216216
value: '',
217+
};
218+
219+
export const DEFAULT_MCP_SERVERS = {
220+
id: uuidv4(),
221+
name: "mcpServers",
222+
type: DATA_TYPES.OBJECT,
223+
from: FROM_TYPE.INPUT,
224+
value: {}
217225
};

framework/elsa/fit-elsa-react/src/components/common/prompt/Prompt.jsx

Lines changed: 5 additions & 3 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 from 'react';
7+
import React, {useState} from 'react';
88
import {Button, Col, Form, Input, Popover, Row} from 'antd';
99
import {QuestionCircleOutlined} from '@ant-design/icons';
1010
import './prompt.css';
@@ -39,10 +39,12 @@ export const Prompt = (
3939
}) => {
4040
const shape = useShapeContext();
4141
const form = useFormContext();
42+
const [promptContent, setPromptContent] = useState(prompt.value);
4243

4344
const _onChange = (promptText) => {
4445
onChange(promptText);
4546
form.setFieldsValue({[name]: promptText});
47+
setPromptContent(promptText);
4648
};
4749

4850
/**
@@ -96,7 +98,7 @@ export const Prompt = (
9698
name={name}
9799
label={getLabel()}
98100
rules={rules}
99-
initialValue={prompt.value}
101+
initialValue={promptContent}
100102
validateTrigger='onBlur'
101103
>
102104
<TextArea
@@ -108,7 +110,7 @@ export const Prompt = (
108110
/>
109111
</Form.Item>
110112
<PromptDrawer
111-
value={prompt.value}
113+
value={promptContent}
112114
name={name}
113115
title={title}
114116
rules={rules}

framework/elsa/fit-elsa-react/src/components/llm/LlmFormWrapper.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {Trans, useTranslation} from 'react-i18next';
1616
import {v4 as uuidv4} from 'uuid';
1717
import {SkillForm} from '@/components/llm/SkillForm.jsx';
1818
import {KnowledgeForm} from './KnowledgeForm.jsx';
19+
import {McpConfigForm} from '@/components/llm/McpConfigForm.jsx';
1920

2021
LlmFormWrapper.propTypes = {
2122
data: PropTypes.object.isRequired,
@@ -53,6 +54,7 @@ export default function LlmFormWrapper({data, shapeStatus}) {
5354
prompt: data.inputParams.filter(item => item.name === 'prompt')
5455
.flatMap(item => item.value)
5556
.find(item => item.name === 'template'),
57+
mcpServers: data.inputParams.find(item => item.name === 'mcpServers'),
5658
};
5759
const knowledgeData = data.inputParams.find(item => item.name === 'knowledgeBases');
5860
const enableLogData = data.inputParams.find(item => item.name === 'enableLog');
@@ -195,6 +197,7 @@ export default function LlmFormWrapper({data, shapeStatus}) {
195197
modelData={modelData} shapeId={shape.id}
196198
modelOptions={modelOptions}/>
197199
<SkillForm disabled={shapeStatus.disabled} toolOptions={toolOptions}/>
200+
<McpConfigForm disabled={shapeStatus.disabled} modelData={modelData} shapeId={shape.id}/>
198201
<LlmOutput outputItems={data.outputParams} enableLogData={enableLogData} disabled={shapeStatus.disabled}/>
199202
</div>
200203
);
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
3+
* This file is a part of the ModelEngine Project.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
import {Collapse} from 'antd';
8+
import {useDispatch} from '@/components/DefaultRoot.jsx';
9+
import PropTypes from 'prop-types';
10+
import {Trans, useTranslation} from 'react-i18next';
11+
import React, {useState} from 'react';
12+
import {Prompt} from '@/components/common/prompt/Prompt.jsx';
13+
import FullScreenIcon from '../asserts/icon-full-screen.svg?react';
14+
import {JadeCollapse} from '@/components/common/JadeCollapse.jsx';
15+
16+
const {Panel} = Collapse;
17+
18+
/**
19+
* 大模型节点MCP配置表单。
20+
*
21+
* @param shapeId 所属图形唯一标识。
22+
* @param modelData 数据.
23+
* @param disabled 是否禁用.
24+
* @returns {JSX.Element} 大模型节点模型表单的DOM。
25+
*/
26+
const _McpConfigForm = ({shapeId, modelData, disabled}) => {
27+
const dispatch = useDispatch();
28+
const {t} = useTranslation();
29+
const [configOpen, setConfigOpen] = useState(false);
30+
31+
const JsonValidator = () => ({
32+
validator(_, value) {
33+
try {
34+
if (value) JSON.parse(value);
35+
return Promise.resolve();
36+
} catch (err) {
37+
return Promise.reject(t('pleaseEnterValidJson'));
38+
}
39+
},
40+
});
41+
42+
const mcpServerConfigContent = (<div className={'jade-font-size'} style={{lineHeight: '1.2', whiteSpace: 'pre'}}>
43+
<Trans i18nKey='mcpServerConfigPopover' components={{p: <p/>}}/>
44+
</div>);
45+
46+
return (<>
47+
<JadeCollapse defaultActiveKey={['mcpServerConfigPanel']}>
48+
{<Panel
49+
key={'mcpServerConfigPanel'}
50+
header={<div className='panel-header'>
51+
<span className='jade-panel-header-font'>{t('mcpServerConfig')}</span>
52+
</div>}
53+
className='jade-panel'
54+
>
55+
<div className={'jade-custom-panel-content'}>
56+
<Prompt
57+
prompt={{
58+
...modelData.mcpServers,
59+
value: JSON.stringify(modelData.mcpServers.value, null, 2),
60+
}}
61+
rules={[{required: true, message: t('paramCannotBeEmpty')}, JsonValidator]}
62+
name={`mcpServers-${shapeId}`}
63+
tips={mcpServerConfigContent}
64+
onChange={(text) => {
65+
try {
66+
const newObj = JSON.parse(text); // 解析字符串
67+
dispatch({type: 'changeMcpServers', id: modelData.mcpServers.id, value: newObj});
68+
} catch (err) {
69+
// 不影响
70+
}
71+
}}
72+
buttonConfigs={[{
73+
icon: <FullScreenIcon/>, onClick: () => {
74+
setConfigOpen(true);
75+
},
76+
}]}
77+
disabled={disabled}
78+
open={configOpen}
79+
setOpen={setConfigOpen}/>
80+
</div>
81+
</Panel>}
82+
</JadeCollapse>
83+
</>);
84+
};
85+
86+
_McpConfigForm.propTypes = {
87+
shapeId: PropTypes.string.isRequired, // 确保 shapeId 是一个必需的string类型
88+
modelData: PropTypes.object.isRequired, // 确保 modelData 是一个必需的object类型
89+
disabled: PropTypes.bool, // 确保 modelOptions 是一个必需的array类型
90+
};
91+
92+
const areEqual = (prevProps, nextProps) => {
93+
return prevProps.modelData.mcpServers === nextProps.modelData.mcpServers &&
94+
prevProps.disabled === nextProps.disabled;
95+
};
96+
97+
export const McpConfigForm = React.memo(_McpConfigForm, areEqual);

framework/elsa/fit-elsa-react/src/components/llm/ModelForm.jsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,4 @@ DialogueRound.propTypes = {
223223
onConversationTurnChange: PropTypes.func,
224224
};
225225

226-
_ModelForm.propTypes = {
227-
shapeId: PropTypes.string.isRequired, // 确保 shapeId 是一个必需的string类型
228-
modelData: PropTypes.object.isRequired, // 确保 modelData 是一个必需的object类型
229-
modelOptions: PropTypes.array.isRequired, // 确保 modelOptions 是一个必需的array类型
230-
disabled: PropTypes.bool, // 确保 modelOptions 是一个必需的array类型
231-
};
232-
233226
export const ModelForm = React.memo(_ModelForm, areEqual);

framework/elsa/fit-elsa-react/src/components/llm/llmComponent.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
import LlmFormWrapper from './LlmFormWrapper.jsx';
88
import {v4 as uuidv4} from 'uuid';
99
import {defaultComponent} from '@/components/defaultComponent.js';
10-
import {DEFAULT_MAX_MEMORY_ROUNDS} from '@/common/Consts.js';
10+
import {DEFAULT_MAX_MEMORY_ROUNDS, DEFAULT_MCP_SERVERS} from '@/common/Consts.js';
1111
import {
1212
AddInputParamReducer,
1313
AddOutputParamReducer,
1414
AddSkillReducer,
1515
ChangeAccessInfoConfigReducer,
1616
ChangeConfigReducer,
1717
ChangeInputParamsReducer,
18-
ChangeKnowledgeReducer,
18+
ChangeKnowledgeReducer, ChangeMcpServersReducer,
1919
ChangeOutputParamReducer,
2020
ChangePromptReducer,
2121
ChangeSkillConfigReducer,
@@ -55,6 +55,7 @@ export const llmComponent = (jadeConfig, shape) => {
5555
addReducer(builtInReducers, DeleteToolReducer(shape, self));
5656
addReducer(builtInReducers, MoveKnowledgeItemReducer(shape, self));
5757
addReducer(builtInReducers, UpdateLogStatusReducer(shape, self));
58+
addReducer(builtInReducers, ChangeMcpServersReducer(shape, self));
5859

5960
/**
6061
* 必须.
@@ -126,6 +127,7 @@ export const llmComponent = (jadeConfig, shape) => {
126127
type: 'Array',
127128
value: [],
128129
},
130+
JSON.parse(JSON.stringify(DEFAULT_MCP_SERVERS)),
129131
],
130132
outputParams: [
131133
{

framework/elsa/fit-elsa-react/src/components/llm/reducers/reducers.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,5 +815,45 @@ export const UpdateLogStatusReducer = () => {
815815
return newConfig;
816816
};
817817

818+
return self;
819+
};
820+
821+
/**
822+
* changeMcpServers 事件处理器.
823+
*
824+
* @return {{}} 处理器对象.
825+
* @constructor
826+
*/
827+
export const ChangeMcpServersReducer = () => {
828+
const self = {};
829+
self.type = 'changeMcpServers';
830+
831+
/**
832+
* 处理方法.
833+
*
834+
* @param config 配置数据.
835+
* @param action 事件对象.
836+
* @return {*} 处理之后的数据.
837+
*/
838+
self.reduce = (config, action) => {
839+
const newConfig = {};
840+
Object.entries(config).forEach(([key, value]) => {
841+
if (key === 'inputParams') {
842+
newConfig[key] = value.map(item => {
843+
if (item.name === 'mcpServers') {
844+
return {
845+
...item, value: action.value,
846+
};
847+
} else {
848+
return item;
849+
}
850+
});
851+
} else {
852+
newConfig[key] = value;
853+
}
854+
});
855+
return newConfig;
856+
};
857+
818858
return self;
819859
};

framework/elsa/fit-elsa-react/src/flow/compatibility/compatibilityProcessors.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
DEFAULT_MAX_MEMORY_ROUNDS,
1717
END_NODE_TYPE,
1818
FLOW_TYPE,
19-
FROM_TYPE,
19+
FROM_TYPE, DEFAULT_MCP_SERVERS,
2020
} from '@/common/Consts.js';
2121
import {getEndNodeType} from '@/components/end/endNodeUtils.js';
2222
import {pageProcessor} from '@/flow/pageProcessors.js';
@@ -432,6 +432,7 @@ export const llmCompatibilityProcessor = (shapeData, graph, pageHandler) => {
432432
const outputObject = self.shapeData.flowMeta.jober.converter.entity.outputParams.find(i => i.name === 'output');
433433
ensureParam(inputParams, DEFAULT_MAX_MEMORY_ROUNDS);
434434
ensureParam(inputParams, DEFAULT_LLM_KNOWLEDGE_BASES);
435+
ensureParam(inputParams, DEFAULT_MCP_SERVERS);
435436
ensureParam(outputObject.value, DEFAULT_LLM_REFERENCE_OUTPUT);
436437
addEnableLog(inputParams);
437438
if (!self.shapeData.flowMeta.jober.converter.entity.tempReference) {

framework/elsa/fit-elsa-react/src/i18n/en_US.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,5 +417,8 @@
417417
"parallelNode": "Parallel Node",
418418
"type": "Type",
419419
"orchestration": "Orchestration",
420-
"manual": "Manual"
420+
"manual": "Manual",
421+
"mcpServerConfig": "MCP Server Configuration",
422+
"pleaseEnterValidJson": "Please enter a valid JSON format!",
423+
"mcpServerConfigPopover": "Example: \n{\n \"server_name\": {\n \"url\": \"https://127.0.0.1:80/sse\",\n \"headers\": {},\n \"timeout\": 10,\n \"sse_read_timeout\": 300\n }\n}"
421424
}

framework/elsa/fit-elsa-react/src/i18n/zh_CN.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,5 +710,8 @@
710710
"parallelNode": "并行节点",
711711
"type": "类型",
712712
"orchestration": "编排",
713-
"manual": "人工"
713+
"manual": "人工",
714+
"mcpServerConfig": "MCP服务配置",
715+
"pleaseEnterValidJson": "请输入合法的 JSON 格式!",
716+
"mcpServerConfigPopover": "示例:\n{\n \"server_name\": {\n \"url\": \"https://127.0.0.1:80/sse\",\n \"headers\": {},\n \"timeout\": 10,\n \"sse_read_timeout\": 300\n }\n}"
714717
}

0 commit comments

Comments
 (0)