Skip to content

Commit 643aa73

Browse files
authored
[elsa] knowledge node add rerank UI (#227)
* [elsa] Add OutsideRerankForm module of knowledgeRetrievalNode. * [elsa] Fix bug. * [elsa] Modify knowledge retrieval node flow meta struct. Add accessInfo provide params let backend can find specify model info. * [elsa] Modify mcp server config popover translation.
1 parent dd2f1a8 commit 643aa73

File tree

9 files changed

+330
-7
lines changed

9 files changed

+330
-7
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,32 @@ export const DEFAULT_MCP_SERVERS = {
222222
type: DATA_TYPES.OBJECT,
223223
from: FROM_TYPE.INPUT,
224224
value: {}
225+
};
226+
227+
export const DEFAULT_KNOWLEDGE_NODE_ACCESS_INFO = {
228+
id: uuidv4(),
229+
name: 'accessInfo',
230+
type: DATA_TYPES.OBJECT,
231+
from: FROM_TYPE.EXPAND,
232+
value: [{
233+
id: uuidv4(),
234+
name: 'serviceName',
235+
type: DATA_TYPES.STRING,
236+
from: FROM_TYPE.INPUT,
237+
value: '',
238+
}, {
239+
id: uuidv4(),
240+
name: 'tag',
241+
type: DATA_TYPES.STRING,
242+
from: FROM_TYPE.INPUT,
243+
value: '',
244+
}],
245+
};
246+
247+
export const DEFAULT_KNOWLEDGE_NODE_RERANK_TOP_N = {
248+
id: uuidv4(),
249+
name: 'topN',
250+
type: DATA_TYPES.INTEGER,
251+
from: FROM_TYPE.INPUT,
252+
value: 3,
225253
};

framework/elsa/fit-elsa-react/src/components/knowledgeRetrieval/KnowledgeRetrievalWrapper.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {useTranslation} from 'react-i18next';
1313
import {useDispatch} from '@/components/DefaultRoot.jsx';
1414
import {SearchForm} from '@/components/knowledgeRetrieval/SearchForm.jsx';
1515
import {getConfigValue} from '@/components/util/JadeConfigUtils.js';
16+
import {OutsideRerankForm} from '@/components/knowledgeRetrieval/OutsideRerankForm.jsx';
1617

1718
/**
1819
* retrieval组件Wrapper
@@ -46,6 +47,7 @@ export const KnowledgeRetrievalWrapper = ({data, shapeStatus}) => {
4647
editable={false}/>
4748
<KnowledgeForm knowledge={knowledge} groupId={groupId} knowledgeConfigId={knowledgeConfigId} disabled={shapeStatus.disabled}/>
4849
<SearchForm option={option} groupId={groupId} shapeStatus={shapeStatus}/>
50+
<OutsideRerankForm option={option} shapeStatus={shapeStatus}/>
4951
<OutputForm outputParams={outputParams} outputPopover={'knowledgeBaseOutputPopover'}/>
5052
</>);
5153
};
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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 React, {useState} from 'react';
8+
import {Collapse, Form, Slider, Spin, Switch} from 'antd';
9+
import {useTranslation} from 'react-i18next';
10+
import PropTypes from 'prop-types';
11+
import './knowledge.css';
12+
import {useDispatch, useShapeContext} from '@/components/DefaultRoot.jsx';
13+
import {getConfigValue} from '@/components/util/JadeConfigUtils.js';
14+
import {JadeCollapse} from '@/components/common/JadeCollapse.jsx';
15+
import {JadeStopPropagationSelect} from '@/components/common/JadeStopPropagationSelect.jsx';
16+
import httpUtil from '@/components/util/httpUtil.jsx';
17+
18+
const {Panel} = Collapse;
19+
20+
/**
21+
* 重排参数设置表单.
22+
*
23+
* @param option 搜索数据.
24+
* @param shapeStatus 图形状态.
25+
* @return {JSX.Element} 组件.
26+
* @constructor
27+
*/
28+
export const OutsideRerankForm = ({option, shapeStatus}) => {
29+
const {t} = useTranslation();
30+
const text = 'rerankConfig';
31+
const dispatch = useDispatch();
32+
const shape = useShapeContext();
33+
let config;
34+
if (!shape || !shape.graph || !shape.graph.configs) {
35+
// 没关系,继续.
36+
} else {
37+
config = shape.graph.configs.find(node => node.node === 'knowledgeRetrievalNodeState');
38+
}
39+
const topK = getConfigValue(option, ['referenceLimit', 'value'], 'value');
40+
const enableRerank = getConfigValue(option, ['rerankParam', 'enableRerank'], 'value');
41+
const model = getConfigValue(option, ['rerankParam', 'model'], 'value');
42+
const topN = getConfigValue(option, ['rerankParam', 'topN'], 'value');
43+
const [options, setOptions] = useState([]);
44+
const [loading, setLoading] = useState(false);
45+
const [hasLoaded, setHasLoaded] = useState(false);
46+
47+
const handleAccessInfoChange = (serviceName, tag) => {
48+
dispatch({type: 'changeAccessInfo', serviceName: serviceName, tag: tag});
49+
};
50+
51+
const handleRerankParamChange = (name, e) => {
52+
dispatch({type: 'changeRerankParam', name: name, value: e});
53+
};
54+
55+
const loadOptions = async () => {
56+
setLoading(true);
57+
try {
58+
httpUtil.get(`${config.urls.llmModelEndpoint}/fetch/model-list?type=rerank`, new Map(), (jsonData) => setOptions(jsonData.models.map(item => {
59+
return {
60+
value: item.serviceName,
61+
label: item.serviceName,
62+
title: t(item.tag),
63+
tag: item.tag,
64+
};
65+
})));
66+
setHasLoaded(true);
67+
} catch (error) {
68+
console.error('加载选项失败:', error);
69+
} finally {
70+
setLoading(false);
71+
}
72+
};
73+
74+
const handleDropdownVisibleChange = (open) => {
75+
if (open && !hasLoaded && !loading) {
76+
loadOptions();
77+
}
78+
};
79+
80+
return (<>
81+
<JadeCollapse defaultActiveKey={['inputPanel']}>
82+
{
83+
<Panel key={'inputPanel'}
84+
header={<>
85+
<div style={{display: 'flex', alignItems: 'center'}}>
86+
<span className="jade-panel-header-font">{t(text)}</span>
87+
</div>
88+
</>}
89+
className="jade-panel"
90+
>
91+
<div style={{display: 'flex', flexDirection: 'column', gap: '4px'}}>
92+
<Form.Item
93+
className="jade-form-item"
94+
label={t('whetherRerank')}
95+
name={`whetherRerank-${option.id}`}
96+
rules={[{required: true, message: t('fieldValueCannotBeEmpty')}]}
97+
validateTrigger="onBlur"
98+
initialValue={enableRerank}
99+
>
100+
<Switch style={{marginLeft: '4px', width: '40px'}}
101+
disabled={shapeStatus.disabled}
102+
onChange={(e) => handleRerankParamChange('enableRerank', e)}
103+
checked={enableRerank}></Switch>
104+
</Form.Item>
105+
</div>
106+
{enableRerank && <div style={{display: 'flex', flexDirection: 'column', gap: '4px'}}>
107+
<Form.Item
108+
className="jade-form-item"
109+
label={t('rerankModel')}
110+
name={`rerankModel-${option.id}`}
111+
rules={[{required: true, message: t('fieldValueCannotBeEmpty')}]}
112+
validateTrigger="onBlur"
113+
initialValue={model}
114+
>
115+
<JadeStopPropagationSelect
116+
disabled={shapeStatus.disabled}
117+
placeholder={t('pleaseSelect')}
118+
loading={loading}
119+
onDropdownVisibleChange={handleDropdownVisibleChange}
120+
onChange={(value, option) => {
121+
handleAccessInfoChange(value, option.tag);
122+
}}
123+
notFoundContent={loading ? <Spin size="small"/> : t('noContent')}
124+
options={options}
125+
/>
126+
</Form.Item>
127+
</div>}
128+
{enableRerank && <div style={{display: 'flex', flexDirection: 'column', gap: '4px'}}>
129+
<Form.Item
130+
className="jade-form-item"
131+
label={t('topN')}
132+
name={`topN-${option.id}`}
133+
rules={[{required: true, message: t('fieldValueCannotBeEmpty')}]}
134+
validateTrigger="onBlur"
135+
initialValue={topN}
136+
>
137+
<Slider style={{width: '95%'}} // 设置固定宽度
138+
min={1}
139+
max={topK}
140+
disabled={shapeStatus.disabled}
141+
defaultValue={3}
142+
marks={{[1]: 1, [topK]: topK}}
143+
step={1} // 设置步长为1
144+
onChange={(value) => handleRerankParamChange('topN', value)}
145+
value={topN}/>
146+
</Form.Item>
147+
</div>}
148+
</Panel>
149+
}
150+
</JadeCollapse>
151+
</>);
152+
};
153+
154+
OutsideRerankForm.propTypes = {
155+
option: PropTypes.object,
156+
shapeStatus: PropTypes.object,
157+
};

framework/elsa/fit-elsa-react/src/components/knowledgeRetrieval/SearchForm.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*--------------------------------------------------------------------------------------------*/
66

77
import React from 'react';
8-
import {Button, Collapse, Switch} from 'antd';
8+
import {Button, Collapse} from 'antd';
99
import {useTranslation} from 'react-i18next';
1010
import SearchConfigIcon from '../asserts/icon-search-args-config.svg?react';
1111
import PropTypes from 'prop-types';
@@ -65,7 +65,6 @@ export const _SearchForm = ({option, groupId, shapeStatus}) => {
6565
const referenceLimitType = getConfigValue(option, ['referenceLimit', 'type']);
6666
const referenceLimitValue = getConfigValue(option, ['referenceLimit', 'value']);
6767
const similarityThreshold = getConfigValue(option, ['similarityThreshold']);
68-
const rerank = getConfigValue(option, ['rerankParam', 'enableRerank']);
6968
return (<>
7069
<div className={'search-args-config-form-content'}>
7170
<div className={'search-args-config-form-column'}>

framework/elsa/fit-elsa-react/src/components/knowledgeRetrieval/knowledgeRetrievalComponent.jsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@
77
import {v4 as uuidv4} from 'uuid';
88
import {KnowledgeRetrievalWrapper} from '@/components/knowledgeRetrieval/KnowledgeRetrievalWrapper.jsx';
99
import {retrievalComponent} from '@/components/retrieval/retrievalComponent.jsx';
10-
import {DATA_TYPES, DEFAULT_KNOWLEDGE_REPO_GROUP, DEFAULT_KNOWLEDGE_RETRIEVAL_NODE_KNOWLEDGE_CONFIG_ID, FROM_TYPE} from '@/common/Consts.js';
1110
import {
11+
DATA_TYPES,
12+
DEFAULT_KNOWLEDGE_NODE_ACCESS_INFO,
13+
DEFAULT_KNOWLEDGE_NODE_RERANK_TOP_N,
14+
DEFAULT_KNOWLEDGE_REPO_GROUP,
15+
DEFAULT_KNOWLEDGE_RETRIEVAL_NODE_KNOWLEDGE_CONFIG_ID,
16+
FROM_TYPE,
17+
} from '@/common/Consts.js';
18+
import {
19+
ChangeAccessInfoReducer,
20+
ChangeRerankParamReducer,
1221
UpdateGroupIdAndConfigIdReducer,
1322
UpdateInputParamReducer,
1423
UpdateKnowledgeReducer,
@@ -29,6 +38,8 @@ export const knowledgeRetrievalComponent = (jadeConfig, shape) => {
2938
addReducer(builtInReducers, UpdateOptionReducer());
3039
addReducer(builtInReducers, UpdateKnowledgeReducer());
3140
addReducer(builtInReducers, UpdateGroupIdAndConfigIdReducer());
41+
addReducer(builtInReducers, ChangeRerankParamReducer());
42+
addReducer(builtInReducers, ChangeAccessInfoReducer());
3243

3344
/**
3445
* 必填
@@ -129,6 +140,8 @@ export const knowledgeRetrievalComponent = (jadeConfig, shape) => {
129140
from: FROM_TYPE.INPUT,
130141
value: false,
131142
},
143+
DEFAULT_KNOWLEDGE_NODE_ACCESS_INFO,
144+
DEFAULT_KNOWLEDGE_NODE_RERANK_TOP_N,
132145
],
133146
},
134147
{

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,104 @@ export const UpdateGroupIdAndConfigIdReducer = () => {
173173
knowledgeRepos.value = [];
174174
};
175175

176+
return self;
177+
};
178+
179+
/**
180+
* changeRerankParam 事件处理器.
181+
* 修改为每次更新都创建全新的对象引用
182+
*/
183+
export const ChangeRerankParamReducer = () => {
184+
const self = {};
185+
self.type = 'changeRerankParam';
186+
187+
self.reduce = (config, action) => {
188+
return {
189+
...config,
190+
inputParams: config.inputParams.map(ip => {
191+
if (ip.name !== 'option') {
192+
return {...ip};
193+
}
194+
return {
195+
...ip,
196+
value: ip.value.map(v => {
197+
if (v.name !== 'rerankParam') {
198+
return {...v};
199+
}
200+
return {
201+
...v,
202+
value: v.value.map(param => {
203+
if (param.name !== action.name) {
204+
return {...param};
205+
}
206+
return {
207+
...param,
208+
value: action.value,
209+
};
210+
})
211+
};
212+
})
213+
};
214+
})
215+
};
216+
};
217+
218+
return self;
219+
};
220+
221+
/**
222+
* changeAccessInfo 事件处理器.
223+
*
224+
* @return {{}} 处理器对象.
225+
* @constructor
226+
*/
227+
export const ChangeAccessInfoReducer = () => {
228+
const self = {};
229+
self.type = 'changeAccessInfo';
230+
231+
const _updateAccessInfoValue = (accessInfoValue, serviceName, tag) => {
232+
if (accessInfoValue.name === 'serviceName') {
233+
return {...accessInfoValue, value: serviceName};
234+
} else if (accessInfoValue.name === 'tag') {
235+
return {...accessInfoValue, value: tag};
236+
}
237+
return accessInfoValue;
238+
};
239+
240+
/**
241+
* 处理方法.
242+
*
243+
* @param config 配置数据.
244+
* @param action 事件对象.
245+
* @return {*} 处理之后的数据.
246+
*/
247+
self.reduce = (config, action) => {
248+
return {
249+
...config,
250+
inputParams: config.inputParams.map(ip => {
251+
if (ip.name !== 'option') {
252+
return {...ip};
253+
}
254+
return {
255+
...ip,
256+
value: ip.value.map(v => {
257+
if (v.name !== 'rerankParam') {
258+
return {...v};
259+
}
260+
return {
261+
...v,
262+
value: v.value.map(item => {
263+
return item.name === 'accessInfo' ? {
264+
...item,
265+
value: item.value.map(accessInfoValue => _updateAccessInfoValue(accessInfoValue, action.serviceName, action.tag)),
266+
} : item;
267+
})
268+
};
269+
})
270+
};
271+
})
272+
};
273+
};
274+
176275
return self;
177276
};

0 commit comments

Comments
 (0)