balibabu commited on
Commit
8207a08
·
1 Parent(s): b2c85d3

feat: add FlowChatBox #918 (#1086)

Browse files

### What problem does this PR solve?

feat: add FlowChatBox #918

### Type of change

- [x] New Feature (non-breaking change which adds functionality)

web/src/components/message-item/index.less ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .messageItem {
2
+ padding: 24px 0;
3
+ .messageItemSection {
4
+ display: inline-block;
5
+ }
6
+ .messageItemSectionLeft {
7
+ width: 70%;
8
+ }
9
+ .messageItemSectionRight {
10
+ width: 40%;
11
+ }
12
+ .messageItemContent {
13
+ display: inline-flex;
14
+ gap: 20px;
15
+ }
16
+ .messageItemContentReverse {
17
+ flex-direction: row-reverse;
18
+ }
19
+ .messageText {
20
+ .chunkText();
21
+ padding: 0 14px;
22
+ background-color: rgba(249, 250, 251, 1);
23
+ word-break: break-all;
24
+ }
25
+ .messageEmpty {
26
+ width: 300px;
27
+ }
28
+
29
+ .thumbnailImg {
30
+ max-width: 20px;
31
+ }
32
+ }
33
+
34
+ .messageItemLeft {
35
+ text-align: left;
36
+ }
37
+
38
+ .messageItemRight {
39
+ text-align: right;
40
+ }
web/src/components/message-item/index.tsx ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
2
+ import { MessageType } from '@/constants/chat';
3
+ import { useTranslate } from '@/hooks/commonHooks';
4
+ import { useGetDocumentUrl } from '@/hooks/documentHooks';
5
+ import { useSelectFileThumbnails } from '@/hooks/knowledgeHook';
6
+ import { useSelectUserInfo } from '@/hooks/userSettingHook';
7
+ import { IReference, Message } from '@/interfaces/database/chat';
8
+ import { IChunk } from '@/interfaces/database/knowledge';
9
+ import classNames from 'classnames';
10
+ import { useMemo } from 'react';
11
+
12
+ import MarkdownContent from '@/pages/chat/markdown-content';
13
+ import { getExtension, isPdf } from '@/utils/documentUtils';
14
+ import { Avatar, Flex, List } from 'antd';
15
+ import NewDocumentLink from '../new-document-link';
16
+ import SvgIcon from '../svg-icon';
17
+ import styles from './index.less';
18
+
19
+ const MessageItem = ({
20
+ item,
21
+ reference,
22
+ loading = false,
23
+ clickDocumentButton,
24
+ }: {
25
+ item: Message;
26
+ reference: IReference;
27
+ loading?: boolean;
28
+ clickDocumentButton: (documentId: string, chunk: IChunk) => void;
29
+ }) => {
30
+ const userInfo = useSelectUserInfo();
31
+ const fileThumbnails = useSelectFileThumbnails();
32
+ const getDocumentUrl = useGetDocumentUrl();
33
+ const { t } = useTranslate('chat');
34
+
35
+ const isAssistant = item.role === MessageType.Assistant;
36
+
37
+ const referenceDocumentList = useMemo(() => {
38
+ return reference?.doc_aggs ?? [];
39
+ }, [reference?.doc_aggs]);
40
+
41
+ const content = useMemo(() => {
42
+ let text = item.content;
43
+ if (text === '') {
44
+ text = t('searching');
45
+ }
46
+ return loading ? text?.concat('~~2$$') : text;
47
+ }, [item.content, loading, t]);
48
+
49
+ return (
50
+ <div
51
+ className={classNames(styles.messageItem, {
52
+ [styles.messageItemLeft]: item.role === MessageType.Assistant,
53
+ [styles.messageItemRight]: item.role === MessageType.User,
54
+ })}
55
+ >
56
+ <section
57
+ className={classNames(styles.messageItemSection, {
58
+ [styles.messageItemSectionLeft]: item.role === MessageType.Assistant,
59
+ [styles.messageItemSectionRight]: item.role === MessageType.User,
60
+ })}
61
+ >
62
+ <div
63
+ className={classNames(styles.messageItemContent, {
64
+ [styles.messageItemContentReverse]: item.role === MessageType.User,
65
+ })}
66
+ >
67
+ {item.role === MessageType.User ? (
68
+ <Avatar
69
+ size={40}
70
+ src={
71
+ userInfo.avatar ??
72
+ 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
73
+ }
74
+ />
75
+ ) : (
76
+ <AssistantIcon></AssistantIcon>
77
+ )}
78
+ <Flex vertical gap={8} flex={1}>
79
+ <b>{isAssistant ? '' : userInfo.nickname}</b>
80
+ <div className={styles.messageText}>
81
+ <MarkdownContent
82
+ content={content}
83
+ reference={reference}
84
+ clickDocumentButton={clickDocumentButton}
85
+ ></MarkdownContent>
86
+ </div>
87
+ {isAssistant && referenceDocumentList.length > 0 && (
88
+ <List
89
+ bordered
90
+ dataSource={referenceDocumentList}
91
+ renderItem={(item) => {
92
+ const fileThumbnail = fileThumbnails[item.doc_id];
93
+ const fileExtension = getExtension(item.doc_name);
94
+ return (
95
+ <List.Item>
96
+ <Flex gap={'small'} align="center">
97
+ {fileThumbnail ? (
98
+ <img
99
+ src={fileThumbnail}
100
+ className={styles.thumbnailImg}
101
+ ></img>
102
+ ) : (
103
+ <SvgIcon
104
+ name={`file-icon/${fileExtension}`}
105
+ width={24}
106
+ ></SvgIcon>
107
+ )}
108
+
109
+ <NewDocumentLink
110
+ link={getDocumentUrl(item.doc_id)}
111
+ preventDefault={!isPdf(item.doc_name)}
112
+ >
113
+ {item.doc_name}
114
+ </NewDocumentLink>
115
+ </Flex>
116
+ </List.Item>
117
+ );
118
+ }}
119
+ />
120
+ )}
121
+ </Flex>
122
+ </div>
123
+ </section>
124
+ </div>
125
+ );
126
+ };
127
+
128
+ export default MessageItem;
web/src/hooks/logicHooks.ts CHANGED
@@ -9,7 +9,14 @@ import { getAuthorization } from '@/utils/authorizationUtil';
9
  import { PaginationProps } from 'antd';
10
  import axios from 'axios';
11
  import { EventSourceParserStream } from 'eventsource-parser/stream';
12
- import { useCallback, useEffect, useMemo, useState } from 'react';
 
 
 
 
 
 
 
13
  import { useTranslation } from 'react-i18next';
14
  import { useDispatch } from 'umi';
15
  import { useSetModalState, useTranslate } from './commonHooks';
@@ -196,3 +203,39 @@ export const useSendMessageWithSse = (
196
 
197
  return { send, answer, done };
198
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  import { PaginationProps } from 'antd';
10
  import axios from 'axios';
11
  import { EventSourceParserStream } from 'eventsource-parser/stream';
12
+ import {
13
+ ChangeEventHandler,
14
+ useCallback,
15
+ useEffect,
16
+ useMemo,
17
+ useRef,
18
+ useState,
19
+ } from 'react';
20
  import { useTranslation } from 'react-i18next';
21
  import { useDispatch } from 'umi';
22
  import { useSetModalState, useTranslate } from './commonHooks';
 
203
 
204
  return { send, answer, done };
205
  };
206
+
207
+ //#region chat hooks
208
+
209
+ export const useScrollToBottom = (id?: string) => {
210
+ const ref = useRef<HTMLDivElement>(null);
211
+
212
+ const scrollToBottom = useCallback(() => {
213
+ if (id) {
214
+ ref.current?.scrollIntoView({ behavior: 'instant' });
215
+ }
216
+ }, [id]);
217
+
218
+ useEffect(() => {
219
+ scrollToBottom();
220
+ }, [scrollToBottom]);
221
+
222
+ return ref;
223
+ };
224
+
225
+ export const useHandleMessageInputChange = () => {
226
+ const [value, setValue] = useState('');
227
+
228
+ const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
229
+ const value = e.target.value;
230
+ const nextValue = value.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
231
+ setValue(nextValue);
232
+ };
233
+
234
+ return {
235
+ handleInputChange,
236
+ value,
237
+ setValue,
238
+ };
239
+ };
240
+
241
+ // #endregion
web/src/interfaces/database/flow.ts CHANGED
@@ -4,7 +4,7 @@ export type DSLComponents = Record<string, IOperator>;
4
 
5
  export interface DSL {
6
  components: DSLComponents;
7
- history?: any[];
8
  path?: string[];
9
  answer?: any[];
10
  graph?: IGraph;
 
4
 
5
  export interface DSL {
6
  components: DSLComponents;
7
+ history: any[];
8
  path?: string[];
9
  answer?: any[];
10
  graph?: IGraph;
web/src/pages/flow/canvas/edge/index.tsx CHANGED
@@ -4,7 +4,7 @@ import {
4
  EdgeProps,
5
  getBezierPath,
6
  } from 'reactflow';
7
- import useStore from '../../store';
8
 
9
  import { useMemo } from 'react';
10
  import styles from './index.less';
@@ -21,7 +21,7 @@ export function ButtonEdge({
21
  markerEnd,
22
  selected,
23
  }: EdgeProps) {
24
- const deleteEdgeById = useStore((state) => state.deleteEdgeById);
25
  const [edgePath, labelX, labelY] = getBezierPath({
26
  sourceX,
27
  sourceY,
 
4
  EdgeProps,
5
  getBezierPath,
6
  } from 'reactflow';
7
+ import useGraphStore from '../../store';
8
 
9
  import { useMemo } from 'react';
10
  import styles from './index.less';
 
21
  markerEnd,
22
  selected,
23
  }: EdgeProps) {
24
+ const deleteEdgeById = useGraphStore((state) => state.deleteEdgeById);
25
  const [edgePath, labelX, labelY] = getBezierPath({
26
  sourceX,
27
  sourceY,
web/src/pages/flow/chat/box.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import MessageItem from '@/components/message-item';
2
+ import DocumentPreviewer from '@/components/pdf-previewer';
3
+ import { MessageType } from '@/constants/chat';
4
+ import { useTranslate } from '@/hooks/commonHooks';
5
+ import {
6
+ useClickDrawer,
7
+ useFetchConversationOnMount,
8
+ useGetFileIcon,
9
+ useGetSendButtonDisabled,
10
+ useSelectConversationLoading,
11
+ useSendMessage,
12
+ } from '@/pages/chat/hooks';
13
+ import { buildMessageItemReference } from '@/pages/chat/utils';
14
+ import { Button, Drawer, Flex, Input, Spin } from 'antd';
15
+
16
+ import styles from './index.less';
17
+
18
+ const FlowChatBox = () => {
19
+ const {
20
+ ref,
21
+ currentConversation: conversation,
22
+ addNewestConversation,
23
+ removeLatestMessage,
24
+ addNewestAnswer,
25
+ } = useFetchConversationOnMount();
26
+ const {
27
+ handleInputChange,
28
+ handlePressEnter,
29
+ value,
30
+ loading: sendLoading,
31
+ } = useSendMessage(
32
+ conversation,
33
+ addNewestConversation,
34
+ removeLatestMessage,
35
+ addNewestAnswer,
36
+ );
37
+ const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
38
+ useClickDrawer();
39
+ const disabled = useGetSendButtonDisabled();
40
+ useGetFileIcon();
41
+ const loading = useSelectConversationLoading();
42
+ const { t } = useTranslate('chat');
43
+
44
+ return (
45
+ <>
46
+ <Flex flex={1} className={styles.chatContainer} vertical>
47
+ <Flex flex={1} vertical className={styles.messageContainer}>
48
+ <div>
49
+ <Spin spinning={loading}>
50
+ {conversation?.message?.map((message, i) => {
51
+ return (
52
+ <MessageItem
53
+ loading={
54
+ message.role === MessageType.Assistant &&
55
+ sendLoading &&
56
+ conversation?.message.length - 1 === i
57
+ }
58
+ key={message.id}
59
+ item={message}
60
+ reference={buildMessageItemReference(conversation, message)}
61
+ clickDocumentButton={clickDocumentButton}
62
+ ></MessageItem>
63
+ );
64
+ })}
65
+ </Spin>
66
+ </div>
67
+ <div ref={ref} />
68
+ </Flex>
69
+ <Input
70
+ size="large"
71
+ placeholder={t('sendPlaceholder')}
72
+ value={value}
73
+ disabled={disabled}
74
+ suffix={
75
+ <Button
76
+ type="primary"
77
+ onClick={handlePressEnter}
78
+ loading={sendLoading}
79
+ disabled={disabled}
80
+ >
81
+ {t('send')}
82
+ </Button>
83
+ }
84
+ onPressEnter={handlePressEnter}
85
+ onChange={handleInputChange}
86
+ />
87
+ </Flex>
88
+ <Drawer
89
+ title="Document Previewer"
90
+ onClose={hideModal}
91
+ open={visible}
92
+ width={'50vw'}
93
+ >
94
+ <DocumentPreviewer
95
+ documentId={documentId}
96
+ chunk={selectedChunk}
97
+ visible={visible}
98
+ ></DocumentPreviewer>
99
+ </Drawer>
100
+ </>
101
+ );
102
+ };
103
+
104
+ export default FlowChatBox;
web/src/pages/flow/chat/hooks.ts ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MessageType } from '@/constants/chat';
2
+ import { useFetchFlow } from '@/hooks/flow-hooks';
3
+ import {
4
+ useHandleMessageInputChange,
5
+ // useScrollToBottom,
6
+ useSendMessageWithSse,
7
+ } from '@/hooks/logicHooks';
8
+ import { IAnswer } from '@/interfaces/database/chat';
9
+ import { IMessage } from '@/pages/chat/interface';
10
+ import omit from 'lodash/omit';
11
+ import { useCallback, useEffect, useState } from 'react';
12
+ import { useParams } from 'umi';
13
+ import { v4 as uuid } from 'uuid';
14
+ import { Operator } from '../constant';
15
+ import useGraphStore from '../store';
16
+
17
+ export const useSelectCurrentConversation = () => {
18
+ const { id: id } = useParams();
19
+ const findNodeByName = useGraphStore((state) => state.findNodeByName);
20
+ const [currentMessages, setCurrentMessages] = useState<IMessage[]>([]);
21
+
22
+ const { data: flowDetail } = useFetchFlow();
23
+ const messages = flowDetail.dsl.history;
24
+
25
+ const prologue = findNodeByName(Operator.Begin)?.data?.form?.prologue;
26
+
27
+ const addNewestQuestion = useCallback(
28
+ (message: string, answer: string = '') => {
29
+ setCurrentMessages((pre) => {
30
+ return [
31
+ ...pre,
32
+ {
33
+ role: MessageType.User,
34
+ content: message,
35
+ id: uuid(),
36
+ },
37
+ {
38
+ role: MessageType.Assistant,
39
+ content: answer,
40
+ id: uuid(),
41
+ },
42
+ ];
43
+ });
44
+ },
45
+ [],
46
+ );
47
+
48
+ const addNewestAnswer = useCallback(
49
+ (answer: IAnswer) => {
50
+ setCurrentMessages((pre) => {
51
+ const latestMessage = currentMessages?.at(-1);
52
+
53
+ if (latestMessage) {
54
+ return [
55
+ ...pre.slice(0, -1),
56
+ {
57
+ ...latestMessage,
58
+ content: answer.answer,
59
+ reference: answer.reference,
60
+ },
61
+ ];
62
+ }
63
+ return pre;
64
+ });
65
+ },
66
+ [currentMessages],
67
+ );
68
+
69
+ const removeLatestMessage = useCallback(() => {
70
+ setCurrentMessages((pre) => {
71
+ const nextMessages = pre?.slice(0, -2) ?? [];
72
+ return [...pre, ...nextMessages];
73
+ });
74
+ }, []);
75
+
76
+ const addPrologue = useCallback(() => {
77
+ if (id === '') {
78
+ const nextMessage = {
79
+ role: MessageType.Assistant,
80
+ content: prologue,
81
+ id: uuid(),
82
+ } as IMessage;
83
+
84
+ setCurrentMessages({
85
+ id: '',
86
+ reference: [],
87
+ message: [nextMessage],
88
+ } as any);
89
+ }
90
+ }, [id, prologue]);
91
+
92
+ useEffect(() => {
93
+ addPrologue();
94
+ }, [addPrologue]);
95
+
96
+ useEffect(() => {
97
+ if (id) {
98
+ setCurrentMessages(messages);
99
+ }
100
+ }, [messages, id]);
101
+
102
+ return {
103
+ currentConversation: currentMessages,
104
+ addNewestQuestion,
105
+ removeLatestMessage,
106
+ addNewestAnswer,
107
+ };
108
+ };
109
+
110
+ // export const useFetchConversationOnMount = () => {
111
+ // const { conversationId } = useGetChatSearchParams();
112
+ // const fetchConversation = useFetchConversation();
113
+ // const {
114
+ // currentConversation,
115
+ // addNewestQuestion,
116
+ // removeLatestMessage,
117
+ // addNewestAnswer,
118
+ // } = useSelectCurrentConversation();
119
+ // const ref = useScrollToBottom(currentConversation);
120
+
121
+ // const fetchConversationOnMount = useCallback(() => {
122
+ // if (isConversationIdExist(conversationId)) {
123
+ // fetchConversation(conversationId);
124
+ // }
125
+ // }, [fetchConversation, conversationId]);
126
+
127
+ // useEffect(() => {
128
+ // fetchConversationOnMount();
129
+ // }, [fetchConversationOnMount]);
130
+
131
+ // return {
132
+ // currentConversation,
133
+ // addNewestQuestion,
134
+ // ref,
135
+ // removeLatestMessage,
136
+ // addNewestAnswer,
137
+ // };
138
+ // };
139
+
140
+ export const useSendMessage = (
141
+ conversation: any,
142
+ addNewestQuestion: (message: string, answer?: string) => void,
143
+ removeLatestMessage: () => void,
144
+ addNewestAnswer: (answer: IAnswer) => void,
145
+ ) => {
146
+ const { id: conversationId } = useParams();
147
+ const { handleInputChange, value, setValue } = useHandleMessageInputChange();
148
+
149
+ const { send, answer, done } = useSendMessageWithSse();
150
+
151
+ const sendMessage = useCallback(
152
+ async (message: string, id?: string) => {
153
+ const res: Response | undefined = await send({
154
+ conversation_id: id ?? conversationId,
155
+ messages: [
156
+ ...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
157
+ {
158
+ role: MessageType.User,
159
+ content: message,
160
+ },
161
+ ],
162
+ });
163
+
164
+ if (res?.status !== 200) {
165
+ // cancel loading
166
+ setValue(message);
167
+ removeLatestMessage();
168
+ }
169
+ },
170
+ [
171
+ conversation?.message,
172
+ conversationId,
173
+ removeLatestMessage,
174
+ setValue,
175
+ send,
176
+ ],
177
+ );
178
+
179
+ const handleSendMessage = useCallback(
180
+ async (message: string) => {
181
+ sendMessage(message);
182
+ },
183
+ [sendMessage],
184
+ );
185
+
186
+ useEffect(() => {
187
+ if (answer.answer) {
188
+ addNewestAnswer(answer);
189
+ }
190
+ }, [answer, addNewestAnswer]);
191
+
192
+ const handlePressEnter = useCallback(() => {
193
+ if (done) {
194
+ setValue('');
195
+ handleSendMessage(value.trim());
196
+ }
197
+ addNewestQuestion(value);
198
+ }, [addNewestQuestion, handleSendMessage, done, setValue, value]);
199
+
200
+ return {
201
+ handlePressEnter,
202
+ handleInputChange,
203
+ value,
204
+ loading: !done,
205
+ };
206
+ };
web/src/pages/flow/chat/index.less ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .chatContainer {
2
+ padding: 0 0 24px 24px;
3
+ .messageContainer {
4
+ overflow-y: auto;
5
+ padding-right: 24px;
6
+ }
7
+ }
web/src/pages/flow/hooks.ts CHANGED
@@ -18,7 +18,7 @@ import { Node, Position, ReactFlowInstance } from 'reactflow';
18
  import { v4 as uuidv4 } from 'uuid';
19
  // import { shallow } from 'zustand/shallow';
20
  import { useParams } from 'umi';
21
- import useStore, { RFState } from './store';
22
  import { buildDslComponentsByGraph } from './utils';
23
 
24
  const selector = (state: RFState) => ({
@@ -34,7 +34,7 @@ const selector = (state: RFState) => ({
34
  export const useSelectCanvasData = () => {
35
  // return useStore(useShallow(selector)); // throw error
36
  // return useStore(selector, shallow);
37
- return useStore(selector);
38
  };
39
 
40
  export const useHandleDrag = () => {
@@ -50,7 +50,7 @@ export const useHandleDrag = () => {
50
  };
51
 
52
  export const useHandleDrop = () => {
53
- const addNode = useStore((state) => state.addNode);
54
  const [reactFlowInstance, setReactFlowInstance] =
55
  useState<ReactFlowInstance<any, any>>();
56
 
@@ -124,7 +124,7 @@ export const useShowDrawer = () => {
124
  };
125
 
126
  export const useHandleKeyUp = () => {
127
- const deleteEdge = useStore((state) => state.deleteEdge);
128
  const handleKeyUp: KeyboardEventHandler = useCallback(
129
  (e) => {
130
  if (e.code === 'Delete') {
@@ -141,7 +141,7 @@ export const useSaveGraph = () => {
141
  const { data } = useFetchFlow();
142
  const { setFlow } = useSetFlow();
143
  const { id } = useParams();
144
- const { nodes, edges } = useStore((state) => state);
145
  const saveGraph = useCallback(() => {
146
  const dslComponents = buildDslComponentsByGraph(nodes, edges);
147
  setFlow({
@@ -155,7 +155,7 @@ export const useSaveGraph = () => {
155
  };
156
 
157
  export const useHandleFormValuesChange = (id?: string) => {
158
- const updateNodeForm = useStore((state) => state.updateNodeForm);
159
  const handleValuesChange = useCallback(
160
  (changedValues: any, values: any) => {
161
  console.info(changedValues, values);
@@ -170,7 +170,7 @@ export const useHandleFormValuesChange = (id?: string) => {
170
  };
171
 
172
  const useSetGraphInfo = () => {
173
- const { setEdges, setNodes } = useStore((state) => state);
174
  const setGraphInfo = useCallback(
175
  ({ nodes = [], edges = [] }: IGraph) => {
176
  if (nodes.length && edges.length) {
@@ -205,7 +205,7 @@ export const useRunGraph = () => {
205
  const { data } = useFetchFlow();
206
  const { runFlow } = useRunFlow();
207
  const { id } = useParams();
208
- const { nodes, edges } = useStore((state) => state);
209
  const runGraph = useCallback(() => {
210
  const dslComponents = buildDslComponentsByGraph(nodes, edges);
211
  runFlow({
 
18
  import { v4 as uuidv4 } from 'uuid';
19
  // import { shallow } from 'zustand/shallow';
20
  import { useParams } from 'umi';
21
+ import useGraphStore, { RFState } from './store';
22
  import { buildDslComponentsByGraph } from './utils';
23
 
24
  const selector = (state: RFState) => ({
 
34
  export const useSelectCanvasData = () => {
35
  // return useStore(useShallow(selector)); // throw error
36
  // return useStore(selector, shallow);
37
+ return useGraphStore(selector);
38
  };
39
 
40
  export const useHandleDrag = () => {
 
50
  };
51
 
52
  export const useHandleDrop = () => {
53
+ const addNode = useGraphStore((state) => state.addNode);
54
  const [reactFlowInstance, setReactFlowInstance] =
55
  useState<ReactFlowInstance<any, any>>();
56
 
 
124
  };
125
 
126
  export const useHandleKeyUp = () => {
127
+ const deleteEdge = useGraphStore((state) => state.deleteEdge);
128
  const handleKeyUp: KeyboardEventHandler = useCallback(
129
  (e) => {
130
  if (e.code === 'Delete') {
 
141
  const { data } = useFetchFlow();
142
  const { setFlow } = useSetFlow();
143
  const { id } = useParams();
144
+ const { nodes, edges } = useGraphStore((state) => state);
145
  const saveGraph = useCallback(() => {
146
  const dslComponents = buildDslComponentsByGraph(nodes, edges);
147
  setFlow({
 
155
  };
156
 
157
  export const useHandleFormValuesChange = (id?: string) => {
158
+ const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
159
  const handleValuesChange = useCallback(
160
  (changedValues: any, values: any) => {
161
  console.info(changedValues, values);
 
170
  };
171
 
172
  const useSetGraphInfo = () => {
173
+ const { setEdges, setNodes } = useGraphStore((state) => state);
174
  const setGraphInfo = useCallback(
175
  ({ nodes = [], edges = [] }: IGraph) => {
176
  if (nodes.length && edges.length) {
 
205
  const { data } = useFetchFlow();
206
  const { runFlow } = useRunFlow();
207
  const { id } = useParams();
208
+ const { nodes, edges } = useGraphStore((state) => state);
209
  const runGraph = useCallback(() => {
210
  const dslComponents = buildDslComponentsByGraph(nodes, edges);
211
  runFlow({
web/src/pages/flow/store.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
  } from 'reactflow';
17
  import { create } from 'zustand';
18
  import { devtools } from 'zustand/middleware';
 
19
  import { NodeData } from './interface';
20
 
21
  export type RFState = {
@@ -33,10 +34,11 @@ export type RFState = {
33
  addNode: (nodes: Node) => void;
34
  deleteEdge: () => void;
35
  deleteEdgeById: (id: string) => void;
 
36
  };
37
 
38
  // this is our useStore hook that we can use in our components to get parts of the store and call actions
39
- const useStore = create<RFState>()(
40
  devtools((set, get) => ({
41
  nodes: [] as Node[],
42
  edges: [] as Edge[],
@@ -86,6 +88,9 @@ const useStore = create<RFState>()(
86
  edges: edges.filter((edge) => edge.id !== id),
87
  });
88
  },
 
 
 
89
  updateNodeForm: (nodeId: string, values: any) => {
90
  set({
91
  nodes: get().nodes.map((node) => {
@@ -100,4 +105,4 @@ const useStore = create<RFState>()(
100
  })),
101
  );
102
 
103
- export default useStore;
 
16
  } from 'reactflow';
17
  import { create } from 'zustand';
18
  import { devtools } from 'zustand/middleware';
19
+ import { Operator } from './constant';
20
  import { NodeData } from './interface';
21
 
22
  export type RFState = {
 
34
  addNode: (nodes: Node) => void;
35
  deleteEdge: () => void;
36
  deleteEdgeById: (id: string) => void;
37
+ findNodeByName: (operatorName: Operator) => Node | undefined;
38
  };
39
 
40
  // this is our useStore hook that we can use in our components to get parts of the store and call actions
41
+ const useGraphStore = create<RFState>()(
42
  devtools((set, get) => ({
43
  nodes: [] as Node[],
44
  edges: [] as Edge[],
 
88
  edges: edges.filter((edge) => edge.id !== id),
89
  });
90
  },
91
+ findNodeByName: (name: Operator) => {
92
+ return get().nodes.find((x) => x.data.label === name);
93
+ },
94
  updateNodeForm: (nodeId: string, values: any) => {
95
  set({
96
  nodes: get().nodes.map((node) => {
 
105
  })),
106
  );
107
 
108
+ export default useGraphStore;