From 0fce86f76bd1964dd4fc6f9bc0d9354c67977ff5 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 29 Dec 2025 14:39:57 +0800 Subject: [PATCH 1/2] feat: workflow add knowledge-retrieval node --- web/src/components/RadioGroupCard/index.tsx | 18 +- web/src/i18n/zh.ts | 34 +++- .../Properties/Knowledge/Knowledge.tsx | 155 +++++++++++++++ .../Knowledge/KnowledgeConfigModal.tsx | 186 ++++++++++++++++++ .../Knowledge/KnowledgeGlobalConfigModal.tsx | 121 ++++++++++++ .../Knowledge/KnowledgeListModal.tsx | 148 ++++++++++++++ .../components/Properties/Knowledge/types.ts | 32 +++ .../components/Properties/VariableSelect.tsx | 82 ++++++++ .../Workflow/components/Properties/index.tsx | 114 ++++++++++- web/src/views/Workflow/constant.ts | 11 +- .../views/Workflow/hooks/useWorkflowGraph.ts | 23 ++- web/src/views/Workflow/types.ts | 2 +- 12 files changed, 899 insertions(+), 27 deletions(-) create mode 100644 web/src/views/Workflow/components/Properties/Knowledge/Knowledge.tsx create mode 100644 web/src/views/Workflow/components/Properties/Knowledge/KnowledgeConfigModal.tsx create mode 100644 web/src/views/Workflow/components/Properties/Knowledge/KnowledgeGlobalConfigModal.tsx create mode 100644 web/src/views/Workflow/components/Properties/Knowledge/KnowledgeListModal.tsx create mode 100644 web/src/views/Workflow/components/Properties/Knowledge/types.ts create mode 100644 web/src/views/Workflow/components/Properties/VariableSelect.tsx diff --git a/web/src/components/RadioGroupCard/index.tsx b/web/src/components/RadioGroupCard/index.tsx index ac0c10ac..aa68852c 100644 --- a/web/src/components/RadioGroupCard/index.tsx +++ b/web/src/components/RadioGroupCard/index.tsx @@ -15,6 +15,7 @@ interface RadioCardProps extends Omit { onValueChange?: (value: string | null | undefined, option?: RadioCardOption) => void; onChange?: (value: string | null | undefined, option?: RadioCardOption) => void; itemRender?: (option: RadioCardOption) => ReactNode; + allowClear?: boolean; } const RadioGroupCard: FC = ({ @@ -22,7 +23,8 @@ const RadioGroupCard: FC = ({ value, onValueChange, onChange, - itemRender + itemRender, + allowClear = true }) => { // 监听value变化 useEffect(() => { @@ -34,23 +36,27 @@ const RadioGroupCard: FC = ({ const handleChange = (option: RadioCardOption) => { if (option.disabled) return if (onChange) { - onChange(value === option.value ? null : String(option.value), value === option.value ? undefined : option); + if (allowClear && value === option.value) { + onChange(null, undefined); + } else { + onChange(String(option.value), option); + } } } return ( -
+
{options.map(option => ( -
handleChange(option)}> {itemRender ? itemRender(option) : ( <> - {option.icon && } + {option.icon && }
{option.label}
-
{option.labelDesc}
+
{option.labelDesc}
)}
diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index f664fa14..c02c8a70 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -393,12 +393,6 @@ export const zh = { clusterName: '集群名称', clusterDescription: '集群描述', clusterDescriptionPlaceholder: '这是一个专门处理核心业务的Agent集群,能够协作完成复杂的业务处理任务。', - toolCalling: '工具调用', - toolCallingDesc: '主控代理将子代理作为工具调用', - toolCallingFeature: '集中控制,适合结构化工作流', - handoffs: '交接', - handoffsDesc: '代理之间动态转移控制权', - handoffsFeature: '去中心化控制,适合复杂对话场景', recommend: '推荐', advanced: '高级', multiAgentArchitecture: '多代理架构模式', @@ -425,7 +419,7 @@ export const zh = { stateSharingStrategy: '状态共享策略', intermediateResultProcessing: '中间结果处理', metadataTransfer: '元数据传输', - + knowledgeConfig: '知识库配置', temperature: '温度', temperature_desc: '温度参数,控制输出的随机性', max_tokens: '最大令牌数', @@ -453,9 +447,9 @@ export const zh = { releasePreview: '发布预览', globalConfig: '全局配置', globalConfigDesc: '全局配置将应用于所有关联的知识库作为默认配置。单个知识库的配置将覆盖全局配置。', - rerankModel: '重排序模型', + rerankModel: 'Reranker 模型', rerankModelDesc: '激活后,搜索结果将被重新排序以提高相关性', - rearrangementModel: '重排序模型', + rearrangementModel: 'Reranker 模型', rearrangementModelDesc: '选择用于重新排序的模型', reranker_top_k: 'Top K', reranker_top_k_desc: '返回的文档片段数量,范围从1到20', @@ -514,6 +508,21 @@ export const zh = { promptChatPlaceholder: '描述你需要的提示词,例如:我需要一个客服助手', promptChatEmpty: '目前没有对话内容', promptEmpty: '在左侧描述您的用例,编排预览将在此处显示。', + + master: '主管模式', + master_agent: '主管模式', + masterDesc: '由主 Agent 统一调度和管理,子 Agent 按照主管分配的任务执行,适合需要集中控制的场景。', + handoffs: '协作模式', + handoffsDesc: '多个 Agent 平等协作,根据任务需求自主协调配合,适合需要灵活互动的复杂场景。', + masterConfig: '主管配置', + orchestrationMode: '任务分配策略', + conditional: '智能分配', + sequential: '顺序分配', + parallel: '并行分配', + aggregationStrategy: '结果汇总方式', + merge: '完整汇总', + vote: '关键信息提取', + priority: '结构化整合', }, // 角色管理相关翻译 role: { @@ -1629,7 +1638,7 @@ export const zh = { llm: '大语言模型 (LLM)', model_selection: '多模型选择', model_voting: '多模型投票', - rag: '知识检索 (RAG)', + 'knowledge-retrieval': '知识检索 (RAG)', classification: '智能分类', parameter_extraction: '参数提取', flowControl: '流程控制', @@ -1694,6 +1703,11 @@ export const zh = { }, end: { output: '回复' + }, + 'knowledge-retrieval': { + query: '查询变量', + knowledge_retrieval: '知识库', + recallConfig: '召回测试', } }, diff --git a/web/src/views/Workflow/components/Properties/Knowledge/Knowledge.tsx b/web/src/views/Workflow/components/Properties/Knowledge/Knowledge.tsx new file mode 100644 index 00000000..384de5ec --- /dev/null +++ b/web/src/views/Workflow/components/Properties/Knowledge/Knowledge.tsx @@ -0,0 +1,155 @@ +import { type FC, useRef, useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { Space, Button, List } from 'antd' +import knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg' +import type { + KnowledgeConfigForm, + KnowledgeConfig, + RerankerConfig, + KnowledgeBase, + KnowledgeModalRef, + KnowledgeConfigModalRef, + KnowledgeGlobalConfigModalRef, +} from './types' +import Empty from '@/components/Empty' +import KnowledgeListModal from './KnowledgeListModal' +import KnowledgeConfigModal from './KnowledgeConfigModal' +import KnowledgeGlobalConfigModal from './KnowledgeGlobalConfigModal' +import Tag from '@/components/Tag' + +const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfig) => void}> = ({value = {knowledge_bases: []}, onChange}) => { + const { t } = useTranslation() + const knowledgeModalRef = useRef(null) + const knowledgeConfigModalRef = useRef(null) + const knowledgeGlobalConfigModalRef = useRef(null) + const [knowledgeList, setKnowledgeList] = useState([]) + const [editConfig, setEditConfig] = useState({} as KnowledgeConfig) + + useEffect(() => { + if (value && JSON.stringify(value) !== JSON.stringify(editConfig)) { + setEditConfig({ ...(value || {}) }) + const knowledge_bases = [...(value.knowledge_bases || [])] + setKnowledgeList(knowledge_bases) + } + }, [value]) + + const handleKnowledgeConfig = () => { + knowledgeGlobalConfigModalRef.current?.handleOpen() + } + const handleAddKnowledge = () => { + knowledgeModalRef.current?.handleOpen() + } + const handleDeleteKnowledge = (id: string) => { + const list = knowledgeList.filter(item => item.id !== id) + setKnowledgeList([...list]) + onChange && onChange({ + ...editConfig, + knowledge_bases: [...list], + }) + } + const handleEditKnowledge = (item: KnowledgeBase) => { + knowledgeConfigModalRef.current?.handleOpen(item) + } + const refresh = (values: KnowledgeBase[] | KnowledgeConfigForm | RerankerConfig, type: 'knowledge' | 'knowledgeConfig' | 'rerankerConfig') => { + if (type === 'knowledge') { + let list = [...knowledgeList] + if (list.length > 0) { + (Array.isArray(values) ? values : [values]).forEach(vo => { + const index = list.findIndex(item => item.id === (vo as KnowledgeBase).id) + if (index === -1) { + list.push(vo as KnowledgeBase) + } + }) + } else { + list = [...values as KnowledgeBase[]] + } + setKnowledgeList([...list]) + onChange && onChange({ + ...editConfig, + knowledge_bases: [...list], + }) + } else if (type === 'knowledgeConfig') { + const index = knowledgeList.findIndex(item => item.id === (values as KnowledgeBase).kb_id) + const list = [...knowledgeList] + list[index] = { + ...list[index], + config: {...values as KnowledgeConfigForm} + } + setKnowledgeList([...list]) + onChange && onChange({ + ...editConfig, + knowledge_bases: [...list], + }) + } else if (type === 'rerankerConfig') { + const rerankerValues = values as RerankerConfig + setEditConfig(prev => ({ ...prev, ...rerankerValues })) + onChange && onChange({ + ...editConfig, + ...rerankerValues, + reranker_id: rerankerValues.rerank_model ? rerankerValues.reranker_id : undefined, + reranker_top_k: rerankerValues.rerank_model ? rerankerValues.reranker_top_k : undefined, + }) + } + } + return ( +
+
+
{t('application.knowledgeBaseAssociation')}
+ + + + + +
+ + {knowledgeList.length === 0 + ? + : + ( + +
+
+ {item.name} + + {item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')} + +
{t('application.contains', {include_count: item.doc_num})}
+
+ +
handleEditKnowledge(item)} + >
+
handleDeleteKnowledge(item.id)} + >
+
+
+
+ )} + /> + } + {/* 全局设置 */} + + {/* 知识库列表 */} + + +
+ ) +} +export default Knowledge \ No newline at end of file diff --git a/web/src/views/Workflow/components/Properties/Knowledge/KnowledgeConfigModal.tsx b/web/src/views/Workflow/components/Properties/Knowledge/KnowledgeConfigModal.tsx new file mode 100644 index 00000000..e2c87ada --- /dev/null +++ b/web/src/views/Workflow/components/Properties/Knowledge/KnowledgeConfigModal.tsx @@ -0,0 +1,186 @@ +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import { Form, Select, InputNumber } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import type { KnowledgeConfigModalRef, KnowledgeBase, KnowledgeConfigForm, RetrieveType } from './types' +import RbModal from '@/components/RbModal' +import RbSlider from '@/components/RbSlider' +import { formatDateTime } from '@/utils/format'; + +const FormItem = Form.Item; + +interface KnowledgeConfigModalProps { + refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void; +} +const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid'] + +const KnowledgeConfigModal = forwardRef(({ + refresh, +}, ref) => { + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const [form] = Form.useForm(); + const [data, setData] = useState(null); + + const values = Form.useWatch([], form); + + // 封装取消方法,添加关闭弹窗逻辑 + const handleClose = () => { + setVisible(false); + form.resetFields(); + setData(null) + }; + + const handleOpen = (data: KnowledgeBase) => { + form.setFieldsValue({ + retrieve_type: data?.config?.retrieve_type || retrieveTypes[0], + kb_id: data.id, + top_k: data?.config?.top_k || 5, + similarity_threshold: data?.config?.similarity_threshold || 0.5, + vector_similarity_weight: data?.config?.vector_similarity_weight || 0.5, + ...(data || {}), + ...(data?.config || {}), + }) + setData({...data}) + setVisible(true); + }; + // 封装保存方法,添加提交逻辑 + const handleSave = () => { + form + .validateFields() + .then(() => { + refresh(values, 'knowledgeConfig') + handleClose() + }) + .catch((err) => { + console.log('err', err) + }); + } + + // 暴露给父组件的方法 + useImperativeHandle(ref, () => ({ + handleOpen, + handleClose + })); + + useEffect(() => { + if (values?.retrieve_type) { + const fieldsToReset = Object.keys(values).filter(key => + key !== 'kb_id' && key !== 'retrieve_type' + ) as (keyof KnowledgeConfigForm)[]; + form.resetFields(fieldsToReset); + } + }, [values?.retrieve_type]) + + return ( + +
+ {data && ( +
+
+ {data.name} +
{t('application.contains', {include_count: data.doc_num})}
+
+
{formatDateTime(data.updated_at, 'YYYY-MM-DD HH:mm:ss')}
+
+ )} +