From f4c812f7d82fb55c5bcc2da0494ca0636d278672 Mon Sep 17 00:00:00 2001 From: wangdan-fit2cloud Date: Fri, 4 Sep 2026 16:41:30 +0800 Subject: [PATCH] feat: reply node --- ui/package.json | 1 + ui/src/api/enums/knowledge.ts | 3 + .../workflow/icon_global-variables_color.svg | 4 + .../workflow/icon_session-variables_color.svg | 3 + ui/src/components/COMPONENT_README.md | 35 +- .../knowledge-selection-dialog/index.vue | 154 ++++++ .../knowledge-selection-dialog/types.ts | 4 + .../model-select}/ModelParamsDialog.vue | 2 +- .../business/model-select/index.vue | 130 +++-- .../global/markdown-editor/md-editor.scss | 2 + ui/src/styles/element-plus.scss | 3 + .../create-application/template.ts | 489 ++++++++++++++---- ui/src/workflow-canvas/WORKFLOW_README.md | 17 +- ui/src/workflow-canvas/config/node-data.ts | 19 +- ui/src/workflow-canvas/config/node-mapping.ts | 3 - ui/src/workflow-canvas/core/NodeCascader.vue | 48 +- .../node-container/NodeConditionDropdown.vue | 20 +- .../node-container/NodeOperateDropdown.vue | 6 +- .../core/node-container/index.vue | 15 +- ui/src/workflow-canvas/core/utils.ts | 23 +- ui/src/workflow-canvas/icons/chat-icon.vue | 4 + ui/src/workflow-canvas/icons/global-icon.vue | 4 + .../workflow-canvas/icons/reply-node-icon.vue | 2 +- .../node-menu/BasicNodeMenu.vue | 31 +- .../component/api-parameter/index.vue | 5 +- .../file-upload/FileUploadSettingDialog.vue | 27 +- .../base-node/component/file-upload/index.vue | 37 +- .../LongTermSettingDialog.vue | 42 +- .../component/speech-playback/index.vue | 2 +- .../base-node/component/user-input/index.vue | 19 +- .../nodes/base-node/constant.ts | 14 + .../workflow-canvas/nodes/base-node/index.vue | 25 +- .../workflow-canvas/nodes/base-node/types.ts | 17 - .../workflow-canvas/nodes/form-node/index.vue | 12 +- .../nodes/image-generate-node/index.vue | 38 +- .../nodes/image-understand-node/index.vue | 54 +- .../nodes/intent-node/index.vue | 54 +- .../component/ParametersFieldTable.vue | 18 +- .../nodes/parameter-extraction-node/index.vue | 44 +- .../nodes/question-node/index.vue | 56 +- .../nodes/reply-node/index.vue | 68 ++- .../component/search-scope/index.vue | 132 +++++ .../search-setting/SearchSettingDialog.vue | 103 ++++ .../component/search-setting/index.vue | 37 ++ .../nodes/search-knowledge-node/config.ts | 15 + .../nodes/search-knowledge-node/index.ts | 15 + .../nodes/search-knowledge-node/index.vue | 102 ++++ .../nodes/search-knowledge-node/types.ts | 21 + .../nodes/speech-to-text-node/index.vue | 34 +- .../nodes/text-to-speech-node/index.vue | 34 +- .../nodes/text-to-video-node/index.vue | 38 +- 51 files changed, 1563 insertions(+), 522 deletions(-) create mode 100644 ui/src/assets/workflow/icon_global-variables_color.svg create mode 100644 ui/src/assets/workflow/icon_session-variables_color.svg create mode 100644 ui/src/components/business/knowledge-selection-dialog/index.vue create mode 100644 ui/src/components/business/knowledge-selection-dialog/types.ts rename ui/src/{workflow-canvas/nodes/base-node => components/business/model-select}/ModelParamsDialog.vue (97%) create mode 100644 ui/src/workflow-canvas/icons/chat-icon.vue create mode 100644 ui/src/workflow-canvas/icons/global-icon.vue create mode 100644 ui/src/workflow-canvas/nodes/base-node/constant.ts create mode 100644 ui/src/workflow-canvas/nodes/search-knowledge-node/component/search-scope/index.vue create mode 100644 ui/src/workflow-canvas/nodes/search-knowledge-node/component/search-setting/SearchSettingDialog.vue create mode 100644 ui/src/workflow-canvas/nodes/search-knowledge-node/component/search-setting/index.vue create mode 100644 ui/src/workflow-canvas/nodes/search-knowledge-node/config.ts create mode 100644 ui/src/workflow-canvas/nodes/search-knowledge-node/index.ts create mode 100644 ui/src/workflow-canvas/nodes/search-knowledge-node/index.vue create mode 100644 ui/src/workflow-canvas/nodes/search-knowledge-node/types.ts diff --git a/ui/package.json b/ui/package.json index 80b5a11638c..163392a314c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -59,6 +59,7 @@ "@types/lodash": "^4.17.25", "@types/node": "^24.13.2", "@types/nprogress": "^0.2.3", + "@types/sortablejs": "^1.15.9", "@vitejs/plugin-vue": "^6.0.7", "@vitejs/plugin-vue-jsx": "^5.1.5", "@vue/eslint-config-typescript": "^14.8.0", diff --git a/ui/src/api/enums/knowledge.ts b/ui/src/api/enums/knowledge.ts index 26cfd5dfb80..253949fb535 100644 --- a/ui/src/api/enums/knowledge.ts +++ b/ui/src/api/enums/knowledge.ts @@ -1,3 +1,6 @@ /** 后端知识库类型。 */ // TODO v3不再用数字表示 待修改 export const KNOWLEDGE_TYPE = { BASE: 0, WEB: 1, LARK: 2, YUQUE: 3, WORKFLOW: 4 } as const + +/** 知识库分段检索模式。 */ +export const KNOWLEDGE_SEARCH_MODE = { EMBEDDING: 'embedding', KEYWORDS: 'keywords', BLEND: 'blend' } as const diff --git a/ui/src/assets/workflow/icon_global-variables_color.svg b/ui/src/assets/workflow/icon_global-variables_color.svg new file mode 100644 index 00000000000..f92502f732a --- /dev/null +++ b/ui/src/assets/workflow/icon_global-variables_color.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/assets/workflow/icon_session-variables_color.svg b/ui/src/assets/workflow/icon_session-variables_color.svg new file mode 100644 index 00000000000..fc24be875aa --- /dev/null +++ b/ui/src/assets/workflow/icon_session-variables_color.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 54190b48b46..c50e3dc7554 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -40,8 +40,12 @@ src/components/ │ │ ├── MoveToDialog.vue │ │ ├── VirtualizedTree.vue │ │ └── types.ts +│ ├── knowledge-selection-dialog/ +│ │ ├── index.vue # 关联知识库选择、文件夹与共享资源查询 +│ │ └── types.ts # 已选知识库快照类型 │ ├── model-select/ -│ │ └── index.vue # 按供应商分组的模型选择器 +│ │ ├── index.vue # 按供应商分组的模型选择器与可选参数按钮 +│ │ └── ModelParamsDialog.vue # 模型参数动态表单弹窗 │ ├── workspace-dropdown/ │ │ └── index.vue # 工作空间选择下拉框 │ └── workspace-relation-tags/ @@ -1035,18 +1039,39 @@ import FolderTree from '@/components/business/folder-tree/index.vue' ``` +### KnowledgeSelectionDialog + +关联知识库选择弹窗,手动导入 `@/components/business/knowledge-selection-dialog/index.vue`。 +通过 `open(knowledge)` 传入已选知识库快照,确认后通过 `submit` 返回新的选择;取消不修改调用方 +数据,关闭后统一清理临时状态。`KnowledgeSelection` 类型允许仅包含 ID,以兼容缺少详情的旧数据。 +组件复用只读 `FolderTree` 和 `MkInfiniteScroll` 查询工作空间及共享知识库,支持按名称搜索, +跨文件夹、搜索和分页保留选择,并限制新选知识库使用相同的 Embedding 模型。固定业务请求由 +该组件负责,调用方维护最终关联 ID 和快照。 + ### ModelSelect -按供应商分组展示模型,通过 `v-model` 控制模型 ID,并在选择变化时触发 `change`。`options` 使用 -供应商标识作为键、模型数组作为值;组件根据 `modelType` 调用供应商接口补充分组名称和图标, -模型列表仍由使用方按当前业务上下文查询。 +按供应商分组展示模型,通过 `v-model` 控制模型 ID,并在选择变化时触发 `change`。`options` 为 +`ModelItem[]`,`providerOptions` 为 `ModelProviderItem[]`,模型列表和供应商列表均由使用方查询。 + +`showModelParams` 默认为 `false`,设为 `true` 时显示参数按钮,设为 `false` 时隐藏。 +未选择模型或传入 `disabled` 时按钮禁用;点击打开组件内的 `ModelParamsDialog`。 +通过 `v-model:model-params` 绑定参数:切换模型时加载默认值,清空模型时清空参数,弹窗确认后 +回写配置,取消不修改已保存参数。参数表单沿用上层提供的 `getModelParamsForm(modelId)` 注入方法; +隐藏参数入口时,组件只处理模型选择,不加载或修改参数。 ```vue - + ``` ### WorkspaceDropdown diff --git a/ui/src/components/business/knowledge-selection-dialog/index.vue b/ui/src/components/business/knowledge-selection-dialog/index.vue new file mode 100644 index 00000000000..d6253d58def --- /dev/null +++ b/ui/src/components/business/knowledge-selection-dialog/index.vue @@ -0,0 +1,154 @@ + + + diff --git a/ui/src/components/business/knowledge-selection-dialog/types.ts b/ui/src/components/business/knowledge-selection-dialog/types.ts new file mode 100644 index 00000000000..2fb3231cf30 --- /dev/null +++ b/ui/src/components/business/knowledge-selection-dialog/types.ts @@ -0,0 +1,4 @@ +import type { KnowledgeItem } from '@/api/types' + +/** 已选知识库允许只保留 ID,避免旧工作流缺少快照时丢失关联。 */ +export type KnowledgeSelection = Pick & Partial diff --git a/ui/src/workflow-canvas/nodes/base-node/ModelParamsDialog.vue b/ui/src/components/business/model-select/ModelParamsDialog.vue similarity index 97% rename from ui/src/workflow-canvas/nodes/base-node/ModelParamsDialog.vue rename to ui/src/components/business/model-select/ModelParamsDialog.vue index 7df25a29701..0cca6e26718 100644 --- a/ui/src/workflow-canvas/nodes/base-node/ModelParamsDialog.vue +++ b/ui/src/components/business/model-select/ModelParamsDialog.vue @@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash' import type { DynamicFormField } from '@/api/types' import { MkDynamicsForm, type FormField } from '@/components/mk-dynamics-form' -defineOptions({ name: 'BaseNodeModelParamsDialog' }) +defineOptions({ name: 'ModelParamsDialog' }) const emit = defineEmits<{ submit: [settings: Record] }>() diff --git a/ui/src/components/business/model-select/index.vue b/ui/src/components/business/model-select/index.vue index 1f6d8c1ac62..a3f4e3a7754 100644 --- a/ui/src/components/business/model-select/index.vue +++ b/ui/src/components/business/model-select/index.vue @@ -1,9 +1,10 @@ diff --git a/ui/src/components/global/markdown-editor/md-editor.scss b/ui/src/components/global/markdown-editor/md-editor.scss index 4b7f1c9c421..a4279f38df6 100644 --- a/ui/src/components/global/markdown-editor/md-editor.scss +++ b/ui/src/components/global/markdown-editor/md-editor.scss @@ -9,6 +9,7 @@ font-weight: 400; line-height: 22px; font-family: var(--mk-font-family) !important; + height: 150px; .cm-content { color: var(--mk-N900); @@ -16,6 +17,7 @@ margin-block: 0 !important; margin-inline: 11px !important; padding: 5px 0 !important; + min-height: auto !important; } .cm-line { padding: 0 !important; diff --git a/ui/src/styles/element-plus.scss b/ui/src/styles/element-plus.scss index 42e6bc00aae..d527afaa193 100644 --- a/ui/src/styles/element-plus.scss +++ b/ui/src/styles/element-plus.scss @@ -84,6 +84,9 @@ &.ai-avatar-gradient { background: linear-gradient(180deg, #3370ff 0%, #9258f7 100%) !important; } + &.small { + --el-avatar-border-radius: 4px; + } } /* button */ diff --git a/ui/src/views/application/create-application/template.ts b/ui/src/views/application/create-application/template.ts index 1a46a8f370a..3160cc90277 100644 --- a/ui/src/views/application/create-application/template.ts +++ b/ui/src/views/application/create-application/template.ts @@ -1,91 +1,190 @@ import type LogicFlow from '@logicflow/core' -import { defaultNodes } from '@/workflow-canvas/config/node-mapping' +import { defaultApplicationNodes } from '@/workflow-canvas/config/node-mapping' export const applicationTemplate = { - blank: { edges: [], nodes: defaultNodes }, + blank: { edges: [], nodes: defaultApplicationNodes }, assistant: { nodes: [ { + x: 131.39509127035535, + y: 455.9261768016129, id: 'base-node', type: 'base-node', - x: 120, - y: 260.30849999999987, properties: { + width: 600, config: {}, - height: 734.766, + height: 725, showNode: true, - stepName: '基本信息', + stepName: '\u57fa\u672c\u4fe1\u606f', + condition: 'AND', node_data: { - desc: '模板', - name: '知识库问答助手', - prologue: '您好,我是 XXX 小助手,您可以向我提出 XXX 使用问题。\n- XXX 主要功能有什么?\n- XXX 如何收费?\n- 需要转人工服务', + desc: '', + name: 'dan-template', + prologue: + '\u60a8\u597d\uff0c\u6211\u662f XXX \u5c0f\u52a9\u624b\uff0c\u60a8\u53ef\u4ee5\u5411\u6211\u63d0\u51fa XXX \u4f7f\u7528\u95ee\u9898\u3002\n- XXX \u4e3b\u8981\u529f\u80fd\u6709\u4ec0\u4e48\uff1f\n- XXX \u5982\u4f55\u6536\u8d39\uff1f\n- \u9700\u8981\u8f6c\u4eba\u5de5\u670d\u52a1', tts_type: 'BROWSER', + stt_autosend: false, + stt_model_id: '', + tts_autoplay: false, + tts_model_id: '', + long_term_enable: false, + stt_model_enable: false, + tts_model_enable: false, + stt_model_id_type: 'default', + file_upload_enable: false, + long_term_model_id: '', + file_upload_setting: { + audio: false, + image: false, + other: false, + video: false, + document: true, + maxFiles: 3, + fileLimit: 50, + url_upload: false, + local_upload: true, + otherExtensions: ['PPT', 'DOC'], + }, + long_term_trigger_type: 'ROUND', + long_term_model_id_type: 'default', + tts_model_params_setting: {}, + long_term_trigger_setting: { + rounds: 10, + }, + long_term_model_params_setting: {}, }, + enableException: false, input_field_list: [], - user_input_config: { title: '用户输入' }, + user_input_config: { + title: '\u7528\u6237\u8f93\u5165', + }, api_input_field_list: [], + chat_input_field_list: [], user_input_field_list: [], }, }, { + x: 715.0184297448741, + y: 320.4313532704871, id: 'start-node', type: 'start-node', - x: 120, - y: 929.6914999999999, properties: { + width: 320, config: { - fields: [{ label: '用户问题', value: 'question' }], + fields: [ + { + label: '\u7528\u6237\u95ee\u9898', + value: 'question', + }, + ], + chatFields: [], globalFields: [ - { label: '当前时间', value: 'time' }, - { label: '历史聊天记录', value: 'history_context' }, - { label: '对话 ID', value: 'chat_id' }, + { + label: '\u5f53\u524d\u65f6\u95f4', + value: 'time', + }, + { + label: '\u5386\u53f2\u804a\u5929\u8bb0\u5f55', + value: 'history_context', + }, + { + label: '\u5bf9\u8bdd ID', + value: 'chat_id', + }, + { + label: '\u5bf9\u8bdd\u7528\u6237 ID', + value: 'chat_user_id', + }, + { + label: '\u5bf9\u8bdd\u7528\u6237\u7c7b\u578b', + value: 'chat_user_type', + }, + { + label: '\u5bf9\u8bdd\u7528\u6237\u7ec4', + value: 'chat_user_group', + }, + { + label: '\u5bf9\u8bdd\u7528\u6237', + value: 'chat_user', + }, ], }, - fields: [{ label: '用户问题', value: 'question' }], - height: 364, + height: 456, showNode: true, - stepName: '开始', - globalFields: [{ label: '当前时间', value: 'time' }], + stepName: '\u5f00\u59cb', + condition: 'AND', + enableException: false, }, }, { + x: 1211.870063753232, + y: 443.29042897959704, id: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605', type: 'search-knowledge-node', - x: 710, - y: 929.6914999999999, properties: { + width: 320, config: { fields: [ - { label: '检索结果的分段列表', value: 'paragraph_list' }, - { label: '满足直接回答的分段列表', value: 'is_hit_handling_method_list' }, - { label: '检索结果', value: 'data' }, - { label: '满足直接回答的分段内容', value: 'directly_return' }, + { + label: '\u68c0\u7d22\u7ed3\u679c\u7684\u5206\u6bb5\u5217\u8868', + value: 'paragraph_list', + }, + { + label: '\u6ee1\u8db3\u76f4\u63a5\u56de\u7b54\u7684\u5206\u6bb5\u5217\u8868', + value: 'is_hit_handling_method_list', + }, + { + label: '\u68c0\u7d22\u7ed3\u679c', + value: 'data', + }, + { + label: '\u6ee1\u8db3\u76f4\u63a5\u56de\u7b54\u7684\u5206\u6bb5\u5185\u5bb9', + value: 'directly_return', + }, ], }, - height: 794, + height: 698, showNode: true, - stepName: '知识库检索', + stepName: '\u77e5\u8bc6\u5e93\u68c0\u7d22', condition: 'AND', node_data: { + knowledge_list: [], + show_knowledge: false, knowledge_id_list: [], - knowledge_setting: { top_n: 3, similarity: 0.6, search_mode: 'embedding', max_paragraph_char_number: 5000 }, - question_reference_address: ['start-node', 'question'], + knowledge_setting: { + top_n: 3, + similarity: 0.6, + search_mode: 'embedding', + max_paragraph_char_number: 5000, + }, + search_scope_type: 'custom', + search_scope_source: 'knowledge', all_knowledge_id_list: [], - knowledge_list: [], + search_scope_reference: [], + question_reference_address: ['start-node', 'question'], + no_permission_knowledge_id_list: [], }, + enableException: false, }, }, { + x: 1674.3959141131284, + y: 355.1933576515028, id: '420a6e4f-44ff-4847-bb81-0923630846b5', type: 'condition-node', - x: 1300, - y: 929.6914999999999, properties: { - width: 600, - config: { fields: [{ label: '分支名称', value: 'branch_name' }] }, - height: 544.148, + width: 320, + config: { + fields: [ + { + label: '\u5206\u652f\u540d\u79f0', + value: 'branch_name', + }, + ], + }, + height: 521, showNode: true, - stepName: '判断器', + stepName: '\u5224\u65ad\u5668', condition: 'AND', node_data: { branch: [ @@ -93,79 +192,163 @@ export const applicationTemplate = { id: '7887', type: 'IF', condition: 'and', - conditions: [{ field: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'is_hit_handling_method_list'], value: 1, compare: 'is_not_null' }], + conditions: [ + { + field: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'is_hit_handling_method_list'], + value: 1, + compare: 'is_not_null', + }, + ], }, { id: '6847', type: 'ELSE IF 1', condition: 'and', - conditions: [{ field: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'paragraph_list'], value: 1, compare: 'is_not_null' }], + conditions: [ + { + field: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'paragraph_list'], + value: 1, + compare: 'is_not_null', + }, + ], + }, + { + id: '2794', + type: 'ELSE', + condition: 'and', + conditions: [], }, - { id: '2794', type: 'ELSE', condition: 'and', conditions: [] }, ], }, + enableException: false, branch_condition_list: [ - { index: 0, height: 121.383, id: '7887' }, - { index: 1, height: 121.383, id: '6847' }, - { index: 2, height: 44, id: '2794' }, + { + id: '7887', + index: 0, + height: 131, + }, + { + id: '6847', + index: 1, + height: 131, + }, + { + id: '2794', + index: 2, + height: 53, + }, ], }, }, { + x: 2355.996367613652, + y: -103.96767312799687, id: '36a440a9-5b00-4d82-b13a-8e7819112918', type: 'reply-node', - x: 1890, - y: 120, properties: { - config: { fields: [{ label: '内容', value: 'answer' }] }, - height: 386, + width: 320, + config: { + fields: [ + { + label: '\u5185\u5bb9', + value: 'answer', + }, + ], + }, + height: 279.375, showNode: true, - stepName: '指定回复', + stepName: '\u6307\u5b9a\u56de\u590d', condition: 'AND', - node_data: { fields: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'directly_return'], content: '', is_result: true, reply_type: 'referencing' }, + node_data: { + fields: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'directly_return'], + content: '12323232', + is_result: true, + reply_type: 'referencing', + }, + enableException: false, }, }, { + x: 2359.3250515262043, + y: 660.4876603554833, id: 'f7c3b4a2-cb80-4e47-b050-7fef0315daaf', type: 'ai-chat-node', - x: 1890, - y: 929.6914999999999, properties: { + width: 320, config: { fields: [ - { label: 'AI 回答内容', value: 'answer' }, - { label: '思考过程', value: 'reasoning_content' }, + { + label: 'AI \u56de\u7b54\u5185\u5bb9', + value: 'answer', + }, + { + label: '\u601d\u8003\u8fc7\u7a0b', + value: 'reasoning_content', + }, ], }, - height: 993.383, + height: 1134, showNode: true, - stepName: 'AI 对话', + stepName: 'AI \u5bf9\u8bdd', condition: 'AND', node_data: { - prompt: '已知信息:\n{{知识库检索.data}}\n问题:\n{{开始.question}}', + prompt: '\u5df2\u77e5\u4fe1\u606f\uff1a\n{{\u77e5\u8bc6\u5e93\u68c0\u7d22.data}}\n\u95ee\u9898\uff1a\n{{\u5f00\u59cb.question}}', system: '', + vision: false, model_id: '', + tool_ids: [], is_result: true, + image_list: [], max_tokens: null, + mcp_source: 'referencing', + video_list: [], + mcp_servers: '', temperature: null, + mcp_tool_ids: [], dialogue_type: 'WORKFLOW', - model_setting: { reasoning_content_end: '', reasoning_content_start: '', reasoning_content_enable: false }, + model_id_type: 'custom', + model_setting: { + reasoning_content_end: '', + reasoning_content_start: '', + reasoning_content_enable: false, + }, + skill_tool_ids: [], + application_ids: [], dialogue_number: 1, + mcp_output_enable: true, + model_id_reference: [], + model_params_setting: {}, }, + enableException: false, }, }, { + x: 2361.1510484115624, + y: 1489.7353190606702, id: '04dd6c1e-95f9-4757-bb3e-134d503fce54', type: 'reply-node', - x: 1890, - y: 1798.383, properties: { - config: { fields: [{ label: '内容', value: 'answer' }] }, - height: 504, + width: 320, + config: { + fields: [ + { + label: '\u5185\u5bb9', + value: 'answer', + }, + ], + }, + height: 397.375, showNode: true, - stepName: '指定回复1', + stepName: '\u6307\u5b9a\u56de\u590d1', condition: 'AND', - node_data: { fields: [], content: '抱歉,没有在知识库查询到相关内容,请提供更详细的信息。', is_result: true, reply_type: 'content' }, + node_data: { + fields: [], + content: + '\u62b1\u6b49\uff0c\u6ca1\u6709\u5728\u77e5\u8bc6\u5e93\u67e5\u8be2\u5230\u76f8\u5173\u5185\u5bb9\uff0c\u8bf7\u63d0\u4f9b\u66f4\u8be6\u7ec6\u7684\u4fe1\u606f\u3002', + is_result: true, + reply_type: 'content', + }, + enableException: false, }, }, ], @@ -173,85 +356,175 @@ export const applicationTemplate = { { id: '73f8992c-65ef-409a-a151-378d0927f2aa', type: 'app-edge', - sourceNodeId: 'start-node', - targetNodeId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605', - startPoint: { x: 280, y: 929.6914999999999 }, - endPoint: { x: 550, y: 929.6914999999999 }, - properties: {}, + endPoint: { + x: 1051.870063753232, + y: 443.29042897959704, + }, pointsList: [ - { x: 280, y: 929.6914999999999 }, - { x: 390, y: 929.6914999999999 }, - { x: 440, y: 929.6914999999999 }, - { x: 550, y: 929.6914999999999 }, + { + x: 875.0184297448741, + y: 320.4313532704871, + }, + { + x: 975.0184297448741, + y: 320.4313532704871, + }, + { + x: 951.870063753232, + y: 443.29042897959704, + }, + { + x: 1051.870063753232, + y: 443.29042897959704, + }, ], + properties: {}, + startPoint: { + x: 875.0184297448741, + y: 320.4313532704871, + }, + sourceNodeId: 'start-node', + targetNodeId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605', sourceAnchorId: 'start-node_right', targetAnchorId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605_left', }, { id: '6a8d23d9-5179-424e-80c2-f08d37cdb8d4', type: 'app-edge', - sourceNodeId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605', - targetNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', - startPoint: { x: 870, y: 929.6914999999999 }, - endPoint: { x: 1010, y: 929.6914999999999 }, - properties: {}, + endPoint: { + x: 1514.3959141131284, + y: 355.1933576515028, + }, pointsList: [ - { x: 870, y: 929.6914999999999 }, - { x: 980, y: 929.6914999999999 }, - { x: 900, y: 929.6914999999999 }, - { x: 1010, y: 929.6914999999999 }, + { + x: 1371.870063753232, + y: 443.29042897959704, + }, + { + x: 1471.870063753232, + y: 443.29042897959704, + }, + { + x: 1414.3959141131284, + y: 355.1933576515028, + }, + { + x: 1514.3959141131284, + y: 355.1933576515028, + }, ], + properties: {}, + startPoint: { + x: 1371.870063753232, + y: 443.29042897959704, + }, + sourceNodeId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605', + targetNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', sourceAnchorId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605_right', targetAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_left', }, { id: '56006748-d9fe-491b-a14b-04fd568cac08', type: 'app-edge', - sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', - targetNodeId: '36a440a9-5b00-4d82-b13a-8e7819112918', - startPoint: { x: 1590, y: 793.3089999999999 }, - endPoint: { x: 1730, y: 120 }, - properties: {}, + endPoint: { + x: 2195.996367613652, + y: -103.96767312799687, + }, pointsList: [ - { x: 1590, y: 793.3089999999999 }, - { x: 1700, y: 793.3089999999999 }, - { x: 1620, y: 120 }, - { x: 1730, y: 120 }, + { + x: 1834.3959141131284, + y: 235.19335765150282, + }, + { + x: 1934.3959141131284, + y: 235.19335765150282, + }, + { + x: 2095.996367613652, + y: -103.96767312799687, + }, + { + x: 2195.996367613652, + y: -103.96767312799687, + }, ], + properties: {}, + startPoint: { + x: 1834.3959141131284, + y: 235.19335765150282, + }, + sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', + targetNodeId: '36a440a9-5b00-4d82-b13a-8e7819112918', sourceAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_7887_right', targetAnchorId: '36a440a9-5b00-4d82-b13a-8e7819112918_left', }, { id: '9bc8721b-07aa-4730-9347-910ed64e26b9', type: 'app-edge', - sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', - targetNodeId: 'f7c3b4a2-cb80-4e47-b050-7fef0315daaf', - startPoint: { x: 1590, y: 922.6919999999999 }, - endPoint: { x: 1730, y: 929.6914999999999 }, - properties: {}, + endPoint: { + x: 2199.3250515262043, + y: 660.4876603554833, + }, pointsList: [ - { x: 1590, y: 922.6919999999999 }, - { x: 1700, y: 922.6919999999999 }, - { x: 1620, y: 929.6914999999999 }, - { x: 1730, y: 929.6914999999999 }, + { + x: 1834.3959141131284, + y: 374.1933576515028, + }, + { + x: 1934.3959141131284, + y: 374.1933576515028, + }, + { + x: 2099.3250515262043, + y: 660.4876603554833, + }, + { + x: 2199.3250515262043, + y: 660.4876603554833, + }, ], + properties: {}, + startPoint: { + x: 1834.3959141131284, + y: 374.1933576515028, + }, + sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', + targetNodeId: 'f7c3b4a2-cb80-4e47-b050-7fef0315daaf', sourceAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_6847_right', targetAnchorId: 'f7c3b4a2-cb80-4e47-b050-7fef0315daaf_left', }, { id: 'c276a5b6-ec29-4ab9-b911-a0a929ff193f', type: 'app-edge', - sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', - targetNodeId: '04dd6c1e-95f9-4757-bb3e-134d503fce54', - startPoint: { x: 1590, y: 1013.3834999999998 }, - endPoint: { x: 1730, y: 1798.383 }, - properties: {}, + endPoint: { + x: 2201.1510484115624, + y: 1489.7353190606702, + }, pointsList: [ - { x: 1590, y: 1013.3834999999998 }, - { x: 1700, y: 1013.3834999999998 }, - { x: 1620, y: 1798.383 }, - { x: 1730, y: 1798.383 }, + { + x: 1834.3959141131284, + y: 474.1933576515028, + }, + { + x: 1934.3959141131284, + y: 474.1933576515028, + }, + { + x: 2101.1510484115624, + y: 1489.7353190606702, + }, + { + x: 2201.1510484115624, + y: 1489.7353190606702, + }, ], + properties: {}, + startPoint: { + x: 1834.3959141131284, + y: 474.1933576515028, + }, + sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5', + targetNodeId: '04dd6c1e-95f9-4757-bb3e-134d503fce54', sourceAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_2794_right', targetAnchorId: '04dd6c1e-95f9-4757-bb3e-134d503fce54_left', }, diff --git a/ui/src/workflow-canvas/WORKFLOW_README.md b/ui/src/workflow-canvas/WORKFLOW_README.md index e8d36f98e85..6da30e80de9 100644 --- a/ui/src/workflow-canvas/WORKFLOW_README.md +++ b/ui/src/workflow-canvas/WORKFLOW_README.md @@ -49,6 +49,10 @@ src/workflow-canvas/ `visible-change` 状态,并在节点组件卸载时调用 `reset()`;不要通过 Teleport 到 `body` 或增加 `z-index` 绕过画布的缩放和 SVG 绘制顺序。 +`NodeCascader` 已在内部统一管理下拉层的锚点保护,使用方无需重复接入。 +同一节点的多个 `createAnchorGuard()` 实例共享浮层状态,最后一个浮层关闭或卸载后才恢复 +节点原本的 `hittable` 状态。 + 锚点按钮与 `el-tooltip` 集中在 `core/node-container/NodeAnchor.vue`。`workflow-node.ts` 负责 锚点坐标、连接状态,并将 LogicFlow 组件的挂载、Props 更新和卸载同步到现有 Vue Teleport 容器;`teleport.connect()` 的可选第五个参数用于传入组件 Props。节点容器 @@ -79,6 +83,9 @@ src/workflow-canvas/ `index.vue`。节点注册由 `index.vue` 中的 `import.meta.glob('./nodes/**/index.ts')` 自动收集, 不要再维护一份逐项导入列表。 +长期记忆模型选择使用 `ModelSelect` 的 `showModelParams` 和 `v-model:model-params`,由公共组件 +维护参数按钮、弹窗和切换模型后的默认值;语音播放的参数弹窗复用该组件目录中的 `ModelParamsDialog.vue`。 + 节点自身的表单、状态和专属校验留在节点目录;多个节点共享且属于画布基础协议的能力才上移到 `core`。节点应复用 `core/node-container/index.vue`,需要选择上游节点字段时复用 `core/NodeCascader.vue`。 @@ -91,6 +98,10 @@ src/workflow-canvas/ `model.validate = validate`,不使用 Lodash `set`。写入嵌套字段前保留必要的父对象初始化; Vue `computed` 的 `set` 和原生 `Map.set()` 按各自 API 正常使用。 +默认值补齐和旧数据兼容在节点初始化阶段执行,`computed` getter 只读取数据,不修改节点属性、 +调用会修改数据的归一化方法,或通过 Lodash、类型断言绕过检查。容器私有下拉组件通过类型化的 +更新事件通知 `NodeContainer` 写回条件、禁用状态和节点名称,不直接修改 Props 中的字段。 + ## View 接入约定 - 所有画布路由页面均放在 `src/views/workflow/`,并以 `XxxWorkflowView.vue` 命名,例如 @@ -133,8 +144,12 @@ Vue `computed` 的 `set` 和原生 `Map.set()` 按各自 API 正常使用。 ## 当前迁移范围 -- 已实现并注册:基本信息、开始、AI 对话、意图识别、文本转语音、判断器、指定回复、智能体、 +- 已实现并注册:基本信息、开始、AI 对话、意图识别、文本转语音、知识库检索、判断器、指定回复、智能体、 自定义工具和工具库工具。 +- 知识库检索节点在 `component/search-scope/` 和 `component/search-setting/` 中分别维护检索范围 + 与参数配置,节点入口统一写回数据并独立校验范围引用和问题引用。知识库选择复用 + `KnowledgeSelectionDialog`,保留相同 Embedding 模型约束;移除关联只清理明确取消的 ID, + 不丢弃全量关联中当前用户不可见的知识库。检索模式协议复用 `KNOWLEDGE_SEARCH_MODE`。 - `config/node-mapping.ts` 保留节点类型映射,以及基本信息和开始节点的默认数据集合。 - `NodeMenu` 使用 Element Plus Tabs 组织基础组件、工具和智能体;基础组件直接渲染 `node-menu/menu.ts` 返回的菜单分组,工具和智能体复用 Workspace 文件夹树加载可用资源。页面“添加组件”和节点锚点菜单均支持点击创建与拖拽到画布创建。 - `ApplicationWorkflowView` 已接入详情加载、默认工作流、手动与自动保存、发布以及未保存退出确认。 diff --git a/ui/src/workflow-canvas/config/node-data.ts b/ui/src/workflow-canvas/config/node-data.ts index 7db6d506e14..f17814735f6 100644 --- a/ui/src/workflow-canvas/config/node-data.ts +++ b/ui/src/workflow-canvas/config/node-data.ts @@ -105,6 +105,16 @@ export const toolStartNode = { // 以上是默认基础节点 + + + +export const replyNode = { + type: WorkflowNodeType.Reply, + text: '指定回复内容,引用变量会转换为字符串进行输出', + label: '指定回复', + properties: { stepName: '指定回复', config: { fields: [{ label: '内容', value: 'answer' }] } }, +} + /** * ai对话节点配置数据 */ @@ -238,13 +248,8 @@ export const conditionNode = { properties: { width: 600, stepName: '判断器', config: { fields: [{ label: '分支名称', value: 'branch_name' }] } }, } -export const replyNode = { - type: WorkflowNodeType.Reply, - text: '指定回复内容,引用变量会转换为字符串进行输出', - label: '指定回复', - height: 210, - properties: { stepName: '指定回复', config: { fields: [{ label: '内容', value: 'answer' }] } }, -} + + export const rerankerNode = { type: WorkflowNodeType.RerankerNode, diff --git a/ui/src/workflow-canvas/config/node-mapping.ts b/ui/src/workflow-canvas/config/node-mapping.ts index b20fdc3f730..2c26445cdc9 100644 --- a/ui/src/workflow-canvas/config/node-mapping.ts +++ b/ui/src/workflow-canvas/config/node-mapping.ts @@ -92,6 +92,3 @@ export const nodeDict: Partial> = { } export const defaultApplicationNodes = [NodeData.baseNode, NodeData.startNode] export const defaultToolNodes = [NodeData.toolBaseNode, NodeData.toolStartNode] - -/** @deprecated 使用对应工作流模式的默认节点集合。 */ -export const defaultNodes = defaultApplicationNodes diff --git a/ui/src/workflow-canvas/core/NodeCascader.vue b/ui/src/workflow-canvas/core/NodeCascader.vue index 82bb9a6dfa9..8d0260e3925 100644 --- a/ui/src/workflow-canvas/core/NodeCascader.vue +++ b/ui/src/workflow-canvas/core/NodeCascader.vue @@ -1,7 +1,7 @@ diff --git a/ui/src/workflow-canvas/core/node-container/NodeConditionDropdown.vue b/ui/src/workflow-canvas/core/node-container/NodeConditionDropdown.vue index 31b435e710b..7d73ab41b5a 100644 --- a/ui/src/workflow-canvas/core/node-container/NodeConditionDropdown.vue +++ b/ui/src/workflow-canvas/core/node-container/NodeConditionDropdown.vue @@ -1,6 +1,6 @@ diff --git a/ui/src/workflow-canvas/core/node-container/NodeOperateDropdown.vue b/ui/src/workflow-canvas/core/node-container/NodeOperateDropdown.vue index 410d4e02069..8e7b63c6ab0 100644 --- a/ui/src/workflow-canvas/core/node-container/NodeOperateDropdown.vue +++ b/ui/src/workflow-canvas/core/node-container/NodeOperateDropdown.vue @@ -10,6 +10,8 @@ defineOptions({ name: 'NodeOperateDropdown' }) const props = defineProps<{ model: BaseNodeModel }>() const emit = defineEmits<{ + 'update:disabled': [disabled: boolean] + 'update:stepName': [stepName: string] 'visible-change': [visible: boolean] }>() @@ -45,7 +47,7 @@ const canChangeEnabled = computed(() => { const nodeEnabled = computed({ get: () => !nodeProperties.value.disabled, set: (value: boolean) => { - props.model.properties.disabled = !value + emit('update:disabled', !value) }, }) const sourceName = computed(() => { @@ -78,7 +80,7 @@ async function saveNodeName(formInstance: FormInstance | null | undefined) { return } - props.model.properties.stepName = nodeNameForm.value.title + emit('update:stepName', nodeNameForm.value.title) props.model.clearNextNodeField(true) nodeNameDialogVisible.value = false formInstance.resetFields() diff --git a/ui/src/workflow-canvas/core/node-container/index.vue b/ui/src/workflow-canvas/core/node-container/index.vue index bd85ab79979..d1bfeae5b48 100644 --- a/ui/src/workflow-canvas/core/node-container/index.vue +++ b/ui/src/workflow-canvas/core/node-container/index.vue @@ -293,8 +293,17 @@ onBeforeUnmount(() => { - - + + @@ -314,7 +323,7 @@ onBeforeUnmount(() => { diff --git a/ui/src/workflow-canvas/nodes/base-node/component/api-parameter/index.vue b/ui/src/workflow-canvas/nodes/base-node/component/api-parameter/index.vue index 11773d7cf25..47a27fbe6d3 100644 --- a/ui/src/workflow-canvas/nodes/base-node/component/api-parameter/index.vue +++ b/ui/src/workflow-canvas/nodes/base-node/component/api-parameter/index.vue @@ -2,13 +2,14 @@ import { ref, useTemplateRef } from 'vue' import { cloneDeep } from 'lodash' import type { FormInstance } from 'element-plus' +import type { FormField } from '@/components/mk-dynamics-form' import { Delete, Edit } from '@element-plus/icons-vue' import { MsgError } from '@/utils/message' -import type { ApiInputField, UserInputField } from '../../types' +import type { ApiInputField } from '../../types' defineOptions({ name: 'BaseNodeApiParameter' }) -const props = defineProps<{ fields: ApiInputField[]; userFields: UserInputField[] }>() +const props = defineProps<{ fields: ApiInputField[]; userFields: FormField[] }>() const emit = defineEmits<{ 'update:fields': [fields: ApiInputField[]] }>() const dialogVisible = ref(false) diff --git a/ui/src/workflow-canvas/nodes/base-node/component/file-upload/FileUploadSettingDialog.vue b/ui/src/workflow-canvas/nodes/base-node/component/file-upload/FileUploadSettingDialog.vue index c98b27aaf81..450dab27b46 100644 --- a/ui/src/workflow-canvas/nodes/base-node/component/file-upload/FileUploadSettingDialog.vue +++ b/ui/src/workflow-canvas/nodes/base-node/component/file-upload/FileUploadSettingDialog.vue @@ -3,7 +3,8 @@ import { nextTick, ref, useTemplateRef } from 'vue' import { cloneDeep } from 'lodash' import type { InputInstance } from 'element-plus' import { MsgWarning } from '@/utils/message' -import { defaultFileUploadSetting, type FileUploadSetting } from '../../types' +import { defaultFileUploadSetting } from '../../constant' +import type { FileUploadSetting } from '../../types' defineOptions({ name: 'BaseNodeFileUploadSettingDialog' }) @@ -24,19 +25,6 @@ const fileTypes = [ const reservedExtensions = new Set(fileTypes.flatMap(({ description }) => description.split('、'))) -function resetData() { - formData.value = cloneDeep(defaultFileUploadSetting) - extensionInput.value = '' - extensionInputVisible.value = false -} - -function open(setting?: Partial) { - resetData() - formData.value = { ...formData.value, ...cloneDeep(setting ?? {}) } - formData.value.otherExtensions = [...(setting?.otherExtensions ?? defaultFileUploadSetting.otherExtensions)] - visible.value = true -} - function showExtensionInput() { extensionInputVisible.value = true nextTick(() => inputRef.value?.focus()) @@ -68,6 +56,17 @@ function submit() { visible.value = false } +function open(setting: FileUploadSetting) { + formData.value = cloneDeep(setting) + visible.value = true +} + +function resetData() { + formData.value = cloneDeep(defaultFileUploadSetting) + extensionInput.value = '' + extensionInputVisible.value = false +} + defineExpose({ open }) diff --git a/ui/src/workflow-canvas/nodes/base-node/component/file-upload/index.vue b/ui/src/workflow-canvas/nodes/base-node/component/file-upload/index.vue index 67f4f2dd1d4..6edfebf2787 100644 --- a/ui/src/workflow-canvas/nodes/base-node/component/file-upload/index.vue +++ b/ui/src/workflow-canvas/nodes/base-node/component/file-upload/index.vue @@ -1,6 +1,5 @@ diff --git a/ui/src/workflow-canvas/nodes/base-node/component/long-term-memory/LongTermSettingDialog.vue b/ui/src/workflow-canvas/nodes/base-node/component/long-term-memory/LongTermSettingDialog.vue index c157484b3fe..994152b978b 100644 --- a/ui/src/workflow-canvas/nodes/base-node/component/long-term-memory/LongTermSettingDialog.vue +++ b/ui/src/workflow-canvas/nodes/base-node/component/long-term-memory/LongTermSettingDialog.vue @@ -2,10 +2,8 @@ import { computed, ref, useTemplateRef } from 'vue' import { cloneDeep } from 'lodash' import type { FormInstance } from 'element-plus' -import { Operation } from '@element-plus/icons-vue' import ModelSelect from '@/components/business/model-select/index.vue' import type { ModelItem, ModelProviderItem } from '@/api/types' -import ModelParamsDialog from '../../ModelParamsDialog.vue' import type { LongTermSetting } from '../../types' defineOptions({ name: 'BaseNodeLongTermSettingDialog' }) @@ -15,7 +13,6 @@ const emit = defineEmits<{ submit: [setting: LongTermSetting] }>() const visible = ref(false) const formRef = useTemplateRef('formRef') -const modelParamsDialogRef = useTemplateRef>('modelParamsDialogRef') const formData = ref({ long_term_model_id: '', long_term_model_id_type: 'default', @@ -110,22 +107,6 @@ function validateCron(_rule: unknown, value: unknown, callback: (error?: Error) callback() } -function handleModelChange(modelId: string) { - formData.value.long_term_model_id = modelId - if (!modelId) { - formData.value.long_term_model_params_setting = {} - return - } - modelParamsDialogRef.value?.resetDefault(modelId).then((settings) => { - formData.value.long_term_model_params_setting = settings - }) -} - -function openModelParams() { - if (!formData.value.long_term_model_id) return - modelParamsDialogRef.value?.open(formData.value.long_term_model_id, formData.value.long_term_model_params_setting) -} - function validateModel(_rule: unknown, _value: unknown, callback: (error?: Error) => void) { if (formData.value.long_term_model_id_type === 'custom' && !formData.value.long_term_model_id) { callback(new Error('请选择长期记忆模型')) @@ -153,18 +134,15 @@ defineExpose({ open }) 自定义 -
- - - - -
+ @@ -199,8 +177,6 @@ defineExpose({ open }) - -