diff --git a/web/src/assets/svg/begin.svg b/web/src/assets/svg/begin.svg
new file mode 100644
index 0000000000000000000000000000000000000000..c1e77892f07047387c425673121c184391145aa8
--- /dev/null
+++ b/web/src/assets/svg/begin.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/web/src/assets/svg/concentrator.svg b/web/src/assets/svg/concentrator.svg
index d1ecdfad1b7318c414a7e73decb6d63767fde4bc..a5ddc57968a34987c2997cc14ddfa4178d66f7b0 100644
--- a/web/src/assets/svg/concentrator.svg
+++ b/web/src/assets/svg/concentrator.svg
@@ -1,7 +1,7 @@
\ No newline at end of file
diff --git a/web/src/assets/svg/keyword.svg b/web/src/assets/svg/keyword.svg
index 0ab63a101815231c538343b01d7178a1032d989c..9d0350dcf158365f6684ae29ee4a806f147d2a9a 100644
--- a/web/src/assets/svg/keyword.svg
+++ b/web/src/assets/svg/keyword.svg
@@ -2,8 +2,8 @@
p-id="11640" width="200" height="200">
+ p-id="11641" fill="#0f0e0f">
+ p-id="11642" fill="#0f0e0f">
\ No newline at end of file
diff --git a/web/src/assets/svg/llm/baai.svg b/web/src/assets/svg/llm/baai.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7e365e377071ffa14c1b1061a4ebc4fc6223bb06
--- /dev/null
+++ b/web/src/assets/svg/llm/baai.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/web/src/assets/svg/llm/nomic-ai.svg b/web/src/assets/svg/llm/nomic-ai.svg
new file mode 100644
index 0000000000000000000000000000000000000000..26e624a88b8b0848c5d4e357d00e4b1977b90d81
--- /dev/null
+++ b/web/src/assets/svg/llm/nomic-ai.svg
@@ -0,0 +1,7 @@
+
diff --git a/web/src/assets/svg/llm/sentence-transformers.svg b/web/src/assets/svg/llm/sentence-transformers.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f777b3d26ccfc3156731ec99c860b0b414279903
--- /dev/null
+++ b/web/src/assets/svg/llm/sentence-transformers.svg
@@ -0,0 +1,29 @@
+
\ No newline at end of file
diff --git a/web/src/assets/svg/llm/youdao.svg b/web/src/assets/svg/llm/youdao.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5af58851fd330ee4f545f2abcaebb1feb6524328
--- /dev/null
+++ b/web/src/assets/svg/llm/youdao.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/web/src/assets/svg/plus-circle-fill.svg b/web/src/assets/svg/plus-circle-fill.svg
index b0fc908a53a618028f8cf8ab68254aac9347bfd4..118318d2a5042ec78140d1fefe303d3d8ef4e307 100644
--- a/web/src/assets/svg/plus-circle-fill.svg
+++ b/web/src/assets/svg/plus-circle-fill.svg
@@ -1,5 +1,7 @@
\ No newline at end of file
diff --git a/web/src/components/knowledge-base-item.tsx b/web/src/components/knowledge-base-item.tsx
index 155b09af7d4c235386c75ce24f7cf11c636e7a20..331da8e68c9070a7d1f43584e4e7055b1f22f472 100644
--- a/web/src/components/knowledge-base-item.tsx
+++ b/web/src/components/knowledge-base-item.tsx
@@ -1,6 +1,7 @@
import { useTranslate } from '@/hooks/common-hooks';
import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
-import { Form, Select } from 'antd';
+import { UserOutlined } from '@ant-design/icons';
+import { Avatar, Form, Select, Space } from 'antd';
const KnowledgeBaseItem = () => {
const { t } = useTranslate('chat');
@@ -8,7 +9,12 @@ const KnowledgeBaseItem = () => {
const { list: knowledgeList } = useNextFetchKnowledgeList(true);
const knowledgeOptions = knowledgeList.map((x) => ({
- label: x.name,
+ label: (
+
+ } src={x.avatar} />
+ {x.name}
+
+ ),
value: x.id,
}));
diff --git a/web/src/components/llm-select/index.less b/web/src/components/llm-select/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..341768ae13a67310582f7f388b9159dde10e61a8
--- /dev/null
+++ b/web/src/components/llm-select/index.less
@@ -0,0 +1,3 @@
+.llmLabel {
+ font-size: 14px;
+}
diff --git a/web/src/components/llm-select/index.tsx b/web/src/components/llm-select/index.tsx
index 1100390b81fcd98c9e90c1666ec182b72e0efbc1..6804f1bd1c0f253636927b837ceea61d82fba184 100644
--- a/web/src/components/llm-select/index.tsx
+++ b/web/src/components/llm-select/index.tsx
@@ -7,9 +7,10 @@ interface IProps {
id?: string;
value?: string;
onChange?: (value: string) => void;
+ disabled?: boolean;
}
-const LLMSelect = ({ id, value, onChange }: IProps) => {
+const LLMSelect = ({ id, value, onChange, disabled }: IProps) => {
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
@@ -38,6 +39,7 @@ const LLMSelect = ({ id, value, onChange }: IProps) => {
id={id}
value={value}
onChange={onChange}
+ disabled={disabled}
/>
);
diff --git a/web/src/components/llm-select/llm-label.tsx b/web/src/components/llm-select/llm-label.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..dcdffab5e219ef43ab55da213a71f638f62e122b
--- /dev/null
+++ b/web/src/components/llm-select/llm-label.tsx
@@ -0,0 +1,31 @@
+import { LlmModelType } from '@/constants/knowledge';
+import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
+import { useMemo } from 'react';
+
+interface IProps {
+ id?: string;
+ value?: string;
+ onChange?: (value: string) => void;
+ disabled?: boolean;
+}
+
+const LLMLabel = ({ value }: IProps) => {
+ const modelOptions = useComposeLlmOptionsByModelTypes([
+ LlmModelType.Chat,
+ LlmModelType.Image2text,
+ ]);
+
+ const label = useMemo(() => {
+ for (const item of modelOptions) {
+ for (const option of item.options) {
+ if (option.value === value) {
+ return option.label;
+ }
+ }
+ }
+ }, [modelOptions, value]);
+
+ return
{label}
;
+};
+
+export default LLMLabel;
diff --git a/web/src/components/svg-icon.tsx b/web/src/components/svg-icon.tsx
index 2171fc42cc17898e59a55b1f0a57acc4bbdb1eea..b329177aca1b1c3e4988fa8546b309f30c834fa8 100644
--- a/web/src/components/svg-icon.tsx
+++ b/web/src/components/svg-icon.tsx
@@ -1,5 +1,8 @@
-import Icon from '@ant-design/icons';
+import { IconMap } from '@/constants/setting';
+import Icon, { UserOutlined } from '@ant-design/icons';
import { IconComponentProps } from '@ant-design/icons/lib/components/Icon';
+import { Avatar } from 'antd';
+import { AvatarSize } from 'antd/es/avatar/AvatarContext';
const importAll = (requireContext: __WebpackModuleApi.RequireContext) => {
const list = requireContext.keys().map((key) => {
@@ -36,4 +39,24 @@ const SvgIcon = ({ name, width, height, ...restProps }: IProps) => {
);
};
+export const LlmIcon = ({
+ name,
+ height = 48,
+ width = 48,
+ size = 'large',
+}: {
+ name: string;
+ height?: number;
+ width?: number;
+ size?: AvatarSize;
+}) => {
+ const icon = IconMap[name as keyof typeof IconMap];
+
+ return icon ? (
+
+ ) : (
+ } />
+ );
+};
+
export default SvgIcon;
diff --git a/web/src/constants/setting.ts b/web/src/constants/setting.ts
index e09dc4c9fc08b606fa4f289d088d45062f64761b..22c473580398db5d4781511262089b257487132b 100644
--- a/web/src/constants/setting.ts
+++ b/web/src/constants/setting.ts
@@ -19,6 +19,56 @@ export const UserSettingRouteMap = {
[UserSettingRouteKey.Logout]: 'Log out',
};
+// Please lowercase the file name
+export const IconMap = {
+ 'Tongyi-Qianwen': 'tongyi',
+ Moonshot: 'moonshot',
+ OpenAI: 'openai',
+ 'ZHIPU-AI': 'zhipu',
+ 文心一言: 'wenxin',
+ Ollama: 'ollama',
+ Xinference: 'xinference',
+ DeepSeek: 'deepseek',
+ VolcEngine: 'volc_engine',
+ BaiChuan: 'baichuan',
+ Jina: 'jina',
+ MiniMax: 'chat-minimax',
+ Mistral: 'mistral',
+ 'Azure-OpenAI': 'azure',
+ Bedrock: 'bedrock',
+ Gemini: 'gemini',
+ Groq: 'groq-next',
+ OpenRouter: 'open-router',
+ LocalAI: 'local-ai',
+ StepFun: 'stepfun',
+ NVIDIA: 'nvidia',
+ 'LM-Studio': 'lm-studio',
+ 'OpenAI-API-Compatible': 'openai-api',
+ cohere: 'cohere',
+ LeptonAI: 'lepton-ai',
+ TogetherAI: 'together-ai',
+ PerfXCloud: 'perfx-cloud',
+ Upstage: 'upstage',
+ 'novita.ai': 'novita-ai',
+ SILICONFLOW: 'siliconflow',
+ '01.AI': 'yi',
+ Replicate: 'replicate',
+ 'Tencent Hunyuan': 'hunyuan',
+ 'XunFei Spark': 'spark',
+ BaiduYiyan: 'yiyan',
+ 'Fish Audio': 'fish-audio',
+ 'Tencent Cloud': 'tencent-cloud',
+ Anthropic: 'anthropic',
+ 'Voyage AI': 'voyage',
+ 'Google Cloud': 'google-cloud',
+ HuggingFace: 'huggingface',
+ Youdao: 'youdao',
+ BAAI: 'baai',
+ 'nomic-ai': 'nomic-ai',
+ jinaai: 'jina',
+ 'sentence-transformers': 'sentence-transformers',
+};
+
export const TimezoneList = [
'UTC-11\tPacific/Midway',
'UTC-11\tPacific/Niue',
diff --git a/web/src/hooks/llm-hooks.ts b/web/src/hooks/llm-hooks.tsx
similarity index 93%
rename from web/src/hooks/llm-hooks.ts
rename to web/src/hooks/llm-hooks.tsx
index 534b896ddd2a252bb8785071e92b49e4c2f3813c..2ee3236c1d96e58788cfb5e0d408cdab0eeca265 100644
--- a/web/src/hooks/llm-hooks.ts
+++ b/web/src/hooks/llm-hooks.tsx
@@ -1,3 +1,4 @@
+import { LlmIcon } from '@/components/svg-icon';
import { LlmModelType } from '@/constants/knowledge';
import { ResponseGetType } from '@/interfaces/database/base';
import {
@@ -13,7 +14,7 @@ import {
import userService from '@/services/user-service';
import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/common-util';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
-import { message } from 'antd';
+import { Flex, message } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -53,6 +54,14 @@ export const useSelectLlmOptions = () => {
return embeddingModelOptions;
};
+const getLLMIconName = (fid: string, llm_name: string) => {
+ if (fid === 'FastEmbed') {
+ return llm_name.split('/').at(0) ?? '';
+ }
+
+ return fid;
+};
+
export const useSelectLlmOptionsByModelType = () => {
const llmInfo: IThirdOAIModelCollection = useFetchLlmList();
@@ -71,7 +80,17 @@ export const useSelectLlmOptionsByModelType = () => {
x.available,
)
.map((x) => ({
- label: x.llm_name,
+ label: (
+
+
+ {x.llm_name}
+
+ ),
value: `${x.llm_name}@${x.fid}`,
disabled: !x.available,
})),
diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts
index 5d8cfcf3782730551b69c018f15dd4b57e3814a0..402f9b2c856e42a141ae89d18c96d845f96dd30e 100644
--- a/web/src/locales/en.ts
+++ b/web/src/locales/en.ts
@@ -728,18 +728,20 @@ The above is the content you need to summarize.`,
'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.',
wikipedia: 'Wikipedia',
pubMed: 'PubMed',
+ pubMedDescription:
+ '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.',
email: 'Email',
emailTip:
'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.',
arXiv: 'ArXiv',
- arXivTip:
+ arXivDescription:
'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.',
sortBy: 'Sort by',
submittedDate: 'Submitted date',
lastUpdatedDate: 'Last updated date',
relevance: 'Relevance',
google: 'Google',
- googleTip:
+ googleDescription:
'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.',
bing: 'Bing',
bingTip:
diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts
index 4157c298e094ddc940968d84312e54386d90e88b..9335c3619db132cb9e2f8a58cc6a2241150e2c08 100644
--- a/web/src/locales/zh-traditional.ts
+++ b/web/src/locales/zh-traditional.ts
@@ -680,18 +680,21 @@ export default {
messageHistoryWindowSizeTip:
'LLM需要查看的對話記錄的視窗大小。越大越好。但要注意LLM的最大內容長度。',
wikipedia: '維基百科',
+ pubMed: 'PubMed',
+ pubMedDescription:
+ '此元件用於從 https://pubmed.ncbi.nlm.nih.gov/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。電子郵件是必填欄位。',
email: '信箱',
emailTip:
'此元件用於從 https://pubmed.ncbi.nlm.nih.gov/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。電子郵件是必填欄位。',
arXiv: 'ArXiv',
- arXivTip:
+ arXivDescription:
'此元件用於從 https://arxiv.org/ 取得搜尋結果。通常,它充當知識庫的補充。 Top N 指定您需要適應的搜尋結果的數量。',
sortBy: '排序方式',
submittedDate: '提交日期',
lastUpdatedDate: '最後更新日期',
relevance: '關聯',
google: 'Google',
- googleTip:
+ googleDescription:
'此元件用於從https://www.google.com/取得搜尋結果。通常,它作為知識庫的補充。 Top N 和 SerpApi API 金鑰指定您需要調整的搜尋結果數量。',
bing: 'Bing',
bingTip:
diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts
index 693929d9fef9b186c7e1d7bef4ea7a3af8264165..e0a32171cc0410e96424bfbf64e10e493e208083 100644
--- a/web/src/locales/zh.ts
+++ b/web/src/locales/zh.ts
@@ -688,7 +688,7 @@ export default {
keywordExtract: '关键词',
keywordExtractDescription: `该组件用于从用户的问题中提取关键词。Top N指定需要提取的关键词数量。`,
baidu: '百度',
- baiduDescription: `此元件用於取得www.baidu.com的搜尋結果。通常作為知識庫的補充。 Top N指定您需要適配的搜尋結果數。`,
+ baiduDescription: `此组件用于从 www.baidu.com 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数量。`,
duckDuckGo: 'DuckDuckGo',
duckDuckGoDescription:
'此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要調整的搜尋結果數。',
@@ -700,18 +700,21 @@ export default {
messageHistoryWindowSizeTip:
'LLM 需要查看的对话历史窗口大小。越大越好。但要注意 LLM 的最大内容长度。',
wikipedia: '维基百科',
- email: '邮箱',
emailTip:
'此组件用于从 https://pubmed.ncbi.nlm.nih.gov/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数。电子邮件是必填字段。',
+ email: '邮箱',
+ pubMed: 'PubMed',
+ pubMedDescription:
+ '此组件用于从 https://pubmed.ncbi.nlm.nih.gov/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数。电子邮件是必填字段。',
arXiv: 'ArXiv',
- arXivTip:
+ arXivDescription:
'此组件用于从 https://arxiv.org/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数量。',
sortBy: '排序方式',
submittedDate: '提交日期',
lastUpdatedDate: '最后更新日期',
relevance: '关联',
google: 'Google',
- googleTip:
+ googleDescription:
'此组件用于从https://www.google.com/获取搜索结果。通常,它作为知识库的补充。Top N 和 SerpApi API 密钥指定您需要调整的搜索结果数量。',
bing: 'Bing',
bingTip:
diff --git a/web/src/pages/flow/canvas/index.tsx b/web/src/pages/flow/canvas/index.tsx
index 39cce7096ffcb7eb147e727f3bedf5d192c412eb..8d6751c0790ec6a43fa1ab8590e7cedc55add4ae 100644
--- a/web/src/pages/flow/canvas/index.tsx
+++ b/web/src/pages/flow/canvas/index.tsx
@@ -22,9 +22,15 @@ import styles from './index.less';
import { RagNode } from './node';
import { BeginNode } from './node/begin-node';
import { CategorizeNode } from './node/categorize-node';
+import { GenerateNode } from './node/generate-node';
+import { KeywordNode } from './node/keyword-node';
import { LogicNode } from './node/logic-node';
+import { MessageNode } from './node/message-node';
import NoteNode from './node/note-node';
import { RelevantNode } from './node/relevant-node';
+import { RetrievalNode } from './node/retrieval-node';
+import { RewriteNode } from './node/rewrite-node';
+import { SwitchNode } from './node/switch-node';
const nodeTypes = {
ragNode: RagNode,
@@ -33,6 +39,12 @@ const nodeTypes = {
relevantNode: RelevantNode,
logicNode: LogicNode,
noteNode: NoteNode,
+ switchNode: SwitchNode,
+ generateNode: GenerateNode,
+ retrievalNode: RetrievalNode,
+ messageNode: MessageNode,
+ rewriteNode: RewriteNode,
+ keywordNode: KeywordNode,
};
const edgeTypes = {
diff --git a/web/src/pages/flow/canvas/node/begin-node.tsx b/web/src/pages/flow/canvas/node/begin-node.tsx
index a802cc84d881b6e4d12657df7ec4ec4a62bf0227..2506870471d76b39dd67845daef6f5ee3a496a61 100644
--- a/web/src/pages/flow/canvas/node/begin-node.tsx
+++ b/web/src/pages/flow/canvas/node/begin-node.tsx
@@ -1,25 +1,24 @@
-import { useTranslate } from '@/hooks/common-hooks';
import { Flex } from 'antd';
import classNames from 'classnames';
-import lowerFirst from 'lodash/lowerFirst';
+import { useTranslation } from 'react-i18next';
import { Handle, NodeProps, Position } from 'reactflow';
import { Operator, operatorMap } from '../../constant';
import { NodeData } from '../../interface';
+import OperatorIcon from '../../operator-icon';
+import { RightHandleStyle } from './handle-icon';
import styles from './index.less';
// TODO: do not allow other nodes to connect to this node
-export function BeginNode({ id, data, selected }: NodeProps) {
- const { t } = useTranslate('flow');
+export function BeginNode({ selected, data }: NodeProps) {
+ const { t } = useTranslation();
+
return (
) {
position={Position.Right}
isConnectable
className={styles.handle}
+ style={RightHandleStyle}
>
-
- {t(lowerFirst(data.label))}
+
+
+
+ {t(`flow.begin`)}
-
);
}
diff --git a/web/src/pages/flow/canvas/node/categorize-node.tsx b/web/src/pages/flow/canvas/node/categorize-node.tsx
index fcdace773b7436c92c90cd84582556614620efe2..b367bd93f002201a439bcd1b3aa7872739d998d7 100644
--- a/web/src/pages/flow/canvas/node/categorize-node.tsx
+++ b/web/src/pages/flow/canvas/node/categorize-node.tsx
@@ -1,22 +1,17 @@
-import { useTranslate } from '@/hooks/common-hooks';
+import LLMLabel from '@/components/llm-select/llm-label';
import { Flex } from 'antd';
import classNames from 'classnames';
-import lowerFirst from 'lodash/lowerFirst';
+import { get } from 'lodash';
import { Handle, NodeProps, Position } from 'reactflow';
-import { Operator, SwitchElseTo, operatorMap } from '../../constant';
import { NodeData } from '../../interface';
-import OperatorIcon from '../../operator-icon';
-import CategorizeHandle from './categorize-handle';
-import NodeDropdown from './dropdown';
+import { RightHandleStyle } from './handle-icon';
import { useBuildCategorizeHandlePositions } from './hooks';
import styles from './index.less';
+import NodeHeader from './node-header';
import NodePopover from './popover';
export function CategorizeNode({ id, data, selected }: NodeProps) {
- const style = operatorMap[data.label as Operator];
- const { t } = useTranslate('flow');
const { positions } = useBuildCategorizeHandlePositions({ data, id });
- const operatorName = data.label;
return (
@@ -24,10 +19,6 @@ export function CategorizeNode({ id, data, selected }: NodeProps) {
className={classNames(styles.logicNode, {
[styles.selectedNode]: selected,
})}
- style={{
- backgroundColor: style.backgroundColor,
- color: style.color,
- }}
>
) {
className={styles.handle}
id={'a'}
>
-
-
- {operatorName === Operator.Switch && (
-
- To
-
- )}
- {positions.map((position, idx) => {
- return (
-
- );
- })}
-
-
- {t(lowerFirst(data.label))}
-
+
+
+
+
+
+
+
+ {positions.map((position, idx) => {
+ return (
+
+ );
+ })}
-
);
diff --git a/web/src/pages/flow/canvas/node/dropdown.tsx b/web/src/pages/flow/canvas/node/dropdown.tsx
index f2b77509be52a364eed16b85d5ee52f780610abe..aefa8147541da7f528d3b212b1346e0fd3f41036 100644
--- a/web/src/pages/flow/canvas/node/dropdown.tsx
+++ b/web/src/pages/flow/canvas/node/dropdown.tsx
@@ -38,7 +38,7 @@ const NodeDropdown = ({ id, iconFontColor }: IProps) => {
return (
) {
+ const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
+ const getLabel = useGetComponentLabelByValue(id);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {parameters.map((x) => (
+
+
+
+ {getLabel(x.component_id)}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/web/src/pages/flow/canvas/node/handle-icon.tsx b/web/src/pages/flow/canvas/node/handle-icon.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c71f05c3bf868dd6ded55443a53cee1780320a8e
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/handle-icon.tsx
@@ -0,0 +1,20 @@
+import { PlusOutlined } from '@ant-design/icons';
+import { CSSProperties } from 'react';
+
+export const HandleIcon = () => {
+ return (
+
+ );
+};
+
+export const RightHandleStyle: CSSProperties = {
+ right: -5,
+};
+
+export const LeftHandleStyle: CSSProperties = {
+ left: -7,
+};
+
+export default HandleIcon;
diff --git a/web/src/pages/flow/canvas/node/hooks.ts b/web/src/pages/flow/canvas/node/hooks.ts
index cdfecd7fe110a80aaa4212f82cbfcc75d10681b6..c1bd80025c7f690ca362811c1f5507dff9b9d2ae 100644
--- a/web/src/pages/flow/canvas/node/hooks.ts
+++ b/web/src/pages/flow/canvas/node/hooks.ts
@@ -1,14 +1,13 @@
import get from 'lodash/get';
-import pick from 'lodash/pick';
-import { useEffect, useMemo, useState } from 'react';
+import { useEffect, useMemo } from 'react';
import { useUpdateNodeInternals } from 'reactflow';
-import { Operator } from '../../constant';
-import { IPosition, NodeData } from '../../interface';
+import { SwitchElseTo } from '../../constant';
import {
- buildNewPositionMap,
- generateSwitchHandleText,
- isKeysEqual,
-} from '../../utils';
+ ICategorizeItemResult,
+ ISwitchCondition,
+ NodeData,
+} from '../../interface';
+import { generateSwitchHandleText } from '../../utils';
export const useBuildCategorizeHandlePositions = ({
data,
@@ -17,85 +16,86 @@ export const useBuildCategorizeHandlePositions = ({
id: string;
data: NodeData;
}) => {
- const operatorName = data.label as Operator;
const updateNodeInternals = useUpdateNodeInternals();
- const [positionMap, setPositionMap] = useState>({});
- const categoryData = useMemo(() => {
- if (operatorName === Operator.Categorize) {
- return get(data, `form.category_description`, {});
- } else if (operatorName === Operator.Switch) {
- return get(data, 'form.conditions', []);
- }
- return {};
- }, [data, operatorName]);
+ const categoryData: ICategorizeItemResult = useMemo(() => {
+ return get(data, `form.category_description`, {});
+ }, [data]);
const positions = useMemo(() => {
- return Object.keys(categoryData)
- .map((x, idx) => {
- const position = positionMap[x];
- let text = x;
- if (operatorName === Operator.Switch) {
- text = generateSwitchHandleText(idx);
- }
- return { text, ...position };
- })
- .filter((x) => typeof x?.right === 'number');
- }, [categoryData, positionMap, operatorName]);
-
- useEffect(() => {
- // Cache used coordinates
- setPositionMap((state) => {
- const categoryDataKeys = Object.keys(categoryData);
- const stateKeys = Object.keys(state);
- if (!isKeysEqual(categoryDataKeys, stateKeys)) {
- const { newPositionMap, intersectionKeys } = buildNewPositionMap(
- categoryDataKeys,
- state,
- );
-
- const nextPositionMap = {
- ...pick(state, intersectionKeys),
- ...newPositionMap,
- };
+ const list: Array<{
+ text: string;
+ top: number;
+ idx: number;
+ }> = [];
- return nextPositionMap;
- }
- return state;
+ Object.keys(categoryData).forEach((x, idx) => {
+ list.push({
+ text: x,
+ idx,
+ top: idx === 0 ? 98 : list[idx - 1].top + 8 + 26,
+ });
});
+
+ return list;
}, [categoryData]);
useEffect(() => {
updateNodeInternals(id);
- }, [id, updateNodeInternals, positionMap]);
+ }, [id, updateNodeInternals, categoryData]);
return { positions };
};
-// export const useBuildSwitchHandlePositions = ({
-// data,
-// id,
-// }: {
-// id: string;
-// data: NodeData;
-// }) => {
-// const [positionMap, setPositionMap] = useState>({});
-// const conditions = useMemo(() => get(data, 'form.conditions', []), [data]);
-// const updateNodeInternals = useUpdateNodeInternals();
+export const useBuildSwitchHandlePositions = ({
+ data,
+ id,
+}: {
+ id: string;
+ data: NodeData;
+}) => {
+ const updateNodeInternals = useUpdateNodeInternals();
-// const positions = useMemo(() => {
-// return conditions
-// .map((x, idx) => {
-// const text = `Item ${idx}`;
-// const position = positionMap[text];
-// return { text: text, ...position };
-// })
-// .filter((x) => typeof x?.right === 'number');
-// }, [conditions, positionMap]);
+ const conditions: ISwitchCondition[] = useMemo(() => {
+ return get(data, 'form.conditions', []);
+ }, [data]);
-// useEffect(() => {
-// updateNodeInternals(id);
-// }, [id, updateNodeInternals, positionMap]);
+ const positions = useMemo(() => {
+ const list: Array<{
+ text: string;
+ top: number;
+ idx: number;
+ condition?: ISwitchCondition;
+ }> = [];
+
+ [...conditions, ''].forEach((x, idx) => {
+ let top = idx === 0 ? 58 : list[idx - 1].top + 32; // case number (Case 1) height + flex gap
+ if (idx - 1 >= 0) {
+ const previousItems = conditions[idx - 1]?.items ?? [];
+ if (previousItems.length > 0) {
+ top += 12; // ConditionBlock padding
+ top += previousItems.length * 22; // condition variable height
+ top += (previousItems.length - 1) * 25; // operator height
+ }
+ }
+
+ list.push({
+ text:
+ idx < conditions.length
+ ? generateSwitchHandleText(idx)
+ : SwitchElseTo,
+ idx,
+ top,
+ condition: typeof x === 'string' ? undefined : x,
+ });
+ });
-// return { positions };
-// };
+ return list;
+ }, [conditions]);
+
+ useEffect(() => {
+ updateNodeInternals(id);
+ }, [id, updateNodeInternals, conditions]);
+
+ return { positions };
+};
diff --git a/web/src/pages/flow/canvas/node/index.less b/web/src/pages/flow/canvas/node/index.less
index e41ab176c548c6b6ab66a4a4a8cab4444ab26860..a9907df2dcd86d32d28d18e155aa4a752630aa6b 100644
--- a/web/src/pages/flow/canvas/node/index.less
+++ b/web/src/pages/flow/canvas/node/index.less
@@ -3,22 +3,16 @@
-6px 0 12px 0 rgba(179, 177, 177, 0.08),
-3px 0 6px -4px rgba(0, 0, 0, 0.12),
-6px 0 16px 6px rgba(0, 0, 0, 0.05);
+
+ padding: 10px;
+ border-radius: 10px;
+ background: white;
+ width: 200px;
}
.ragNode {
- position: relative;
.commonNode();
- padding: 5px;
- border-radius: 5px;
- background: white;
- width: 50px;
- height: 50px;
- border-radius: 50%;
- display: flex;
- // align-items: center;
- // justify-self: center;
- justify-content: center;
.nodeName {
font-size: 10px;
color: black;
@@ -28,23 +22,10 @@
color: #777;
font-size: 12px;
}
- .type {
- // font-size: 12px;
- }
.description {
font-size: 10px;
}
- .bottomBox {
- position: absolute;
- bottom: -34px;
- background: white;
- padding: 2px 5px;
- border-radius: 5px;
- box-shadow:
- -6px 0 12px 0 rgba(179, 177, 177, 0.08),
- -3px 0 6px -4px rgba(0, 0, 0, 0.12),
- -6px 0 16px 6px rgba(0, 0, 0, 0.05);
- }
+
.categorizeAnchorPointText {
position: absolute;
top: -4px;
@@ -53,14 +34,25 @@
}
}
+@lightBackgroundColor: rgba(150, 150, 150, 0.1);
+@darkBackgroundColor: rgba(150, 150, 150, 0.2);
+
.selectedNode {
- border: 1px solid rgb(59, 118, 244);
+ border: 1.5px solid rgb(59, 118, 244);
}
.handle {
display: inline-flex;
- text-align: center;
- // align-items: center;
+ align-items: center;
+ justify-content: center;
+ width: 12px;
+ height: 12px;
+ background: rgb(59, 88, 253);
+ border: 1px solid white;
+ z-index: 1;
+ background-image: url('@/assets/svg/plus.svg');
+ background-size: cover;
+ background-position: center;
}
.jsonView {
@@ -71,19 +63,8 @@
}
.logicNode {
- position: relative;
.commonNode();
- padding: 5px;
- border-radius: 5px;
- background: white;
- width: 100px;
- height: 100px;
- border-radius: 50%;
- display: flex;
- // align-items: center;
- // justify-self: center;
- justify-content: center;
.nodeName {
font-size: 10px;
color: black;
@@ -93,41 +74,122 @@
color: #777;
font-size: 12px;
}
- .type {
- // font-size: 12px;
- }
+
.description {
font-size: 10px;
}
- .bottomBox {
- position: absolute;
- bottom: -34px;
- background: white;
- padding: 2px 5px;
- border-radius: 5px;
- box-shadow:
- -6px 0 12px 0 rgba(179, 177, 177, 0.08),
- -3px 0 6px -4px rgba(0, 0, 0, 0.12),
- -6px 0 16px 6px rgba(0, 0, 0, 0.05);
- }
+
.categorizeAnchorPointText {
position: absolute;
top: -4px;
left: 8px;
white-space: nowrap;
}
+ .relevantSourceLabel {
+ font-size: 10px;
+ }
}
.noteNode {
.commonNode();
- width: 140px;
- padding: 4px 6px 6px;
+ min-width: 140px;
+ width: auto;
+ height: 100%;
+ padding: 0;
border-radius: 10px;
- background-color: #dbf8f4;
+ min-height: 128px;
.noteTitle {
+ background-color: #edfcff;
font-size: 12px;
+ padding: 6px 6px 4px;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
}
.noteForm {
margin-top: 4px;
+ height: calc(100% - 50px);
+ }
+ .noteName {
+ padding: 0px 4px;
+ }
+ .noteTextarea {
+ resize: none;
+ border: 0;
+ border-radius: 0;
+ height: 100%;
+ &:focus {
+ border: none;
+ box-shadow: none;
+ }
+ }
+}
+
+.nodeText {
+ padding-inline: 0.4em;
+ padding-block: 0.2em 0.1em;
+ background: @lightBackgroundColor;
+ border-radius: 3px;
+ min-height: 22px;
+ .textEllipsis();
+}
+
+.nodeTitle {
+ font-weight: 600;
+ text-align: center;
+ .textEllipsis();
+}
+
+.nodeHeader {
+ padding-bottom: 12px;
+}
+
+.zeroDivider {
+ margin: 0 !important;
+}
+
+.conditionBlock {
+ border-radius: 4px;
+ padding: 6px;
+ background: @lightBackgroundColor;
+}
+
+.conditionLine {
+ border-radius: 4px;
+ padding: 0 4px;
+ background: @darkBackgroundColor;
+ .textEllipsis();
+}
+
+.conditionKey {
+ flex: 1;
+}
+
+.conditionOperator {
+ padding: 0 2px;
+ text-align: center;
+}
+
+.relevantLabel {
+ text-align: right;
+}
+
+.knowledgeNodeName {
+ .textEllipsis();
+}
+
+.messageNodeContainer {
+ overflow-y: auto;
+ max-height: 300px;
+}
+
+.generateParameters {
+ padding-top: 8px;
+ label {
+ flex: 2;
+ .textEllipsis();
+ }
+ .parameterValue {
+ flex: 3;
+ .conditionLine;
}
}
diff --git a/web/src/pages/flow/canvas/node/index.tsx b/web/src/pages/flow/canvas/node/index.tsx
index 5ba3469338092f2c9bd3a6aba10e831d3e25c0fd..0001507fb55f810913ea4a1454869db2fd6acb60 100644
--- a/web/src/pages/flow/canvas/node/index.tsx
+++ b/web/src/pages/flow/canvas/node/index.tsx
@@ -1,12 +1,9 @@
-import { Flex } from 'antd';
import classNames from 'classnames';
-import pick from 'lodash/pick';
import { Handle, NodeProps, Position } from 'reactflow';
-import { Operator, operatorMap } from '../../constant';
import { NodeData } from '../../interface';
-import OperatorIcon from '../../operator-icon';
-import NodeDropdown from './dropdown';
+import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
+import NodeHeader from './node-header';
import NodePopover from './popover';
export function RagNode({
@@ -15,17 +12,12 @@ export function RagNode({
isConnectable = true,
selected,
}: NodeProps) {
- const style = operatorMap[data.label as Operator];
-
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
);
diff --git a/web/src/pages/flow/canvas/node/keyword-node.tsx b/web/src/pages/flow/canvas/node/keyword-node.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6c74a0b97f483da1912fb376ade78bf2fa49bd37
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/keyword-node.tsx
@@ -0,0 +1,54 @@
+import LLMLabel from '@/components/llm-select/llm-label';
+import classNames from 'classnames';
+import { get } from 'lodash';
+import { Handle, NodeProps, Position } from 'reactflow';
+import { NodeData } from '../../interface';
+import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
+import styles from './index.less';
+import NodeHeader from './node-header';
+import NodePopover from './popover';
+
+export function KeywordNode({
+ id,
+ data,
+ isConnectable = true,
+ selected,
+}: NodeProps) {
+ return (
+
+
+
+ );
+}
diff --git a/web/src/pages/flow/canvas/node/logic-node.tsx b/web/src/pages/flow/canvas/node/logic-node.tsx
index e155694b63fb351b9e2e90a543712049c90c6a82..3501917d90fab9ad2359b14b15a5757046515ef5 100644
--- a/web/src/pages/flow/canvas/node/logic-node.tsx
+++ b/web/src/pages/flow/canvas/node/logic-node.tsx
@@ -1,38 +1,23 @@
-import { useTranslate } from '@/hooks/common-hooks';
-import { Flex } from 'antd';
import classNames from 'classnames';
-import lowerFirst from 'lodash/lowerFirst';
-import pick from 'lodash/pick';
import { Handle, NodeProps, Position } from 'reactflow';
-import { Operator, operatorMap } from '../../constant';
import { NodeData } from '../../interface';
-import OperatorIcon from '../../operator-icon';
-import NodeDropdown from './dropdown';
+import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
+import NodeHeader from './node-header';
import NodePopover from './popover';
-const ZeroGapOperators = [
- Operator.RewriteQuestion,
- Operator.KeywordExtract,
- Operator.ArXiv,
-];
-
export function LogicNode({
id,
data,
isConnectable = true,
selected,
}: NodeProps) {
- const style = operatorMap[data.label as Operator];
- const { t } = useTranslate('flow');
-
return (
-
-
- x === data.label) ? 0 : 6}
- >
-
-
-
-
-
-
- {t(lowerFirst(data.label))}
-
-
-
-
-
-
-
-
+
);
diff --git a/web/src/pages/flow/canvas/node/message-node.tsx b/web/src/pages/flow/canvas/node/message-node.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a0db834919dc552798040ed346e530f441227f45
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/message-node.tsx
@@ -0,0 +1,63 @@
+import { Flex } from 'antd';
+import classNames from 'classnames';
+import { get } from 'lodash';
+import { Handle, NodeProps, Position } from 'reactflow';
+import { NodeData } from '../../interface';
+import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
+import styles from './index.less';
+import NodeHeader from './node-header';
+import NodePopover from './popover';
+
+export function MessageNode({
+ id,
+ data,
+ isConnectable = true,
+ selected,
+}: NodeProps) {
+ const messages: string[] = get(data, 'form.messages', []);
+
+ return (
+
+
+
+
+ 0,
+ })}
+ >
+
+
+ {messages.map((message, idx) => {
+ return (
+
+ {message}
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/web/src/pages/flow/canvas/node/node-header.tsx b/web/src/pages/flow/canvas/node/node-header.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4386f9c475283bdea479821fae797616a89c4993
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/node-header.tsx
@@ -0,0 +1,35 @@
+import { Flex } from 'antd';
+
+import { Operator, operatorMap } from '../../constant';
+import OperatorIcon from '../../operator-icon';
+import NodeDropdown from './dropdown';
+import styles from './index.less';
+
+interface IProps {
+ id: string;
+ label: string;
+ name: string;
+ gap?: number;
+ className?: string;
+}
+
+const NodeHeader = ({ label, id, name, gap = 4, className }: IProps) => {
+ return (
+
+
+ {name}
+
+
+ );
+};
+
+export default NodeHeader;
diff --git a/web/src/pages/flow/canvas/node/note-node.tsx b/web/src/pages/flow/canvas/node/note-node.tsx
index 1686255e08c3a7a8f77f9853a485e2a89af61361..5028baa1d892be7a8f1286e7d4b7fbcb4a4a9ecf 100644
--- a/web/src/pages/flow/canvas/node/note-node.tsx
+++ b/web/src/pages/flow/canvas/node/note-node.tsx
@@ -1,20 +1,33 @@
-import { Flex, Form, Input, Space } from 'antd';
-import { NodeProps } from 'reactflow';
+import { Flex, Form, Input } from 'antd';
+import classNames from 'classnames';
+import { NodeProps, NodeResizeControl } from 'reactflow';
import { NodeData } from '../../interface';
import NodeDropdown from './dropdown';
import SvgIcon from '@/components/svg-icon';
-import { useEffect } from 'react';
+import { memo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
-import { useHandleFormValuesChange } from '../../hooks';
+import {
+ useHandleFormValuesChange,
+ useHandleNodeNameChange,
+} from '../../hooks';
import styles from './index.less';
const { TextArea } = Input;
+const controlStyle = {
+ background: 'transparent',
+ border: 'none',
+};
+
function NoteNode({ data, id }: NodeProps) {
const { t } = useTranslation();
const [form] = Form.useForm();
+ const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
+ id,
+ data,
+ });
const { handleValuesChange } = useHandleFormValuesChange(id);
useEffect(() => {
@@ -22,25 +35,51 @@ function NoteNode({ data, id }: NodeProps) {
}, [form, data?.form]);
return (
-
-
-
+ <>
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ >
);
}
-export default NoteNode;
+export default memo(NoteNode);
diff --git a/web/src/pages/flow/canvas/node/relevant-node.tsx b/web/src/pages/flow/canvas/node/relevant-node.tsx
index c27c94f3fc10efb6506d9c2301086c1680e2d6dc..3820282eafb578e4162dbdca885b7460c6a47e24 100644
--- a/web/src/pages/flow/canvas/node/relevant-node.tsx
+++ b/web/src/pages/flow/canvas/node/relevant-node.tsx
@@ -1,28 +1,23 @@
-import { useTranslate } from '@/hooks/common-hooks';
import { Flex } from 'antd';
import classNames from 'classnames';
-import lowerFirst from 'lodash/lowerFirst';
-import pick from 'lodash/pick';
import { Handle, NodeProps, Position } from 'reactflow';
-import { Operator, operatorMap } from '../../constant';
import { NodeData } from '../../interface';
-import OperatorIcon from '../../operator-icon';
-import NodeDropdown from './dropdown';
+import { RightHandleStyle } from './handle-icon';
+import NodePopover from './popover';
-import CategorizeHandle from './categorize-handle';
+import { get } from 'lodash';
import styles from './index.less';
-import NodePopover from './popover';
+import NodeHeader from './node-header';
export function RelevantNode({ id, data, selected }: NodeProps) {
- const style = operatorMap[data.label as Operator];
- const { t } = useTranslate('flow');
+ const yes = get(data, 'form.yes');
+ const no = get(data, 'form.no');
return (
) {
id={'a'}
>
-
-
-
-
-
-
-
-
- {t(lowerFirst(data.label))}
-
+
+
+
+
+ Yes
+ {yes}
-
-
+
+ No
+ {no}
-
);
diff --git a/web/src/pages/flow/canvas/node/retrieval-node.tsx b/web/src/pages/flow/canvas/node/retrieval-node.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8ab79f62c896ebb7910f642a48772be03f31a9c6
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/retrieval-node.tsx
@@ -0,0 +1,85 @@
+import { useNextFetchKnowledgeList } from '@/hooks/knowledge-hooks';
+import { UserOutlined } from '@ant-design/icons';
+import { Avatar, Flex } from 'antd';
+import classNames from 'classnames';
+import { get } from 'lodash';
+import { useMemo } from 'react';
+import { Handle, NodeProps, Position } from 'reactflow';
+import { NodeData } from '../../interface';
+import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
+import styles from './index.less';
+import NodeHeader from './node-header';
+import NodePopover from './popover';
+
+export function RetrievalNode({
+ id,
+ data,
+ isConnectable = true,
+ selected,
+}: NodeProps) {
+ const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
+ const { list: knowledgeList } = useNextFetchKnowledgeList(true);
+ const knowledgeBases = useMemo(() => {
+ return knowledgeBaseIds.map((x) => {
+ const item = knowledgeList.find((y) => x === y.id);
+ return {
+ name: item?.name,
+ avatar: item?.avatar,
+ id: x,
+ };
+ });
+ }, [knowledgeList, knowledgeBaseIds]);
+
+ return (
+
+
+
+
+ 0,
+ })}
+ >
+
+ {knowledgeBases.map((knowledge) => {
+ return (
+
+
+ }
+ src={knowledge.avatar}
+ />
+
+ {knowledge.name}
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/web/src/pages/flow/canvas/node/rewrite-node.tsx b/web/src/pages/flow/canvas/node/rewrite-node.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8f645eceac4bd0e9937dbae91a5065a60e044d1d
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/rewrite-node.tsx
@@ -0,0 +1,54 @@
+import LLMLabel from '@/components/llm-select/llm-label';
+import classNames from 'classnames';
+import { get } from 'lodash';
+import { Handle, NodeProps, Position } from 'reactflow';
+import { NodeData } from '../../interface';
+import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
+import styles from './index.less';
+import NodeHeader from './node-header';
+import NodePopover from './popover';
+
+export function RewriteNode({
+ id,
+ data,
+ isConnectable = true,
+ selected,
+}: NodeProps) {
+ return (
+
+
+
+ );
+}
diff --git a/web/src/pages/flow/canvas/node/switch-node.tsx b/web/src/pages/flow/canvas/node/switch-node.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..06eb5f0568b7c13163e10b265606f3f01318ebf7
--- /dev/null
+++ b/web/src/pages/flow/canvas/node/switch-node.tsx
@@ -0,0 +1,112 @@
+import { Divider, Flex } from 'antd';
+import classNames from 'classnames';
+import { Handle, NodeProps, Position } from 'reactflow';
+import { useGetComponentLabelByValue } from '../../hooks';
+import { ISwitchCondition, NodeData } from '../../interface';
+import { RightHandleStyle } from './handle-icon';
+import { useBuildSwitchHandlePositions } from './hooks';
+import styles from './index.less';
+import NodeHeader from './node-header';
+import NodePopover from './popover';
+
+const getConditionKey = (idx: number, length: number) => {
+ if (idx === 0 && length !== 1) {
+ return 'If';
+ } else if (idx === length - 1) {
+ return 'Else';
+ }
+
+ return 'ElseIf';
+};
+
+const ConditionBlock = ({
+ condition,
+ nodeId,
+}: {
+ condition: ISwitchCondition;
+ nodeId: string;
+}) => {
+ const items = condition?.items ?? [];
+ const getLabel = useGetComponentLabelByValue(nodeId);
+ return (
+
+ {items.map((x, idx) => (
+
+
+
+ {getLabel(x?.cpn_id)}
+
+ {x?.operator}
+
+ {x?.value}
+
+
+ {idx + 1 < items.length && (
+
+ {condition?.logical_operator}
+
+ )}
+
+ ))}
+
+ );
+};
+
+export function SwitchNode({ id, data, selected }: NodeProps) {
+ const { positions } = useBuildSwitchHandlePositions({ data, id });
+
+ return (
+
+
+
+
+
+ {positions.map((position, idx) => {
+ return (
+
+
+
+ {idx < positions.length - 1 && position.text}
+ {getConditionKey(idx, positions.length)}
+
+ {position.condition && (
+
+ )}
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/web/src/pages/flow/constant.tsx b/web/src/pages/flow/constant.tsx
index 48d1019ff9e7b9ee5acb5517ac1ecd7ce7d51f89..2874b2135d81b5885f4c0226371b46d96f09af00 100644
--- a/web/src/pages/flow/constant.tsx
+++ b/web/src/pages/flow/constant.tsx
@@ -2,6 +2,7 @@ import { ReactComponent as AkShareIcon } from '@/assets/svg/akshare.svg';
import { ReactComponent as ArXivIcon } from '@/assets/svg/arxiv.svg';
import { ReactComponent as baiduFanyiIcon } from '@/assets/svg/baidu-fanyi.svg';
import { ReactComponent as BaiduIcon } from '@/assets/svg/baidu.svg';
+import { ReactComponent as BeginIcon } from '@/assets/svg/begin.svg';
import { ReactComponent as BingIcon } from '@/assets/svg/bing.svg';
import { ReactComponent as ConcentratorIcon } from '@/assets/svg/concentrator.svg';
import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
@@ -39,7 +40,6 @@ import {
MessageOutlined,
RocketOutlined,
SendOutlined,
- SlidersOutlined,
} from '@ant-design/icons';
import upperFirst from 'lodash/upperFirst';
@@ -85,7 +85,7 @@ export const operatorIconMap = {
[Operator.Retrieval]: RocketOutlined,
[Operator.Generate]: MergeCellsOutlined,
[Operator.Answer]: SendOutlined,
- [Operator.Begin]: SlidersOutlined,
+ [Operator.Begin]: BeginIcon,
[Operator.Categorize]: DatabaseOutlined,
[Operator.Message]: MessageOutlined,
[Operator.Relevant]: BranchesOutlined,
@@ -142,7 +142,7 @@ export const operatorMap: Record<
},
[Operator.Answer]: {
backgroundColor: '#f4816d',
- color: 'white',
+ color: '#f4816d',
},
[Operator.Begin]: {
backgroundColor: '#4f51d6',
@@ -157,7 +157,7 @@ export const operatorMap: Record<
},
[Operator.Relevant]: {
backgroundColor: '#9fd94d',
- color: 'white',
+ color: '#8ef005',
width: 70,
height: 70,
fontSize: 12,
@@ -165,7 +165,7 @@ export const operatorMap: Record<
},
[Operator.RewriteQuestion]: {
backgroundColor: '#f8c7f8',
- color: 'white',
+ color: '#f32bf3',
width: 70,
height: 70,
fontSize: 12,
@@ -175,7 +175,7 @@ export const operatorMap: Record<
width: 70,
height: 70,
backgroundColor: '#0f0e0f',
- color: '#e1dcdc',
+ color: '#0f0e0f',
fontSize: 12,
iconWidth: 16,
// iconFontSize: 16,
@@ -221,14 +221,14 @@ export const operatorMap: Record<
[Operator.BaiduFanyi]: { backgroundColor: '#e5f2d3' },
[Operator.QWeather]: { backgroundColor: '#a4bbf3' },
[Operator.ExeSQL]: { backgroundColor: '#b9efe8' },
- [Operator.Switch]: { backgroundColor: '#dbaff6' },
+ [Operator.Switch]: { backgroundColor: '#dbaff6', color: '#dbaff6' },
[Operator.WenCai]: { backgroundColor: '#faac5b' },
[Operator.AkShare]: { backgroundColor: '#8085f5' },
[Operator.YahooFinance]: { backgroundColor: '#b474ff' },
[Operator.Jin10]: { backgroundColor: '#a0b9f8' },
[Operator.Concentrator]: {
backgroundColor: '#32d2a3',
- color: 'white',
+ color: '#32d2a3',
width: 70,
height: 70,
fontSize: 10,
@@ -586,18 +586,19 @@ export const RestrictedUpstreamMap = {
[Operator.Concentrator]: [Operator.Begin],
[Operator.TuShare]: [Operator.Begin],
[Operator.Crawler]: [Operator.Begin],
+ [Operator.Note]: [],
};
export const NodeMap = {
[Operator.Begin]: 'beginNode',
[Operator.Categorize]: 'categorizeNode',
- [Operator.Retrieval]: 'logicNode',
- [Operator.Generate]: 'logicNode',
+ [Operator.Retrieval]: 'retrievalNode',
+ [Operator.Generate]: 'generateNode',
[Operator.Answer]: 'logicNode',
- [Operator.Message]: 'logicNode',
+ [Operator.Message]: 'messageNode',
[Operator.Relevant]: 'relevantNode',
- [Operator.RewriteQuestion]: 'logicNode',
- [Operator.KeywordExtract]: 'logicNode',
+ [Operator.RewriteQuestion]: 'rewriteNode',
+ [Operator.KeywordExtract]: 'keywordNode',
[Operator.DuckDuckGo]: 'ragNode',
[Operator.Baidu]: 'ragNode',
[Operator.Wikipedia]: 'ragNode',
@@ -611,7 +612,7 @@ export const NodeMap = {
[Operator.BaiduFanyi]: 'ragNode',
[Operator.QWeather]: 'ragNode',
[Operator.ExeSQL]: 'ragNode',
- [Operator.Switch]: 'categorizeNode',
+ [Operator.Switch]: 'switchNode',
[Operator.Concentrator]: 'logicNode',
[Operator.WenCai]: 'ragNode',
[Operator.AkShare]: 'ragNode',
diff --git a/web/src/pages/flow/flow-drawer/index.less b/web/src/pages/flow/flow-drawer/index.less
index b3a5fcb43c2b298d0f5ad0a9ab79856fd40c6179..6c725fbb57b9a5623e3455a8c8541766e230a0df 100644
--- a/web/src/pages/flow/flow-drawer/index.less
+++ b/web/src/pages/flow/flow-drawer/index.less
@@ -7,3 +7,9 @@
font-weight: 600;
}
}
+
+.operatorDescription {
+ font-size: 14px;
+ padding-top: 16px;
+ font-weight: normal;
+}
diff --git a/web/src/pages/flow/flow-drawer/index.tsx b/web/src/pages/flow/flow-drawer/index.tsx
index a505bfbb13d08f2bfb72392302faf05c49ae8976..e85976bc9d8789dd1baf394ada394e75cafa1819 100644
--- a/web/src/pages/flow/flow-drawer/index.tsx
+++ b/web/src/pages/flow/flow-drawer/index.tsx
@@ -3,7 +3,7 @@ import { IModalProps } from '@/interfaces/common';
import { Drawer, Flex, Form, Input } from 'antd';
import { useEffect } from 'react';
import { Node } from 'reactflow';
-import { Operator } from '../constant';
+import { Operator, operatorMap } from '../constant';
import AkShareForm from '../form/akshare-form';
import AnswerForm from '../form/answer-form';
import ArXivForm from '../form/arxiv-form';
@@ -36,6 +36,8 @@ import YahooFinanceForm from '../form/yahoo-finance-form';
import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
import OperatorIcon from '../operator-icon';
+import { CloseOutlined } from '@ant-design/icons';
+import { lowerFirst } from 'lodash';
import styles from './index.less';
interface IProps {
@@ -74,7 +76,7 @@ const FormMap = {
[Operator.Crawler]: CrawlerForm,
};
-const EmptyContent = () => empty
;
+const EmptyContent = () => ;
const FlowDrawer = ({
visible,
@@ -84,8 +86,10 @@ const FlowDrawer = ({
const operatorName: Operator = node?.data.label;
const OperatorForm = FormMap[operatorName] ?? EmptyContent;
const [form] = Form.useForm();
- const { name, handleNameBlur, handleNameChange } =
- useHandleNodeNameChange(node);
+ const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
+ id: node?.id,
+ data: node?.data,
+ });
const { t } = useTranslate('flow');
const { handleValuesChange } = useHandleFormValuesChange(node?.id);
@@ -99,18 +103,27 @@ const FlowDrawer = ({
return (
-
-
-
-
+
+
+
+
+
+
+
+
+
+ {t(`${lowerFirst(operatorName)}Description`)}
+
}
placement="right"
@@ -119,6 +132,7 @@ const FlowDrawer = ({
getContainer={false}
mask={false}
width={470}
+ closeIcon={null}
>
{visible && (
diff --git a/web/src/pages/flow/flow-sider/index.tsx b/web/src/pages/flow/flow-sider/index.tsx
index 58faed8483d5e7973d50218b58955b5346258c54..b8e888b70cab64e923fb0cbcfcab02d409eeba11 100644
--- a/web/src/pages/flow/flow-sider/index.tsx
+++ b/web/src/pages/flow/flow-sider/index.tsx
@@ -3,7 +3,7 @@ import { Card, Divider, Flex, Layout, Tooltip } from 'antd';
import classNames from 'classnames';
import lowerFirst from 'lodash/lowerFirst';
import React from 'react';
-import { Operator, componentMenuList } from '../constant';
+import { Operator, componentMenuList, operatorMap } from '../constant';
import { useHandleDrag } from '../hooks';
import OperatorIcon from '../operator-icon';
import styles from './index.less';
@@ -53,7 +53,10 @@ const FlowSide = ({ setCollapsed, collapsed }: IProps) => {
onDragStart={handleDragStart(x.name)}
>
-
+
{t(lowerFirst(x.name))}
diff --git a/web/src/pages/flow/form-hooks.ts b/web/src/pages/flow/form-hooks.ts
index a5a2c9ac1cdddd5ce63fa537d9591fc24aad9f98..372288e6fa4110908b76fbb8a6303216baa36226 100644
--- a/web/src/pages/flow/form-hooks.ts
+++ b/web/src/pages/flow/form-hooks.ts
@@ -3,19 +3,6 @@ import { useCallback, useMemo } from 'react';
import { Operator, RestrictedUpstreamMap } from './constant';
import useGraphStore from './store';
-const ExcludedNodesMap = {
- // exclude some nodes downstream of the classification node
- [Operator.Categorize]: [
- Operator.Categorize,
- Operator.Answer,
- Operator.Begin,
- Operator.Relevant,
- ],
- [Operator.Relevant]: [Operator.Begin, Operator.Answer, Operator.Relevant],
- [Operator.Generate]: [Operator.Begin],
- [Operator.Switch]: [Operator.Begin],
-};
-
export const useBuildFormSelectOptions = (
operatorName: Operator,
selfId?: string, // exclude the current node
@@ -24,8 +11,10 @@ export const useBuildFormSelectOptions = (
const buildCategorizeToOptions = useCallback(
(toList: string[]) => {
- const excludedNodes: Operator[] =
- RestrictedUpstreamMap[operatorName] ?? [];
+ const excludedNodes: Operator[] = [
+ Operator.Note,
+ ...(RestrictedUpstreamMap[operatorName] ?? []),
+ ];
return nodes
.filter(
(x) =>
diff --git a/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx b/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx
index cf8784f9508be5b1a80bd061505753d9259fcb94..331071da767ba4f22de7ca9a90991a939dcf67b8 100644
--- a/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx
+++ b/web/src/pages/flow/form/categorize-form/dynamic-categorize.tsx
@@ -1,6 +1,14 @@
import { useTranslate } from '@/hooks/common-hooks';
import { CloseOutlined } from '@ant-design/icons';
-import { Button, Card, Form, FormListFieldData, Input, Select } from 'antd';
+import {
+ Button,
+ Card,
+ Flex,
+ Form,
+ FormListFieldData,
+ Input,
+ Select,
+} from 'antd';
import { FormInstance } from 'antd/lib';
import { humanId } from 'human-id';
import trim from 'lodash/trim';
@@ -15,6 +23,8 @@ import { useUpdateNodeInternals } from 'reactflow';
import { Operator } from '../../constant';
import { useBuildFormSelectOptions } from '../../form-hooks';
+import styles from './index.less';
+
interface IProps {
nodeId?: string;
}
@@ -105,13 +115,12 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
if (nodeId) updateNodeInternals(nodeId);
};
return (
-
+
{fields.map((field) => (
{
@@ -172,10 +181,15 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
))}
-
+
);
}}
diff --git a/web/src/pages/flow/form/categorize-form/index.less b/web/src/pages/flow/form/categorize-form/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..95ef3b6c2410e5c60c77b737fbd1bea65f10e6bc
--- /dev/null
+++ b/web/src/pages/flow/form/categorize-form/index.less
@@ -0,0 +1,11 @@
+@lightBackgroundColor: rgba(150, 150, 150, 0.07);
+@darkBackgroundColor: rgba(150, 150, 150, 0.12);
+
+.caseCard {
+ background-color: @darkBackgroundColor;
+}
+
+.addButton {
+ color: rgb(22, 119, 255);
+ font-weight: 600;
+}
diff --git a/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx b/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx
index d23dcc06326fdecd9d55509001d4e9cf2dcb8790..96e9ed28f43b54d3e3cec6a24324e1f04abfb308 100644
--- a/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx
+++ b/web/src/pages/flow/form/generate-form/dynamic-parameters.tsx
@@ -90,6 +90,7 @@ const DynamicParameters = ({ nodeId }: IProps) => {
components={components}
rowClassName={() => styles.editableRow}
scroll={{ x: true }}
+ bordered
/>
);
diff --git a/web/src/pages/flow/form/switch-form/index.less b/web/src/pages/flow/form/switch-form/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..c5e2c7fe83c70382e9409fc4b1db2e3470c6704c
--- /dev/null
+++ b/web/src/pages/flow/form/switch-form/index.less
@@ -0,0 +1,21 @@
+@lightBackgroundColor: rgba(150, 150, 150, 0.07);
+@darkBackgroundColor: rgba(150, 150, 150, 0.12);
+
+.caseCard {
+ background-color: @lightBackgroundColor;
+}
+
+.conditionCard {
+ background-color: @darkBackgroundColor;
+}
+
+.elseCase {
+ background-color: @lightBackgroundColor;
+ padding: 12px;
+ border-radius: 8px;
+}
+
+.addButton {
+ color: rgb(22, 119, 255);
+ font-weight: 600;
+}
diff --git a/web/src/pages/flow/form/switch-form/index.tsx b/web/src/pages/flow/form/switch-form/index.tsx
index b17d7b19d87c70ea9b9fb7a5d6d61965838368d5..c548277fb33c2ed3dc20bea52d2a76bd3ef8308b 100644
--- a/web/src/pages/flow/form/switch-form/index.tsx
+++ b/web/src/pages/flow/form/switch-form/index.tsx
@@ -13,6 +13,8 @@ import { useBuildComponentIdSelectOptions } from '../../hooks';
import { IOperatorForm, ISwitchForm } from '../../interface';
import { getOtherFieldValues } from '../../utils';
+import styles from './index.less';
+
const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
const { t } = useTranslation();
const buildCategorizeToOptions = useBuildFormSelectOptions(
@@ -55,112 +57,134 @@ const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
{(fields, { add, remove }) => (
- {fields.map((field) => (
-
{
- remove(field.name);
- }}
- />
- }
- >
-
- {({ getFieldValue }) =>
- getFieldValue(['conditions', field.name, 'items'])?.length >
- 1 && (
-
-
-
- )
+ {fields.map((field) => {
+ return (
+ {
+ remove(field.name);
+ }}
+ />
}
-
-
-
-
-
-
- {(subFields, subOpt) => (
-
+ )}
+
+
+
+ );
+ })}
-
add()} block>
+ add()} block className={styles.addButton}>
+ Add Case
)}
-
+