Skip to content

Commit ad134d5

Browse files
authored
[frontend]增加前端对话界面插件化功能 (#226)
1 parent 72f67a9 commit ad134d5

File tree

26 files changed

+520
-39
lines changed

26 files changed

+520
-39
lines changed

app-engine/frontend/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
package-lock.json
22
node_modules/
33
build/
4+
packages/
5+
plugins/*
6+
!plugins/manifest.json
7+
!plugins/plugin.sh
8+
!plugins/plugin.js

app-engine/frontend/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
"scripts": {
77
"start": "webpack serve --progress --profile --mode development --host localhost --port 3310 --config webpack.dev.js",
88
"start:single": "webpack serve --config webpack.dev.single.js",
9-
"build:prod": "webpack --mode production --config webpack.prod.js",
10-
"build:single": "webpack --mode production --config webpack.prod.single.js"
9+
"start:plugin": "bash ./plugins/plugin.sh && npm run start",
10+
"build:prod": "npm run build:plugin:prod && webpack --mode production --config webpack.prod.js",
11+
"build:single": "npm run build:plugin:single && webpack --mode production --config webpack.prod.single.js",
12+
"build:plugin:prod": "bash ./plugins/plugin.sh prod",
13+
"build:plugin:single": "bash ./plugins/plugin.sh prod spa"
1114
},
1215
"license": "ISC",
1316
"devDependencies": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
const fs = require('fs');
8+
9+
function ensure(fileName) {
10+
if (!fs.existsSync(fileName)) {
11+
fs.writeFileSync(fileName, '[]', { encoding: 'utf8' });
12+
}
13+
}
14+
15+
function read(fileName) {
16+
return fs.readFileSync(fileName, 'utf8');
17+
}
18+
19+
function write(fileName, content, appendContent = '') {
20+
if (!appendContent) return;
21+
try {
22+
const array = JSON.parse(content || '[]');
23+
const appendItem = JSON.parse(appendContent);
24+
25+
// 不存在时新增,存在时更新
26+
const found = array.find(item => item.name === appendItem.name);
27+
if (!found) {
28+
array.push(appendItem)
29+
} else {
30+
Object.assign(found, appendItem);
31+
}
32+
33+
fs.writeFileSync(fileName, JSON.stringify(array, null, 2), { encoding: 'utf8' });
34+
} catch (error) {
35+
console.error(error);
36+
}
37+
}
38+
39+
function main() {
40+
const args = process.argv.slice(2);
41+
const content = args[0] || '';
42+
const fileName = args[1] || './manifest.json';
43+
44+
ensure(fileName);
45+
write(fileName, read(fileName), content);
46+
}
47+
48+
main();
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/bin/bash
2+
#set -x
3+
4+
ENV=$1
5+
MODE=$2
6+
PLUGIN_NAME=$3
7+
CODE_URL=$4
8+
CODE_BRANCH=$5
9+
10+
PLUGIN_URL=""
11+
PLUGIN_ICON=""
12+
PLUGIN_DEV_URL="http://localhost:3099/#/chat?sidebar=0"
13+
if [ "$MODE" = "spa" ]; then
14+
PLUGIN_PROD_URL="/apps/appengine/plugins/$PLUGIN_NAME/index.html#/chat?sidebar=0"
15+
else
16+
PLUGIN_PROD_URL="/plugins/$PLUGIN_NAME/index.html#/chat?sidebar=0"
17+
fi
18+
WORKSPACE=packages
19+
20+
cd_workspace() {
21+
if [ ! -d $WORKSPACE ]; then
22+
mkdir $WORKSPACE
23+
fi
24+
cd ./$WORKSPACE
25+
pwd
26+
}
27+
28+
# 下载插件代码,或者更新代码
29+
clone_repo() {
30+
# if [ -d $PLUGIN_NAME ]; then
31+
# cd $PLUGIN_NAME
32+
# git pull
33+
# cd ..
34+
# else
35+
# git clone $CODE_URL --branch $CODE_BRANCH $PLUGIN_NAME
36+
# cd $PLUGIN_NAME
37+
# fi
38+
cd $PLUGIN_NAME
39+
}
40+
41+
# 安装插件依赖
42+
install_dep() {
43+
npm cache clean -f
44+
npm install --legacy-peer-deps --registry=https://registry.npmmirror.com
45+
}
46+
47+
# 本地运行
48+
run_start() {
49+
PLUGIN_URL=$PLUGIN_DEV_URL
50+
51+
if [[ $MODE = "spa" ]]; then
52+
start cmd /k "npm run start:spa"
53+
else
54+
start cmd /k "npm run start"
55+
fi
56+
57+
PLUGIN_ICON=""
58+
}
59+
60+
# 生产构建
61+
run_build() {
62+
PLUGIN_URL=$PLUGIN_PROD_URL
63+
64+
if [[ $MODE = "spa" ]]; then
65+
npm run build:single
66+
else
67+
npm run build
68+
fi
69+
70+
PLUGIN_ICON="/apps/appengine/plugins/$PLUGIN_NAME/icon.jpg"
71+
}
72+
73+
# 安装插件
74+
install_plugin() {
75+
rm -rf ../../plugins/$PLUGIN_NAME
76+
cp -r ./dist ../../plugins/$PLUGIN_NAME
77+
}
78+
79+
# 更新插件数据
80+
update_plugin_meta() {
81+
node ../../plugins/plugin.js "{\"name\":\"$PLUGIN_NAME\",\"icon\":\"$PLUGIN_ICON\",\"url\":\"$PLUGIN_URL\"}" ../../plugins/manifest.json
82+
}
83+
84+
echo "Start download and build plugin pathobot with mode:$MODE"
85+
86+
cd_workspace
87+
clone_repo
88+
install_dep
89+
90+
if [[ $ENV == "prod" ]]; then
91+
run_build
92+
install_plugin
93+
else
94+
run_start
95+
fi
96+
97+
update_plugin_meta

app-engine/frontend/src/components/layout/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { Provider } from 'react-redux';
2525
import { Icons, KnowledgeIcons } from '../icons/index';
2626
import store from '@/store/store';
2727
import { setSpaClassName } from '@/shared/utils/common';
28-
import { getUser, getOmsUser, getRole } from '../../pages/helper';
28+
import { getUser, getOmsUser, getRole, getChatPluginList } from '../../pages/helper';
2929
import './style.scoped.scss';
3030

3131
const { Content, Sider } = Layout;
@@ -113,6 +113,7 @@ const AppLayout: React.FC = () => {
113113
getOmsUser();
114114
getRole();
115115
}
116+
getChatPluginList();
116117
}, [])
117118
return (
118119
<Layout>

app-engine/frontend/src/locale/en_US.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@
233233
"testTip2": "Debug the workflow to ensure it runs properly before release.",
234234
"debug": "Debug",
235235
"plsEnterRequiredItem": "Set mandatory parameters",
236+
"expandConfig": "Expand config",
237+
"collapseConfig": "Collapse config",
236238
"flowChangeWarningContent": "The workflow configuration items have been modified. Debug the workflow again. The workflow can be released once it passes the debugging",
237239
"noMoreTips": "Do not remind me again",
238240
"plsEnterString": "Enter a string",

app-engine/frontend/src/locale/zh_CN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@
203203
"knowledgeCreated": "恭喜您,知识库已完成创建",
204204
"enterNameRule": "输入字符长度范围:1 - 64",
205205
"expandArrange": "展开编排区",
206+
"expandConfig": "展开配置区",
207+
"collapseConfig": "收起配置区",
206208
"flowChangeWarningContent": "工作流配置项已变更,请重新调试,调试成功后即可发布",
207209
"debugAlert": "工具流暂不支持调试历史记录",
208210
"consumer": "自定义",

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const Stage = (props) => {
7373
const testStatus = useAppSelector((state) => state.flowTestStore.testStatus);
7474
const appValidateInfo = useAppSelector((state) => state.appStore.validateInfo);
7575
const choseNodeId = useAppSelector((state) => state.appStore.choseNodeId);
76+
const pluginList = useAppSelector((state) => state.chatCommonStore.pluginList);
7677
const { tenantId, appId } = useParams();
7778
const testStatusRef = useRef<any>();
7879
const modelCallback = useRef<any>();
@@ -112,6 +113,29 @@ const Stage = (props) => {
112113
return null;
113114
}
114115
const realAppId = getQueryString('appId');
116+
const updateConfigs = () => {
117+
const startNodeIndex = CONFIGS.findIndex(item => item.node === 'startNodeStart');
118+
if (startNodeIndex === -1) {
119+
return;
120+
}
121+
const startNode = CONFIGS[startNodeIndex];
122+
if (!startNode.appConfig?.appChatStyle?.options) {
123+
return;
124+
}
125+
// 生成 pluginOptions 并去重
126+
const existingValues = new Set(
127+
startNode.appConfig.appChatStyle.options.map(opt => opt.value)
128+
);
129+
const pluginOptions = pluginList
130+
.filter(plugin => !existingValues.has(plugin.name))
131+
.map(plugin => ({
132+
value: plugin.name,
133+
label: plugin.chineseName ?? plugin.name,
134+
image: plugin.icon,
135+
}));
136+
137+
startNode.appConfig.appChatStyle.options.push(...pluginOptions);
138+
};
115139
// 编辑工作流
116140
function setElsaData(readOnly: boolean) {
117141
let graphData = appInfo.flowGraph?.appearance || {};
@@ -120,6 +144,7 @@ const Stage = (props) => {
120144
let configIndex = CONFIGS.findIndex(item => item.node === 'llmNodeState');
121145
CONFIGS[configIndex].params.tenantId = tenantId;
122146
CONFIGS[configIndex].params.appId = appId;
147+
updateConfigs();
123148
setSpinning && setSpinning(true);
124149
const flow = types === 'evaluate'
125150
? JadeFlow.evaluate(stageDom, tenantId, realAppId, data, false, CONFIGS, i18n)

app-engine/frontend/src/pages/aippIndex/index.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ConfigForm from '../configForm';
1212
import CommonChat from '../chatPreview/chatComminPage';
1313
import ChoreographyHead from '../components/header';
1414
import { getAppInfo, updateFormInfo } from '@/shared/http/aipp';
15-
import { debounce, getCurrentTime, getUiD, setSpaClassName } from '@/shared/utils/common';
15+
import { debounce, getCurrentTime, getUiD, setSpaClassName, getAppConfig } from '@/shared/utils/common';
1616
import { useAppDispatch, useAppSelector } from '@/store/hook';
1717
import { setInspirationOpen } from '@/store/chatStore/chatStore';
1818
import { setAippId, setAppId, setAppInfo, setChoseNodeId, setValidateInfo } from '@/store/appInfo/appInfo';
@@ -29,6 +29,7 @@ import { RenderContext } from '@/pages/aippIndex/context';
2929
const AippIndex = () => {
3030
const { appId, tenantId, aippId } = useParams();
3131
const [showElsa, setShowElsa] = useState(false);
32+
const [showConfig, setShowConfig] = useState(true);
3233
const [spinning, setSpinning] = useState(false);
3334
const [saveTime, setSaveTime] = useState('');
3435
const [reloadInspiration, setReloadInspiration] = useState('');
@@ -40,15 +41,35 @@ const AippIndex = () => {
4041
const inspirationRefresh = useRef<any>(false);
4142
const dispatch = useAppDispatch();
4243
const appInfo = useAppSelector((state) => state.appStore.appInfo);
44+
const pluginList = useAppSelector((state) => state.chatCommonStore.pluginList);
4345
const addFlowRef = useRef<any>(null);
4446
const renderRef = useRef(false);
4547
const elsaReadOnlyRef = useRef(false);
4648

49+
const [pluginName, setPluginName] = useState('default');
50+
const [plugin, setPlugin] = useState();
51+
52+
4753
const elsaChange = () => {
4854
setShowElsa(!showElsa);
4955
showElsa && getAippDetails(true);
5056
}
5157

58+
const handleChangeShowConfig = () => {
59+
setShowConfig(!showConfig);
60+
};
61+
62+
useEffect(() => {
63+
const found = pluginList.find((item: any) => item.name === pluginName);
64+
setPlugin(found);
65+
}, [pluginList, pluginName]);
66+
67+
useEffect(() => {
68+
if (plugin) {
69+
setShowConfig(false);
70+
}
71+
}, [plugin]);
72+
5273
useEffect(() => {
5374
dispatch(setAppInfo({}));
5475
dispatch(setAppId(appId));
@@ -83,11 +104,20 @@ const AippIndex = () => {
83104
res.data.hideHistory = true;
84105
aippRef.current = res.data;
85106
dispatch(setAppInfo(res.data));
107+
RefreshChatStyle(res.data);
86108
}
87109
} finally {
88110
setSpinning(false);
89111
}
90112
}
113+
114+
// 基于appInfo更新对话界面
115+
const RefreshChatStyle = (appInfo) => {
116+
const appChatStyle = getAppConfig(appInfo) ? getAppConfig(appInfo).appChatStyle : null;
117+
setPluginName(appChatStyle || 'default');
118+
};
119+
120+
91121
// 修改aipp更新回调
92122
const updateAippCallBack = (partialData) => {
93123
if (partialData) {
@@ -181,9 +211,16 @@ const AippIndex = () => {
181211
handleConfigDataChange={handleConfigDataChange}
182212
inspirationChange={inspirationChange}
183213
showElsa={showElsa}
214+
showConfig={showConfig}
215+
onChangeShowConfig={handleChangeShowConfig}
184216
/>
185217
)}
186-
<CommonChat contextProvider={contextProvider} previewBack={changeChat} />
218+
<CommonChat
219+
showElsa={showElsa}
220+
contextProvider={contextProvider}
221+
previewBack={changeChat}
222+
pluginName={pluginName}
223+
/>
187224
</div>
188225
</RenderContext.Provider>
189226
</div>

0 commit comments

Comments
 (0)