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 {
|