balibabu commited on
Commit
4138aee
·
1 Parent(s): 845e389

feat: Add hint for operators, round to square, input variable, readable operator ID. #3056 (#3057)

Browse files

### What problem does this PR solve?

feat: Add hint for operators, round to square, input variable, readable
operator ID. #3056

### Type of change

- [ ] Bug Fix (non-breaking change which fixes an issue)
- [x] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. web/src/assets/svg/begin.svg +9 -0
  2. web/src/assets/svg/concentrator.svg +2 -2
  3. web/src/assets/svg/keyword.svg +2 -2
  4. web/src/assets/svg/llm/baai.svg +12 -0
  5. web/src/assets/svg/llm/nomic-ai.svg +7 -0
  6. web/src/assets/svg/llm/sentence-transformers.svg +29 -0
  7. web/src/assets/svg/llm/youdao.svg +13 -0
  8. web/src/assets/svg/plus-circle-fill.svg +2 -0
  9. web/src/assets/svg/plus.svg +5 -0
  10. web/src/assets/svg/resize.svg +6 -0
  11. web/src/assets/svg/switch.svg +1 -1
  12. web/src/components/knowledge-base-item.tsx +8 -2
  13. web/src/components/llm-select/index.less +3 -0
  14. web/src/components/llm-select/index.tsx +3 -1
  15. web/src/components/llm-select/llm-label.tsx +31 -0
  16. web/src/components/svg-icon.tsx +24 -1
  17. web/src/constants/setting.ts +50 -0
  18. web/src/hooks/{llm-hooks.ts → llm-hooks.tsx} +21 -2
  19. web/src/locales/en.ts +4 -2
  20. web/src/locales/zh-traditional.ts +5 -2
  21. web/src/locales/zh.ts +7 -4
  22. web/src/pages/flow/canvas/index.tsx +12 -0
  23. web/src/pages/flow/canvas/node/begin-node.tsx +16 -13
  24. web/src/pages/flow/canvas/node/categorize-node.tsx +32 -53
  25. web/src/pages/flow/canvas/node/dropdown.tsx +1 -1
  26. web/src/pages/flow/canvas/node/generate-node.tsx +74 -0
  27. web/src/pages/flow/canvas/node/handle-icon.tsx +20 -0
  28. web/src/pages/flow/canvas/node/hooks.ts +73 -73
  29. web/src/pages/flow/canvas/node/index.less +118 -56
  30. web/src/pages/flow/canvas/node/index.tsx +5 -35
  31. web/src/pages/flow/canvas/node/keyword-node.tsx +54 -0
  32. web/src/pages/flow/canvas/node/logic-node.tsx +5 -52
  33. web/src/pages/flow/canvas/node/message-node.tsx +63 -0
  34. web/src/pages/flow/canvas/node/node-header.tsx +35 -0
  35. web/src/pages/flow/canvas/node/note-node.tsx +61 -22
  36. web/src/pages/flow/canvas/node/relevant-node.tsx +28 -38
  37. web/src/pages/flow/canvas/node/retrieval-node.tsx +85 -0
  38. web/src/pages/flow/canvas/node/rewrite-node.tsx +54 -0
  39. web/src/pages/flow/canvas/node/switch-node.tsx +112 -0
  40. web/src/pages/flow/constant.tsx +15 -14
  41. web/src/pages/flow/flow-drawer/index.less +6 -0
  42. web/src/pages/flow/flow-drawer/index.tsx +29 -15
  43. web/src/pages/flow/flow-sider/index.tsx +5 -2
  44. web/src/pages/flow/form-hooks.ts +4 -15
  45. web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx +20 -6
  46. web/src/pages/flow/form/categorize-form/index.less +11 -0
  47. web/src/pages/flow/form/generate-form/dynamic-parameters.tsx +1 -0
  48. web/src/pages/flow/form/switch-form/index.less +21 -0
  49. web/src/pages/flow/form/switch-form/index.tsx +121 -97
  50. web/src/pages/flow/hooks.ts +23 -3
web/src/assets/svg/begin.svg ADDED
web/src/assets/svg/concentrator.svg CHANGED
web/src/assets/svg/keyword.svg CHANGED
web/src/assets/svg/llm/baai.svg ADDED
web/src/assets/svg/llm/nomic-ai.svg ADDED
web/src/assets/svg/llm/sentence-transformers.svg ADDED
web/src/assets/svg/llm/youdao.svg ADDED
web/src/assets/svg/plus-circle-fill.svg CHANGED
web/src/assets/svg/plus.svg ADDED
web/src/assets/svg/resize.svg ADDED
web/src/assets/svg/switch.svg CHANGED
web/src/components/knowledge-base-item.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import { useTranslate } from '@/hooks/common-hooks';
2
  import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
3
- import { Form, Select } from 'antd';
 
4
 
5
  const KnowledgeBaseItem = () => {
6
  const { t } = useTranslate('chat');
@@ -8,7 +9,12 @@ const KnowledgeBaseItem = () => {
8
  const { list: knowledgeList } = useNextFetchKnowledgeList(true);
9
 
10
  const knowledgeOptions = knowledgeList.map((x) => ({
11
- label: x.name,
 
 
 
 
 
12
  value: x.id,
13
  }));
14
 
 
1
  import { useTranslate } from '@/hooks/common-hooks';
2
  import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
3
+ import { UserOutlined } from '@ant-design/icons';
4
+ import { Avatar, Form, Select, Space } from 'antd';
5
 
6
  const KnowledgeBaseItem = () => {
7
  const { t } = useTranslate('chat');
 
9
  const { list: knowledgeList } = useNextFetchKnowledgeList(true);
10
 
11
  const knowledgeOptions = knowledgeList.map((x) => ({
12
+ label: (
13
+ <Space>
14
+ <Avatar size={20} icon={<UserOutlined />} src={x.avatar} />
15
+ {x.name}
16
+ </Space>
17
+ ),
18
  value: x.id,
19
  }));
20
 
web/src/components/llm-select/index.less ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .llmLabel {
2
+ font-size: 14px;
3
+ }
web/src/components/llm-select/index.tsx CHANGED
@@ -7,9 +7,10 @@ interface IProps {
7
  id?: string;
8
  value?: string;
9
  onChange?: (value: string) => void;
 
10
  }
11
 
12
- const LLMSelect = ({ id, value, onChange }: IProps) => {
13
  const modelOptions = useComposeLlmOptionsByModelTypes([
14
  LlmModelType.Chat,
15
  LlmModelType.Image2text,
@@ -38,6 +39,7 @@ const LLMSelect = ({ id, value, onChange }: IProps) => {
38
  id={id}
39
  value={value}
40
  onChange={onChange}
 
41
  />
42
  </Popover>
43
  );
 
7
  id?: string;
8
  value?: string;
9
  onChange?: (value: string) => void;
10
+ disabled?: boolean;
11
  }
12
 
13
+ const LLMSelect = ({ id, value, onChange, disabled }: IProps) => {
14
  const modelOptions = useComposeLlmOptionsByModelTypes([
15
  LlmModelType.Chat,
16
  LlmModelType.Image2text,
 
39
  id={id}
40
  value={value}
41
  onChange={onChange}
42
+ disabled={disabled}
43
  />
44
  </Popover>
45
  );
web/src/components/llm-select/llm-label.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { LlmModelType } from '@/constants/knowledge';
2
+ import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
3
+ import { useMemo } from 'react';
4
+
5
+ interface IProps {
6
+ id?: string;
7
+ value?: string;
8
+ onChange?: (value: string) => void;
9
+ disabled?: boolean;
10
+ }
11
+
12
+ const LLMLabel = ({ value }: IProps) => {
13
+ const modelOptions = useComposeLlmOptionsByModelTypes([
14
+ LlmModelType.Chat,
15
+ LlmModelType.Image2text,
16
+ ]);
17
+
18
+ const label = useMemo(() => {
19
+ for (const item of modelOptions) {
20
+ for (const option of item.options) {
21
+ if (option.value === value) {
22
+ return option.label;
23
+ }
24
+ }
25
+ }
26
+ }, [modelOptions, value]);
27
+
28
+ return <div>{label}</div>;
29
+ };
30
+
31
+ export default LLMLabel;
web/src/components/svg-icon.tsx CHANGED
@@ -1,5 +1,8 @@
1
- import Icon from '@ant-design/icons';
 
2
  import { IconComponentProps } from '@ant-design/icons/lib/components/Icon';
 
 
3
 
4
  const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
5
  const list = requireContext.keys().map((key) => {
@@ -36,4 +39,24 @@ const SvgIcon = ({ name, width, height, ...restProps }: IProps) => {
36
  );
37
  };
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  export default SvgIcon;
 
1
+ import { IconMap } from '@/constants/setting';
2
+ import Icon, { UserOutlined } from '@ant-design/icons';
3
  import { IconComponentProps } from '@ant-design/icons/lib/components/Icon';
4
+ import { Avatar } from 'antd';
5
+ import { AvatarSize } from 'antd/es/avatar/AvatarContext';
6
 
7
  const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
8
  const list = requireContext.keys().map((key) => {
 
39
  );
40
  };
41
 
42
+ export const LlmIcon = ({
43
+ name,
44
+ height = 48,
45
+ width = 48,
46
+ size = 'large',
47
+ }: {
48
+ name: string;
49
+ height?: number;
50
+ width?: number;
51
+ size?: AvatarSize;
52
+ }) => {
53
+ const icon = IconMap[name as keyof typeof IconMap];
54
+
55
+ return icon ? (
56
+ <SvgIcon name={`llm/${icon}`} width={width} height={height}></SvgIcon>
57
+ ) : (
58
+ <Avatar shape="square" size={size} icon={<UserOutlined />} />
59
+ );
60
+ };
61
+
62
  export default SvgIcon;
web/src/constants/setting.ts CHANGED
@@ -19,6 +19,56 @@ export const UserSettingRouteMap = {
19
  [UserSettingRouteKey.Logout]: 'Log out',
20
  };
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  export const TimezoneList = [
23
  'UTC-11\tPacific/Midway',
24
  'UTC-11\tPacific/Niue',
 
19
  [UserSettingRouteKey.Logout]: 'Log out',
20
  };
21
 
22
+ // Please lowercase the file name
23
+ export const IconMap = {
24
+ 'Tongyi-Qianwen': 'tongyi',
25
+ Moonshot: 'moonshot',
26
+ OpenAI: 'openai',
27
+ 'ZHIPU-AI': 'zhipu',
28
+ 文心一言: 'wenxin',
29
+ Ollama: 'ollama',
30
+ Xinference: 'xinference',
31
+ DeepSeek: 'deepseek',
32
+ VolcEngine: 'volc_engine',
33
+ BaiChuan: 'baichuan',
34
+ Jina: 'jina',
35
+ MiniMax: 'chat-minimax',
36
+ Mistral: 'mistral',
37
+ 'Azure-OpenAI': 'azure',
38
+ Bedrock: 'bedrock',
39
+ Gemini: 'gemini',
40
+ Groq: 'groq-next',
41
+ OpenRouter: 'open-router',
42
+ LocalAI: 'local-ai',
43
+ StepFun: 'stepfun',
44
+ NVIDIA: 'nvidia',
45
+ 'LM-Studio': 'lm-studio',
46
+ 'OpenAI-API-Compatible': 'openai-api',
47
+ cohere: 'cohere',
48
+ LeptonAI: 'lepton-ai',
49
+ TogetherAI: 'together-ai',
50
+ PerfXCloud: 'perfx-cloud',
51
+ Upstage: 'upstage',
52
+ 'novita.ai': 'novita-ai',
53
+ SILICONFLOW: 'siliconflow',
54
+ '01.AI': 'yi',
55
+ Replicate: 'replicate',
56
+ 'Tencent Hunyuan': 'hunyuan',
57
+ 'XunFei Spark': 'spark',
58
+ BaiduYiyan: 'yiyan',
59
+ 'Fish Audio': 'fish-audio',
60
+ 'Tencent Cloud': 'tencent-cloud',
61
+ Anthropic: 'anthropic',
62
+ 'Voyage AI': 'voyage',
63
+ 'Google Cloud': 'google-cloud',
64
+ HuggingFace: 'huggingface',
65
+ Youdao: 'youdao',
66
+ BAAI: 'baai',
67
+ 'nomic-ai': 'nomic-ai',
68
+ jinaai: 'jina',
69
+ 'sentence-transformers': 'sentence-transformers',
70
+ };
71
+
72
  export const TimezoneList = [
73
  'UTC-11\tPacific/Midway',
74
  'UTC-11\tPacific/Niue',
web/src/hooks/{llm-hooks.ts → llm-hooks.tsx} RENAMED
@@ -1,3 +1,4 @@
 
1
  import { LlmModelType } from '@/constants/knowledge';
2
  import { ResponseGetType } from '@/interfaces/database/base';
3
  import {
@@ -13,7 +14,7 @@ import {
13
  import userService from '@/services/user-service';
14
  import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/common-util';
15
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
16
- import { message } from 'antd';
17
  import { DefaultOptionType } from 'antd/es/select';
18
  import { useMemo } from 'react';
19
  import { useTranslation } from 'react-i18next';
@@ -53,6 +54,14 @@ export const useSelectLlmOptions = () => {
53
  return embeddingModelOptions;
54
  };
55
 
 
 
 
 
 
 
 
 
56
  export const useSelectLlmOptionsByModelType = () => {
57
  const llmInfo: IThirdOAIModelCollection = useFetchLlmList();
58
 
@@ -71,7 +80,17 @@ export const useSelectLlmOptionsByModelType = () => {
71
  x.available,
72
  )
73
  .map((x) => ({
74
- label: x.llm_name,
 
 
 
 
 
 
 
 
 
 
75
  value: `${x.llm_name}@${x.fid}`,
76
  disabled: !x.available,
77
  })),
 
1
+ import { LlmIcon } from '@/components/svg-icon';
2
  import { LlmModelType } from '@/constants/knowledge';
3
  import { ResponseGetType } from '@/interfaces/database/base';
4
  import {
 
14
  import userService from '@/services/user-service';
15
  import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/common-util';
16
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
17
+ import { Flex, message } from 'antd';
18
  import { DefaultOptionType } from 'antd/es/select';
19
  import { useMemo } from 'react';
20
  import { useTranslation } from 'react-i18next';
 
54
  return embeddingModelOptions;
55
  };
56
 
57
+ const getLLMIconName = (fid: string, llm_name: string) => {
58
+ if (fid === 'FastEmbed') {
59
+ return llm_name.split('/').at(0) ?? '';
60
+ }
61
+
62
+ return fid;
63
+ };
64
+
65
  export const useSelectLlmOptionsByModelType = () => {
66
  const llmInfo: IThirdOAIModelCollection = useFetchLlmList();
67
 
 
80
  x.available,
81
  )
82
  .map((x) => ({
83
+ label: (
84
+ <Flex align="center" gap={6}>
85
+ <LlmIcon
86
+ name={getLLMIconName(x.fid, x.llm_name)}
87
+ width={26}
88
+ height={26}
89
+ size={'small'}
90
+ />
91
+ <span>{x.llm_name}</span>
92
+ </Flex>
93
+ ),
94
  value: `${x.llm_name}@${x.fid}`,
95
  disabled: !x.available,
96
  })),
web/src/locales/en.ts CHANGED
@@ -728,18 +728,20 @@ The above is the content you need to summarize.`,
728
  'The window size of conversation history that needed to be seen by LLM. The larger the better. But be careful with the maximum content length of LLM.',
729
  wikipedia: 'Wikipedia',
730
  pubMed: 'PubMed',
 
 
731
  email: 'Email',
732
  emailTip:
733
  'This component is used to get search result from https://pubmed.ncbi.nlm.nih.gov/. Typically, it performs as a supplement to knowledgebases. Top N specifies the number of search results you need to adapt. E-mail is a required field.',
734
  arXiv: 'ArXiv',
735
- arXivTip:
736
  'This component is used to get search result from https://arxiv.org/. Typically, it performs as a supplement to knowledgebases. Top N specifies the number of search results you need to adapt.',
737
  sortBy: 'Sort by',
738
  submittedDate: 'Submitted date',
739
  lastUpdatedDate: 'Last updated date',
740
  relevance: 'Relevance',
741
  google: 'Google',
742
- googleTip:
743
  'This component is used to get search result fromhttps://www.google.com/ . Typically, it performs as a supplement to knowledgebases. Top N and SerpApi API key specifies the number of search results you need to adapt.',
744
  bing: 'Bing',
745
  bingTip:
 
728
  'The window size of conversation history that needed to be seen by LLM. The larger the better. But be careful with the maximum content length of LLM.',
729
  wikipedia: 'Wikipedia',
730
  pubMed: 'PubMed',
731
+ pubMedDescription:
732
+ 'This component is used to get search result from https://pubmed.ncbi.nlm.nih.gov/. Typically, it performs as a supplement to knowledgebases. Top N specifies the number of search results you need to adapt. E-mail is a required field.',
733
  email: 'Email',
734
  emailTip:
735
  'This component is used to get search result from https://pubmed.ncbi.nlm.nih.gov/. Typically, it performs as a supplement to knowledgebases. Top N specifies the number of search results you need to adapt. E-mail is a required field.',
736
  arXiv: 'ArXiv',
737
+ arXivDescription:
738
  'This component is used to get search result from https://arxiv.org/. Typically, it performs as a supplement to knowledgebases. Top N specifies the number of search results you need to adapt.',
739
  sortBy: 'Sort by',
740
  submittedDate: 'Submitted date',
741
  lastUpdatedDate: 'Last updated date',
742
  relevance: 'Relevance',
743
  google: 'Google',
744
+ googleDescription:
745
  'This component is used to get search result fromhttps://www.google.com/ . Typically, it performs as a supplement to knowledgebases. Top N and SerpApi API key specifies the number of search results you need to adapt.',
746
  bing: 'Bing',
747
  bingTip:
web/src/locales/zh-traditional.ts CHANGED
@@ -680,18 +680,21 @@ export default {
680
  messageHistoryWindowSizeTip:
681
  'LLM需要查看的對話記錄的視窗大小。越大越好。但要注意LLM的最大內容長度。',
682
  wikipedia: '維基百科',
 
 
 
683
  email: '信箱',
684
  emailTip:
685
  '此元件用於從 https://pubmed.ncbi.nlm.nih.gov/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。電子郵件是必填欄位。',
686
  arXiv: 'ArXiv',
687
- arXivTip:
688
  '此元件用於從 https://arxiv.org/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。',
689
  sortBy: '排序方式',
690
  submittedDate: '提交日期',
691
  lastUpdatedDate: '最後更新日期',
692
  relevance: '關聯',
693
  google: 'Google',
694
- googleTip:
695
  '此元件用於從https://www.google.com/取得搜尋結果。通常,它作為知識庫的補充。 Top N 和 SerpApi API 金鑰指定您需要調整的搜尋結果數量。',
696
  bing: 'Bing',
697
  bingTip:
 
680
  messageHistoryWindowSizeTip:
681
  'LLM需要查看的對話記錄的視窗大小。越大越好。但要注意LLM的最大內容長度。',
682
  wikipedia: '維基百科',
683
+ pubMed: 'PubMed',
684
+ pubMedDescription:
685
+ '此元件用於從 https://pubmed.ncbi.nlm.nih.gov/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。電子郵件是必填欄位。',
686
  email: '信箱',
687
  emailTip:
688
  '此元件用於從 https://pubmed.ncbi.nlm.nih.gov/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。電子郵件是必填欄位。',
689
  arXiv: 'ArXiv',
690
+ arXivDescription:
691
  '此元件用於從 https://arxiv.org/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。',
692
  sortBy: '排序方式',
693
  submittedDate: '提交日期',
694
  lastUpdatedDate: '最後更新日期',
695
  relevance: '關聯',
696
  google: 'Google',
697
+ googleDescription:
698
  '此元件用於從https://www.google.com/取得搜尋結果。通常,它作為知識庫的補充。 Top N 和 SerpApi API 金鑰指定您需要調整的搜尋結果數量。',
699
  bing: 'Bing',
700
  bingTip:
web/src/locales/zh.ts CHANGED
@@ -688,7 +688,7 @@ export default {
688
  keywordExtract: '关键词',
689
  keywordExtractDescription: `该组件用于从用户的问题中提取关键词。Top N指定需要提取的关键词数量。`,
690
  baidu: '百度',
691
- baiduDescription: `此元件用於取得www.baidu.com的搜尋結果。通常作為知識庫的補充。 Top N指定您需要適配的搜尋結果數。`,
692
  duckDuckGo: 'DuckDuckGo',
693
  duckDuckGoDescription:
694
  '此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要調整的搜尋結果數。',
@@ -700,18 +700,21 @@ export default {
700
  messageHistoryWindowSizeTip:
701
  'LLM 需要查看的对话历史窗口大小。越大越好。但要注意 LLM 的最大内容长度。',
702
  wikipedia: '维基百科',
703
- email: '邮箱',
704
  emailTip:
705
  '此组件用于从 https://pubmed.ncbi.nlm.nih.gov/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数。电子邮件是必填字段。',
 
 
 
 
706
  arXiv: 'ArXiv',
707
- arXivTip:
708
  '此组件用于从 https://arxiv.org/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数量。',
709
  sortBy: '排序方式',
710
  submittedDate: '提交日期',
711
  lastUpdatedDate: '最后更新日期',
712
  relevance: '关联',
713
  google: 'Google',
714
- googleTip:
715
  '此组件用于从https://www.google.com/获取搜索结果。通常,它作为知识库的补充。Top N 和 SerpApi API 密钥指定您需要调整的搜索结果数量。',
716
  bing: 'Bing',
717
  bingTip:
 
688
  keywordExtract: '关键词',
689
  keywordExtractDescription: `该组件用于从用户的问题中提取关键词。Top N指定需要提取的关键词数量。`,
690
  baidu: '百度',
691
+ baiduDescription: `此组件用于从 www.baidu.com 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数量。`,
692
  duckDuckGo: 'DuckDuckGo',
693
  duckDuckGoDescription:
694
  '此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要調整的搜尋結果數。',
 
700
  messageHistoryWindowSizeTip:
701
  'LLM 需要查看的对话历史窗口大小。越大越好。但要注意 LLM 的最大内容长度。',
702
  wikipedia: '维基百科',
 
703
  emailTip:
704
  '此组件用于从 https://pubmed.ncbi.nlm.nih.gov/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数。电子邮件是必填字段。',
705
+ email: '邮箱',
706
+ pubMed: 'PubMed',
707
+ pubMedDescription:
708
+ '此组件用于从 https://pubmed.ncbi.nlm.nih.gov/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数。电子邮件是必填字段。',
709
  arXiv: 'ArXiv',
710
+ arXivDescription:
711
  '此组件用于从 https://arxiv.org/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数量。',
712
  sortBy: '排序方式',
713
  submittedDate: '提交日期',
714
  lastUpdatedDate: '最后更新日期',
715
  relevance: '关联',
716
  google: 'Google',
717
+ googleDescription:
718
  '此组件用于从https://www.google.com/获取搜索结果。通常,它作为知识库的补充。Top N 和 SerpApi API 密钥指定您需要调整的搜索结果数量。',
719
  bing: 'Bing',
720
  bingTip:
web/src/pages/flow/canvas/index.tsx CHANGED
@@ -22,9 +22,15 @@ import styles from './index.less';
22
  import { RagNode } from './node';
23
  import { BeginNode } from './node/begin-node';
24
  import { CategorizeNode } from './node/categorize-node';
 
 
25
  import { LogicNode } from './node/logic-node';
 
26
  import NoteNode from './node/note-node';
27
  import { RelevantNode } from './node/relevant-node';
 
 
 
28
 
29
  const nodeTypes = {
30
  ragNode: RagNode,
@@ -33,6 +39,12 @@ const nodeTypes = {
33
  relevantNode: RelevantNode,
34
  logicNode: LogicNode,
35
  noteNode: NoteNode,
 
 
 
 
 
 
36
  };
37
 
38
  const edgeTypes = {
 
22
  import { RagNode } from './node';
23
  import { BeginNode } from './node/begin-node';
24
  import { CategorizeNode } from './node/categorize-node';
25
+ import { GenerateNode } from './node/generate-node';
26
+ import { KeywordNode } from './node/keyword-node';
27
  import { LogicNode } from './node/logic-node';
28
+ import { MessageNode } from './node/message-node';
29
  import NoteNode from './node/note-node';
30
  import { RelevantNode } from './node/relevant-node';
31
+ import { RetrievalNode } from './node/retrieval-node';
32
+ import { RewriteNode } from './node/rewrite-node';
33
+ import { SwitchNode } from './node/switch-node';
34
 
35
  const nodeTypes = {
36
  ragNode: RagNode,
 
39
  relevantNode: RelevantNode,
40
  logicNode: LogicNode,
41
  noteNode: NoteNode,
42
+ switchNode: SwitchNode,
43
+ generateNode: GenerateNode,
44
+ retrievalNode: RetrievalNode,
45
+ messageNode: MessageNode,
46
+ rewriteNode: RewriteNode,
47
+ keywordNode: KeywordNode,
48
  };
49
 
50
  const edgeTypes = {
web/src/pages/flow/canvas/node/begin-node.tsx CHANGED
@@ -1,25 +1,24 @@
1
- import { useTranslate } from '@/hooks/common-hooks';
2
  import { Flex } from 'antd';
3
  import classNames from 'classnames';
4
- import lowerFirst from 'lodash/lowerFirst';
5
  import { Handle, NodeProps, Position } from 'reactflow';
6
  import { Operator, operatorMap } from '../../constant';
7
  import { NodeData } from '../../interface';
 
 
8
  import styles from './index.less';
9
 
10
  // TODO: do not allow other nodes to connect to this node
11
- export function BeginNode({ id, data, selected }: NodeProps<NodeData>) {
12
- const { t } = useTranslate('flow');
 
13
  return (
14
  <section
15
  className={classNames(styles.ragNode, {
16
  [styles.selectedNode]: selected,
17
  })}
18
  style={{
19
- backgroundColor: operatorMap[data.label as Operator].backgroundColor,
20
- color: 'white',
21
- width: 50,
22
- height: 50,
23
  }}
24
  >
25
  <Handle
@@ -27,13 +26,17 @@ export function BeginNode({ id, data, selected }: NodeProps<NodeData>) {
27
  position={Position.Right}
28
  isConnectable
29
  className={styles.handle}
 
30
  ></Handle>
31
- <Flex vertical align="center" justify="center" gap={6}>
32
- <span className={styles.type}>{t(lowerFirst(data.label))}</span>
 
 
 
 
 
 
33
  </Flex>
34
- <section className={styles.bottomBox}>
35
- <div className={styles.nodeName}>{data.name}</div>
36
- </section>
37
  </section>
38
  );
39
  }
 
 
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 { Operator, operatorMap } from '../../constant';
6
  import { NodeData } from '../../interface';
7
+ import OperatorIcon from '../../operator-icon';
8
+ import { RightHandleStyle } from './handle-icon';
9
  import styles from './index.less';
10
 
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
 
26
  position={Position.Right}
27
  isConnectable
28
  className={styles.handle}
29
+ style={RightHandleStyle}
30
  ></Handle>
31
+
32
+ <Flex align="center" justify={'space-around'}>
33
+ <OperatorIcon
34
+ name={data.label as Operator}
35
+ fontSize={24}
36
+ color={operatorMap[data.label as Operator].color}
37
+ ></OperatorIcon>
38
+ <div className={styles.nodeTitle}>{t(`flow.begin`)}</div>
39
  </Flex>
 
 
 
40
  </section>
41
  );
42
  }
web/src/pages/flow/canvas/node/categorize-node.tsx CHANGED
@@ -1,22 +1,17 @@
1
- import { useTranslate } from '@/hooks/common-hooks';
2
  import { Flex } from 'antd';
3
  import classNames from 'classnames';
4
- import lowerFirst from 'lodash/lowerFirst';
5
  import { Handle, NodeProps, Position } from 'reactflow';
6
- import { Operator, SwitchElseTo, operatorMap } from '../../constant';
7
  import { NodeData } from '../../interface';
8
- import OperatorIcon from '../../operator-icon';
9
- import CategorizeHandle from './categorize-handle';
10
- import NodeDropdown from './dropdown';
11
  import { useBuildCategorizeHandlePositions } from './hooks';
12
  import styles from './index.less';
 
13
  import NodePopover from './popover';
14
 
15
  export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
16
- const style = operatorMap[data.label as Operator];
17
- const { t } = useTranslate('flow');
18
  const { positions } = useBuildCategorizeHandlePositions({ data, id });
19
- const operatorName = data.label;
20
 
21
  return (
22
  <NodePopover nodeId={id}>
@@ -24,10 +19,6 @@ export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
24
  className={classNames(styles.logicNode, {
25
  [styles.selectedNode]: selected,
26
  })}
27
- style={{
28
- backgroundColor: style.backgroundColor,
29
- color: style.color,
30
- }}
31
  >
32
  <Handle
33
  type="target"
@@ -36,47 +27,35 @@ export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
36
  className={styles.handle}
37
  id={'a'}
38
  ></Handle>
39
- <Handle
40
- type="target"
41
- position={Position.Top}
42
- isConnectable
43
- className={styles.handle}
44
- id={'b'}
45
- ></Handle>
46
- <Handle
47
- type="target"
48
- position={Position.Bottom}
49
- isConnectable
50
- className={styles.handle}
51
- id={'c'}
52
- ></Handle>
53
- {operatorName === Operator.Switch && (
54
- <CategorizeHandle top={50} right={-4} id={SwitchElseTo}>
55
- To
56
- </CategorizeHandle>
57
- )}
58
- {positions.map((position, idx) => {
59
- return (
60
- <CategorizeHandle
61
- top={position.top}
62
- right={position.right}
63
- key={idx}
64
- id={position.text}
65
- idx={idx}
66
- ></CategorizeHandle>
67
- );
68
- })}
69
- <Flex vertical align="center" justify="center" gap={6}>
70
- <OperatorIcon
71
- name={data.label as Operator}
72
- fontSize={24}
73
- ></OperatorIcon>
74
- <span className={styles.type}>{t(lowerFirst(data.label))}</span>
75
- <NodeDropdown id={id}></NodeDropdown>
76
  </Flex>
77
- <section className={styles.bottomBox}>
78
- <div className={styles.nodeName}>{data.name}</div>
79
- </section>
80
  </section>
81
  </NodePopover>
82
  );
 
1
+ import LLMLabel from '@/components/llm-select/llm-label';
2
  import { Flex } from 'antd';
3
  import classNames from 'classnames';
4
+ import { get } from 'lodash';
5
  import { Handle, NodeProps, Position } from 'reactflow';
 
6
  import { NodeData } from '../../interface';
7
+ import { RightHandleStyle } from './handle-icon';
 
 
8
  import { useBuildCategorizeHandlePositions } from './hooks';
9
  import styles from './index.less';
10
+ import NodeHeader from './node-header';
11
  import NodePopover from './popover';
12
 
13
  export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
 
 
14
  const { positions } = useBuildCategorizeHandlePositions({ data, id });
 
15
 
16
  return (
17
  <NodePopover nodeId={id}>
 
19
  className={classNames(styles.logicNode, {
20
  [styles.selectedNode]: selected,
21
  })}
 
 
 
 
22
  >
23
  <Handle
24
  type="target"
 
27
  className={styles.handle}
28
  id={'a'}
29
  ></Handle>
30
+
31
+ <NodeHeader
32
+ id={id}
33
+ name={data.name}
34
+ label={data.label}
35
+ className={styles.nodeHeader}
36
+ ></NodeHeader>
37
+
38
+ <Flex vertical gap={8}>
39
+ <div className={styles.nodeText}>
40
+ <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
41
+ </div>
42
+ {positions.map((position, idx) => {
43
+ return (
44
+ <div key={idx}>
45
+ <div className={styles.nodeText}>{position.text}</div>
46
+ <Handle
47
+ key={position.text}
48
+ id={position.text}
49
+ type="source"
50
+ position={Position.Right}
51
+ isConnectable
52
+ className={styles.handle}
53
+ style={{ ...RightHandleStyle, top: position.top }}
54
+ ></Handle>
55
+ </div>
56
+ );
57
+ })}
 
 
 
 
 
 
 
 
 
58
  </Flex>
 
 
 
59
  </section>
60
  </NodePopover>
61
  );
web/src/pages/flow/canvas/node/dropdown.tsx CHANGED
@@ -38,7 +38,7 @@ const NodeDropdown = ({ id, iconFontColor }: IProps) => {
38
 
39
  return (
40
  <OperateDropdown
41
- iconFontSize={14}
42
  height={14}
43
  deleteItem={deleteNode}
44
  items={items}
 
38
 
39
  return (
40
  <OperateDropdown
41
+ iconFontSize={22}
42
  height={14}
43
  deleteItem={deleteNode}
44
  items={items}
web/src/pages/flow/canvas/node/generate-node.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import LLMLabel from '@/components/llm-select/llm-label';
2
+ import { Flex } from 'antd';
3
+ import classNames from 'classnames';
4
+ import { get } from 'lodash';
5
+ import { Handle, NodeProps, Position } from 'reactflow';
6
+ import { useGetComponentLabelByValue } from '../../hooks';
7
+ import { IGenerateParameter, NodeData } from '../../interface';
8
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
9
+ import styles from './index.less';
10
+ import NodeHeader from './node-header';
11
+ import NodePopover from './popover';
12
+
13
+ export function GenerateNode({
14
+ id,
15
+ data,
16
+ isConnectable = true,
17
+ selected,
18
+ }: NodeProps<NodeData>) {
19
+ const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
20
+ const getLabel = useGetComponentLabelByValue(id);
21
+
22
+ return (
23
+ <NodePopover nodeId={id}>
24
+ <section
25
+ className={classNames(styles.logicNode, {
26
+ [styles.selectedNode]: selected,
27
+ })}
28
+ >
29
+ <Handle
30
+ id="c"
31
+ type="source"
32
+ position={Position.Left}
33
+ isConnectable={isConnectable}
34
+ className={styles.handle}
35
+ style={LeftHandleStyle}
36
+ ></Handle>
37
+ <Handle
38
+ type="source"
39
+ position={Position.Right}
40
+ isConnectable={isConnectable}
41
+ className={styles.handle}
42
+ style={RightHandleStyle}
43
+ id="b"
44
+ ></Handle>
45
+
46
+ <NodeHeader
47
+ id={id}
48
+ name={data.name}
49
+ label={data.label}
50
+ className={styles.nodeHeader}
51
+ ></NodeHeader>
52
+
53
+ <div className={styles.nodeText}>
54
+ <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
55
+ </div>
56
+ <Flex gap={8} vertical className={styles.generateParameters}>
57
+ {parameters.map((x) => (
58
+ <Flex
59
+ key={x.id}
60
+ align="center"
61
+ gap={6}
62
+ className={styles.conditionBlock}
63
+ >
64
+ <label htmlFor="">{x.key}</label>
65
+ <span className={styles.parameterValue}>
66
+ {getLabel(x.component_id)}
67
+ </span>
68
+ </Flex>
69
+ ))}
70
+ </Flex>
71
+ </section>
72
+ </NodePopover>
73
+ );
74
+ }
web/src/pages/flow/canvas/node/handle-icon.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PlusOutlined } from '@ant-design/icons';
2
+ import { CSSProperties } from 'react';
3
+
4
+ export const HandleIcon = () => {
5
+ return (
6
+ <PlusOutlined
7
+ style={{ fontSize: 6, color: 'white', position: 'absolute', zIndex: 10 }}
8
+ />
9
+ );
10
+ };
11
+
12
+ export const RightHandleStyle: CSSProperties = {
13
+ right: -5,
14
+ };
15
+
16
+ export const LeftHandleStyle: CSSProperties = {
17
+ left: -7,
18
+ };
19
+
20
+ export default HandleIcon;
web/src/pages/flow/canvas/node/hooks.ts CHANGED
@@ -1,14 +1,13 @@
1
  import get from 'lodash/get';
2
- import pick from 'lodash/pick';
3
- import { useEffect, useMemo, useState } from 'react';
4
  import { useUpdateNodeInternals } from 'reactflow';
5
- import { Operator } from '../../constant';
6
- import { IPosition, NodeData } from '../../interface';
7
  import {
8
- buildNewPositionMap,
9
- generateSwitchHandleText,
10
- isKeysEqual,
11
- } from '../../utils';
 
12
 
13
  export const useBuildCategorizeHandlePositions = ({
14
  data,
@@ -17,85 +16,86 @@ export const useBuildCategorizeHandlePositions = ({
17
  id: string;
18
  data: NodeData;
19
  }) => {
20
- const operatorName = data.label as Operator;
21
  const updateNodeInternals = useUpdateNodeInternals();
22
- const [positionMap, setPositionMap] = useState<Record<string, IPosition>>({});
23
 
24
- const categoryData = useMemo(() => {
25
- if (operatorName === Operator.Categorize) {
26
- return get(data, `form.category_description`, {});
27
- } else if (operatorName === Operator.Switch) {
28
- return get(data, 'form.conditions', []);
29
- }
30
- return {};
31
- }, [data, operatorName]);
32
 
33
  const positions = useMemo(() => {
34
- return Object.keys(categoryData)
35
- .map((x, idx) => {
36
- const position = positionMap[x];
37
- let text = x;
38
- if (operatorName === Operator.Switch) {
39
- text = generateSwitchHandleText(idx);
40
- }
41
- return { text, ...position };
42
- })
43
- .filter((x) => typeof x?.right === 'number');
44
- }, [categoryData, positionMap, operatorName]);
45
-
46
- useEffect(() => {
47
- // Cache used coordinates
48
- setPositionMap((state) => {
49
- const categoryDataKeys = Object.keys(categoryData);
50
- const stateKeys = Object.keys(state);
51
- if (!isKeysEqual(categoryDataKeys, stateKeys)) {
52
- const { newPositionMap, intersectionKeys } = buildNewPositionMap(
53
- categoryDataKeys,
54
- state,
55
- );
56
-
57
- const nextPositionMap = {
58
- ...pick(state, intersectionKeys),
59
- ...newPositionMap,
60
- };
61
 
62
- return nextPositionMap;
63
- }
64
- return state;
 
 
 
65
  });
 
 
66
  }, [categoryData]);
67
 
68
  useEffect(() => {
69
  updateNodeInternals(id);
70
- }, [id, updateNodeInternals, positionMap]);
71
 
72
  return { positions };
73
  };
74
 
75
- // export const useBuildSwitchHandlePositions = ({
76
- // data,
77
- // id,
78
- // }: {
79
- // id: string;
80
- // data: NodeData;
81
- // }) => {
82
- // const [positionMap, setPositionMap] = useState<Record<string, IPosition>>({});
83
- // const conditions = useMemo(() => get(data, 'form.conditions', []), [data]);
84
- // const updateNodeInternals = useUpdateNodeInternals();
85
 
86
- // const positions = useMemo(() => {
87
- // return conditions
88
- // .map((x, idx) => {
89
- // const text = `Item ${idx}`;
90
- // const position = positionMap[text];
91
- // return { text: text, ...position };
92
- // })
93
- // .filter((x) => typeof x?.right === 'number');
94
- // }, [conditions, positionMap]);
95
 
96
- // useEffect(() => {
97
- // updateNodeInternals(id);
98
- // }, [id, updateNodeInternals, positionMap]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- // return { positions };
101
- // };
 
 
 
 
 
 
 
 
1
  import get from 'lodash/get';
2
+ import { useEffect, useMemo } from 'react';
 
3
  import { useUpdateNodeInternals } from 'reactflow';
4
+ import { SwitchElseTo } from '../../constant';
 
5
  import {
6
+ ICategorizeItemResult,
7
+ ISwitchCondition,
8
+ NodeData,
9
+ } from '../../interface';
10
+ import { generateSwitchHandleText } from '../../utils';
11
 
12
  export const useBuildCategorizeHandlePositions = ({
13
  data,
 
16
  id: string;
17
  data: NodeData;
18
  }) => {
 
19
  const updateNodeInternals = useUpdateNodeInternals();
 
20
 
21
+ const categoryData: ICategorizeItemResult = useMemo(() => {
22
+ return get(data, `form.category_description`, {});
23
+ }, [data]);
 
 
 
 
 
24
 
25
  const positions = useMemo(() => {
26
+ const list: Array<{
27
+ text: string;
28
+ top: number;
29
+ idx: number;
30
+ }> = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ Object.keys(categoryData).forEach((x, idx) => {
33
+ list.push({
34
+ text: x,
35
+ idx,
36
+ top: idx === 0 ? 98 : list[idx - 1].top + 8 + 26,
37
+ });
38
  });
39
+
40
+ return list;
41
  }, [categoryData]);
42
 
43
  useEffect(() => {
44
  updateNodeInternals(id);
45
+ }, [id, updateNodeInternals, categoryData]);
46
 
47
  return { positions };
48
  };
49
 
50
+ export const useBuildSwitchHandlePositions = ({
51
+ data,
52
+ id,
53
+ }: {
54
+ id: string;
55
+ data: NodeData;
56
+ }) => {
57
+ const updateNodeInternals = useUpdateNodeInternals();
 
 
58
 
59
+ const conditions: ISwitchCondition[] = useMemo(() => {
60
+ return get(data, 'form.conditions', []);
61
+ }, [data]);
 
 
 
 
 
 
62
 
63
+ const positions = useMemo(() => {
64
+ const list: Array<{
65
+ text: string;
66
+ top: number;
67
+ idx: number;
68
+ condition?: ISwitchCondition;
69
+ }> = [];
70
+
71
+ [...conditions, ''].forEach((x, idx) => {
72
+ let top = idx === 0 ? 58 : list[idx - 1].top + 32; // case number (Case 1) height + flex gap
73
+ if (idx - 1 >= 0) {
74
+ const previousItems = conditions[idx - 1]?.items ?? [];
75
+ if (previousItems.length > 0) {
76
+ top += 12; // ConditionBlock padding
77
+ top += previousItems.length * 22; // condition variable height
78
+ top += (previousItems.length - 1) * 25; // operator height
79
+ }
80
+ }
81
+
82
+ list.push({
83
+ text:
84
+ idx < conditions.length
85
+ ? generateSwitchHandleText(idx)
86
+ : SwitchElseTo,
87
+ idx,
88
+ top,
89
+ condition: typeof x === 'string' ? undefined : x,
90
+ });
91
+ });
92
 
93
+ return list;
94
+ }, [conditions]);
95
+
96
+ useEffect(() => {
97
+ updateNodeInternals(id);
98
+ }, [id, updateNodeInternals, conditions]);
99
+
100
+ return { positions };
101
+ };
web/src/pages/flow/canvas/node/index.less CHANGED
@@ -3,22 +3,16 @@
3
  -6px 0 12px 0 rgba(179, 177, 177, 0.08),
4
  -3px 0 6px -4px rgba(0, 0, 0, 0.12),
5
  -6px 0 16px 6px rgba(0, 0, 0, 0.05);
 
 
 
 
 
6
  }
7
 
8
  .ragNode {
9
- position: relative;
10
  .commonNode();
11
 
12
- padding: 5px;
13
- border-radius: 5px;
14
- background: white;
15
- width: 50px;
16
- height: 50px;
17
- border-radius: 50%;
18
- display: flex;
19
- // align-items: center;
20
- // justify-self: center;
21
- justify-content: center;
22
  .nodeName {
23
  font-size: 10px;
24
  color: black;
@@ -28,23 +22,10 @@
28
  color: #777;
29
  font-size: 12px;
30
  }
31
- .type {
32
- // font-size: 12px;
33
- }
34
  .description {
35
  font-size: 10px;
36
  }
37
- .bottomBox {
38
- position: absolute;
39
- bottom: -34px;
40
- background: white;
41
- padding: 2px 5px;
42
- border-radius: 5px;
43
- box-shadow:
44
- -6px 0 12px 0 rgba(179, 177, 177, 0.08),
45
- -3px 0 6px -4px rgba(0, 0, 0, 0.12),
46
- -6px 0 16px 6px rgba(0, 0, 0, 0.05);
47
- }
48
  .categorizeAnchorPointText {
49
  position: absolute;
50
  top: -4px;
@@ -53,14 +34,25 @@
53
  }
54
  }
55
 
 
 
 
56
  .selectedNode {
57
- border: 1px solid rgb(59, 118, 244);
58
  }
59
 
60
  .handle {
61
  display: inline-flex;
62
- text-align: center;
63
- // align-items: center;
 
 
 
 
 
 
 
 
64
  }
65
 
66
  .jsonView {
@@ -71,19 +63,8 @@
71
  }
72
 
73
  .logicNode {
74
- position: relative;
75
  .commonNode();
76
 
77
- padding: 5px;
78
- border-radius: 5px;
79
- background: white;
80
- width: 100px;
81
- height: 100px;
82
- border-radius: 50%;
83
- display: flex;
84
- // align-items: center;
85
- // justify-self: center;
86
- justify-content: center;
87
  .nodeName {
88
  font-size: 10px;
89
  color: black;
@@ -93,41 +74,122 @@
93
  color: #777;
94
  font-size: 12px;
95
  }
96
- .type {
97
- // font-size: 12px;
98
- }
99
  .description {
100
  font-size: 10px;
101
  }
102
- .bottomBox {
103
- position: absolute;
104
- bottom: -34px;
105
- background: white;
106
- padding: 2px 5px;
107
- border-radius: 5px;
108
- box-shadow:
109
- -6px 0 12px 0 rgba(179, 177, 177, 0.08),
110
- -3px 0 6px -4px rgba(0, 0, 0, 0.12),
111
- -6px 0 16px 6px rgba(0, 0, 0, 0.05);
112
- }
113
  .categorizeAnchorPointText {
114
  position: absolute;
115
  top: -4px;
116
  left: 8px;
117
  white-space: nowrap;
118
  }
 
 
 
119
  }
120
 
121
  .noteNode {
122
  .commonNode();
123
- width: 140px;
124
- padding: 4px 6px 6px;
 
 
125
  border-radius: 10px;
126
- background-color: #dbf8f4;
127
  .noteTitle {
 
128
  font-size: 12px;
 
 
 
129
  }
130
  .noteForm {
131
  margin-top: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
  }
 
3
  -6px 0 12px 0 rgba(179, 177, 177, 0.08),
4
  -3px 0 6px -4px rgba(0, 0, 0, 0.12),
5
  -6px 0 16px 6px rgba(0, 0, 0, 0.05);
6
+
7
+ padding: 10px;
8
+ border-radius: 10px;
9
+ background: white;
10
+ width: 200px;
11
  }
12
 
13
  .ragNode {
 
14
  .commonNode();
15
 
 
 
 
 
 
 
 
 
 
 
16
  .nodeName {
17
  font-size: 10px;
18
  color: black;
 
22
  color: #777;
23
  font-size: 12px;
24
  }
 
 
 
25
  .description {
26
  font-size: 10px;
27
  }
28
+
 
 
 
 
 
 
 
 
 
 
29
  .categorizeAnchorPointText {
30
  position: absolute;
31
  top: -4px;
 
34
  }
35
  }
36
 
37
+ @lightBackgroundColor: rgba(150, 150, 150, 0.1);
38
+ @darkBackgroundColor: rgba(150, 150, 150, 0.2);
39
+
40
  .selectedNode {
41
+ border: 1.5px solid rgb(59, 118, 244);
42
  }
43
 
44
  .handle {
45
  display: inline-flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ width: 12px;
49
+ height: 12px;
50
+ background: rgb(59, 88, 253);
51
+ border: 1px solid white;
52
+ z-index: 1;
53
+ background-image: url('@/assets/svg/plus.svg');
54
+ background-size: cover;
55
+ background-position: center;
56
  }
57
 
58
  .jsonView {
 
63
  }
64
 
65
  .logicNode {
 
66
  .commonNode();
67
 
 
 
 
 
 
 
 
 
 
 
68
  .nodeName {
69
  font-size: 10px;
70
  color: black;
 
74
  color: #777;
75
  font-size: 12px;
76
  }
77
+
 
 
78
  .description {
79
  font-size: 10px;
80
  }
81
+
 
 
 
 
 
 
 
 
 
 
82
  .categorizeAnchorPointText {
83
  position: absolute;
84
  top: -4px;
85
  left: 8px;
86
  white-space: nowrap;
87
  }
88
+ .relevantSourceLabel {
89
+ font-size: 10px;
90
+ }
91
  }
92
 
93
  .noteNode {
94
  .commonNode();
95
+ min-width: 140px;
96
+ width: auto;
97
+ height: 100%;
98
+ padding: 0;
99
  border-radius: 10px;
100
+ min-height: 128px;
101
  .noteTitle {
102
+ background-color: #edfcff;
103
  font-size: 12px;
104
+ padding: 6px 6px 4px;
105
+ border-top-left-radius: 10px;
106
+ border-top-right-radius: 10px;
107
  }
108
  .noteForm {
109
  margin-top: 4px;
110
+ height: calc(100% - 50px);
111
+ }
112
+ .noteName {
113
+ padding: 0px 4px;
114
+ }
115
+ .noteTextarea {
116
+ resize: none;
117
+ border: 0;
118
+ border-radius: 0;
119
+ height: 100%;
120
+ &:focus {
121
+ border: none;
122
+ box-shadow: none;
123
+ }
124
+ }
125
+ }
126
+
127
+ .nodeText {
128
+ padding-inline: 0.4em;
129
+ padding-block: 0.2em 0.1em;
130
+ background: @lightBackgroundColor;
131
+ border-radius: 3px;
132
+ min-height: 22px;
133
+ .textEllipsis();
134
+ }
135
+
136
+ .nodeTitle {
137
+ font-weight: 600;
138
+ text-align: center;
139
+ .textEllipsis();
140
+ }
141
+
142
+ .nodeHeader {
143
+ padding-bottom: 12px;
144
+ }
145
+
146
+ .zeroDivider {
147
+ margin: 0 !important;
148
+ }
149
+
150
+ .conditionBlock {
151
+ border-radius: 4px;
152
+ padding: 6px;
153
+ background: @lightBackgroundColor;
154
+ }
155
+
156
+ .conditionLine {
157
+ border-radius: 4px;
158
+ padding: 0 4px;
159
+ background: @darkBackgroundColor;
160
+ .textEllipsis();
161
+ }
162
+
163
+ .conditionKey {
164
+ flex: 1;
165
+ }
166
+
167
+ .conditionOperator {
168
+ padding: 0 2px;
169
+ text-align: center;
170
+ }
171
+
172
+ .relevantLabel {
173
+ text-align: right;
174
+ }
175
+
176
+ .knowledgeNodeName {
177
+ .textEllipsis();
178
+ }
179
+
180
+ .messageNodeContainer {
181
+ overflow-y: auto;
182
+ max-height: 300px;
183
+ }
184
+
185
+ .generateParameters {
186
+ padding-top: 8px;
187
+ label {
188
+ flex: 2;
189
+ .textEllipsis();
190
+ }
191
+ .parameterValue {
192
+ flex: 3;
193
+ .conditionLine;
194
  }
195
  }
web/src/pages/flow/canvas/node/index.tsx CHANGED
@@ -1,12 +1,9 @@
1
- import { Flex } from 'antd';
2
  import classNames from 'classnames';
3
- import pick from 'lodash/pick';
4
  import { Handle, NodeProps, Position } from 'reactflow';
5
- import { Operator, operatorMap } from '../../constant';
6
  import { NodeData } from '../../interface';
7
- import OperatorIcon from '../../operator-icon';
8
- import NodeDropdown from './dropdown';
9
  import styles from './index.less';
 
10
  import NodePopover from './popover';
11
 
12
  export function RagNode({
@@ -15,17 +12,12 @@ export function RagNode({
15
  isConnectable = true,
16
  selected,
17
  }: NodeProps<NodeData>) {
18
- const style = operatorMap[data.label as Operator];
19
-
20
  return (
21
  <NodePopover nodeId={id}>
22
  <section
23
  className={classNames(styles.ragNode, {
24
  [styles.selectedNode]: selected,
25
  })}
26
- style={{
27
- ...pick(style, ['backgroundColor', 'color']),
28
- }}
29
  >
30
  <Handle
31
  id="c"
@@ -33,39 +25,17 @@ export function RagNode({
33
  position={Position.Left}
34
  isConnectable={isConnectable}
35
  className={styles.handle}
 
36
  ></Handle>
37
- <Handle type="source" position={Position.Top} id="d" isConnectable />
38
  <Handle
39
  type="source"
40
  position={Position.Right}
41
  isConnectable={isConnectable}
42
  className={styles.handle}
43
  id="b"
 
44
  ></Handle>
45
- <Handle type="source" position={Position.Bottom} id="a" isConnectable />
46
- <Flex vertical align="center" justify={'space-around'}>
47
- <Flex flex={1} justify="center" align="center">
48
- <label htmlFor=""> </label>
49
- </Flex>
50
-
51
- <Flex flex={1}>
52
- <OperatorIcon
53
- name={data.label as Operator}
54
- fontSize={style?.iconFontSize ?? 16}
55
- width={style?.iconWidth}
56
- ></OperatorIcon>
57
- </Flex>
58
- <Flex flex={1}>
59
- <NodeDropdown
60
- id={id}
61
- iconFontColor={style?.moreIconColor}
62
- ></NodeDropdown>
63
- </Flex>
64
- </Flex>
65
-
66
- <section className={styles.bottomBox}>
67
- <div className={styles.nodeName}>{data.name}</div>
68
- </section>
69
  </section>
70
  </NodePopover>
71
  );
 
 
1
  import classNames from 'classnames';
 
2
  import { Handle, NodeProps, Position } from 'reactflow';
 
3
  import { NodeData } from '../../interface';
4
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
 
5
  import styles from './index.less';
6
+ import NodeHeader from './node-header';
7
  import NodePopover from './popover';
8
 
9
  export function RagNode({
 
12
  isConnectable = true,
13
  selected,
14
  }: NodeProps<NodeData>) {
 
 
15
  return (
16
  <NodePopover nodeId={id}>
17
  <section
18
  className={classNames(styles.ragNode, {
19
  [styles.selectedNode]: selected,
20
  })}
 
 
 
21
  >
22
  <Handle
23
  id="c"
 
25
  position={Position.Left}
26
  isConnectable={isConnectable}
27
  className={styles.handle}
28
+ style={LeftHandleStyle}
29
  ></Handle>
 
30
  <Handle
31
  type="source"
32
  position={Position.Right}
33
  isConnectable={isConnectable}
34
  className={styles.handle}
35
  id="b"
36
+ style={RightHandleStyle}
37
  ></Handle>
38
+ <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  </section>
40
  </NodePopover>
41
  );
web/src/pages/flow/canvas/node/keyword-node.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import LLMLabel from '@/components/llm-select/llm-label';
2
+ import classNames from 'classnames';
3
+ import { get } from 'lodash';
4
+ import { Handle, NodeProps, Position } from 'reactflow';
5
+ import { NodeData } from '../../interface';
6
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
7
+ import styles from './index.less';
8
+ import NodeHeader from './node-header';
9
+ import NodePopover from './popover';
10
+
11
+ export function KeywordNode({
12
+ id,
13
+ data,
14
+ isConnectable = true,
15
+ selected,
16
+ }: NodeProps<NodeData>) {
17
+ return (
18
+ <NodePopover nodeId={id}>
19
+ <section
20
+ className={classNames(styles.logicNode, {
21
+ [styles.selectedNode]: selected,
22
+ })}
23
+ >
24
+ <Handle
25
+ id="c"
26
+ type="source"
27
+ position={Position.Left}
28
+ isConnectable={isConnectable}
29
+ className={styles.handle}
30
+ style={LeftHandleStyle}
31
+ ></Handle>
32
+ <Handle
33
+ type="source"
34
+ position={Position.Right}
35
+ isConnectable={isConnectable}
36
+ className={styles.handle}
37
+ style={RightHandleStyle}
38
+ id="b"
39
+ ></Handle>
40
+
41
+ <NodeHeader
42
+ id={id}
43
+ name={data.name}
44
+ label={data.label}
45
+ className={styles.nodeHeader}
46
+ ></NodeHeader>
47
+
48
+ <div className={styles.nodeText}>
49
+ <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
50
+ </div>
51
+ </section>
52
+ </NodePopover>
53
+ );
54
+ }
web/src/pages/flow/canvas/node/logic-node.tsx CHANGED
@@ -1,38 +1,23 @@
1
- import { useTranslate } from '@/hooks/common-hooks';
2
- import { Flex } from 'antd';
3
  import classNames from 'classnames';
4
- import lowerFirst from 'lodash/lowerFirst';
5
- import pick from 'lodash/pick';
6
  import { Handle, NodeProps, Position } from 'reactflow';
7
- import { Operator, operatorMap } from '../../constant';
8
  import { NodeData } from '../../interface';
9
- import OperatorIcon from '../../operator-icon';
10
- import NodeDropdown from './dropdown';
11
  import styles from './index.less';
 
12
  import NodePopover from './popover';
13
 
14
- const ZeroGapOperators = [
15
- Operator.RewriteQuestion,
16
- Operator.KeywordExtract,
17
- Operator.ArXiv,
18
- ];
19
-
20
  export function LogicNode({
21
  id,
22
  data,
23
  isConnectable = true,
24
  selected,
25
  }: NodeProps<NodeData>) {
26
- const style = operatorMap[data.label as Operator];
27
- const { t } = useTranslate('flow');
28
-
29
  return (
30
  <NodePopover nodeId={id}>
31
  <section
32
  className={classNames(styles.logicNode, {
33
  [styles.selectedNode]: selected,
34
  })}
35
- style={pick(style, ['backgroundColor', 'width', 'height', 'color'])}
36
  >
37
  <Handle
38
  id="c"
@@ -40,49 +25,17 @@ export function LogicNode({
40
  position={Position.Left}
41
  isConnectable={isConnectable}
42
  className={styles.handle}
 
43
  ></Handle>
44
- <Handle type="source" position={Position.Top} id="d" isConnectable />
45
  <Handle
46
  type="source"
47
  position={Position.Right}
48
  isConnectable={isConnectable}
49
  className={styles.handle}
 
50
  id="b"
51
  ></Handle>
52
- <Handle type="source" position={Position.Bottom} id="a" isConnectable />
53
- <Flex
54
- vertical
55
- align="center"
56
- justify={'space-around'}
57
- gap={ZeroGapOperators.some((x) => x === data.label) ? 0 : 6}
58
- >
59
- <Flex flex={1} justify="center" align="center">
60
- <OperatorIcon
61
- name={data.label as Operator}
62
- fontSize={style?.iconFontSize ?? 24}
63
- width={style?.iconWidth}
64
- ></OperatorIcon>
65
- </Flex>
66
-
67
- <Flex flex={1}>
68
- <span
69
- className={styles.type}
70
- style={{ fontSize: style?.fontSize ?? 14 }}
71
- >
72
- {t(lowerFirst(data.label))}
73
- </span>
74
- </Flex>
75
- <Flex flex={1}>
76
- <NodeDropdown
77
- id={id}
78
- iconFontColor={style?.moreIconColor}
79
- ></NodeDropdown>
80
- </Flex>
81
- </Flex>
82
-
83
- <section className={styles.bottomBox}>
84
- <div className={styles.nodeName}>{data.name}</div>
85
- </section>
86
  </section>
87
  </NodePopover>
88
  );
 
 
 
1
  import classNames from 'classnames';
 
 
2
  import { Handle, NodeProps, Position } from 'reactflow';
 
3
  import { NodeData } from '../../interface';
4
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
 
5
  import styles from './index.less';
6
+ import NodeHeader from './node-header';
7
  import NodePopover from './popover';
8
 
 
 
 
 
 
 
9
  export function LogicNode({
10
  id,
11
  data,
12
  isConnectable = true,
13
  selected,
14
  }: NodeProps<NodeData>) {
 
 
 
15
  return (
16
  <NodePopover nodeId={id}>
17
  <section
18
  className={classNames(styles.logicNode, {
19
  [styles.selectedNode]: selected,
20
  })}
 
21
  >
22
  <Handle
23
  id="c"
 
25
  position={Position.Left}
26
  isConnectable={isConnectable}
27
  className={styles.handle}
28
+ style={LeftHandleStyle}
29
  ></Handle>
 
30
  <Handle
31
  type="source"
32
  position={Position.Right}
33
  isConnectable={isConnectable}
34
  className={styles.handle}
35
+ style={RightHandleStyle}
36
  id="b"
37
  ></Handle>
38
+ <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  </section>
40
  </NodePopover>
41
  );
web/src/pages/flow/canvas/node/message-node.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Flex } from 'antd';
2
+ import classNames from 'classnames';
3
+ import { get } from 'lodash';
4
+ import { Handle, NodeProps, Position } from 'reactflow';
5
+ import { NodeData } from '../../interface';
6
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
7
+ import styles from './index.less';
8
+ import NodeHeader from './node-header';
9
+ import NodePopover from './popover';
10
+
11
+ export function MessageNode({
12
+ id,
13
+ data,
14
+ isConnectable = true,
15
+ selected,
16
+ }: NodeProps<NodeData>) {
17
+ const messages: string[] = get(data, 'form.messages', []);
18
+
19
+ return (
20
+ <NodePopover nodeId={id}>
21
+ <section
22
+ className={classNames(styles.logicNode, {
23
+ [styles.selectedNode]: selected,
24
+ })}
25
+ >
26
+ <Handle
27
+ id="c"
28
+ type="source"
29
+ position={Position.Left}
30
+ isConnectable={isConnectable}
31
+ className={styles.handle}
32
+ style={LeftHandleStyle}
33
+ ></Handle>
34
+ <Handle
35
+ type="source"
36
+ position={Position.Right}
37
+ isConnectable={isConnectable}
38
+ className={styles.handle}
39
+ style={RightHandleStyle}
40
+ id="b"
41
+ ></Handle>
42
+ <NodeHeader
43
+ id={id}
44
+ name={data.name}
45
+ label={data.label}
46
+ className={classNames({
47
+ [styles.nodeHeader]: messages.length > 0,
48
+ })}
49
+ ></NodeHeader>
50
+
51
+ <Flex vertical gap={8} className={styles.messageNodeContainer}>
52
+ {messages.map((message, idx) => {
53
+ return (
54
+ <div className={styles.nodeText} key={idx}>
55
+ {message}
56
+ </div>
57
+ );
58
+ })}
59
+ </Flex>
60
+ </section>
61
+ </NodePopover>
62
+ );
63
+ }
web/src/pages/flow/canvas/node/node-header.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Flex } from 'antd';
2
+
3
+ import { Operator, operatorMap } from '../../constant';
4
+ import OperatorIcon from '../../operator-icon';
5
+ import NodeDropdown from './dropdown';
6
+ import styles from './index.less';
7
+
8
+ interface IProps {
9
+ id: string;
10
+ label: string;
11
+ name: string;
12
+ gap?: number;
13
+ className?: string;
14
+ }
15
+
16
+ const NodeHeader = ({ label, id, name, gap = 4, className }: IProps) => {
17
+ return (
18
+ <Flex
19
+ flex={1}
20
+ align="center"
21
+ justify={'space-between'}
22
+ gap={gap}
23
+ className={className}
24
+ >
25
+ <OperatorIcon
26
+ name={label as Operator}
27
+ color={operatorMap[label as Operator].color}
28
+ ></OperatorIcon>
29
+ <span className={styles.nodeTitle}>{name}</span>
30
+ <NodeDropdown id={id}></NodeDropdown>
31
+ </Flex>
32
+ );
33
+ };
34
+
35
+ export default NodeHeader;
web/src/pages/flow/canvas/node/note-node.tsx CHANGED
@@ -1,20 +1,33 @@
1
- import { Flex, Form, Input, Space } from 'antd';
2
- import { NodeProps } from 'reactflow';
 
3
  import { NodeData } from '../../interface';
4
  import NodeDropdown from './dropdown';
5
 
6
  import SvgIcon from '@/components/svg-icon';
7
- import { useEffect } from 'react';
8
  import { useTranslation } from 'react-i18next';
9
- import { useHandleFormValuesChange } from '../../hooks';
 
 
 
10
  import styles from './index.less';
11
 
12
  const { TextArea } = Input;
13
 
 
 
 
 
 
14
  function NoteNode({ data, id }: NodeProps<NodeData>) {
15
  const { t } = useTranslation();
16
  const [form] = Form.useForm();
17
 
 
 
 
 
18
  const { handleValuesChange } = useHandleFormValuesChange(id);
19
 
20
  useEffect(() => {
@@ -22,25 +35,51 @@ function NoteNode({ data, id }: NodeProps<NodeData>) {
22
  }, [form, data?.form]);
23
 
24
  return (
25
- <section className={styles.noteNode}>
26
- <Flex justify={'space-between'}>
27
- <Space size={'small'}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  <SvgIcon name="note" width={14}></SvgIcon>
29
- <span className={styles.noteTitle}>{t('flow.note')}</span>
30
- </Space>
31
- <NodeDropdown id={id}></NodeDropdown>
32
- </Flex>
33
- <Form
34
- onValuesChange={handleValuesChange}
35
- form={form}
36
- className={styles.noteForm}
37
- >
38
- <Form.Item name="text" noStyle>
39
- <TextArea rows={3} placeholder={t('flow.notePlaceholder')} />
40
- </Form.Item>
41
- </Form>
42
- </section>
 
 
 
 
 
 
 
 
 
43
  );
44
  }
45
 
46
- export default NoteNode;
 
1
+ import { Flex, Form, Input } from 'antd';
2
+ import classNames from 'classnames';
3
+ import { NodeProps, NodeResizeControl } from 'reactflow';
4
  import { NodeData } from '../../interface';
5
  import NodeDropdown from './dropdown';
6
 
7
  import SvgIcon from '@/components/svg-icon';
8
+ import { memo, useEffect } from 'react';
9
  import { useTranslation } from 'react-i18next';
10
+ import {
11
+ useHandleFormValuesChange,
12
+ useHandleNodeNameChange,
13
+ } from '../../hooks';
14
  import styles from './index.less';
15
 
16
  const { TextArea } = Input;
17
 
18
+ const controlStyle = {
19
+ background: 'transparent',
20
+ border: 'none',
21
+ };
22
+
23
  function NoteNode({ data, id }: NodeProps<NodeData>) {
24
  const { t } = useTranslation();
25
  const [form] = Form.useForm();
26
 
27
+ const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
28
+ id,
29
+ data,
30
+ });
31
  const { handleValuesChange } = useHandleFormValuesChange(id);
32
 
33
  useEffect(() => {
 
35
  }, [form, data?.form]);
36
 
37
  return (
38
+ <>
39
+ <NodeResizeControl style={controlStyle} minWidth={190} minHeight={128}>
40
+ <SvgIcon
41
+ name="resize"
42
+ width={12}
43
+ style={{
44
+ position: 'absolute',
45
+ right: 5,
46
+ bottom: 5,
47
+ cursor: 'nwse-resize',
48
+ }}
49
+ ></SvgIcon>
50
+ </NodeResizeControl>
51
+ <section className={styles.noteNode}>
52
+ <Flex
53
+ justify={'space-between'}
54
+ className={classNames(styles.noteTitle, 'note-drag-handle')}
55
+ align="center"
56
+ gap={6}
57
+ >
58
  <SvgIcon name="note" width={14}></SvgIcon>
59
+ <Input
60
+ value={name ?? t('flow.note')}
61
+ onBlur={handleNameBlur}
62
+ onChange={handleNameChange}
63
+ className={styles.noteName}
64
+ ></Input>
65
+ <NodeDropdown id={id}></NodeDropdown>
66
+ </Flex>
67
+ <Form
68
+ onValuesChange={handleValuesChange}
69
+ form={form}
70
+ className={styles.noteForm}
71
+ >
72
+ <Form.Item name="text" noStyle>
73
+ <TextArea
74
+ rows={3}
75
+ placeholder={t('flow.notePlaceholder')}
76
+ className={styles.noteTextarea}
77
+ />
78
+ </Form.Item>
79
+ </Form>
80
+ </section>
81
+ </>
82
  );
83
  }
84
 
85
+ export default memo(NoteNode);
web/src/pages/flow/canvas/node/relevant-node.tsx CHANGED
@@ -1,28 +1,23 @@
1
- import { useTranslate } from '@/hooks/common-hooks';
2
  import { Flex } from 'antd';
3
  import classNames from 'classnames';
4
- import lowerFirst from 'lodash/lowerFirst';
5
- import pick from 'lodash/pick';
6
  import { Handle, NodeProps, Position } from 'reactflow';
7
- import { Operator, operatorMap } from '../../constant';
8
  import { NodeData } from '../../interface';
9
- import OperatorIcon from '../../operator-icon';
10
- import NodeDropdown from './dropdown';
11
 
12
- import CategorizeHandle from './categorize-handle';
13
  import styles from './index.less';
14
- import NodePopover from './popover';
15
 
16
  export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
17
- const style = operatorMap[data.label as Operator];
18
- const { t } = useTranslate('flow');
19
  return (
20
  <NodePopover nodeId={id}>
21
  <section
22
  className={classNames(styles.logicNode, {
23
  [styles.selectedNode]: selected,
24
  })}
25
- style={pick(style, ['backgroundColor', 'width', 'height', 'color'])}
26
  >
27
  <Handle
28
  type="target"
@@ -32,43 +27,38 @@ export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
32
  id={'a'}
33
  ></Handle>
34
  <Handle
35
- type="target"
36
- position={Position.Top}
37
  isConnectable
38
  className={styles.handle}
39
- id={'b'}
 
40
  ></Handle>
41
  <Handle
42
- type="target"
43
- position={Position.Bottom}
44
  isConnectable
45
  className={styles.handle}
46
- id={'c'}
 
47
  ></Handle>
48
- <CategorizeHandle top={20} right={6} id={'yes'}></CategorizeHandle>
49
- <CategorizeHandle top={80} right={6} id={'no'}></CategorizeHandle>
50
- <Flex vertical align="center" justify="center" gap={0}>
51
- <Flex flex={1}>
52
- <OperatorIcon
53
- name={data.label as Operator}
54
- fontSize={style.iconFontSize}
55
- ></OperatorIcon>
56
- </Flex>
57
- <Flex flex={1}>
58
- <span
59
- className={styles.type}
60
- style={{ fontSize: style.fontSize ?? 14 }}
61
- >
62
- {t(lowerFirst(data.label))}
63
- </span>
64
  </Flex>
65
- <Flex flex={1}>
66
- <NodeDropdown id={id}></NodeDropdown>
 
67
  </Flex>
68
  </Flex>
69
- <section className={styles.bottomBox}>
70
- <div className={styles.nodeName}>{data.name}</div>
71
- </section>
72
  </section>
73
  </NodePopover>
74
  );
 
 
1
  import { Flex } from 'antd';
2
  import classNames from 'classnames';
 
 
3
  import { Handle, NodeProps, Position } from 'reactflow';
 
4
  import { NodeData } from '../../interface';
5
+ import { RightHandleStyle } from './handle-icon';
6
+ import NodePopover from './popover';
7
 
8
+ import { get } from 'lodash';
9
  import styles from './index.less';
10
+ import NodeHeader from './node-header';
11
 
12
  export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
13
+ const yes = get(data, 'form.yes');
14
+ const no = get(data, 'form.no');
15
  return (
16
  <NodePopover nodeId={id}>
17
  <section
18
  className={classNames(styles.logicNode, {
19
  [styles.selectedNode]: selected,
20
  })}
 
21
  >
22
  <Handle
23
  type="target"
 
27
  id={'a'}
28
  ></Handle>
29
  <Handle
30
+ type="source"
31
+ position={Position.Right}
32
  isConnectable
33
  className={styles.handle}
34
+ id={'yes'}
35
+ style={{ ...RightHandleStyle, top: 59 }}
36
  ></Handle>
37
  <Handle
38
+ type="source"
39
+ position={Position.Right}
40
  isConnectable
41
  className={styles.handle}
42
+ id={'no'}
43
+ style={{ ...RightHandleStyle, top: 112 }}
44
  ></Handle>
45
+ <NodeHeader
46
+ id={id}
47
+ name={data.name}
48
+ label={data.label}
49
+ className={styles.nodeHeader}
50
+ ></NodeHeader>
51
+
52
+ <Flex vertical gap={10}>
53
+ <Flex vertical>
54
+ <div className={styles.relevantLabel}>Yes</div>
55
+ <div className={styles.nodeText}>{yes}</div>
 
 
 
 
 
56
  </Flex>
57
+ <Flex vertical>
58
+ <div className={styles.relevantLabel}>No</div>
59
+ <div className={styles.nodeText}>{no}</div>
60
  </Flex>
61
  </Flex>
 
 
 
62
  </section>
63
  </NodePopover>
64
  );
web/src/pages/flow/canvas/node/retrieval-node.tsx ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
2
+ import { UserOutlined } from '@ant-design/icons';
3
+ import { Avatar, Flex } from 'antd';
4
+ import classNames from 'classnames';
5
+ import { get } from 'lodash';
6
+ import { useMemo } from 'react';
7
+ import { Handle, NodeProps, Position } from 'reactflow';
8
+ import { NodeData } from '../../interface';
9
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
10
+ import styles from './index.less';
11
+ import NodeHeader from './node-header';
12
+ import NodePopover from './popover';
13
+
14
+ export function RetrievalNode({
15
+ id,
16
+ data,
17
+ isConnectable = true,
18
+ selected,
19
+ }: NodeProps<NodeData>) {
20
+ const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
21
+ const { list: knowledgeList } = useNextFetchKnowledgeList(true);
22
+ const knowledgeBases = useMemo(() => {
23
+ return knowledgeBaseIds.map((x) => {
24
+ const item = knowledgeList.find((y) => x === y.id);
25
+ return {
26
+ name: item?.name,
27
+ avatar: item?.avatar,
28
+ id: x,
29
+ };
30
+ });
31
+ }, [knowledgeList, knowledgeBaseIds]);
32
+
33
+ return (
34
+ <NodePopover nodeId={id}>
35
+ <section
36
+ className={classNames(styles.logicNode, {
37
+ [styles.selectedNode]: selected,
38
+ })}
39
+ >
40
+ <Handle
41
+ id="c"
42
+ type="source"
43
+ position={Position.Left}
44
+ isConnectable={isConnectable}
45
+ className={styles.handle}
46
+ style={LeftHandleStyle}
47
+ ></Handle>
48
+ <Handle
49
+ type="source"
50
+ position={Position.Right}
51
+ isConnectable={isConnectable}
52
+ className={styles.handle}
53
+ style={RightHandleStyle}
54
+ id="b"
55
+ ></Handle>
56
+ <NodeHeader
57
+ id={id}
58
+ name={data.name}
59
+ label={data.label}
60
+ className={classNames({
61
+ [styles.nodeHeader]: knowledgeBaseIds.length > 0,
62
+ })}
63
+ ></NodeHeader>
64
+ <Flex vertical gap={8}>
65
+ {knowledgeBases.map((knowledge) => {
66
+ return (
67
+ <div className={styles.nodeText} key={knowledge.id}>
68
+ <Flex align={'center'} gap={6}>
69
+ <Avatar
70
+ size={26}
71
+ icon={<UserOutlined />}
72
+ src={knowledge.avatar}
73
+ />
74
+ <Flex className={styles.knowledgeNodeName} flex={1}>
75
+ {knowledge.name}
76
+ </Flex>
77
+ </Flex>
78
+ </div>
79
+ );
80
+ })}
81
+ </Flex>
82
+ </section>
83
+ </NodePopover>
84
+ );
85
+ }
web/src/pages/flow/canvas/node/rewrite-node.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import LLMLabel from '@/components/llm-select/llm-label';
2
+ import classNames from 'classnames';
3
+ import { get } from 'lodash';
4
+ import { Handle, NodeProps, Position } from 'reactflow';
5
+ import { NodeData } from '../../interface';
6
+ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
7
+ import styles from './index.less';
8
+ import NodeHeader from './node-header';
9
+ import NodePopover from './popover';
10
+
11
+ export function RewriteNode({
12
+ id,
13
+ data,
14
+ isConnectable = true,
15
+ selected,
16
+ }: NodeProps<NodeData>) {
17
+ return (
18
+ <NodePopover nodeId={id}>
19
+ <section
20
+ className={classNames(styles.logicNode, {
21
+ [styles.selectedNode]: selected,
22
+ })}
23
+ >
24
+ <Handle
25
+ id="c"
26
+ type="source"
27
+ position={Position.Left}
28
+ isConnectable={isConnectable}
29
+ className={styles.handle}
30
+ style={LeftHandleStyle}
31
+ ></Handle>
32
+ <Handle
33
+ type="source"
34
+ position={Position.Right}
35
+ isConnectable={isConnectable}
36
+ className={styles.handle}
37
+ style={RightHandleStyle}
38
+ id="b"
39
+ ></Handle>
40
+
41
+ <NodeHeader
42
+ id={id}
43
+ name={data.name}
44
+ label={data.label}
45
+ className={styles.nodeHeader}
46
+ ></NodeHeader>
47
+
48
+ <div className={styles.nodeText}>
49
+ <LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
50
+ </div>
51
+ </section>
52
+ </NodePopover>
53
+ );
54
+ }
web/src/pages/flow/canvas/node/switch-node.tsx ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Divider, Flex } from 'antd';
2
+ import classNames from 'classnames';
3
+ import { Handle, NodeProps, Position } from 'reactflow';
4
+ import { useGetComponentLabelByValue } from '../../hooks';
5
+ import { ISwitchCondition, NodeData } from '../../interface';
6
+ import { RightHandleStyle } from './handle-icon';
7
+ import { useBuildSwitchHandlePositions } from './hooks';
8
+ import styles from './index.less';
9
+ import NodeHeader from './node-header';
10
+ import NodePopover from './popover';
11
+
12
+ const getConditionKey = (idx: number, length: number) => {
13
+ if (idx === 0 && length !== 1) {
14
+ return 'If';
15
+ } else if (idx === length - 1) {
16
+ return 'Else';
17
+ }
18
+
19
+ return 'ElseIf';
20
+ };
21
+
22
+ const ConditionBlock = ({
23
+ condition,
24
+ nodeId,
25
+ }: {
26
+ condition: ISwitchCondition;
27
+ nodeId: string;
28
+ }) => {
29
+ const items = condition?.items ?? [];
30
+ const getLabel = useGetComponentLabelByValue(nodeId);
31
+ return (
32
+ <Flex vertical className={styles.conditionBlock}>
33
+ {items.map((x, idx) => (
34
+ <div key={idx}>
35
+ <Flex>
36
+ <div
37
+ className={classNames(styles.conditionLine, styles.conditionKey)}
38
+ >
39
+ {getLabel(x?.cpn_id)}
40
+ </div>
41
+ <span className={styles.conditionOperator}>{x?.operator}</span>
42
+ <Flex flex={1} className={styles.conditionLine}>
43
+ {x?.value}
44
+ </Flex>
45
+ </Flex>
46
+ {idx + 1 < items.length && (
47
+ <Divider orientationMargin="0" className={styles.zeroDivider}>
48
+ {condition?.logical_operator}
49
+ </Divider>
50
+ )}
51
+ </div>
52
+ ))}
53
+ </Flex>
54
+ );
55
+ };
56
+
57
+ export function SwitchNode({ id, data, selected }: NodeProps<NodeData>) {
58
+ const { positions } = useBuildSwitchHandlePositions({ data, id });
59
+
60
+ return (
61
+ <NodePopover nodeId={id}>
62
+ <section
63
+ className={classNames(styles.logicNode, {
64
+ [styles.selectedNode]: selected,
65
+ })}
66
+ >
67
+ <Handle
68
+ type="target"
69
+ position={Position.Left}
70
+ isConnectable
71
+ className={styles.handle}
72
+ id={'a'}
73
+ ></Handle>
74
+ <NodeHeader
75
+ id={id}
76
+ name={data.name}
77
+ label={data.label}
78
+ className={styles.nodeHeader}
79
+ ></NodeHeader>
80
+ <Flex vertical gap={10}>
81
+ {positions.map((position, idx) => {
82
+ return (
83
+ <div key={idx}>
84
+ <Flex vertical>
85
+ <Flex justify={'space-between'}>
86
+ <span>{idx < positions.length - 1 && position.text}</span>
87
+ <span>{getConditionKey(idx, positions.length)}</span>
88
+ </Flex>
89
+ {position.condition && (
90
+ <ConditionBlock
91
+ nodeId={id}
92
+ condition={position.condition}
93
+ ></ConditionBlock>
94
+ )}
95
+ </Flex>
96
+ <Handle
97
+ key={position.text}
98
+ id={position.text}
99
+ type="source"
100
+ position={Position.Right}
101
+ isConnectable
102
+ className={styles.handle}
103
+ style={{ ...RightHandleStyle, top: position.top }}
104
+ ></Handle>
105
+ </div>
106
+ );
107
+ })}
108
+ </Flex>
109
+ </section>
110
+ </NodePopover>
111
+ );
112
+ }
web/src/pages/flow/constant.tsx CHANGED
@@ -2,6 +2,7 @@ import { ReactComponent as AkShareIcon } from '@/assets/svg/akshare.svg';
2
  import { ReactComponent as ArXivIcon } from '@/assets/svg/arxiv.svg';
3
  import { ReactComponent as baiduFanyiIcon } from '@/assets/svg/baidu-fanyi.svg';
4
  import { ReactComponent as BaiduIcon } from '@/assets/svg/baidu.svg';
 
5
  import { ReactComponent as BingIcon } from '@/assets/svg/bing.svg';
6
  import { ReactComponent as ConcentratorIcon } from '@/assets/svg/concentrator.svg';
7
  import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
@@ -39,7 +40,6 @@ import {
39
  MessageOutlined,
40
  RocketOutlined,
41
  SendOutlined,
42
- SlidersOutlined,
43
  } from '@ant-design/icons';
44
  import upperFirst from 'lodash/upperFirst';
45
 
@@ -85,7 +85,7 @@ export const operatorIconMap = {
85
  [Operator.Retrieval]: RocketOutlined,
86
  [Operator.Generate]: MergeCellsOutlined,
87
  [Operator.Answer]: SendOutlined,
88
- [Operator.Begin]: SlidersOutlined,
89
  [Operator.Categorize]: DatabaseOutlined,
90
  [Operator.Message]: MessageOutlined,
91
  [Operator.Relevant]: BranchesOutlined,
@@ -142,7 +142,7 @@ export const operatorMap: Record<
142
  },
143
  [Operator.Answer]: {
144
  backgroundColor: '#f4816d',
145
- color: 'white',
146
  },
147
  [Operator.Begin]: {
148
  backgroundColor: '#4f51d6',
@@ -157,7 +157,7 @@ export const operatorMap: Record<
157
  },
158
  [Operator.Relevant]: {
159
  backgroundColor: '#9fd94d',
160
- color: 'white',
161
  width: 70,
162
  height: 70,
163
  fontSize: 12,
@@ -165,7 +165,7 @@ export const operatorMap: Record<
165
  },
166
  [Operator.RewriteQuestion]: {
167
  backgroundColor: '#f8c7f8',
168
- color: 'white',
169
  width: 70,
170
  height: 70,
171
  fontSize: 12,
@@ -175,7 +175,7 @@ export const operatorMap: Record<
175
  width: 70,
176
  height: 70,
177
  backgroundColor: '#0f0e0f',
178
- color: '#e1dcdc',
179
  fontSize: 12,
180
  iconWidth: 16,
181
  // iconFontSize: 16,
@@ -221,14 +221,14 @@ export const operatorMap: Record<
221
  [Operator.BaiduFanyi]: { backgroundColor: '#e5f2d3' },
222
  [Operator.QWeather]: { backgroundColor: '#a4bbf3' },
223
  [Operator.ExeSQL]: { backgroundColor: '#b9efe8' },
224
- [Operator.Switch]: { backgroundColor: '#dbaff6' },
225
  [Operator.WenCai]: { backgroundColor: '#faac5b' },
226
  [Operator.AkShare]: { backgroundColor: '#8085f5' },
227
  [Operator.YahooFinance]: { backgroundColor: '#b474ff' },
228
  [Operator.Jin10]: { backgroundColor: '#a0b9f8' },
229
  [Operator.Concentrator]: {
230
  backgroundColor: '#32d2a3',
231
- color: 'white',
232
  width: 70,
233
  height: 70,
234
  fontSize: 10,
@@ -586,18 +586,19 @@ export const RestrictedUpstreamMap = {
586
  [Operator.Concentrator]: [Operator.Begin],
587
  [Operator.TuShare]: [Operator.Begin],
588
  [Operator.Crawler]: [Operator.Begin],
 
589
  };
590
 
591
  export const NodeMap = {
592
  [Operator.Begin]: 'beginNode',
593
  [Operator.Categorize]: 'categorizeNode',
594
- [Operator.Retrieval]: 'logicNode',
595
- [Operator.Generate]: 'logicNode',
596
  [Operator.Answer]: 'logicNode',
597
- [Operator.Message]: 'logicNode',
598
  [Operator.Relevant]: 'relevantNode',
599
- [Operator.RewriteQuestion]: 'logicNode',
600
- [Operator.KeywordExtract]: 'logicNode',
601
  [Operator.DuckDuckGo]: 'ragNode',
602
  [Operator.Baidu]: 'ragNode',
603
  [Operator.Wikipedia]: 'ragNode',
@@ -611,7 +612,7 @@ export const NodeMap = {
611
  [Operator.BaiduFanyi]: 'ragNode',
612
  [Operator.QWeather]: 'ragNode',
613
  [Operator.ExeSQL]: 'ragNode',
614
- [Operator.Switch]: 'categorizeNode',
615
  [Operator.Concentrator]: 'logicNode',
616
  [Operator.WenCai]: 'ragNode',
617
  [Operator.AkShare]: 'ragNode',
 
2
  import { ReactComponent as ArXivIcon } from '@/assets/svg/arxiv.svg';
3
  import { ReactComponent as baiduFanyiIcon } from '@/assets/svg/baidu-fanyi.svg';
4
  import { ReactComponent as BaiduIcon } from '@/assets/svg/baidu.svg';
5
+ import { ReactComponent as BeginIcon } from '@/assets/svg/begin.svg';
6
  import { ReactComponent as BingIcon } from '@/assets/svg/bing.svg';
7
  import { ReactComponent as ConcentratorIcon } from '@/assets/svg/concentrator.svg';
8
  import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
 
40
  MessageOutlined,
41
  RocketOutlined,
42
  SendOutlined,
 
43
  } from '@ant-design/icons';
44
  import upperFirst from 'lodash/upperFirst';
45
 
 
85
  [Operator.Retrieval]: RocketOutlined,
86
  [Operator.Generate]: MergeCellsOutlined,
87
  [Operator.Answer]: SendOutlined,
88
+ [Operator.Begin]: BeginIcon,
89
  [Operator.Categorize]: DatabaseOutlined,
90
  [Operator.Message]: MessageOutlined,
91
  [Operator.Relevant]: BranchesOutlined,
 
142
  },
143
  [Operator.Answer]: {
144
  backgroundColor: '#f4816d',
145
+ color: '#f4816d',
146
  },
147
  [Operator.Begin]: {
148
  backgroundColor: '#4f51d6',
 
157
  },
158
  [Operator.Relevant]: {
159
  backgroundColor: '#9fd94d',
160
+ color: '#8ef005',
161
  width: 70,
162
  height: 70,
163
  fontSize: 12,
 
165
  },
166
  [Operator.RewriteQuestion]: {
167
  backgroundColor: '#f8c7f8',
168
+ color: '#f32bf3',
169
  width: 70,
170
  height: 70,
171
  fontSize: 12,
 
175
  width: 70,
176
  height: 70,
177
  backgroundColor: '#0f0e0f',
178
+ color: '#0f0e0f',
179
  fontSize: 12,
180
  iconWidth: 16,
181
  // iconFontSize: 16,
 
221
  [Operator.BaiduFanyi]: { backgroundColor: '#e5f2d3' },
222
  [Operator.QWeather]: { backgroundColor: '#a4bbf3' },
223
  [Operator.ExeSQL]: { backgroundColor: '#b9efe8' },
224
+ [Operator.Switch]: { backgroundColor: '#dbaff6', color: '#dbaff6' },
225
  [Operator.WenCai]: { backgroundColor: '#faac5b' },
226
  [Operator.AkShare]: { backgroundColor: '#8085f5' },
227
  [Operator.YahooFinance]: { backgroundColor: '#b474ff' },
228
  [Operator.Jin10]: { backgroundColor: '#a0b9f8' },
229
  [Operator.Concentrator]: {
230
  backgroundColor: '#32d2a3',
231
+ color: '#32d2a3',
232
  width: 70,
233
  height: 70,
234
  fontSize: 10,
 
586
  [Operator.Concentrator]: [Operator.Begin],
587
  [Operator.TuShare]: [Operator.Begin],
588
  [Operator.Crawler]: [Operator.Begin],
589
+ [Operator.Note]: [],
590
  };
591
 
592
  export const NodeMap = {
593
  [Operator.Begin]: 'beginNode',
594
  [Operator.Categorize]: 'categorizeNode',
595
+ [Operator.Retrieval]: 'retrievalNode',
596
+ [Operator.Generate]: 'generateNode',
597
  [Operator.Answer]: 'logicNode',
598
+ [Operator.Message]: 'messageNode',
599
  [Operator.Relevant]: 'relevantNode',
600
+ [Operator.RewriteQuestion]: 'rewriteNode',
601
+ [Operator.KeywordExtract]: 'keywordNode',
602
  [Operator.DuckDuckGo]: 'ragNode',
603
  [Operator.Baidu]: 'ragNode',
604
  [Operator.Wikipedia]: 'ragNode',
 
612
  [Operator.BaiduFanyi]: 'ragNode',
613
  [Operator.QWeather]: 'ragNode',
614
  [Operator.ExeSQL]: 'ragNode',
615
+ [Operator.Switch]: 'switchNode',
616
  [Operator.Concentrator]: 'logicNode',
617
  [Operator.WenCai]: 'ragNode',
618
  [Operator.AkShare]: 'ragNode',
web/src/pages/flow/flow-drawer/index.less CHANGED
@@ -7,3 +7,9 @@
7
  font-weight: 600;
8
  }
9
  }
 
 
 
 
 
 
 
7
  font-weight: 600;
8
  }
9
  }
10
+
11
+ .operatorDescription {
12
+ font-size: 14px;
13
+ padding-top: 16px;
14
+ font-weight: normal;
15
+ }
web/src/pages/flow/flow-drawer/index.tsx CHANGED
@@ -3,7 +3,7 @@ import { IModalProps } from '@/interfaces/common';
3
  import { Drawer, Flex, Form, Input } from 'antd';
4
  import { useEffect } from 'react';
5
  import { Node } from 'reactflow';
6
- import { Operator } from '../constant';
7
  import AkShareForm from '../form/akshare-form';
8
  import AnswerForm from '../form/answer-form';
9
  import ArXivForm from '../form/arxiv-form';
@@ -36,6 +36,8 @@ import YahooFinanceForm from '../form/yahoo-finance-form';
36
  import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
37
  import OperatorIcon from '../operator-icon';
38
 
 
 
39
  import styles from './index.less';
40
 
41
  interface IProps {
@@ -74,7 +76,7 @@ const FormMap = {
74
  [Operator.Crawler]: CrawlerForm,
75
  };
76
 
77
- const EmptyContent = () => <div>empty</div>;
78
 
79
  const FlowDrawer = ({
80
  visible,
@@ -84,8 +86,10 @@ const FlowDrawer = ({
84
  const operatorName: Operator = node?.data.label;
85
  const OperatorForm = FormMap[operatorName] ?? EmptyContent;
86
  const [form] = Form.useForm();
87
- const { name, handleNameBlur, handleNameChange } =
88
- useHandleNodeNameChange(node);
 
 
89
  const { t } = useTranslate('flow');
90
 
91
  const { handleValuesChange } = useHandleFormValuesChange(node?.id);
@@ -99,18 +103,27 @@ const FlowDrawer = ({
99
  return (
100
  <Drawer
101
  title={
102
- <Flex gap={'middle'} align="center">
103
- <OperatorIcon name={operatorName}></OperatorIcon>
104
- <Flex align="center" gap={'small'} flex={1}>
105
- <label htmlFor="" className={styles.title}>
106
- {t('title')}
107
- </label>
108
- <Input
109
- value={name}
110
- onBlur={handleNameBlur}
111
- onChange={handleNameChange}
112
- ></Input>
 
 
 
 
 
 
113
  </Flex>
 
 
 
114
  </Flex>
115
  }
116
  placement="right"
@@ -119,6 +132,7 @@ const FlowDrawer = ({
119
  getContainer={false}
120
  mask={false}
121
  width={470}
 
122
  >
123
  <section className={styles.formWrapper}>
124
  {visible && (
 
3
  import { Drawer, Flex, Form, Input } from 'antd';
4
  import { useEffect } from 'react';
5
  import { Node } from 'reactflow';
6
+ import { Operator, operatorMap } from '../constant';
7
  import AkShareForm from '../form/akshare-form';
8
  import AnswerForm from '../form/answer-form';
9
  import ArXivForm from '../form/arxiv-form';
 
36
  import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
37
  import OperatorIcon from '../operator-icon';
38
 
39
+ import { CloseOutlined } from '@ant-design/icons';
40
+ import { lowerFirst } from 'lodash';
41
  import styles from './index.less';
42
 
43
  interface IProps {
 
76
  [Operator.Crawler]: CrawlerForm,
77
  };
78
 
79
+ const EmptyContent = () => <div></div>;
80
 
81
  const FlowDrawer = ({
82
  visible,
 
86
  const operatorName: Operator = node?.data.label;
87
  const OperatorForm = FormMap[operatorName] ?? EmptyContent;
88
  const [form] = Form.useForm();
89
+ const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
90
+ id: node?.id,
91
+ data: node?.data,
92
+ });
93
  const { t } = useTranslate('flow');
94
 
95
  const { handleValuesChange } = useHandleFormValuesChange(node?.id);
 
103
  return (
104
  <Drawer
105
  title={
106
+ <Flex vertical>
107
+ <Flex gap={'middle'} align="center">
108
+ <OperatorIcon
109
+ name={operatorName}
110
+ color={operatorMap[operatorName]?.color}
111
+ ></OperatorIcon>
112
+ <Flex align="center" gap={'small'} flex={1}>
113
+ <label htmlFor="" className={styles.title}>
114
+ {t('title')}
115
+ </label>
116
+ <Input
117
+ value={name}
118
+ onBlur={handleNameBlur}
119
+ onChange={handleNameChange}
120
+ ></Input>
121
+ </Flex>
122
+ <CloseOutlined onClick={hideModal} />
123
  </Flex>
124
+ <span className={styles.operatorDescription}>
125
+ {t(`${lowerFirst(operatorName)}Description`)}
126
+ </span>
127
  </Flex>
128
  }
129
  placement="right"
 
132
  getContainer={false}
133
  mask={false}
134
  width={470}
135
+ closeIcon={null}
136
  >
137
  <section className={styles.formWrapper}>
138
  {visible && (
web/src/pages/flow/flow-sider/index.tsx CHANGED
@@ -3,7 +3,7 @@ import { Card, Divider, Flex, Layout, Tooltip } from 'antd';
3
  import classNames from 'classnames';
4
  import lowerFirst from 'lodash/lowerFirst';
5
  import React from 'react';
6
- import { Operator, componentMenuList } from '../constant';
7
  import { useHandleDrag } from '../hooks';
8
  import OperatorIcon from '../operator-icon';
9
  import styles from './index.less';
@@ -53,7 +53,10 @@ const FlowSide = ({ setCollapsed, collapsed }: IProps) => {
53
  onDragStart={handleDragStart(x.name)}
54
  >
55
  <Flex align="center" gap={15}>
56
- <OperatorIcon name={x.name}></OperatorIcon>
 
 
 
57
  <section>
58
  <Tooltip title={t(`${lowerFirst(x.name)}Description`)}>
59
  <b>{t(lowerFirst(x.name))}</b>
 
3
  import classNames from 'classnames';
4
  import lowerFirst from 'lodash/lowerFirst';
5
  import React from 'react';
6
+ import { Operator, componentMenuList, operatorMap } from '../constant';
7
  import { useHandleDrag } from '../hooks';
8
  import OperatorIcon from '../operator-icon';
9
  import styles from './index.less';
 
53
  onDragStart={handleDragStart(x.name)}
54
  >
55
  <Flex align="center" gap={15}>
56
+ <OperatorIcon
57
+ name={x.name}
58
+ color={operatorMap[x.name].color}
59
+ ></OperatorIcon>
60
  <section>
61
  <Tooltip title={t(`${lowerFirst(x.name)}Description`)}>
62
  <b>{t(lowerFirst(x.name))}</b>
web/src/pages/flow/form-hooks.ts CHANGED
@@ -3,19 +3,6 @@ import { useCallback, useMemo } from 'react';
3
  import { Operator, RestrictedUpstreamMap } from './constant';
4
  import useGraphStore from './store';
5
 
6
- const ExcludedNodesMap = {
7
- // exclude some nodes downstream of the classification node
8
- [Operator.Categorize]: [
9
- Operator.Categorize,
10
- Operator.Answer,
11
- Operator.Begin,
12
- Operator.Relevant,
13
- ],
14
- [Operator.Relevant]: [Operator.Begin, Operator.Answer, Operator.Relevant],
15
- [Operator.Generate]: [Operator.Begin],
16
- [Operator.Switch]: [Operator.Begin],
17
- };
18
-
19
  export const useBuildFormSelectOptions = (
20
  operatorName: Operator,
21
  selfId?: string, // exclude the current node
@@ -24,8 +11,10 @@ export const useBuildFormSelectOptions = (
24
 
25
  const buildCategorizeToOptions = useCallback(
26
  (toList: string[]) => {
27
- const excludedNodes: Operator[] =
28
- RestrictedUpstreamMap[operatorName] ?? [];
 
 
29
  return nodes
30
  .filter(
31
  (x) =>
 
3
  import { Operator, RestrictedUpstreamMap } from './constant';
4
  import useGraphStore from './store';
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  export const useBuildFormSelectOptions = (
7
  operatorName: Operator,
8
  selfId?: string, // exclude the current node
 
11
 
12
  const buildCategorizeToOptions = useCallback(
13
  (toList: string[]) => {
14
+ const excludedNodes: Operator[] = [
15
+ Operator.Note,
16
+ ...(RestrictedUpstreamMap[operatorName] ?? []),
17
+ ];
18
  return nodes
19
  .filter(
20
  (x) =>
web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx CHANGED
@@ -1,6 +1,14 @@
1
  import { useTranslate } from '@/hooks/common-hooks';
2
  import { CloseOutlined } from '@ant-design/icons';
3
- import { Button, Card, Form, FormListFieldData, Input, Select } from 'antd';
 
 
 
 
 
 
 
 
4
  import { FormInstance } from 'antd/lib';
5
  import { humanId } from 'human-id';
6
  import trim from 'lodash/trim';
@@ -15,6 +23,8 @@ import { useUpdateNodeInternals } from 'reactflow';
15
  import { Operator } from '../../constant';
16
  import { useBuildFormSelectOptions } from '../../form-hooks';
17
 
 
 
18
  interface IProps {
19
  nodeId?: string;
20
  }
@@ -105,13 +115,12 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
105
  if (nodeId) updateNodeInternals(nodeId);
106
  };
107
  return (
108
- <div
109
- style={{ display: 'flex', rowGap: 10, flexDirection: 'column' }}
110
- >
111
  {fields.map((field) => (
112
  <Card
113
  size="small"
114
  key={field.key}
 
115
  extra={
116
  <CloseOutlined
117
  onClick={() => {
@@ -172,10 +181,15 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
172
  </Card>
173
  ))}
174
 
175
- <Button type="dashed" onClick={handleAdd} block>
 
 
 
 
 
176
  + {t('addItem')}
177
  </Button>
178
- </div>
179
  );
180
  }}
181
  </Form.List>
 
1
  import { useTranslate } from '@/hooks/common-hooks';
2
  import { CloseOutlined } from '@ant-design/icons';
3
+ import {
4
+ Button,
5
+ Card,
6
+ Flex,
7
+ Form,
8
+ FormListFieldData,
9
+ Input,
10
+ Select,
11
+ } from 'antd';
12
  import { FormInstance } from 'antd/lib';
13
  import { humanId } from 'human-id';
14
  import trim from 'lodash/trim';
 
23
  import { Operator } from '../../constant';
24
  import { useBuildFormSelectOptions } from '../../form-hooks';
25
 
26
+ import styles from './index.less';
27
+
28
  interface IProps {
29
  nodeId?: string;
30
  }
 
115
  if (nodeId) updateNodeInternals(nodeId);
116
  };
117
  return (
118
+ <Flex gap={18} vertical>
 
 
119
  {fields.map((field) => (
120
  <Card
121
  size="small"
122
  key={field.key}
123
+ className={styles.caseCard}
124
  extra={
125
  <CloseOutlined
126
  onClick={() => {
 
181
  </Card>
182
  ))}
183
 
184
+ <Button
185
+ type="dashed"
186
+ onClick={handleAdd}
187
+ block
188
+ className={styles.addButton}
189
+ >
190
  + {t('addItem')}
191
  </Button>
192
+ </Flex>
193
  );
194
  }}
195
  </Form.List>
web/src/pages/flow/form/categorize-form/index.less ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @lightBackgroundColor: rgba(150, 150, 150, 0.07);
2
+ @darkBackgroundColor: rgba(150, 150, 150, 0.12);
3
+
4
+ .caseCard {
5
+ background-color: @darkBackgroundColor;
6
+ }
7
+
8
+ .addButton {
9
+ color: rgb(22, 119, 255);
10
+ font-weight: 600;
11
+ }
web/src/pages/flow/form/generate-form/dynamic-parameters.tsx CHANGED
@@ -90,6 +90,7 @@ const DynamicParameters = ({ nodeId }: IProps) => {
90
  components={components}
91
  rowClassName={() => styles.editableRow}
92
  scroll={{ x: true }}
 
93
  />
94
  </section>
95
  );
 
90
  components={components}
91
  rowClassName={() => styles.editableRow}
92
  scroll={{ x: true }}
93
+ bordered
94
  />
95
  </section>
96
  );
web/src/pages/flow/form/switch-form/index.less ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @lightBackgroundColor: rgba(150, 150, 150, 0.07);
2
+ @darkBackgroundColor: rgba(150, 150, 150, 0.12);
3
+
4
+ .caseCard {
5
+ background-color: @lightBackgroundColor;
6
+ }
7
+
8
+ .conditionCard {
9
+ background-color: @darkBackgroundColor;
10
+ }
11
+
12
+ .elseCase {
13
+ background-color: @lightBackgroundColor;
14
+ padding: 12px;
15
+ border-radius: 8px;
16
+ }
17
+
18
+ .addButton {
19
+ color: rgb(22, 119, 255);
20
+ font-weight: 600;
21
+ }
web/src/pages/flow/form/switch-form/index.tsx CHANGED
@@ -13,6 +13,8 @@ import { useBuildComponentIdSelectOptions } from '../../hooks';
13
  import { IOperatorForm, ISwitchForm } from '../../interface';
14
  import { getOtherFieldValues } from '../../utils';
15
 
 
 
16
  const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
17
  const { t } = useTranslation();
18
  const buildCategorizeToOptions = useBuildFormSelectOptions(
@@ -55,112 +57,134 @@ const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
55
  <Form.List name="conditions">
56
  {(fields, { add, remove }) => (
57
  <div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}>
58
- {fields.map((field) => (
59
- <Card
60
- size="small"
61
- title={`Case ${field.name + 1}`}
62
- key={field.key}
63
- extra={
64
- <CloseOutlined
65
- onClick={() => {
66
- remove(field.name);
67
- }}
68
- />
69
- }
70
- >
71
- <Form.Item noStyle dependencies={[field.name, 'items']}>
72
- {({ getFieldValue }) =>
73
- getFieldValue(['conditions', field.name, 'items'])?.length >
74
- 1 && (
75
- <Form.Item
76
- label={t('flow.logicalOperator')}
77
- name={[field.name, 'logical_operator']}
78
- >
79
- <Select options={switchLogicOperatorOptions} />
80
- </Form.Item>
81
- )
82
  }
83
- </Form.Item>
84
- <Form.Item label={t('flow.to')} name={[field.name, 'to']}>
85
- <Select
86
- allowClear
87
- options={buildCategorizeToOptions([
88
- form?.getFieldValue(SwitchElseTo),
89
- ...getOtherFieldValues(form!, 'conditions', field, 'to'),
90
- ])}
91
- />
92
- </Form.Item>
93
- <Form.Item label="Condition">
94
- <Form.List name={[field.name, 'items']}>
95
- {(subFields, subOpt) => (
96
- <div
97
- style={{
98
- display: 'flex',
99
- flexDirection: 'column',
100
- rowGap: 16,
101
- }}
102
- >
103
- {subFields.map((subField) => (
104
- <Card
105
- key={subField.key}
106
- title={null}
107
- size="small"
108
- extra={
109
- <CloseOutlined
110
- onClick={() => {
111
- subOpt.remove(subField.name);
112
- }}
113
- />
114
- }
115
- >
116
- <Form.Item
117
- label={t('flow.componentId')}
118
- name={[subField.name, 'cpn_id']}
119
- >
120
- <Select
121
- placeholder={t('flow.componentId')}
122
- options={componentIdOptions}
123
- />
124
- </Form.Item>
125
- <Form.Item
126
- label={t('flow.operator')}
127
- name={[subField.name, 'operator']}
128
- >
129
- <Select
130
- placeholder={t('flow.operator')}
131
- options={switchOperatorOptions}
132
- />
133
- </Form.Item>
134
- <Form.Item
135
- label={t('flow.value')}
136
- name={[subField.name, 'value']}
137
- >
138
- <Input placeholder={t('flow.value')} />
139
- </Form.Item>
140
- </Card>
141
- ))}
142
- <Button
143
- type="dashed"
144
- onClick={() => subOpt.add()}
145
- block
146
  >
147
- + Add Condition
148
- </Button>
149
- </div>
150
- )}
151
- </Form.List>
152
- </Form.Item>
153
- </Card>
154
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- <Button type="dashed" onClick={() => add()} block>
157
  + Add Case
158
  </Button>
159
  </div>
160
  )}
161
  </Form.List>
162
  <Divider />
163
- <Form.Item label={'ELSE'} name={[SwitchElseTo]}>
 
 
 
 
164
  <Select
165
  allowClear
166
  options={buildCategorizeToOptions(getSelectedConditionTos())}
 
13
  import { IOperatorForm, ISwitchForm } from '../../interface';
14
  import { getOtherFieldValues } from '../../utils';
15
 
16
+ import styles from './index.less';
17
+
18
  const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
19
  const { t } = useTranslation();
20
  const buildCategorizeToOptions = useBuildFormSelectOptions(
 
57
  <Form.List name="conditions">
58
  {(fields, { add, remove }) => (
59
  <div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}>
60
+ {fields.map((field) => {
61
+ return (
62
+ <Card
63
+ size="small"
64
+ title={`Case ${field.name + 1}`}
65
+ key={field.key}
66
+ className={styles.caseCard}
67
+ extra={
68
+ <CloseOutlined
69
+ onClick={() => {
70
+ remove(field.name);
71
+ }}
72
+ />
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
+ >
75
+ <Form.Item noStyle dependencies={[field.name, 'items']}>
76
+ {({ getFieldValue }) =>
77
+ getFieldValue(['conditions', field.name, 'items'])
78
+ ?.length > 1 && (
79
+ <Form.Item
80
+ label={t('flow.logicalOperator')}
81
+ name={[field.name, 'logical_operator']}
82
+ >
83
+ <Select options={switchLogicOperatorOptions} />
84
+ </Form.Item>
85
+ )
86
+ }
87
+ </Form.Item>
88
+ <Form.Item label={t('flow.to')} name={[field.name, 'to']}>
89
+ <Select
90
+ allowClear
91
+ options={buildCategorizeToOptions([
92
+ form?.getFieldValue(SwitchElseTo),
93
+ ...getOtherFieldValues(
94
+ form!,
95
+ 'conditions',
96
+ field,
97
+ 'to',
98
+ ),
99
+ ])}
100
+ />
101
+ </Form.Item>
102
+ <Form.Item label="Condition">
103
+ <Form.List name={[field.name, 'items']}>
104
+ {(subFields, subOpt) => (
105
+ <div
106
+ style={{
107
+ display: 'flex',
108
+ flexDirection: 'column',
109
+ rowGap: 16,
110
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  >
112
+ {subFields.map((subField) => (
113
+ <Card
114
+ key={subField.key}
115
+ title={null}
116
+ size="small"
117
+ className={styles.conditionCard}
118
+ bordered
119
+ extra={
120
+ <CloseOutlined
121
+ onClick={() => {
122
+ subOpt.remove(subField.name);
123
+ }}
124
+ />
125
+ }
126
+ >
127
+ <Form.Item
128
+ label={t('flow.componentId')}
129
+ name={[subField.name, 'cpn_id']}
130
+ >
131
+ <Select
132
+ placeholder={t('flow.componentId')}
133
+ options={componentIdOptions}
134
+ />
135
+ </Form.Item>
136
+ <Form.Item
137
+ label={t('flow.operator')}
138
+ name={[subField.name, 'operator']}
139
+ >
140
+ <Select
141
+ placeholder={t('flow.operator')}
142
+ options={switchOperatorOptions}
143
+ />
144
+ </Form.Item>
145
+ <Form.Item
146
+ label={t('flow.value')}
147
+ name={[subField.name, 'value']}
148
+ >
149
+ <Input placeholder={t('flow.value')} />
150
+ </Form.Item>
151
+ </Card>
152
+ ))}
153
+ <Button
154
+ onClick={() => {
155
+ form?.setFieldValue(
156
+ ['conditions', field.name, 'logical_operator'],
157
+ SwitchLogicOperatorOptions[0],
158
+ );
159
+ subOpt.add({
160
+ operator: SwitchOperatorOptions[0].value,
161
+ });
162
+ }}
163
+ block
164
+ className={styles.addButton}
165
+ >
166
+ + Add Condition
167
+ </Button>
168
+ </div>
169
+ )}
170
+ </Form.List>
171
+ </Form.Item>
172
+ </Card>
173
+ );
174
+ })}
175
 
176
+ <Button onClick={() => add()} block className={styles.addButton}>
177
  + Add Case
178
  </Button>
179
  </div>
180
  )}
181
  </Form.List>
182
  <Divider />
183
+ <Form.Item
184
+ label={'ELSE'}
185
+ name={[SwitchElseTo]}
186
+ className={styles.elseCase}
187
+ >
188
  <Select
189
  allowClear
190
  options={buildCategorizeToOptions(getSelectedConditionTos())}
web/src/pages/flow/hooks.ts CHANGED
@@ -69,6 +69,7 @@ import useGraphStore, { RFState } from './store';
69
  import {
70
  buildDslComponentsByGraph,
71
  generateSwitchHandleText,
 
72
  receiveMessageError,
73
  replaceIdWithText,
74
  } from './utils';
@@ -250,6 +251,7 @@ export const useHandleDrop = () => {
250
  },
251
  sourcePosition: Position.Right,
252
  targetPosition: Position.Left,
 
253
  };
254
 
255
  addNode(newNode);
@@ -448,11 +450,16 @@ export const useValidateConnection = () => {
448
  return isValidConnection;
449
  };
450
 
451
- export const useHandleNodeNameChange = (node?: Node) => {
 
 
 
 
 
 
452
  const [name, setName] = useState<string>('');
453
  const { updateNodeName, nodes } = useGraphStore((state) => state);
454
- const previousName = node?.data.name;
455
- const id = node?.id;
456
 
457
  const handleNameBlur = useCallback(() => {
458
  const existsSameName = nodes.some((x) => x.data.name === name);
@@ -639,6 +646,7 @@ const ExcludedNodes = [
639
  Operator.Relevant,
640
  Operator.Begin,
641
  Operator.Answer,
 
642
  ];
643
 
644
  export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
@@ -655,3 +663,15 @@ export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
655
 
656
  return options;
657
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  import {
70
  buildDslComponentsByGraph,
71
  generateSwitchHandleText,
72
+ getNodeDragHandle,
73
  receiveMessageError,
74
  replaceIdWithText,
75
  } from './utils';
 
251
  },
252
  sourcePosition: Position.Right,
253
  targetPosition: Position.Left,
254
+ dragHandle: getNodeDragHandle(type),
255
  };
256
 
257
  addNode(newNode);
 
450
  return isValidConnection;
451
  };
452
 
453
+ export const useHandleNodeNameChange = ({
454
+ id,
455
+ data,
456
+ }: {
457
+ id?: string;
458
+ data: any;
459
+ }) => {
460
  const [name, setName] = useState<string>('');
461
  const { updateNodeName, nodes } = useGraphStore((state) => state);
462
+ const previousName = data?.name;
 
463
 
464
  const handleNameBlur = useCallback(() => {
465
  const existsSameName = nodes.some((x) => x.data.name === name);
 
646
  Operator.Relevant,
647
  Operator.Begin,
648
  Operator.Answer,
649
+ Operator.Note,
650
  ];
651
 
652
  export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
 
663
 
664
  return options;
665
  };
666
+
667
+ export const useGetComponentLabelByValue = (nodeId: string) => {
668
+ const options = useBuildComponentIdSelectOptions(nodeId);
669
+
670
+ const getLabel = useCallback(
671
+ (val?: string) => {
672
+ return options.find((x) => x.value === val)?.label;
673
+ },
674
+ [options],
675
+ );
676
+ return getLabel;
677
+ };