diff --git a/apps/application/flow/step_node/data_source_local_node/__init__.py b/apps/application/flow/step_node/data_source_local_node/__init__.py new file mode 100644 index 000000000..bbf804a70 --- /dev/null +++ b/apps/application/flow/step_node/data_source_local_node/__init__.py @@ -0,0 +1,8 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: __init__.py.py + @date:2025/11/11 10:06 + @desc: +""" diff --git a/apps/application/flow/step_node/data_source_local_node/i_data_source_local_node.py b/apps/application/flow/step_node/data_source_local_node/i_data_source_local_node.py new file mode 100644 index 000000000..ec06f36d5 --- /dev/null +++ b/apps/application/flow/step_node/data_source_local_node/i_data_source_local_node.py @@ -0,0 +1,38 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: i_data_source_local_node.py + @date:2025/11/11 10:06 + @desc: +""" +from abc import abstractmethod +from typing import Type + +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from application.flow.i_step_node import INode, NodeResult + + +class DataSourceLocalNodeParamsSerializer(serializers.Serializer): + file_format = serializers.ListField(child=serializers.CharField) + max_file_number = serializers.IntegerField(required=True, label=_("Number of uploaded files")) + file_max_size = serializers.IntegerField(required=True, label=_("Upload file size")) + + +class IDataSourceLocalNode(INode): + type = 'data-source-local-node' + + @abstractmethod + def get_form_class(self): + pass + + def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: + return DataSourceLocalNodeParamsSerializer + + def _run(self): + return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data) + + def execute(self, file_format, max_file_number, file_max_size, **kwargs) -> NodeResult: + pass diff --git a/apps/application/flow/step_node/data_source_local_node/impl/__init__.py b/apps/application/flow/step_node/data_source_local_node/impl/__init__.py new file mode 100644 index 000000000..6f8301519 --- /dev/null +++ b/apps/application/flow/step_node/data_source_local_node/impl/__init__.py @@ -0,0 +1,8 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: __init__.py.py + @date:2025/11/11 10:08 + @desc: +""" diff --git a/apps/application/flow/step_node/data_source_local_node/impl/base_data_source_local_node.py b/apps/application/flow/step_node/data_source_local_node/impl/base_data_source_local_node.py new file mode 100644 index 000000000..515c0134c --- /dev/null +++ b/apps/application/flow/step_node/data_source_local_node/impl/base_data_source_local_node.py @@ -0,0 +1,27 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: base_data_source_local_node.py + @date:2025/11/11 10:30 + @desc: +""" +from application.flow.i_step_node import NodeResult +from application.flow.step_node.data_source_local_node.i_data_source_local_node import IDataSourceLocalNode +from common import forms +from common.forms import BaseForm + + +class BaseDataSourceLocalNodeForm(BaseForm): + api_key = forms.PasswordInputField('API Key', required=True) + + +class BaseDataSourceLocalNode(IDataSourceLocalNode): + def save_context(self, details, workflow_manage): + pass + + def get_form_class(self): + return BaseDataSourceLocalNodeForm() + + def execute(self, file_format, max_file_number, file_max_size, **kwargs) -> NodeResult: + pass diff --git a/apps/knowledge/serializers/knowledge_workflow.py b/apps/knowledge/serializers/knowledge_workflow.py index c849ac8ef..2473d3868 100644 --- a/apps/knowledge/serializers/knowledge_workflow.py +++ b/apps/knowledge/serializers/knowledge_workflow.py @@ -8,11 +8,13 @@ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from application.flow.step_node import get_node from common.exception.app_exception import AppApiException from knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow from knowledge.serializers.knowledge import KnowledgeModelSerializer from system_manage.models import AuthTargetType from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer +from tools.models import Tool class KnowledgeWorkflowModelSerializer(serializers.ModelSerializer): @@ -22,6 +24,20 @@ class KnowledgeWorkflowModelSerializer(serializers.ModelSerializer): class KnowledgeWorkflowSerializer(serializers.Serializer): + class Form(serializers.Serializer): + type = serializers.CharField(required=True, label=_('type')) + id = serializers.CharField(required=True, label=_('type')) + + def get_form_list(self): + self.is_valid(raise_exception=True) + if self.data.get('type') == 'local': + node = get_node(self.data.get('id')) + return node.get_form_class()().to_form_list() + elif self.data.get('type') == 'tool': + tool = QuerySet(Tool).filter(id=self.data.get("id")).first() + # todo 调用工具数据源的函数获取表单列表 + return None + class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) diff --git a/apps/knowledge/views/knowledge_workflow.py b/apps/knowledge/views/knowledge_workflow.py index 539a1e957..15d7b1166 100644 --- a/apps/knowledge/views/knowledge_workflow.py +++ b/apps/knowledge/views/knowledge_workflow.py @@ -12,10 +12,16 @@ from common.log.log import log from common.result import result from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi from knowledge.serializers.common import get_knowledge_operation_object -from knowledge.serializers.knowledge import KnowledgeSerializer from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer +class KnowledgeWorkflowFormView(APIView): + authentication_classes = [TokenAuth] + + def get(self): + return result.success(KnowledgeWorkflowSerializer.Form().get_form_list()) + + class KnowledgeWorkflowView(APIView): authentication_classes = [TokenAuth] diff --git a/ui/src/enums/application.ts b/ui/src/enums/application.ts index 9f7564c40..873c77b67 100644 --- a/ui/src/enums/application.ts +++ b/ui/src/enums/application.ts @@ -38,6 +38,10 @@ export enum WorkflowType { VariableAggregationNode = 'variable-aggregation-node', VideoUnderstandNode = 'video-understand-node', ParameterExtractionNode = 'parameter-extraction-node', + DataSourceLocalNode = 'data-source-local-node', +} +export enum WorkflowKind { + DataSource = 'data-source', } export enum WorkflowMode { // 应用工作流 diff --git a/ui/src/views/knowledge-workflow/component/DropdownMenu.vue b/ui/src/views/knowledge-workflow/component/DropdownMenu.vue index a0503ae3c..6706a612f 100644 --- a/ui/src/views/knowledge-workflow/component/DropdownMenu.vue +++ b/ui/src/views/knowledge-workflow/component/DropdownMenu.vue @@ -1,5 +1,9 @@ + + diff --git a/ui/src/views/knowledge-workflow/index.vue b/ui/src/views/knowledge-workflow/index.vue index d0ff4c686..d808df5d2 100644 --- a/ui/src/views/knowledge-workflow/index.vue +++ b/ui/src/views/knowledge-workflow/index.vue @@ -301,7 +301,7 @@ const publish = () => { ?.validate() .then(() => { const workflow = getGraphData() - const workflowInstance = new WorkFlowInstance(workflow) + const workflowInstance = new WorkFlowInstance(workflow, WorkflowMode.Knowledge) try { workflowInstance.is_valid() } catch (e: any) { @@ -384,7 +384,7 @@ const clickShowDebug = () => { ?.validate() .then(() => { const graphData = getGraphData() - const workflow = new WorkFlowInstance(graphData) + const workflow = new WorkFlowInstance(graphData, WorkflowMode.Knowledge) try { workflow.is_valid() detail.value = { @@ -396,6 +396,7 @@ const clickShowDebug = () => { showDebug.value = true } catch (e: any) { + console.log(e) MsgError(e.toString()) } }) diff --git a/ui/src/workflow/common/app-node.ts b/ui/src/workflow/common/app-node.ts index 349de8b92..fc0f7bed5 100644 --- a/ui/src/workflow/common/app-node.ts +++ b/ui/src/workflow/common/app-node.ts @@ -1,3 +1,4 @@ +import { WorkflowKind } from './../../enums/application' import Components from '@/components' import ElementPlus from 'element-plus' import * as ElementPlusIcons from '@element-plus/icons-vue' @@ -414,9 +415,11 @@ class AppNodeModel extends HtmlResize.model { const { id, x, y, width } = this const showNode = this.properties.showNode === undefined ? true : this.properties.showNode const anchors: any = [] - if (![WorkflowType.Base as string, WorkflowType.KnowledgeBase as string].includes(this.type)) { - if (![WorkflowType.Start, WorkflowType.LoopStartNode.toString()].includes(this.type)) { + if ( + ![WorkflowType.Start, WorkflowType.LoopStartNode.toString()].includes(this.type) && + this.properties.kind != WorkflowKind.DataSource + ) { anchors.push({ x: x - width / 2 + 10, y: showNode ? y : y - 15, diff --git a/ui/src/workflow/common/data.ts b/ui/src/workflow/common/data.ts index 1e7130a7c..68b4734a4 100644 --- a/ui/src/workflow/common/data.ts +++ b/ui/src/workflow/common/data.ts @@ -1,3 +1,4 @@ +import { WorkflowKind } from './../../enums/application' import { WorkflowType, WorkflowMode } from '@/enums/application' import { t } from '@/locales' @@ -79,6 +80,25 @@ export const knowledgeBaseNode = { user_input_field_list: [], }, } +export const dataSourceLocalNode = { + id: WorkflowType.DataSourceLocalNode, + type: WorkflowType.DataSourceLocalNode, + x: 360, + y: 2761.3875, + text: t('views.applicationWorkflow.nodes.dataSourceLocalNode.text', '本地文件'), + label: t('views.applicationWorkflow.nodes.dataSourceLocalNode.label', '本地文件'), + properties: { + kind: WorkflowKind.DataSource, + height: 728.375, + stepName: t('views.applicationWorkflow.nodes.dataSourceLocalNode.label', '本地文件'), + input_field_list: [], + node_data: {}, + config: {}, + showNode: true, + user_input_config: {}, + user_input_field_list: [], + }, +} /** * 说明 * type 与 nodes 文件对应 @@ -641,6 +661,10 @@ export const loopBreakNode = { } export const knowledgeMenuNodes = [ + { + label: t('views.applicationWorkflow.nodes.classify.dataSource', '数据源'), + list: [dataSourceLocalNode], + }, { label: t('views.applicationWorkflow.nodes.classify.aiCapability'), list: [ @@ -868,7 +892,6 @@ export const compareList = [ { value: 'start_with', label: 'startWith' }, { value: 'end_with', label: 'endWith' }, ] - export const nodeDict: any = { [WorkflowType.AiChat]: aiChatNode, [WorkflowType.SearchKnowledge]: searchKnowledgeNode, @@ -903,6 +926,7 @@ export const nodeDict: any = { [WorkflowType.ParameterExtractionNode]: parameterExtractionNode, [WorkflowType.VariableAggregationNode]: variableAggregationNode, [WorkflowType.KnowledgeBase]: knowledgeBaseNode, + [WorkflowType.DataSourceLocalNode]: dataSourceLocalNode, } export function isWorkFlow(type: string | undefined) { diff --git a/ui/src/workflow/common/validate.ts b/ui/src/workflow/common/validate.ts index 50e7e94e9..913f6799e 100644 --- a/ui/src/workflow/common/validate.ts +++ b/ui/src/workflow/common/validate.ts @@ -1,3 +1,4 @@ +import { WorkflowKind } from './../../enums/application' import { WorkflowType, WorkflowMode } from '@/enums/application' import { t } from '@/locales' @@ -43,7 +44,9 @@ const loop_end_nodes: Array = [ ] const end_nodes_dict = { [WorkflowMode.Application]: end_nodes, + [WorkflowMode.Knowledge]: end_nodes, [WorkflowMode.ApplicationLoop]: loop_end_nodes, + [WorkflowMode.KnowledgeLoop]: loop_end_nodes, } export class WorkFlowInstance { @@ -63,8 +66,10 @@ export class WorkFlowInstance { * 校验开始节点 */ private is_valid_start_node() { - const start_node_list = this.nodes.filter((item) => - [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id), + const start_node_list = this.nodes.filter( + (item) => + [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id) || + item.properties.kind == WorkflowKind.DataSource, ) if (start_node_list.length == 0) { throw t('views.applicationWorkflow.validate.startNodeRequired') @@ -77,6 +82,10 @@ export class WorkFlowInstance { * 校验基本信息节点 */ private is_valid_base_node() { + console.log(this.workflowModel) + if (this.workflowModel == WorkflowMode.Knowledge) { + return + } const start_node_list = this.nodes.filter((item) => item.id === WorkflowType.Base) if (start_node_list.length == 0) { throw t('views.applicationWorkflow.validate.baseNodeRequired') @@ -106,8 +115,10 @@ export class WorkFlowInstance { * @returns */ get_start_node() { - const start_node_list = this.nodes.filter((item) => - [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id), + const start_node_list = this.nodes.filter( + (item) => + [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id) || + item.properties.kind == WorkflowKind.DataSource, ) return start_node_list[0] } @@ -143,9 +154,26 @@ export class WorkFlowInstance { private is_valid_work_flow() { this.workFlowNodes = [] - this._is_valid_work_flow() + if (this.workflowModel == WorkflowMode.Knowledge) { + const start_node_list = this.nodes.filter( + (item) => + [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id) || + item.properties.kind == WorkflowKind.DataSource, + ) + start_node_list.forEach((startNode) => { + this._is_valid_work_flow(startNode) + }) + } else { + this._is_valid_work_flow() + } + const notInWorkFlowNodes = this.nodes - .filter((node: any) => node.id !== WorkflowType.Start && node.id !== WorkflowType.Base) + .filter( + (node: any) => + node.id !== WorkflowType.Start && + node.id !== WorkflowType.Base && + node.id !== WorkflowType.KnowledgeBase, + ) .filter((node) => !this.workFlowNodes.includes(node)) if (notInWorkFlowNodes.length > 0) { throw `${t('views.applicationWorkflow.validate.notInWorkFlowNode')}:${notInWorkFlowNodes.map((node) => node.properties.stepName).join(',')}` @@ -175,7 +203,9 @@ export class WorkFlowInstance { if ( node.type !== WorkflowType.Base && node.type !== WorkflowType.Start && - node.type !== WorkflowType.LoopStartNode + node.type !== WorkflowType.LoopStartNode && + node.type !== WorkflowType.KnowledgeBase && + node.properties.kind !== WorkflowKind.DataSource ) { if (!this.edges.some((edge) => edge.targetNodeId === node.id)) { throw `${t('views.applicationWorkflow.validate.notInWorkFlowNode')}:${node.properties.stepName}` diff --git a/ui/src/workflow/icons/data-source-local-node-icon.vue b/ui/src/workflow/icons/data-source-local-node-icon.vue new file mode 100644 index 000000000..f3457837b --- /dev/null +++ b/ui/src/workflow/icons/data-source-local-node-icon.vue @@ -0,0 +1,6 @@ + + diff --git a/ui/src/workflow/nodes/data-source-local-node/index.ts b/ui/src/workflow/nodes/data-source-local-node/index.ts new file mode 100644 index 000000000..c31422ad0 --- /dev/null +++ b/ui/src/workflow/nodes/data-source-local-node/index.ts @@ -0,0 +1,12 @@ +import DataSourceWebNodeVue from './index.vue' +import { AppNode, AppNodeModel } from '@/workflow/common/app-node' +class DataSourceWebNode extends AppNode { + constructor(props: any) { + super(props, DataSourceWebNodeVue) + } +} +export default { + type: 'data-source-local-node', + model: AppNodeModel, + view: DataSourceWebNode, +} diff --git a/ui/src/workflow/nodes/data-source-local-node/index.vue b/ui/src/workflow/nodes/data-source-local-node/index.vue new file mode 100644 index 000000000..2ca58b13a --- /dev/null +++ b/ui/src/workflow/nodes/data-source-local-node/index.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/ui/src/workflow/nodes/data-source-web-node/index.ts b/ui/src/workflow/nodes/data-source-web-node/index.ts new file mode 100644 index 000000000..0bc7c5283 --- /dev/null +++ b/ui/src/workflow/nodes/data-source-web-node/index.ts @@ -0,0 +1,12 @@ +import DataSourceWebNodeVue from './index.vue' +import { AppNode, AppNodeModel } from '@/workflow/common/app-node' +class DataSourceWebNode extends AppNode { + constructor(props: any) { + super(props, DataSourceWebNodeVue) + } +} +export default { + type: 'data-source-web-node', + model: AppNodeModel, + view: DataSourceWebNode, +} diff --git a/ui/src/workflow/nodes/data-source-web-node/index.vue b/ui/src/workflow/nodes/data-source-web-node/index.vue new file mode 100644 index 000000000..f1d5d6260 --- /dev/null +++ b/ui/src/workflow/nodes/data-source-web-node/index.vue @@ -0,0 +1,61 @@ + + + + +