balibabu
commited on
Commit
·
f859b0d
1
Parent(s):
362b09b
feat: Add RunDrawer #3355 (#3434)
Browse files### What problem does this PR solve?
feat: Translation test run form #3355
feat: Wrap QueryTable with Collapse #3355
feat: If the required fields are not filled in, the submit button will
be grayed out. #3355
feat: Add RunDrawer #3355
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- web/src/hooks/document-hooks.ts +26 -1
- web/src/hooks/login-hooks.ts +19 -1
- web/src/locales/en.ts +5 -1
- web/src/locales/zh-traditional.ts +4 -0
- web/src/locales/zh.ts +4 -0
- web/src/pages/flow/canvas/index.tsx +76 -24
- web/src/pages/flow/canvas/node/begin-node.tsx +28 -6
- web/src/pages/flow/constant.tsx +17 -8
- web/src/pages/flow/flow-drawer/index.tsx +2 -2
- web/src/pages/flow/form/begin-form/index.less +24 -0
- web/src/pages/flow/form/begin-form/index.tsx +11 -7
- web/src/pages/flow/form/begin-form/paramater-modal.tsx +11 -2
- web/src/pages/flow/form/begin-form/query-table.tsx +27 -6
- web/src/pages/flow/form/components/dynamic-input-variable.tsx +16 -6
- web/src/pages/flow/header/index.tsx +16 -3
- web/src/pages/flow/{hooks.ts → hooks.tsx} +98 -42
- web/src/pages/flow/index.tsx +2 -2
- web/src/pages/flow/run-drawer/index.less +5 -0
- web/src/pages/flow/run-drawer/index.tsx +284 -0
- web/src/pages/flow/run-drawer/popover-form.tsx +74 -0
- web/src/pages/flow/store.ts +22 -19
- web/src/utils/api.ts +1 -0
- web/src/utils/request.ts +2 -2
- web/tailwind.config.js +1 -0
- web/tailwind.css +2 -0
web/src/hooks/document-hooks.ts
CHANGED
@@ -4,8 +4,9 @@ import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
|
4 |
import i18n from '@/locales/config';
|
5 |
import chatService from '@/services/chat-service';
|
6 |
import kbService from '@/services/knowledge-service';
|
7 |
-
import { api_host } from '@/utils/api';
|
8 |
import { buildChunkHighlights } from '@/utils/document-util';
|
|
|
9 |
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
10 |
import { UploadFile, message } from 'antd';
|
11 |
import { get } from 'lodash';
|
@@ -442,3 +443,27 @@ export const useUploadAndParseDocument = (uploadMethod: string) => {
|
|
442 |
|
443 |
return { data, loading, uploadAndParseDocument: mutateAsync };
|
444 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
import i18n from '@/locales/config';
|
5 |
import chatService from '@/services/chat-service';
|
6 |
import kbService from '@/services/knowledge-service';
|
7 |
+
import api, { api_host } from '@/utils/api';
|
8 |
import { buildChunkHighlights } from '@/utils/document-util';
|
9 |
+
import { post } from '@/utils/request';
|
10 |
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
11 |
import { UploadFile, message } from 'antd';
|
12 |
import { get } from 'lodash';
|
|
|
443 |
|
444 |
return { data, loading, uploadAndParseDocument: mutateAsync };
|
445 |
};
|
446 |
+
|
447 |
+
export const useParseDocument = () => {
|
448 |
+
const {
|
449 |
+
data,
|
450 |
+
isPending: loading,
|
451 |
+
mutateAsync,
|
452 |
+
} = useMutation({
|
453 |
+
mutationKey: ['parseDocument'],
|
454 |
+
mutationFn: async (url: string) => {
|
455 |
+
try {
|
456 |
+
const data = await post(api.parse, { url });
|
457 |
+
if (data?.code === 0) {
|
458 |
+
message.success(i18n.t('message.uploaded'));
|
459 |
+
}
|
460 |
+
return data;
|
461 |
+
} catch (error) {
|
462 |
+
console.log('🚀 ~ mutationFn: ~ error:', error);
|
463 |
+
message.error('error');
|
464 |
+
}
|
465 |
+
},
|
466 |
+
});
|
467 |
+
|
468 |
+
return { parseDocument: mutateAsync, data, loading };
|
469 |
+
};
|
web/src/hooks/login-hooks.ts
CHANGED
@@ -2,7 +2,9 @@ import { Authorization } from '@/constants/authorization';
|
|
2 |
import userService from '@/services/user-service';
|
3 |
import authorizationUtil from '@/utils/authorization-util';
|
4 |
import { useMutation } from '@tanstack/react-query';
|
5 |
-
import { message } from 'antd';
|
|
|
|
|
6 |
import { useTranslation } from 'react-i18next';
|
7 |
import { history } from 'umi';
|
8 |
|
@@ -95,3 +97,19 @@ export const useLogout = () => {
|
|
95 |
|
96 |
return { data, loading, logout: mutateAsync };
|
97 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import userService from '@/services/user-service';
|
3 |
import authorizationUtil from '@/utils/authorization-util';
|
4 |
import { useMutation } from '@tanstack/react-query';
|
5 |
+
import { Form, message } from 'antd';
|
6 |
+
import { FormInstance } from 'antd/lib';
|
7 |
+
import { useEffect, useState } from 'react';
|
8 |
import { useTranslation } from 'react-i18next';
|
9 |
import { history } from 'umi';
|
10 |
|
|
|
97 |
|
98 |
return { data, loading, logout: mutateAsync };
|
99 |
};
|
100 |
+
|
101 |
+
export const useHandleSubmittable = (form: FormInstance) => {
|
102 |
+
const [submittable, setSubmittable] = useState<boolean>(false);
|
103 |
+
|
104 |
+
// Watch all values
|
105 |
+
const values = Form.useWatch([], form);
|
106 |
+
|
107 |
+
useEffect(() => {
|
108 |
+
form
|
109 |
+
.validateFields({ validateOnly: true })
|
110 |
+
.then(() => setSubmittable(true))
|
111 |
+
.catch(() => setSubmittable(false));
|
112 |
+
}, [form, values]);
|
113 |
+
|
114 |
+
return { submittable };
|
115 |
+
};
|
web/src/locales/en.ts
CHANGED
@@ -32,6 +32,7 @@ export default {
|
|
32 |
s: 'S',
|
33 |
pleaseSelect: 'Please select',
|
34 |
pleaseInput: 'Please input',
|
|
|
35 |
},
|
36 |
login: {
|
37 |
login: 'Sign in',
|
@@ -176,7 +177,7 @@ export default {
|
|
176 |
chunkTokenNumber: 'Chunk token number',
|
177 |
chunkTokenNumberMessage: 'Chunk token number is required',
|
178 |
embeddingModelTip:
|
179 |
-
|
180 |
permissionsTip:
|
181 |
"If set to 'Team', all team members will be able to manage the knowledge base.",
|
182 |
chunkTokenNumberTip:
|
@@ -1025,6 +1026,9 @@ The above is the content you need to summarize.`,
|
|
1025 |
content: 'Content',
|
1026 |
operationResults: 'Operation Results',
|
1027 |
autosaved: 'Autosaved',
|
|
|
|
|
|
|
1028 |
},
|
1029 |
footer: {
|
1030 |
profile: 'All rights reserved @ React',
|
|
|
32 |
s: 'S',
|
33 |
pleaseSelect: 'Please select',
|
34 |
pleaseInput: 'Please input',
|
35 |
+
submit: 'Submit',
|
36 |
},
|
37 |
login: {
|
38 |
login: 'Sign in',
|
|
|
177 |
chunkTokenNumber: 'Chunk token number',
|
178 |
chunkTokenNumberMessage: 'Chunk token number is required',
|
179 |
embeddingModelTip:
|
180 |
+
'The model that converts chunks into embeddings. It cannot be changed once the knowledge base has chunks. To switch to a different embedding model, You must delete all chunks in the knowledge base.',
|
181 |
permissionsTip:
|
182 |
"If set to 'Team', all team members will be able to manage the knowledge base.",
|
183 |
chunkTokenNumberTip:
|
|
|
1026 |
content: 'Content',
|
1027 |
operationResults: 'Operation Results',
|
1028 |
autosaved: 'Autosaved',
|
1029 |
+
optional: 'Optional',
|
1030 |
+
pasteFileLink: 'Paste file link',
|
1031 |
+
testRun: 'Test Run',
|
1032 |
},
|
1033 |
footer: {
|
1034 |
profile: 'All rights reserved @ React',
|
web/src/locales/zh-traditional.ts
CHANGED
@@ -32,6 +32,7 @@ export default {
|
|
32 |
s: '秒',
|
33 |
pleaseSelect: '請選擇',
|
34 |
pleaseInput: '請輸入',
|
|
|
35 |
},
|
36 |
login: {
|
37 |
login: '登入',
|
@@ -985,6 +986,9 @@ export default {
|
|
985 |
content: '內容',
|
986 |
operationResults: '運行結果',
|
987 |
autosaved: '已自動儲存',
|
|
|
|
|
|
|
988 |
},
|
989 |
footer: {
|
990 |
profile: '“保留所有權利 @ react”',
|
|
|
32 |
s: '秒',
|
33 |
pleaseSelect: '請選擇',
|
34 |
pleaseInput: '請輸入',
|
35 |
+
submit: '提交',
|
36 |
},
|
37 |
login: {
|
38 |
login: '登入',
|
|
|
986 |
content: '內容',
|
987 |
operationResults: '運行結果',
|
988 |
autosaved: '已自動儲存',
|
989 |
+
optional: '可選項',
|
990 |
+
pasteFileLink: '貼上文件連結',
|
991 |
+
testRun: '試運行',
|
992 |
},
|
993 |
footer: {
|
994 |
profile: '“保留所有權利 @ react”',
|
web/src/locales/zh.ts
CHANGED
@@ -32,6 +32,7 @@ export default {
|
|
32 |
s: '秒',
|
33 |
pleaseSelect: '请选择',
|
34 |
pleaseInput: '请输入',
|
|
|
35 |
},
|
36 |
login: {
|
37 |
login: '登录',
|
@@ -1005,6 +1006,9 @@ export default {
|
|
1005 |
content: '内容',
|
1006 |
operationResults: '运行结果',
|
1007 |
autosaved: '已自动保存',
|
|
|
|
|
|
|
1008 |
},
|
1009 |
footer: {
|
1010 |
profile: 'All rights reserved @ React',
|
|
|
32 |
s: '秒',
|
33 |
pleaseSelect: '请选择',
|
34 |
pleaseInput: '请输入',
|
35 |
+
submit: '提交',
|
36 |
},
|
37 |
login: {
|
38 |
login: '登录',
|
|
|
1006 |
content: '内容',
|
1007 |
operationResults: '运行结果',
|
1008 |
autosaved: '已自动保存',
|
1009 |
+
optional: '可选项',
|
1010 |
+
pasteFileLink: '粘贴文件链接',
|
1011 |
+
testRun: '试运行',
|
1012 |
},
|
1013 |
footer: {
|
1014 |
profile: 'All rights reserved @ React',
|
web/src/pages/flow/canvas/index.tsx
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
-
import {
|
|
|
2 |
import ReactFlow, {
|
3 |
Background,
|
4 |
ConnectionMode,
|
@@ -8,14 +9,17 @@ import ReactFlow, {
|
|
8 |
import 'reactflow/dist/style.css';
|
9 |
import ChatDrawer from '../chat/drawer';
|
10 |
import { Operator } from '../constant';
|
11 |
-
import
|
12 |
import {
|
|
|
13 |
useHandleDrop,
|
14 |
useSelectCanvasData,
|
15 |
-
|
16 |
useValidateConnection,
|
17 |
useWatchNodeFormDataChange,
|
18 |
} from '../hooks';
|
|
|
|
|
19 |
import { ButtonEdge } from './edge';
|
20 |
import styles from './index.less';
|
21 |
import { RagNode } from './node';
|
@@ -53,11 +57,11 @@ const edgeTypes = {
|
|
53 |
};
|
54 |
|
55 |
interface IProps {
|
56 |
-
|
57 |
-
|
58 |
}
|
59 |
|
60 |
-
function FlowCanvas({
|
61 |
const {
|
62 |
nodes,
|
63 |
edges,
|
@@ -67,26 +71,65 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|
67 |
onSelectionChange,
|
68 |
} = useSelectCanvasData();
|
69 |
const isValidConnection = useValidateConnection();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
|
71 |
-
const {
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
const onNodeClick: NodeMouseHandler = useCallback(
|
75 |
(e, node) => {
|
76 |
if (node.data.label !== Operator.Note) {
|
77 |
-
|
|
|
78 |
}
|
79 |
},
|
80 |
-
[
|
81 |
);
|
82 |
|
83 |
-
const
|
84 |
-
hideDrawer();
|
85 |
-
}, [hideDrawer]);
|
86 |
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
|
91 |
return (
|
92 |
<div className={styles.canvasWrapper}>
|
@@ -147,17 +190,26 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|
147 |
<Background />
|
148 |
<Controls />
|
149 |
</ReactFlow>
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
|
|
|
|
156 |
<ChatDrawer
|
157 |
-
visible={
|
158 |
-
hideModal={
|
159 |
></ChatDrawer>
|
160 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
</div>
|
162 |
);
|
163 |
}
|
|
|
1 |
+
import { useSetModalState } from '@/hooks/common-hooks';
|
2 |
+
import { useCallback, useEffect } from 'react';
|
3 |
import ReactFlow, {
|
4 |
Background,
|
5 |
ConnectionMode,
|
|
|
9 |
import 'reactflow/dist/style.css';
|
10 |
import ChatDrawer from '../chat/drawer';
|
11 |
import { Operator } from '../constant';
|
12 |
+
import FormDrawer from '../flow-drawer';
|
13 |
import {
|
14 |
+
useGetBeginNodeDataQuery,
|
15 |
useHandleDrop,
|
16 |
useSelectCanvasData,
|
17 |
+
useShowFormDrawer,
|
18 |
useValidateConnection,
|
19 |
useWatchNodeFormDataChange,
|
20 |
} from '../hooks';
|
21 |
+
import { BeginQuery } from '../interface';
|
22 |
+
import RunDrawer from '../run-drawer';
|
23 |
import { ButtonEdge } from './edge';
|
24 |
import styles from './index.less';
|
25 |
import { RagNode } from './node';
|
|
|
57 |
};
|
58 |
|
59 |
interface IProps {
|
60 |
+
drawerVisible: boolean;
|
61 |
+
hideDrawer(): void;
|
62 |
}
|
63 |
|
64 |
+
function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
65 |
const {
|
66 |
nodes,
|
67 |
edges,
|
|
|
71 |
onSelectionChange,
|
72 |
} = useSelectCanvasData();
|
73 |
const isValidConnection = useValidateConnection();
|
74 |
+
const {
|
75 |
+
visible: runVisible,
|
76 |
+
showModal: showRunModal,
|
77 |
+
hideModal: hideRunModal,
|
78 |
+
} = useSetModalState();
|
79 |
+
const {
|
80 |
+
visible: chatVisible,
|
81 |
+
showModal: showChatModal,
|
82 |
+
hideModal: hideChatModal,
|
83 |
+
} = useSetModalState();
|
84 |
+
|
85 |
+
const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
|
86 |
+
useShowFormDrawer();
|
87 |
+
|
88 |
+
const onPaneClick = useCallback(() => {
|
89 |
+
hideFormDrawer();
|
90 |
+
}, [hideFormDrawer]);
|
91 |
|
92 |
+
const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();
|
93 |
+
|
94 |
+
useWatchNodeFormDataChange();
|
95 |
+
|
96 |
+
const hideRunOrChatDrawer = useCallback(() => {
|
97 |
+
hideChatModal();
|
98 |
+
hideRunModal();
|
99 |
+
hideDrawer();
|
100 |
+
}, [hideChatModal, hideDrawer, hideRunModal]);
|
101 |
|
102 |
const onNodeClick: NodeMouseHandler = useCallback(
|
103 |
(e, node) => {
|
104 |
if (node.data.label !== Operator.Note) {
|
105 |
+
hideRunOrChatDrawer();
|
106 |
+
showFormDrawer(node);
|
107 |
}
|
108 |
},
|
109 |
+
[hideRunOrChatDrawer, showFormDrawer],
|
110 |
);
|
111 |
|
112 |
+
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
|
|
|
|
113 |
|
114 |
+
useEffect(() => {
|
115 |
+
if (drawerVisible) {
|
116 |
+
const query: BeginQuery[] = getBeginNodeDataQuery();
|
117 |
+
if (query.length > 0) {
|
118 |
+
showRunModal();
|
119 |
+
hideChatModal();
|
120 |
+
} else {
|
121 |
+
showChatModal();
|
122 |
+
hideRunModal();
|
123 |
+
}
|
124 |
+
}
|
125 |
+
}, [
|
126 |
+
hideChatModal,
|
127 |
+
hideRunModal,
|
128 |
+
showChatModal,
|
129 |
+
showRunModal,
|
130 |
+
drawerVisible,
|
131 |
+
getBeginNodeDataQuery,
|
132 |
+
]);
|
133 |
|
134 |
return (
|
135 |
<div className={styles.canvasWrapper}>
|
|
|
190 |
<Background />
|
191 |
<Controls />
|
192 |
</ReactFlow>
|
193 |
+
{formDrawerVisible && (
|
194 |
+
<FormDrawer
|
195 |
+
node={clickedNode}
|
196 |
+
visible={formDrawerVisible}
|
197 |
+
hideModal={hideFormDrawer}
|
198 |
+
></FormDrawer>
|
199 |
+
)}
|
200 |
+
{chatVisible && (
|
201 |
<ChatDrawer
|
202 |
+
visible={chatVisible}
|
203 |
+
hideModal={hideRunOrChatDrawer}
|
204 |
></ChatDrawer>
|
205 |
)}
|
206 |
+
|
207 |
+
{runVisible && (
|
208 |
+
<RunDrawer
|
209 |
+
hideModal={hideRunOrChatDrawer}
|
210 |
+
showModal={showChatModal}
|
211 |
+
></RunDrawer>
|
212 |
+
)}
|
213 |
</div>
|
214 |
);
|
215 |
}
|
web/src/pages/flow/canvas/node/begin-node.tsx
CHANGED
@@ -1,9 +1,15 @@
|
|
1 |
import { Flex } from 'antd';
|
2 |
import classNames from 'classnames';
|
|
|
3 |
import { useTranslation } from 'react-i18next';
|
4 |
import { Handle, NodeProps, Position } from 'reactflow';
|
5 |
-
import {
|
6 |
-
|
|
|
|
|
|
|
|
|
|
|
7 |
import OperatorIcon from '../../operator-icon';
|
8 |
import { RightHandleStyle } from './handle-icon';
|
9 |
import styles from './index.less';
|
@@ -11,15 +17,13 @@ import styles from './index.less';
|
|
11 |
// TODO: do not allow other nodes to connect to this node
|
12 |
export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
13 |
const { t } = useTranslation();
|
|
|
14 |
|
15 |
return (
|
16 |
<section
|
17 |
className={classNames(styles.ragNode, {
|
18 |
[styles.selectedNode]: selected,
|
19 |
})}
|
20 |
-
style={{
|
21 |
-
width: 100,
|
22 |
-
}}
|
23 |
>
|
24 |
<Handle
|
25 |
type="source"
|
@@ -29,7 +33,7 @@ export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
|
29 |
style={RightHandleStyle}
|
30 |
></Handle>
|
31 |
|
32 |
-
<Flex align="center" justify={'
|
33 |
<OperatorIcon
|
34 |
name={data.label as Operator}
|
35 |
fontSize={24}
|
@@ -37,6 +41,24 @@ export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
|
37 |
></OperatorIcon>
|
38 |
<div className={styles.nodeTitle}>{t(`flow.begin`)}</div>
|
39 |
</Flex>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
</section>
|
41 |
);
|
42 |
}
|
|
|
1 |
import { Flex } from 'antd';
|
2 |
import classNames from 'classnames';
|
3 |
+
import get from 'lodash/get';
|
4 |
import { useTranslation } from 'react-i18next';
|
5 |
import { Handle, NodeProps, Position } from 'reactflow';
|
6 |
+
import {
|
7 |
+
BeginQueryType,
|
8 |
+
BeginQueryTypeIconMap,
|
9 |
+
Operator,
|
10 |
+
operatorMap,
|
11 |
+
} from '../../constant';
|
12 |
+
import { BeginQuery, NodeData } from '../../interface';
|
13 |
import OperatorIcon from '../../operator-icon';
|
14 |
import { RightHandleStyle } from './handle-icon';
|
15 |
import styles from './index.less';
|
|
|
17 |
// TODO: do not allow other nodes to connect to this node
|
18 |
export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
19 |
const { t } = useTranslation();
|
20 |
+
const query: BeginQuery[] = get(data, 'form.query', []);
|
21 |
|
22 |
return (
|
23 |
<section
|
24 |
className={classNames(styles.ragNode, {
|
25 |
[styles.selectedNode]: selected,
|
26 |
})}
|
|
|
|
|
|
|
27 |
>
|
28 |
<Handle
|
29 |
type="source"
|
|
|
33 |
style={RightHandleStyle}
|
34 |
></Handle>
|
35 |
|
36 |
+
<Flex align="center" justify={'center'} gap={10}>
|
37 |
<OperatorIcon
|
38 |
name={data.label as Operator}
|
39 |
fontSize={24}
|
|
|
41 |
></OperatorIcon>
|
42 |
<div className={styles.nodeTitle}>{t(`flow.begin`)}</div>
|
43 |
</Flex>
|
44 |
+
<Flex gap={8} vertical className={styles.generateParameters}>
|
45 |
+
{query.map((x, idx) => {
|
46 |
+
const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType];
|
47 |
+
return (
|
48 |
+
<Flex
|
49 |
+
key={idx}
|
50 |
+
align="center"
|
51 |
+
gap={6}
|
52 |
+
className={styles.conditionBlock}
|
53 |
+
>
|
54 |
+
<Icon className="size-4" />
|
55 |
+
<label htmlFor="">{x.key}</label>
|
56 |
+
<span className={styles.parameterValue}>{x.name}</span>
|
57 |
+
<span className="flex-1">{x.optional ? 'Yes' : 'No'}</span>
|
58 |
+
</Flex>
|
59 |
+
);
|
60 |
+
})}
|
61 |
+
</Flex>
|
62 |
</section>
|
63 |
);
|
64 |
}
|
web/src/pages/flow/constant.tsx
CHANGED
@@ -43,6 +43,15 @@ import {
|
|
43 |
SendOutlined,
|
44 |
} from '@ant-design/icons';
|
45 |
import upperFirst from 'lodash/upperFirst';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
export enum Operator {
|
48 |
Begin = 'Begin',
|
@@ -2870,12 +2879,12 @@ export enum BeginQueryType {
|
|
2870 |
Url = 'url',
|
2871 |
}
|
2872 |
|
2873 |
-
export const
|
2874 |
-
[BeginQueryType.Line]:
|
2875 |
-
[BeginQueryType.Paragraph]:
|
2876 |
-
[BeginQueryType.Options]:
|
2877 |
-
[BeginQueryType.File]:
|
2878 |
-
[BeginQueryType.Integer]:
|
2879 |
-
[BeginQueryType.Boolean]:
|
2880 |
-
[BeginQueryType.Url]:
|
2881 |
};
|
|
|
43 |
SendOutlined,
|
44 |
} from '@ant-design/icons';
|
45 |
import upperFirst from 'lodash/upperFirst';
|
46 |
+
import {
|
47 |
+
CloudUpload,
|
48 |
+
Link2,
|
49 |
+
ListOrdered,
|
50 |
+
OptionIcon,
|
51 |
+
TextCursorInput,
|
52 |
+
ToggleLeft,
|
53 |
+
WrapText,
|
54 |
+
} from 'lucide-react';
|
55 |
|
56 |
export enum Operator {
|
57 |
Begin = 'Begin',
|
|
|
2879 |
Url = 'url',
|
2880 |
}
|
2881 |
|
2882 |
+
export const BeginQueryTypeIconMap = {
|
2883 |
+
[BeginQueryType.Line]: TextCursorInput,
|
2884 |
+
[BeginQueryType.Paragraph]: WrapText,
|
2885 |
+
[BeginQueryType.Options]: OptionIcon,
|
2886 |
+
[BeginQueryType.File]: CloudUpload,
|
2887 |
+
[BeginQueryType.Integer]: ListOrdered,
|
2888 |
+
[BeginQueryType.Boolean]: ToggleLeft,
|
2889 |
+
[BeginQueryType.Url]: Link2,
|
2890 |
};
|
web/src/pages/flow/flow-drawer/index.tsx
CHANGED
@@ -83,7 +83,7 @@ const FormMap = {
|
|
83 |
|
84 |
const EmptyContent = () => <div></div>;
|
85 |
|
86 |
-
const
|
87 |
visible,
|
88 |
hideModal,
|
89 |
node,
|
@@ -152,4 +152,4 @@ const FlowDrawer = ({
|
|
152 |
);
|
153 |
};
|
154 |
|
155 |
-
export default
|
|
|
83 |
|
84 |
const EmptyContent = () => <div></div>;
|
85 |
|
86 |
+
const FormDrawer = ({
|
87 |
visible,
|
88 |
hideModal,
|
89 |
node,
|
|
|
152 |
);
|
153 |
};
|
154 |
|
155 |
+
export default FormDrawer;
|
web/src/pages/flow/form/begin-form/index.less
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.dynamicInputVariable {
|
2 |
+
background-color: #ebe9e9;
|
3 |
+
:global(.ant-collapse-content) {
|
4 |
+
background-color: #f6f6f6;
|
5 |
+
}
|
6 |
+
:global(.ant-collapse-content-box) {
|
7 |
+
padding: 0 !important;
|
8 |
+
}
|
9 |
+
margin-bottom: 20px;
|
10 |
+
.title {
|
11 |
+
font-weight: 600;
|
12 |
+
font-size: 16px;
|
13 |
+
}
|
14 |
+
|
15 |
+
.addButton {
|
16 |
+
color: rgb(22, 119, 255);
|
17 |
+
font-weight: 600;
|
18 |
+
}
|
19 |
+
}
|
20 |
+
|
21 |
+
.addButton {
|
22 |
+
color: rgb(22, 119, 255);
|
23 |
+
font-weight: 600;
|
24 |
+
}
|
web/src/pages/flow/form/begin-form/index.tsx
CHANGED
@@ -1,17 +1,20 @@
|
|
1 |
-
import {
|
2 |
import { Button, Form, Input } from 'antd';
|
3 |
import { useCallback } from 'react';
|
|
|
4 |
import { BeginQuery, IOperatorForm } from '../../interface';
|
5 |
import { useEditQueryRecord } from './hooks';
|
6 |
import { ModalForm } from './paramater-modal';
|
7 |
import QueryTable from './query-table';
|
8 |
|
|
|
|
|
9 |
type FieldType = {
|
10 |
prologue?: string;
|
11 |
};
|
12 |
|
13 |
const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
14 |
-
const { t } =
|
15 |
const {
|
16 |
ok,
|
17 |
currentRecord,
|
@@ -55,9 +58,9 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|
55 |
>
|
56 |
<Form.Item<FieldType>
|
57 |
name={'prologue'}
|
58 |
-
label={t('setAnOpener')}
|
59 |
-
tooltip={t('setAnOpenerTip')}
|
60 |
-
initialValue={t('setAnOpenerInitial')}
|
61 |
>
|
62 |
<Input.TextArea autoSize={{ minRows: 5 }} />
|
63 |
</Form.Item>
|
@@ -65,7 +68,6 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|
65 |
<Form.Item name="query" noStyle />
|
66 |
|
67 |
<Form.Item
|
68 |
-
label="Query List"
|
69 |
shouldUpdate={(prevValues, curValues) =>
|
70 |
prevValues.query !== curValues.query
|
71 |
}
|
@@ -86,9 +88,11 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|
86 |
htmlType="button"
|
87 |
style={{ margin: '0 8px' }}
|
88 |
onClick={() => showModal()}
|
|
|
89 |
block
|
|
|
90 |
>
|
91 |
-
|
92 |
</Button>
|
93 |
{visible && (
|
94 |
<ModalForm
|
|
|
1 |
+
import { PlusOutlined } from '@ant-design/icons';
|
2 |
import { Button, Form, Input } from 'antd';
|
3 |
import { useCallback } from 'react';
|
4 |
+
import { useTranslation } from 'react-i18next';
|
5 |
import { BeginQuery, IOperatorForm } from '../../interface';
|
6 |
import { useEditQueryRecord } from './hooks';
|
7 |
import { ModalForm } from './paramater-modal';
|
8 |
import QueryTable from './query-table';
|
9 |
|
10 |
+
import styles from './index.less';
|
11 |
+
|
12 |
type FieldType = {
|
13 |
prologue?: string;
|
14 |
};
|
15 |
|
16 |
const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
17 |
+
const { t } = useTranslation();
|
18 |
const {
|
19 |
ok,
|
20 |
currentRecord,
|
|
|
58 |
>
|
59 |
<Form.Item<FieldType>
|
60 |
name={'prologue'}
|
61 |
+
label={t('chat.setAnOpener')}
|
62 |
+
tooltip={t('chat.setAnOpenerTip')}
|
63 |
+
initialValue={t('chat.setAnOpenerInitial')}
|
64 |
>
|
65 |
<Input.TextArea autoSize={{ minRows: 5 }} />
|
66 |
</Form.Item>
|
|
|
68 |
<Form.Item name="query" noStyle />
|
69 |
|
70 |
<Form.Item
|
|
|
71 |
shouldUpdate={(prevValues, curValues) =>
|
72 |
prevValues.query !== curValues.query
|
73 |
}
|
|
|
88 |
htmlType="button"
|
89 |
style={{ margin: '0 8px' }}
|
90 |
onClick={() => showModal()}
|
91 |
+
icon={<PlusOutlined />}
|
92 |
block
|
93 |
+
className={styles.addButton}
|
94 |
>
|
95 |
+
{t('flow.addItem')}
|
96 |
</Button>
|
97 |
{visible && (
|
98 |
<ModalForm
|
web/src/pages/flow/form/begin-form/paramater-modal.tsx
CHANGED
@@ -3,7 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|
3 |
import { Form, Input, Modal, Select, Switch } from 'antd';
|
4 |
import { DefaultOptionType } from 'antd/es/select';
|
5 |
import { useEffect, useMemo } from 'react';
|
6 |
-
import { BeginQueryType } from '../../constant';
|
7 |
import { BeginQuery } from '../../interface';
|
8 |
import BeginDynamicOptions from './begin-dynamic-options';
|
9 |
|
@@ -20,10 +20,19 @@ export const ModalForm = ({
|
|
20 |
const options = useMemo(() => {
|
21 |
return Object.values(BeginQueryType).reduce<DefaultOptionType[]>(
|
22 |
(pre, cur) => {
|
|
|
|
|
23 |
return [
|
24 |
...pre,
|
25 |
{
|
26 |
-
label:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
value: cur,
|
28 |
},
|
29 |
];
|
|
|
3 |
import { Form, Input, Modal, Select, Switch } from 'antd';
|
4 |
import { DefaultOptionType } from 'antd/es/select';
|
5 |
import { useEffect, useMemo } from 'react';
|
6 |
+
import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant';
|
7 |
import { BeginQuery } from '../../interface';
|
8 |
import BeginDynamicOptions from './begin-dynamic-options';
|
9 |
|
|
|
20 |
const options = useMemo(() => {
|
21 |
return Object.values(BeginQueryType).reduce<DefaultOptionType[]>(
|
22 |
(pre, cur) => {
|
23 |
+
const Icon = BeginQueryTypeIconMap[cur];
|
24 |
+
|
25 |
return [
|
26 |
...pre,
|
27 |
{
|
28 |
+
label: (
|
29 |
+
<div className="flex items-center gap-2">
|
30 |
+
<Icon
|
31 |
+
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
|
32 |
+
></Icon>
|
33 |
+
{cur}
|
34 |
+
</div>
|
35 |
+
),
|
36 |
value: cur,
|
37 |
},
|
38 |
];
|
web/src/pages/flow/form/begin-form/query-table.tsx
CHANGED
@@ -1,8 +1,11 @@
|
|
1 |
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
2 |
import type { TableProps } from 'antd';
|
3 |
-
import { Space, Table, Tooltip } from 'antd';
|
4 |
import { BeginQuery } from '../../interface';
|
5 |
|
|
|
|
|
|
|
6 |
interface IProps {
|
7 |
data: BeginQuery[];
|
8 |
deleteRecord(index: number): void;
|
@@ -10,6 +13,8 @@ interface IProps {
|
|
10 |
}
|
11 |
|
12 |
const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|
|
|
|
13 |
const columns: TableProps<BeginQuery>['columns'] = [
|
14 |
{
|
15 |
title: 'Key',
|
@@ -25,7 +30,7 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|
25 |
),
|
26 |
},
|
27 |
{
|
28 |
-
title: '
|
29 |
dataIndex: 'name',
|
30 |
key: 'name',
|
31 |
ellipsis: {
|
@@ -38,18 +43,18 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|
38 |
),
|
39 |
},
|
40 |
{
|
41 |
-
title: '
|
42 |
dataIndex: 'type',
|
43 |
key: 'type',
|
44 |
},
|
45 |
{
|
46 |
-
title: '
|
47 |
dataIndex: 'optional',
|
48 |
key: 'optional',
|
49 |
render: (optional) => (optional ? 'Yes' : 'No'),
|
50 |
},
|
51 |
{
|
52 |
-
title: '
|
53 |
key: 'action',
|
54 |
render: (_, record, idx) => (
|
55 |
<Space>
|
@@ -64,7 +69,23 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|
64 |
];
|
65 |
|
66 |
return (
|
67 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
);
|
69 |
};
|
70 |
|
|
|
1 |
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
2 |
import type { TableProps } from 'antd';
|
3 |
+
import { Collapse, Space, Table, Tooltip } from 'antd';
|
4 |
import { BeginQuery } from '../../interface';
|
5 |
|
6 |
+
import { useTranslation } from 'react-i18next';
|
7 |
+
import styles from './index.less';
|
8 |
+
|
9 |
interface IProps {
|
10 |
data: BeginQuery[];
|
11 |
deleteRecord(index: number): void;
|
|
|
13 |
}
|
14 |
|
15 |
const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
16 |
+
const { t } = useTranslation();
|
17 |
+
|
18 |
const columns: TableProps<BeginQuery>['columns'] = [
|
19 |
{
|
20 |
title: 'Key',
|
|
|
30 |
),
|
31 |
},
|
32 |
{
|
33 |
+
title: t('flow.name'),
|
34 |
dataIndex: 'name',
|
35 |
key: 'name',
|
36 |
ellipsis: {
|
|
|
43 |
),
|
44 |
},
|
45 |
{
|
46 |
+
title: t('flow.type'),
|
47 |
dataIndex: 'type',
|
48 |
key: 'type',
|
49 |
},
|
50 |
{
|
51 |
+
title: t('flow.optional'),
|
52 |
dataIndex: 'optional',
|
53 |
key: 'optional',
|
54 |
render: (optional) => (optional ? 'Yes' : 'No'),
|
55 |
},
|
56 |
{
|
57 |
+
title: t('common.action'),
|
58 |
key: 'action',
|
59 |
render: (_, record, idx) => (
|
60 |
<Space>
|
|
|
69 |
];
|
70 |
|
71 |
return (
|
72 |
+
<Collapse
|
73 |
+
defaultActiveKey={['1']}
|
74 |
+
className={styles.dynamicInputVariable}
|
75 |
+
items={[
|
76 |
+
{
|
77 |
+
key: '1',
|
78 |
+
label: <span className={styles.title}>{t('flow.input')}</span>,
|
79 |
+
children: (
|
80 |
+
<Table<BeginQuery>
|
81 |
+
columns={columns}
|
82 |
+
dataSource={data}
|
83 |
+
pagination={false}
|
84 |
+
/>
|
85 |
+
),
|
86 |
+
},
|
87 |
+
]}
|
88 |
+
/>
|
89 |
);
|
90 |
};
|
91 |
|
web/src/pages/flow/form/components/dynamic-input-variable.tsx
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
2 |
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
|
3 |
|
4 |
-
import { useCallback } from 'react';
|
5 |
import { useTranslation } from 'react-i18next';
|
6 |
import { useBuildComponentIdSelectOptions } from '../../hooks';
|
7 |
import styles from './index.less';
|
@@ -95,9 +95,10 @@ const DynamicVariableForm = ({ nodeId }: IProps) => {
|
|
95 |
);
|
96 |
};
|
97 |
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
101 |
return (
|
102 |
<Collapse
|
103 |
className={styles.dynamicInputVariable}
|
@@ -105,12 +106,21 @@ const DynamicInputVariable = ({ nodeId }: IProps) => {
|
|
105 |
items={[
|
106 |
{
|
107 |
key: '1',
|
108 |
-
label: <span className={styles.title}>{
|
109 |
-
children
|
110 |
},
|
111 |
]}
|
112 |
/>
|
113 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
};
|
115 |
|
116 |
export default DynamicInputVariable;
|
|
|
1 |
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
2 |
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
|
3 |
|
4 |
+
import { PropsWithChildren, useCallback } from 'react';
|
5 |
import { useTranslation } from 'react-i18next';
|
6 |
import { useBuildComponentIdSelectOptions } from '../../hooks';
|
7 |
import styles from './index.less';
|
|
|
95 |
);
|
96 |
};
|
97 |
|
98 |
+
export function FormCollapse({
|
99 |
+
children,
|
100 |
+
title,
|
101 |
+
}: PropsWithChildren<{ title: string }>) {
|
102 |
return (
|
103 |
<Collapse
|
104 |
className={styles.dynamicInputVariable}
|
|
|
106 |
items={[
|
107 |
{
|
108 |
key: '1',
|
109 |
+
label: <span className={styles.title}>{title}</span>,
|
110 |
+
children,
|
111 |
},
|
112 |
]}
|
113 |
/>
|
114 |
);
|
115 |
+
}
|
116 |
+
|
117 |
+
const DynamicInputVariable = ({ nodeId }: IProps) => {
|
118 |
+
const { t } = useTranslation();
|
119 |
+
return (
|
120 |
+
<FormCollapse title={t('flow.input')}>
|
121 |
+
<DynamicVariableForm nodeId={nodeId}></DynamicVariableForm>
|
122 |
+
</FormCollapse>
|
123 |
+
);
|
124 |
};
|
125 |
|
126 |
export default DynamicInputVariable;
|
web/src/pages/flow/header/index.tsx
CHANGED
@@ -3,13 +3,16 @@ import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
|
|
3 |
import { useFetchFlow } from '@/hooks/flow-hooks';
|
4 |
import { ArrowLeftOutlined } from '@ant-design/icons';
|
5 |
import { Button, Flex, Space } from 'antd';
|
|
|
6 |
import { Link, useParams } from 'umi';
|
7 |
import FlowIdModal from '../flow-id-modal';
|
8 |
import {
|
|
|
9 |
useSaveGraph,
|
10 |
useSaveGraphBeforeOpeningDebugDrawer,
|
11 |
useWatchAgentChange,
|
12 |
} from '../hooks';
|
|
|
13 |
import styles from './index.less';
|
14 |
|
15 |
interface IProps {
|
@@ -19,7 +22,7 @@ interface IProps {
|
|
19 |
|
20 |
const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
21 |
const { saveGraph } = useSaveGraph();
|
22 |
-
const handleRun = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
23 |
const { data } = useFetchFlow();
|
24 |
const { t } = useTranslate('flow');
|
25 |
const {
|
@@ -30,6 +33,16 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
|
30 |
const { visible, hideModal, showModal } = useSetModalState();
|
31 |
const { id } = useParams();
|
32 |
const time = useWatchAgentChange(chatDrawerVisible);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
return (
|
35 |
<>
|
@@ -51,10 +64,10 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
|
51 |
</div>
|
52 |
</Space>
|
53 |
<Space size={'large'}>
|
54 |
-
<Button onClick={
|
55 |
<b>{t('run')}</b>
|
56 |
</Button>
|
57 |
-
<Button type="primary" onClick={saveGraph}>
|
58 |
<b>{t('save')}</b>
|
59 |
</Button>
|
60 |
{/* <Button type="primary" onClick={showOverviewModal} disabled>
|
|
|
3 |
import { useFetchFlow } from '@/hooks/flow-hooks';
|
4 |
import { ArrowLeftOutlined } from '@ant-design/icons';
|
5 |
import { Button, Flex, Space } from 'antd';
|
6 |
+
import { useCallback } from 'react';
|
7 |
import { Link, useParams } from 'umi';
|
8 |
import FlowIdModal from '../flow-id-modal';
|
9 |
import {
|
10 |
+
useGetBeginNodeDataQuery,
|
11 |
useSaveGraph,
|
12 |
useSaveGraphBeforeOpeningDebugDrawer,
|
13 |
useWatchAgentChange,
|
14 |
} from '../hooks';
|
15 |
+
import { BeginQuery } from '../interface';
|
16 |
import styles from './index.less';
|
17 |
|
18 |
interface IProps {
|
|
|
22 |
|
23 |
const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
24 |
const { saveGraph } = useSaveGraph();
|
25 |
+
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
26 |
const { data } = useFetchFlow();
|
27 |
const { t } = useTranslate('flow');
|
28 |
const {
|
|
|
33 |
const { visible, hideModal, showModal } = useSetModalState();
|
34 |
const { id } = useParams();
|
35 |
const time = useWatchAgentChange(chatDrawerVisible);
|
36 |
+
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
37 |
+
|
38 |
+
const handleRunAgent = useCallback(() => {
|
39 |
+
const query: BeginQuery[] = getBeginNodeDataQuery();
|
40 |
+
if (query.length > 0) {
|
41 |
+
showChatDrawer();
|
42 |
+
} else {
|
43 |
+
handleRun();
|
44 |
+
}
|
45 |
+
}, [getBeginNodeDataQuery, handleRun, showChatDrawer]);
|
46 |
|
47 |
return (
|
48 |
<>
|
|
|
64 |
</div>
|
65 |
</Space>
|
66 |
<Space size={'large'}>
|
67 |
+
<Button onClick={handleRunAgent}>
|
68 |
<b>{t('run')}</b>
|
69 |
</Button>
|
70 |
+
<Button type="primary" onClick={() => saveGraph()}>
|
71 |
<b>{t('save')}</b>
|
72 |
</Button>
|
73 |
{/* <Button type="primary" onClick={showOverviewModal} disabled>
|
web/src/pages/flow/{hooks.ts → hooks.tsx}
RENAMED
@@ -21,6 +21,7 @@ import { Variable } from '@/interfaces/database/chat';
|
|
21 |
import api from '@/utils/api';
|
22 |
import { useDebounceEffect } from 'ahooks';
|
23 |
import { FormInstance, message } from 'antd';
|
|
|
24 |
import dayjs from 'dayjs';
|
25 |
import { humanId } from 'human-id';
|
26 |
import { get, lowerFirst } from 'lodash';
|
@@ -65,7 +66,12 @@ import {
|
|
65 |
initialWikipediaValues,
|
66 |
initialYahooFinanceValues,
|
67 |
} from './constant';
|
68 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
69 |
import useGraphStore, { RFState } from './store';
|
70 |
import {
|
71 |
buildDslComponentsByGraph,
|
@@ -225,49 +231,60 @@ export const useHandleDrop = () => {
|
|
225 |
return { onDrop, onDragOver, setReactFlowInstance };
|
226 |
};
|
227 |
|
228 |
-
export const
|
229 |
const {
|
230 |
clickedNodeId: clickNodeId,
|
231 |
setClickedNodeId,
|
232 |
getNode,
|
233 |
} = useGraphStore((state) => state);
|
234 |
const {
|
235 |
-
visible:
|
236 |
-
hideModal:
|
237 |
-
showModal:
|
238 |
} = useSetModalState();
|
239 |
|
240 |
const handleShow = useCallback(
|
241 |
(node: Node) => {
|
242 |
setClickedNodeId(node.id);
|
243 |
-
|
244 |
},
|
245 |
-
[
|
246 |
);
|
247 |
|
248 |
return {
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
clickedNode: getNode(clickNodeId),
|
253 |
};
|
254 |
};
|
255 |
|
256 |
export const useSaveGraph = () => {
|
257 |
const { data } = useFetchFlow();
|
258 |
-
const { setFlow } = useSetFlow();
|
259 |
const { id } = useParams();
|
260 |
const { nodes, edges } = useGraphStore((state) => state);
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
|
270 |
-
return { saveGraph };
|
271 |
};
|
272 |
|
273 |
export const useHandleFormValuesChange = (id?: string) => {
|
@@ -420,32 +437,46 @@ export const useHandleNodeNameChange = ({
|
|
420 |
return { name, handleNameBlur, handleNameChange };
|
421 |
};
|
422 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
|
424 |
const { id } = useParams();
|
425 |
-
const { saveGraph } = useSaveGraph();
|
426 |
const { resetFlow } = useResetFlow();
|
427 |
const { refetch } = useFetchFlow();
|
428 |
const { send } = useSendMessageWithSse(api.runCanvas);
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
//
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
|
|
|
|
|
|
443 |
}
|
444 |
}
|
445 |
-
}
|
446 |
-
|
|
|
447 |
|
448 |
-
return handleRun;
|
449 |
};
|
450 |
|
451 |
export const useReplaceIdWithName = () => {
|
@@ -596,8 +627,10 @@ const ExcludedNodes = [
|
|
596 |
|
597 |
export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
|
598 |
const nodes = useGraphStore((state) => state.nodes);
|
|
|
|
|
599 |
|
600 |
-
const
|
601 |
return nodes
|
602 |
.filter(
|
603 |
(x) =>
|
@@ -606,17 +639,40 @@ export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
|
|
606 |
.map((x) => ({ label: x.data.name, value: x.id }));
|
607 |
}, [nodes, nodeId]);
|
608 |
|
609 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
610 |
};
|
611 |
|
612 |
export const useGetComponentLabelByValue = (nodeId: string) => {
|
613 |
const options = useBuildComponentIdSelectOptions(nodeId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
614 |
|
615 |
const getLabel = useCallback(
|
616 |
(val?: string) => {
|
617 |
-
return
|
618 |
},
|
619 |
-
[
|
620 |
);
|
621 |
return getLabel;
|
622 |
};
|
|
|
21 |
import api from '@/utils/api';
|
22 |
import { useDebounceEffect } from 'ahooks';
|
23 |
import { FormInstance, message } from 'antd';
|
24 |
+
import { DefaultOptionType } from 'antd/es/select';
|
25 |
import dayjs from 'dayjs';
|
26 |
import { humanId } from 'human-id';
|
27 |
import { get, lowerFirst } from 'lodash';
|
|
|
66 |
initialWikipediaValues,
|
67 |
initialYahooFinanceValues,
|
68 |
} from './constant';
|
69 |
+
import {
|
70 |
+
BeginQuery,
|
71 |
+
ICategorizeForm,
|
72 |
+
IRelevantForm,
|
73 |
+
ISwitchForm,
|
74 |
+
} from './interface';
|
75 |
import useGraphStore, { RFState } from './store';
|
76 |
import {
|
77 |
buildDslComponentsByGraph,
|
|
|
231 |
return { onDrop, onDragOver, setReactFlowInstance };
|
232 |
};
|
233 |
|
234 |
+
export const useShowFormDrawer = () => {
|
235 |
const {
|
236 |
clickedNodeId: clickNodeId,
|
237 |
setClickedNodeId,
|
238 |
getNode,
|
239 |
} = useGraphStore((state) => state);
|
240 |
const {
|
241 |
+
visible: formDrawerVisible,
|
242 |
+
hideModal: hideFormDrawer,
|
243 |
+
showModal: showFormDrawer,
|
244 |
} = useSetModalState();
|
245 |
|
246 |
const handleShow = useCallback(
|
247 |
(node: Node) => {
|
248 |
setClickedNodeId(node.id);
|
249 |
+
showFormDrawer();
|
250 |
},
|
251 |
+
[showFormDrawer, setClickedNodeId],
|
252 |
);
|
253 |
|
254 |
return {
|
255 |
+
formDrawerVisible,
|
256 |
+
hideFormDrawer,
|
257 |
+
showFormDrawer: handleShow,
|
258 |
clickedNode: getNode(clickNodeId),
|
259 |
};
|
260 |
};
|
261 |
|
262 |
export const useSaveGraph = () => {
|
263 |
const { data } = useFetchFlow();
|
264 |
+
const { setFlow, loading } = useSetFlow();
|
265 |
const { id } = useParams();
|
266 |
const { nodes, edges } = useGraphStore((state) => state);
|
267 |
+
useEffect(() => {}, [nodes]);
|
268 |
+
const saveGraph = useCallback(
|
269 |
+
async (currentNodes?: Node[]) => {
|
270 |
+
const dslComponents = buildDslComponentsByGraph(
|
271 |
+
currentNodes ?? nodes,
|
272 |
+
edges,
|
273 |
+
);
|
274 |
+
return setFlow({
|
275 |
+
id,
|
276 |
+
title: data.title,
|
277 |
+
dsl: {
|
278 |
+
...data.dsl,
|
279 |
+
graph: { nodes: currentNodes ?? nodes, edges },
|
280 |
+
components: dslComponents,
|
281 |
+
},
|
282 |
+
});
|
283 |
+
},
|
284 |
+
[nodes, edges, setFlow, id, data],
|
285 |
+
);
|
286 |
|
287 |
+
return { saveGraph, loading };
|
288 |
};
|
289 |
|
290 |
export const useHandleFormValuesChange = (id?: string) => {
|
|
|
437 |
return { name, handleNameBlur, handleNameChange };
|
438 |
};
|
439 |
|
440 |
+
export const useGetBeginNodeDataQuery = () => {
|
441 |
+
const getNode = useGraphStore((state) => state.getNode);
|
442 |
+
|
443 |
+
const getBeginNodeDataQuery = useCallback(() => {
|
444 |
+
return get(getNode('begin'), 'data.form.query', []);
|
445 |
+
}, [getNode]);
|
446 |
+
|
447 |
+
return getBeginNodeDataQuery;
|
448 |
+
};
|
449 |
+
|
450 |
export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
|
451 |
const { id } = useParams();
|
452 |
+
const { saveGraph, loading } = useSaveGraph();
|
453 |
const { resetFlow } = useResetFlow();
|
454 |
const { refetch } = useFetchFlow();
|
455 |
const { send } = useSendMessageWithSse(api.runCanvas);
|
456 |
+
|
457 |
+
const handleRun = useCallback(
|
458 |
+
async (nextNodes?: Node[]) => {
|
459 |
+
const saveRet = await saveGraph(nextNodes);
|
460 |
+
if (saveRet?.code === 0) {
|
461 |
+
// Call the reset api before opening the run drawer each time
|
462 |
+
const resetRet = await resetFlow();
|
463 |
+
// After resetting, all previous messages will be cleared.
|
464 |
+
if (resetRet?.code === 0) {
|
465 |
+
// fetch prologue
|
466 |
+
const sendRet = await send({ id });
|
467 |
+
if (receiveMessageError(sendRet)) {
|
468 |
+
message.error(sendRet?.data?.message);
|
469 |
+
} else {
|
470 |
+
refetch();
|
471 |
+
show();
|
472 |
+
}
|
473 |
}
|
474 |
}
|
475 |
+
},
|
476 |
+
[saveGraph, resetFlow, send, id, refetch, show],
|
477 |
+
);
|
478 |
|
479 |
+
return { handleRun, loading };
|
480 |
};
|
481 |
|
482 |
export const useReplaceIdWithName = () => {
|
|
|
627 |
|
628 |
export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
|
629 |
const nodes = useGraphStore((state) => state.nodes);
|
630 |
+
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
631 |
+
const query: BeginQuery[] = getBeginNodeDataQuery();
|
632 |
|
633 |
+
const componentIdOptions = useMemo(() => {
|
634 |
return nodes
|
635 |
.filter(
|
636 |
(x) =>
|
|
|
639 |
.map((x) => ({ label: x.data.name, value: x.id }));
|
640 |
}, [nodes, nodeId]);
|
641 |
|
642 |
+
const groupedOptions = [
|
643 |
+
{
|
644 |
+
label: <span>Component id</span>,
|
645 |
+
title: 'Component Id',
|
646 |
+
options: componentIdOptions,
|
647 |
+
},
|
648 |
+
{
|
649 |
+
label: <span>Begin input</span>,
|
650 |
+
title: 'Begin input',
|
651 |
+
options: query.map((x) => ({
|
652 |
+
label: x.name,
|
653 |
+
value: `begin@${x.key}`,
|
654 |
+
})),
|
655 |
+
},
|
656 |
+
];
|
657 |
+
|
658 |
+
return groupedOptions;
|
659 |
};
|
660 |
|
661 |
export const useGetComponentLabelByValue = (nodeId: string) => {
|
662 |
const options = useBuildComponentIdSelectOptions(nodeId);
|
663 |
+
const flattenOptions = useMemo(
|
664 |
+
() =>
|
665 |
+
options.reduce<DefaultOptionType[]>((pre, cur) => {
|
666 |
+
return [...pre, ...cur.options];
|
667 |
+
}, []),
|
668 |
+
[options],
|
669 |
+
);
|
670 |
|
671 |
const getLabel = useCallback(
|
672 |
(val?: string) => {
|
673 |
+
return flattenOptions.find((x) => x.value === val)?.label;
|
674 |
},
|
675 |
+
[flattenOptions],
|
676 |
);
|
677 |
return getLabel;
|
678 |
};
|
web/src/pages/flow/index.tsx
CHANGED
@@ -31,8 +31,8 @@ function RagFlow() {
|
|
31 |
></FlowHeader>
|
32 |
<Content style={{ margin: 0 }}>
|
33 |
<FlowCanvas
|
34 |
-
|
35 |
-
|
36 |
></FlowCanvas>
|
37 |
</Content>
|
38 |
</Layout>
|
|
|
31 |
></FlowHeader>
|
32 |
<Content style={{ margin: 0 }}>
|
33 |
<FlowCanvas
|
34 |
+
drawerVisible={chatDrawerVisible}
|
35 |
+
hideDrawer={hideChatDrawer}
|
36 |
></FlowCanvas>
|
37 |
</Content>
|
38 |
</Layout>
|
web/src/pages/flow/run-drawer/index.less
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.formWrapper {
|
2 |
+
:global(.ant-form-item-label) {
|
3 |
+
font-weight: 600 !important;
|
4 |
+
}
|
5 |
+
}
|
web/src/pages/flow/run-drawer/index.tsx
ADDED
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Authorization } from '@/constants/authorization';
|
2 |
+
import { useSetModalState } from '@/hooks/common-hooks';
|
3 |
+
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
4 |
+
import { useHandleSubmittable } from '@/hooks/login-hooks';
|
5 |
+
import { IModalProps } from '@/interfaces/common';
|
6 |
+
import api from '@/utils/api';
|
7 |
+
import { getAuthorization } from '@/utils/authorization-util';
|
8 |
+
import { InboxOutlined } from '@ant-design/icons';
|
9 |
+
import {
|
10 |
+
Button,
|
11 |
+
Drawer,
|
12 |
+
Flex,
|
13 |
+
Form,
|
14 |
+
FormItemProps,
|
15 |
+
Input,
|
16 |
+
InputNumber,
|
17 |
+
Select,
|
18 |
+
Switch,
|
19 |
+
Upload,
|
20 |
+
} from 'antd';
|
21 |
+
import { pick } from 'lodash';
|
22 |
+
import { Link2, Trash2 } from 'lucide-react';
|
23 |
+
import { useCallback } from 'react';
|
24 |
+
import { useTranslation } from 'react-i18next';
|
25 |
+
import { BeginQueryType } from '../constant';
|
26 |
+
import {
|
27 |
+
useGetBeginNodeDataQuery,
|
28 |
+
useSaveGraphBeforeOpeningDebugDrawer,
|
29 |
+
} from '../hooks';
|
30 |
+
import { BeginQuery } from '../interface';
|
31 |
+
import useGraphStore from '../store';
|
32 |
+
import { getDrawerWidth } from '../utils';
|
33 |
+
import { PopoverForm } from './popover-form';
|
34 |
+
|
35 |
+
import styles from './index.less';
|
36 |
+
|
37 |
+
const RunDrawer = ({
|
38 |
+
hideModal,
|
39 |
+
showModal: showChatModal,
|
40 |
+
}: IModalProps<any>) => {
|
41 |
+
const { t } = useTranslation();
|
42 |
+
const [form] = Form.useForm();
|
43 |
+
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
44 |
+
const {
|
45 |
+
visible,
|
46 |
+
hideModal: hidePopover,
|
47 |
+
switchVisible,
|
48 |
+
showModal: showPopover,
|
49 |
+
} = useSetModalState();
|
50 |
+
const { setRecord, currentRecord } = useSetSelectedRecord<number>();
|
51 |
+
const { submittable } = useHandleSubmittable(form);
|
52 |
+
|
53 |
+
const handleShowPopover = useCallback(
|
54 |
+
(idx: number) => () => {
|
55 |
+
setRecord(idx);
|
56 |
+
showPopover();
|
57 |
+
},
|
58 |
+
[setRecord, showPopover],
|
59 |
+
);
|
60 |
+
|
61 |
+
const handleRemoveUrl = useCallback(
|
62 |
+
(key: number, index: number) => () => {
|
63 |
+
const list: any[] = form.getFieldValue(key);
|
64 |
+
|
65 |
+
form.setFieldValue(
|
66 |
+
key,
|
67 |
+
list.filter((_, idx) => idx !== index),
|
68 |
+
);
|
69 |
+
},
|
70 |
+
[form],
|
71 |
+
);
|
72 |
+
|
73 |
+
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
74 |
+
const query: BeginQuery[] = getBeginNodeDataQuery();
|
75 |
+
|
76 |
+
const normFile = (e: any) => {
|
77 |
+
if (Array.isArray(e)) {
|
78 |
+
return e;
|
79 |
+
}
|
80 |
+
return e?.fileList;
|
81 |
+
};
|
82 |
+
|
83 |
+
const renderWidget = useCallback(
|
84 |
+
(q: BeginQuery, idx: number) => {
|
85 |
+
const props: FormItemProps & { key: number } = {
|
86 |
+
key: idx,
|
87 |
+
label: q.name,
|
88 |
+
name: idx,
|
89 |
+
};
|
90 |
+
if (q.optional === false) {
|
91 |
+
props.rules = [{ required: true }];
|
92 |
+
}
|
93 |
+
|
94 |
+
const urlList: { url: string; result: string }[] =
|
95 |
+
form.getFieldValue(idx) || [];
|
96 |
+
|
97 |
+
const BeginQueryTypeMap = {
|
98 |
+
[BeginQueryType.Line]: (
|
99 |
+
<Form.Item {...props}>
|
100 |
+
<Input></Input>
|
101 |
+
</Form.Item>
|
102 |
+
),
|
103 |
+
[BeginQueryType.Paragraph]: (
|
104 |
+
<Form.Item {...props}>
|
105 |
+
<Input.TextArea rows={4}></Input.TextArea>
|
106 |
+
</Form.Item>
|
107 |
+
),
|
108 |
+
[BeginQueryType.Options]: (
|
109 |
+
<Form.Item {...props}>
|
110 |
+
<Select
|
111 |
+
allowClear
|
112 |
+
options={q.options?.map((x) => ({ label: x, value: x })) ?? []}
|
113 |
+
></Select>
|
114 |
+
</Form.Item>
|
115 |
+
),
|
116 |
+
[BeginQueryType.File]: (
|
117 |
+
<Form.Item
|
118 |
+
{...props}
|
119 |
+
valuePropName="fileList"
|
120 |
+
getValueFromEvent={normFile}
|
121 |
+
>
|
122 |
+
<Upload.Dragger
|
123 |
+
name="file"
|
124 |
+
action={api.parse}
|
125 |
+
multiple
|
126 |
+
headers={{ [Authorization]: getAuthorization() }}
|
127 |
+
>
|
128 |
+
<p className="ant-upload-drag-icon">
|
129 |
+
<InboxOutlined />
|
130 |
+
</p>
|
131 |
+
<p className="ant-upload-text">{t('fileManager.uploadTitle')}</p>
|
132 |
+
<p className="ant-upload-hint">
|
133 |
+
{t('fileManager.uploadDescription')}
|
134 |
+
</p>
|
135 |
+
</Upload.Dragger>
|
136 |
+
</Form.Item>
|
137 |
+
),
|
138 |
+
[BeginQueryType.Integer]: (
|
139 |
+
<Form.Item {...props}>
|
140 |
+
<InputNumber></InputNumber>
|
141 |
+
</Form.Item>
|
142 |
+
),
|
143 |
+
[BeginQueryType.Boolean]: (
|
144 |
+
<Form.Item valuePropName={'checked'} {...props}>
|
145 |
+
<Switch></Switch>
|
146 |
+
</Form.Item>
|
147 |
+
),
|
148 |
+
[BeginQueryType.Url]: (
|
149 |
+
<>
|
150 |
+
<Form.Item
|
151 |
+
{...pick(props, ['key', 'label', 'rules'])}
|
152 |
+
required={!q.optional}
|
153 |
+
className={urlList.length > 0 ? 'mb-1' : ''}
|
154 |
+
>
|
155 |
+
<PopoverForm visible={visible} switchVisible={switchVisible}>
|
156 |
+
<Button
|
157 |
+
onClick={handleShowPopover(idx)}
|
158 |
+
className="text-buttonBlueText"
|
159 |
+
>
|
160 |
+
{t('flow.pasteFileLink')}
|
161 |
+
</Button>
|
162 |
+
</PopoverForm>
|
163 |
+
</Form.Item>
|
164 |
+
<Form.Item name={idx} noStyle {...pick(props, ['rules'])} />
|
165 |
+
<Form.Item
|
166 |
+
noStyle
|
167 |
+
shouldUpdate={(prevValues, curValues) =>
|
168 |
+
prevValues[idx] !== curValues[idx]
|
169 |
+
}
|
170 |
+
>
|
171 |
+
{({ getFieldValue }) => {
|
172 |
+
const urlInfo: { url: string; result: string }[] =
|
173 |
+
getFieldValue(idx) || [];
|
174 |
+
return urlInfo.length ? (
|
175 |
+
<Flex vertical gap={8} className="mb-3">
|
176 |
+
{urlInfo.map((u, index) => (
|
177 |
+
<div
|
178 |
+
key={index}
|
179 |
+
className="flex items-center justify-between gap-2 hover:bg-slate-100 group"
|
180 |
+
>
|
181 |
+
<Link2 className="size-5"></Link2>
|
182 |
+
<span className="flex-1 truncate"> {u.url}</span>
|
183 |
+
<Trash2
|
184 |
+
className="size-4 invisible group-hover:visible cursor-pointer"
|
185 |
+
onClick={handleRemoveUrl(idx, index)}
|
186 |
+
/>
|
187 |
+
</div>
|
188 |
+
))}
|
189 |
+
</Flex>
|
190 |
+
) : null;
|
191 |
+
}}
|
192 |
+
</Form.Item>
|
193 |
+
</>
|
194 |
+
),
|
195 |
+
};
|
196 |
+
|
197 |
+
return BeginQueryTypeMap[q.type as BeginQueryType];
|
198 |
+
},
|
199 |
+
[form, handleRemoveUrl, handleShowPopover, switchVisible, t, visible],
|
200 |
+
);
|
201 |
+
|
202 |
+
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatModal!);
|
203 |
+
|
204 |
+
const handleRunAgent = useCallback(
|
205 |
+
(nextValues: Record<string, any>) => {
|
206 |
+
const currentNodes = updateNodeForm('begin', nextValues, ['query']);
|
207 |
+
handleRun(currentNodes);
|
208 |
+
hideModal?.();
|
209 |
+
},
|
210 |
+
[handleRun, hideModal, updateNodeForm],
|
211 |
+
);
|
212 |
+
|
213 |
+
const onOk = useCallback(async () => {
|
214 |
+
const values = await form.validateFields();
|
215 |
+
const nextValues = Object.entries(values).map(([key, value]) => {
|
216 |
+
const item = query[Number(key)];
|
217 |
+
let nextValue = value;
|
218 |
+
if (Array.isArray(value)) {
|
219 |
+
nextValue = ``;
|
220 |
+
|
221 |
+
value.forEach((x, idx) => {
|
222 |
+
if (x?.originFileObj instanceof File) {
|
223 |
+
if (idx === 0) {
|
224 |
+
nextValue += `${x.name}\n\n${x.response.data}\n\n`;
|
225 |
+
} else {
|
226 |
+
nextValue += `${x.response.data}\n\n`;
|
227 |
+
}
|
228 |
+
} else {
|
229 |
+
if (idx === 0) {
|
230 |
+
nextValue += `${x.url}\n\n${x.result}\n\n`;
|
231 |
+
} else {
|
232 |
+
nextValue += `${x.result}\n\n`;
|
233 |
+
}
|
234 |
+
}
|
235 |
+
});
|
236 |
+
}
|
237 |
+
return { ...item, value: nextValue };
|
238 |
+
});
|
239 |
+
handleRunAgent(nextValues);
|
240 |
+
}, [form, handleRunAgent, query]);
|
241 |
+
|
242 |
+
return (
|
243 |
+
<Drawer
|
244 |
+
title={t('flow.testRun')}
|
245 |
+
placement="right"
|
246 |
+
onClose={hideModal}
|
247 |
+
open
|
248 |
+
getContainer={false}
|
249 |
+
width={getDrawerWidth()}
|
250 |
+
mask={false}
|
251 |
+
>
|
252 |
+
<section className={styles.formWrapper}>
|
253 |
+
<Form.Provider
|
254 |
+
onFormFinish={(name, { values, forms }) => {
|
255 |
+
if (name === 'urlForm') {
|
256 |
+
const { basicForm } = forms;
|
257 |
+
const urlInfo = basicForm.getFieldValue(currentRecord) || [];
|
258 |
+
basicForm.setFieldsValue({
|
259 |
+
[currentRecord]: [...urlInfo, values],
|
260 |
+
});
|
261 |
+
hidePopover();
|
262 |
+
}
|
263 |
+
}}
|
264 |
+
>
|
265 |
+
<Form
|
266 |
+
name="basicForm"
|
267 |
+
autoComplete="off"
|
268 |
+
layout={'vertical'}
|
269 |
+
form={form}
|
270 |
+
>
|
271 |
+
{query.map((x, idx) => {
|
272 |
+
return renderWidget(x, idx);
|
273 |
+
})}
|
274 |
+
</Form>
|
275 |
+
</Form.Provider>
|
276 |
+
</section>
|
277 |
+
<Button type={'primary'} block onClick={onOk} disabled={!submittable}>
|
278 |
+
{t('common.next')}
|
279 |
+
</Button>
|
280 |
+
</Drawer>
|
281 |
+
);
|
282 |
+
};
|
283 |
+
|
284 |
+
export default RunDrawer;
|
web/src/pages/flow/run-drawer/popover-form.tsx
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useParseDocument } from '@/hooks/document-hooks';
|
2 |
+
import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
|
3 |
+
import { IModalProps } from '@/interfaces/common';
|
4 |
+
import { Button, Form, Input, Popover } from 'antd';
|
5 |
+
import { PropsWithChildren } from 'react';
|
6 |
+
import { useTranslation } from 'react-i18next';
|
7 |
+
|
8 |
+
const reg =
|
9 |
+
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
|
10 |
+
|
11 |
+
export const PopoverForm = ({
|
12 |
+
children,
|
13 |
+
visible,
|
14 |
+
switchVisible,
|
15 |
+
}: PropsWithChildren<IModalProps<any>>) => {
|
16 |
+
const [form] = Form.useForm();
|
17 |
+
const { parseDocument, loading } = useParseDocument();
|
18 |
+
const { t } = useTranslation();
|
19 |
+
|
20 |
+
useResetFormOnCloseModal({
|
21 |
+
form,
|
22 |
+
visible,
|
23 |
+
});
|
24 |
+
|
25 |
+
const onOk = async () => {
|
26 |
+
const values = await form.validateFields();
|
27 |
+
const val = values.url;
|
28 |
+
|
29 |
+
if (reg.test(val)) {
|
30 |
+
const ret = await parseDocument(val);
|
31 |
+
if (ret?.data?.code === 0) {
|
32 |
+
form.setFieldValue('result', ret?.data?.data);
|
33 |
+
form.submit();
|
34 |
+
}
|
35 |
+
}
|
36 |
+
};
|
37 |
+
|
38 |
+
const content = (
|
39 |
+
<Form form={form} name="urlForm">
|
40 |
+
<Form.Item
|
41 |
+
name="url"
|
42 |
+
rules={[{ required: true, type: 'url' }]}
|
43 |
+
className="m-0"
|
44 |
+
>
|
45 |
+
<Input
|
46 |
+
onPressEnter={(e) => e.preventDefault()}
|
47 |
+
placeholder={t('flow.pasteFileLink')}
|
48 |
+
suffix={
|
49 |
+
<Button
|
50 |
+
type="primary"
|
51 |
+
onClick={onOk}
|
52 |
+
size={'small'}
|
53 |
+
loading={loading}
|
54 |
+
>
|
55 |
+
{t('common.submit')}
|
56 |
+
</Button>
|
57 |
+
}
|
58 |
+
/>
|
59 |
+
</Form.Item>
|
60 |
+
<Form.Item name={'result'} noStyle />
|
61 |
+
</Form>
|
62 |
+
);
|
63 |
+
|
64 |
+
return (
|
65 |
+
<Popover
|
66 |
+
content={content}
|
67 |
+
open={visible}
|
68 |
+
trigger={'click'}
|
69 |
+
onOpenChange={switchVisible}
|
70 |
+
>
|
71 |
+
{children}
|
72 |
+
</Popover>
|
73 |
+
);
|
74 |
+
};
|
web/src/pages/flow/store.ts
CHANGED
@@ -47,7 +47,7 @@ export type RFState = {
|
|
47 |
nodeId: string,
|
48 |
values: any,
|
49 |
path?: (string | number)[],
|
50 |
-
) =>
|
51 |
onSelectionChange: OnSelectionChangeFunc;
|
52 |
addNode: (nodes: Node) => void;
|
53 |
getNode: (id?: string | null) => Node<NodeData> | undefined;
|
@@ -331,27 +331,30 @@ const useGraphStore = create<RFState>()(
|
|
331 |
values: any,
|
332 |
path: (string | number)[] = [],
|
333 |
) => {
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
lodashSet(nextForm, path, values);
|
342 |
-
}
|
343 |
-
return {
|
344 |
-
...node,
|
345 |
-
data: {
|
346 |
-
...node.data,
|
347 |
-
form: nextForm,
|
348 |
-
},
|
349 |
-
} as any;
|
350 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
351 |
|
352 |
-
|
353 |
-
}),
|
354 |
});
|
|
|
|
|
|
|
|
|
|
|
355 |
},
|
356 |
updateSwitchFormData: (source, sourceHandle, target) => {
|
357 |
const { updateNodeForm } = get();
|
|
|
47 |
nodeId: string,
|
48 |
values: any,
|
49 |
path?: (string | number)[],
|
50 |
+
) => Node[];
|
51 |
onSelectionChange: OnSelectionChangeFunc;
|
52 |
addNode: (nodes: Node) => void;
|
53 |
getNode: (id?: string | null) => Node<NodeData> | undefined;
|
|
|
331 |
values: any,
|
332 |
path: (string | number)[] = [],
|
333 |
) => {
|
334 |
+
const nextNodes = get().nodes.map((node) => {
|
335 |
+
if (node.id === nodeId) {
|
336 |
+
let nextForm: Record<string, unknown> = { ...node.data.form };
|
337 |
+
if (path.length === 0) {
|
338 |
+
nextForm = Object.assign(nextForm, values);
|
339 |
+
} else {
|
340 |
+
lodashSet(nextForm, path, values);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
341 |
}
|
342 |
+
return {
|
343 |
+
...node,
|
344 |
+
data: {
|
345 |
+
...node.data,
|
346 |
+
form: nextForm,
|
347 |
+
},
|
348 |
+
} as any;
|
349 |
+
}
|
350 |
|
351 |
+
return node;
|
|
|
352 |
});
|
353 |
+
set({
|
354 |
+
nodes: nextNodes,
|
355 |
+
});
|
356 |
+
|
357 |
+
return nextNodes;
|
358 |
},
|
359 |
updateSwitchFormData: (source, sourceHandle, target) => {
|
360 |
const { updateNodeForm } = get();
|
web/src/utils/api.ts
CHANGED
@@ -62,6 +62,7 @@ export default {
|
|
62 |
web_crawl: `${api_host}/document/web_crawl`,
|
63 |
document_infos: `${api_host}/document/infos`,
|
64 |
upload_and_parse: `${api_host}/document/upload_and_parse`,
|
|
|
65 |
|
66 |
// chat
|
67 |
setDialog: `${api_host}/dialog/set`,
|
|
|
62 |
web_crawl: `${api_host}/document/web_crawl`,
|
63 |
document_infos: `${api_host}/document/infos`,
|
64 |
upload_and_parse: `${api_host}/document/upload_and_parse`,
|
65 |
+
parse: `${api_host}/document/parse`,
|
66 |
|
67 |
// chat
|
68 |
setDialog: `${api_host}/dialog/set`,
|
web/src/utils/request.ts
CHANGED
@@ -99,8 +99,8 @@ request.interceptors.request.use((url: string, options: any) => {
|
|
99 |
});
|
100 |
|
101 |
request.interceptors.response.use(async (response: any, options) => {
|
102 |
-
if (response?.status === 413) {
|
103 |
-
message.error(RetcodeMessage[
|
104 |
}
|
105 |
|
106 |
if (options.responseType === 'blob') {
|
|
|
99 |
});
|
100 |
|
101 |
request.interceptors.response.use(async (response: any, options) => {
|
102 |
+
if (response?.status === 413 || response?.status === 504) {
|
103 |
+
message.error(RetcodeMessage[response?.status as ResultCode]);
|
104 |
}
|
105 |
|
106 |
if (options.responseType === 'blob') {
|
web/tailwind.config.js
CHANGED
@@ -24,6 +24,7 @@ module.exports = {
|
|
24 |
ring: 'hsl(var(--ring))',
|
25 |
background: 'var(--background)',
|
26 |
foreground: 'hsl(var(--foreground))',
|
|
|
27 |
primary: {
|
28 |
DEFAULT: 'hsl(var(--primary))',
|
29 |
foreground: 'hsl(var(--primary-foreground))',
|
|
|
24 |
ring: 'hsl(var(--ring))',
|
25 |
background: 'var(--background)',
|
26 |
foreground: 'hsl(var(--foreground))',
|
27 |
+
buttonBlueText: 'var(--button-blue-text)',
|
28 |
primary: {
|
29 |
DEFAULT: 'hsl(var(--primary))',
|
30 |
foreground: 'hsl(var(--primary-foreground))',
|
web/tailwind.css
CHANGED
@@ -37,6 +37,8 @@
|
|
37 |
|
38 |
--background-inverse-standard: rgba(58, 56, 65, 0.15);
|
39 |
--background-inverse-standard-foreground: rgb(92, 81, 81);
|
|
|
|
|
40 |
}
|
41 |
|
42 |
.dark {
|
|
|
37 |
|
38 |
--background-inverse-standard: rgba(58, 56, 65, 0.15);
|
39 |
--background-inverse-standard-foreground: rgb(92, 81, 81);
|
40 |
+
|
41 |
+
--button-blue-text: rgb(22, 119, 255);
|
42 |
}
|
43 |
|
44 |
.dark {
|