From ff2a14817ea445f95827e4fa6fb2358816d613f7 Mon Sep 17 00:00:00 2001 From: zhangzhanwei Date: Fri, 19 Dec 2025 16:45:32 +0800 Subject: [PATCH] feat: Knowledge workflow import and export --- apps/common/constants/permission_constants.py | 93 +++++++--- apps/common/utils/common.py | 3 +- apps/knowledge/api/knowledge_workflow.py | 40 ++++- .../serializers/knowledge_workflow.py | 160 +++++++++++++++++- apps/knowledge/urls.py | 2 + apps/knowledge/views/knowledge_workflow.py | 71 +++++++- apps/locales/en_US/LC_MESSAGES/django.po | 6 + apps/locales/zh_CN/LC_MESSAGES/django.po | 8 +- apps/locales/zh_Hant/LC_MESSAGES/django.po | 6 + ui/src/api/knowledge/knowledge.ts | 34 ++++ ui/src/permission/knowledge/system-manage.ts | 6 + ui/src/permission/knowledge/system-share.ts | 3 +- .../permission/knowledge/workspace-share.ts | 5 +- ui/src/permission/knowledge/workspace.ts | 15 ++ ui/src/utils/permission/data.ts | 3 + ui/src/views/knowledge-workflow/index.vue | 58 +++++++ 16 files changed, 477 insertions(+), 36 deletions(-) diff --git a/apps/common/constants/permission_constants.py b/apps/common/constants/permission_constants.py index 8c44c2f6d..647e881b0 100644 --- a/apps/common/constants/permission_constants.py +++ b/apps/common/constants/permission_constants.py @@ -406,6 +406,9 @@ Permission_Label = { Group.SYSTEM_RES_APPLICATION_ACCESS.value: _("Application Access"), Group.SYSTEM_RES_APPLICATION_CHAT_USER.value: _("Dialogue users"), Group.SYSTEM_RES_APPLICATION_CHAT_LOG.value: _("Conversation log"), + Group.APPLICATION_FOLDER.value: _("Folder"), + Group.KNOWLEDGE_FOLDER.value: _("Folder"), + Group.TOOL_FOLDER.value: _("Folder"), # SystemGroup.RESOURCE.value: _("Resource"), } @@ -468,31 +471,6 @@ class PermissionConstants(Enum): TOOL = Permission( group=Group.TOOL, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER], ) - APPLICATION_FOLDER_READ = Permission( - group=Group.APPLICATION_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], - resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW] - ) - APPLICATION_FOLDER_EDIT = Permission( - group=Group.APPLICATION_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], - resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] - ) - KNOWLEDGE_FOLDER_READ = Permission( - group=Group.KNOWLEDGE_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], - resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW] - ) - KNOWLEDGE_FOLDER_EDIT = Permission( - group=Group.KNOWLEDGE_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], - resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE] - ) - TOOL_FOLDER_READ = Permission( - group=Group.TOOL_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], - resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] - ) - TOOL_FOLDER_EDIT = Permission( - group=Group.TOOL_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], - resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] - ) - USER_READ = Permission( group=Group.USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[SystemGroup.USER_MANAGEMENT] @@ -578,6 +556,26 @@ class PermissionConstants(Enum): parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] ) + TOOL_FOLDER_READ = Permission( + group=Group.TOOL_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], + resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] + ) + TOOL_FOLDER_CREATE = Permission( + group=Group.TOOL_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], + resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW] + ) + TOOL_FOLDER_EDIT = Permission( + group=Group.TOOL_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], + resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] + ) + TOOL_FOLDER_DELETE = Permission( + group=Group.TOOL_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL], + resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE] + ) KNOWLEDGE_READ = Permission( group=Group.KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], @@ -623,6 +621,26 @@ class PermissionConstants(Enum): resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) + KNOWLEDGE_FOLDER_READ = Permission( + group=Group.KNOWLEDGE_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], + parent_group = [WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] + ) + KNOWLEDGE_FOLDER_CREATE = Permission( + group=Group.KNOWLEDGE_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], + parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] + ) + KNOWLEDGE_FOLDER_EDIT = Permission( + group=Group.KNOWLEDGE_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], + parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] + ) + KNOWLEDGE_FOLDER_DELETE = Permission( + group=Group.KNOWLEDGE_FOLDER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], + parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] + ) KNOWLEDGE_WORKFLOW_READ = Permission( group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW], @@ -633,6 +651,11 @@ class PermissionConstants(Enum): resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] ) + KNOWLEDGE_WORKFLOW_EXPORT = Permission( + group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER], + resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE], + parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE] + ) KNOWLEDGE_DOCUMENT_READ = Permission( group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], @@ -926,6 +949,26 @@ class PermissionConstants(Enum): resource_permission_group_list=[ ResourcePermissionConst.APPLICATION_MANGE], ) + APPLICATION_FOLDER_READ = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.READ, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], + resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW] + ) + APPLICATION_FOLDER_CREATE = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.EDIT, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], + resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] + ) + APPLICATION_FOLDER_EDIT = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.EDIT, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], + resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] + ) + APPLICATION_FOLDER_DELETE = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.DELETE, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], + resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE] + ) APPLICATION_OVERVIEW_READ = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER], parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION], diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index 6d9c75233..231db7a1e 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -298,7 +298,8 @@ ALLOWED_CLASSES = { ("builtins", "dict"), ('uuid', 'UUID'), ("application.serializers.application", "MKInstance"), - ("tools.serializers.tool", "ToolInstance") + ("tools.serializers.tool", "ToolInstance"), + ("knowledge.serializers.knowledge_workflow", "KBWFInstance") } diff --git a/apps/knowledge/api/knowledge_workflow.py b/apps/knowledge/api/knowledge_workflow.py index a10863df8..bdf845620 100644 --- a/apps/knowledge/api/knowledge_workflow.py +++ b/apps/knowledge/api/knowledge_workflow.py @@ -3,7 +3,9 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin -from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowActionRequestSerializer +from common.result import DefaultResultSerializer +from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowActionRequestSerializer, \ + KnowledgeWorkflowImportRequest from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowActionListQuerySerializer @@ -71,3 +73,39 @@ class KnowledgeWorkflowActionApi(APIMixin): required=True, ) ] + +class KnowledgeWorkflowExportApi(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="knowledge_id", + description="知识库id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + ] + @staticmethod + def get_response(): + return DefaultResultSerializer + +class KnowledgeWorkflowImportApi(APIMixin): + @staticmethod + def get_parameters(): + return KnowledgeWorkflowExportApi.get_parameters() + + @staticmethod + def get_request(): + return KnowledgeWorkflowImportRequest + + @staticmethod + def get_response(): + return DefaultResultSerializer diff --git a/apps/knowledge/serializers/knowledge_workflow.py b/apps/knowledge/serializers/knowledge_workflow.py index 245869b08..2d3a1cd4c 100644 --- a/apps/knowledge/serializers/knowledge_workflow.py +++ b/apps/knowledge/serializers/knowledge_workflow.py @@ -1,14 +1,17 @@ # coding=utf-8 import asyncio import json -from typing import Dict +import pickle +from functools import reduce +from typing import Dict, List import uuid_utils.compat as uuid from django.db import transaction from django.db.models import QuerySet +from django.http import HttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers +from rest_framework import serializers, status from application.flow.common import Workflow, WorkflowMode from application.flow.i_step_node import KnowledgeWorkflowPostHandler @@ -18,6 +21,9 @@ from application.serializers.application import get_mcp_tools from common.constants.cache_version import Cache_Version from common.db.search import page_search from common.exception.app_exception import AppApiException +from common.field.common import UploadedFileField +from common.result import result +from common.utils.common import restricted_loads, generate_uuid from common.utils.rsa_util import rsa_long_decrypt from common.utils.tool_code import ToolExecutor from knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion @@ -26,12 +32,22 @@ from knowledge.serializers.knowledge import KnowledgeModelSerializer from django.core.cache import cache from system_manage.models import AuthTargetType from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer -from tools.models import Tool +from tools.models import Tool, ToolScope +from tools.serializers.tool import ToolExportModelSerializer from users.models import User tool_executor = ToolExecutor() +def hand_node(node, update_tool_map): + if node.get('type') == 'tool-lib-node': + tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '') + node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id, tool_lib_id) + + if node.get('type') == 'search-knowledge-node': + node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = [] + + class KnowledgeWorkflowModelSerializer(serializers.ModelSerializer): class Meta: model = KnowledgeWorkflow @@ -43,10 +59,24 @@ class KnowledgeWorkflowActionRequestSerializer(serializers.Serializer): knowledge_base = serializers.DictField(required=True, label=_('knowledge base data')) +class KnowledgeWorkflowImportRequest(serializers.Serializer): + file = UploadedFileField(required=True, label=_("file")) + + class KnowledgeWorkflowActionListQuerySerializer(serializers.Serializer): user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True) state = serializers.CharField(required=False, label=_("State"), allow_blank=True, allow_null=True) +class KBWFInstance: + + def __init__(self, knowledge_workflow: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]): + self.knowledge_workflow = knowledge_workflow + self.function_lib_list = function_lib_list + self.version = version + self.tool_list = tool_list + + def get_tool_list(self): + return [*(self.tool_list or []), *(self.function_lib_list or [])] class KnowledgeWorkflowActionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) @@ -217,6 +247,130 @@ class KnowledgeWorkflowSerializer(serializers.Serializer): return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []} + class Import(serializers.Serializer): + user_id = serializers.UUIDField(required=True, label=_('user id')) + workspace_id = serializers.CharField(required=True, label=_('workspace id')) + knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) + + def import_(self, instance: dict, is_import_tool, with_valid=True): + if with_valid: + self.is_valid() + KnowledgeWorkflowImportRequest(data=instance).is_valid(raise_exception=True) + user_id = self.data.get('user_id') + workspace_id = self.data.get('workspace_id') + knowledge_id = self.data.get('knowledge_id') + kbwf_instance_bytes = instance.get('file').read() + try: + kbwf_instance = restricted_loads(kbwf_instance_bytes) + except Exception as e: + raise AppApiException(1001, _("Unsupported file format")) + knowledge_workflow = kbwf_instance.knowledge_workflow + tool_list = kbwf_instance.get_tool_list() + update_tool_map = {} + if len(tool_list) > 0: + tool_id_list = reduce(lambda x, y: [*x, *y], + [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))] + for tool + in + tool_list], []) + # 存在的工具列表 + exits_tool_id_list = [str(tool.id) for tool in + QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)] + # 需要更新的工具集合 + update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool + in + tool_list if + not exits_tool_id_list.__contains__( + tool.get('id'))} + + tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if + not exits_tool_id_list.__contains__( + tool.get('id')) and not exits_tool_id_list.__contains__( + generate_uuid((tool.get('id') + workspace_id or '')))] + + work_flow = self.to_knowledge_workflow( + knowledge_workflow, + update_tool_map, + ) + tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list] + KnowledgeWorkflow.objects.filter(workspace_id=workspace_id,knowledge_id=knowledge_id).update( + work_flow=work_flow + ) + + if is_import_tool: + if len(tool_model_list) > 0: + QuerySet(Tool).bulk_create(tool_model_list) + UserResourcePermissionSerializer(data={ + 'workspace_id': self.data.get('workspace_id'), + 'user_id': self.data.get('user_id'), + 'auth_target_type': AuthTargetType.TOOL.value + }).auth_resource_batch([t.id for t in tool_model_list]) + return True + + @staticmethod + def to_knowledge_workflow(knowledge_workflow, update_tool_map): + work_flow = knowledge_workflow.get("work_flow") + for node in work_flow.get('nodes', []): + hand_node(node, update_tool_map) + if node.get('type') == 'loop_node': + for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []): + hand_node(n, update_tool_map) + return work_flow + + @staticmethod + def to_tool(tool, workspace_id, user_id): + return Tool(id=tool.get('id'), + user_id=user_id, + name=tool.get('name'), + code=tool.get('code'), + template_id=tool.get('template_id'), + input_field_list=tool.get('input_field_list'), + init_field_list=tool.get('init_field_list'), + is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'), + scope=ToolScope.WORKSPACE, + folder_id=workspace_id, + workspace_id=workspace_id) + + class Export(serializers.Serializer): + user_id = serializers.UUIDField(required=True, label=_('user id')) + workspace_id = serializers.CharField(required=True, label=_('workspace id')) + knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id')) + + def export(self, with_valid=True): + try: + if with_valid: + self.is_valid() + knowledge_id = self.data.get('knowledge_id') + knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=knowledge_id).first() + knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first() + tool_id_list = [node.get('properties', {}).get('node_data', {}).get('tool_lib_id') for node + in + knowledge_workflow.work_flow.get('nodes', []) + reduce(lambda x, y: [*x, *y], [ + n.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []) + for n + in + knowledge_workflow.work_flow.get('nodes', []) if n.get('type') == 'loop-node'], []) + if + node.get('type') == 'tool-lib-node'] + tool_list = [] + if len(tool_id_list) > 0: + tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED) + knowledge_workflow_dict = KnowledgeWorkflowModelSerializer(knowledge_workflow).data + + kbwf_instance = KBWFInstance( + knowledge_workflow_dict, + [], + 'v2', + [ToolExportModelSerializer(tool).data for tool in tool_list] + ) + knowledge_workflow_pickle = pickle.dumps(kbwf_instance) + response = HttpResponse(content_type='text/plain', content=knowledge_workflow_pickle) + response['Content-Disposition'] = f'attachment; filename="{knowledge.name}.kbwf"' + return response + except Exception as e: + return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + class Operate(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/urls.py b/apps/knowledge/urls.py index 9f3007306..6b345fae8 100644 --- a/apps/knowledge/urls.py +++ b/apps/knowledge/urls.py @@ -17,6 +17,8 @@ urlpatterns = [ path('workspace//knowledge/', views.KnowledgeView.Operate.as_view()), path('workspace//knowledge//sync', views.KnowledgeView.SyncWeb.as_view()), path('workspace//knowledge//workflow', views.KnowledgeWorkflowView.Operate.as_view()), + path('workspace//knowledge//workflow/export', views.KnowledgeWorkflowView.Export.as_view()), + path('workspace//knowledge//workflow/import', views.KnowledgeWorkflowView.Import.as_view()), path('workspace//knowledge//generate_related', views.KnowledgeView.GenerateRelated.as_view()), path('workspace//knowledge//embedding', views.KnowledgeView.Embedding.as_view()), path('workspace//knowledge//hit_test', views.KnowledgeView.HitTest.as_view()), diff --git a/apps/knowledge/views/knowledge_workflow.py b/apps/knowledge/views/knowledge_workflow.py index 2eb0175fe..fd0cf361d 100644 --- a/apps/knowledge/views/knowledge_workflow.py +++ b/apps/knowledge/views/knowledge_workflow.py @@ -7,12 +7,12 @@ from rest_framework.views import APIView from application.api.application_api import SpeechToTextAPI from common.auth import TokenAuth -from common.auth.authentication import has_permissions +from common.auth.authentication import has_permissions, get_is_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants from common.log.log import log from common.result import result, DefaultResultSerializer from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi, KnowledgeWorkflowActionApi, \ - KnowledgeWorkflowActionPageApi + KnowledgeWorkflowActionPageApi, KnowledgeWorkflowExportApi, KnowledgeWorkflowImportApi from knowledge.serializers.common import get_knowledge_operation_object from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer, KnowledgeWorkflowActionSerializer, \ KnowledgeWorkflowMcpSerializer @@ -244,6 +244,73 @@ class KnowledgeWorkflowView(APIView): data={'knowledge_id': knowledge_id, 'user_id': request.user.id, 'workspace_id': workspace_id, }).publish()) + class Export(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_('Export knowledge workflow'), + summary=_('Export knowledge workflow'), + operation_id=_('Export knowledge workflow'), # type: ignore + parameters=KnowledgeWorkflowExportApi.get_parameters(), + request=None, + responses=KnowledgeWorkflowExportApi.get_response(), + tags=[_('Knowledge Base')] # type: ignore + ) + @has_permissions( + PermissionConstants.KNOWLEDGE_WORKFLOW_EXPORT.get_workspace_knowledge_permission(), + PermissionConstants.KNOWLEDGE_WORKFLOW_EXPORT.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], + CompareConstants.AND + ) + ) + @log(menu='Knowledge', operate="Export knowledge workflow", + get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')), + ) + def get(self, request: Request, workspace_id: str, knowledge_id: str): + return KnowledgeWorkflowSerializer.Export( + data={'knowledge_id': knowledge_id,'user_id': request.user.id,'workspace_id': workspace_id} + ).export() + + class Import(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['POST'], + description=_('Import knowledge workflow'), + summary=_('Import knowledge workflow'), + operation_id=_('Import knowledge workflow'), # type: ignore + parameters=KnowledgeWorkflowImportApi.get_parameters(), + request=KnowledgeWorkflowImportApi.get_request(), + responses=KnowledgeWorkflowImportApi.get_response(), + tags=[_('Knowledge Base')] # type: ignore + ) + @has_permissions( + PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(), + PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), + ViewPermission( + [RoleConstants.USER.get_workspace_role()], + [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], + CompareConstants.AND + ) + ) + @log(menu='Knowledge', operate="Import knowledge workflow", + get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')), + ) + def post(self, request: Request, workspace_id:str, knowledge_id: str): + is_import_tool = get_is_permissions(request, workspace_id=workspace_id)( + PermissionConstants.TOOL_IMPORT.get_workspace_permission(), + PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role() + ) + return result.success(KnowledgeWorkflowSerializer.Import(data={ + 'knowledge_id': knowledge_id, 'user_id': request.user.id, 'workspace_id': workspace_id + }).import_({'file': request.FILES.get('file')}, is_import_tool)) + class Operate(APIView): authentication_classes = [TokenAuth] diff --git a/apps/locales/en_US/LC_MESSAGES/django.po b/apps/locales/en_US/LC_MESSAGES/django.po index b222b658c..37ee7d98f 100644 --- a/apps/locales/en_US/LC_MESSAGES/django.po +++ b/apps/locales/en_US/LC_MESSAGES/django.po @@ -8874,4 +8874,10 @@ msgid "This account has been locked for %s minutes, please try again later" msgstr "" msgid "User does not have permission to use API Key" +msgstr "" + +msgid "Import knowledge workflow" +msgstr "" + +msgid "Export knowledge workflow" msgstr "" \ No newline at end of file diff --git a/apps/locales/zh_CN/LC_MESSAGES/django.po b/apps/locales/zh_CN/LC_MESSAGES/django.po index 733b3a3e0..b5634fc98 100644 --- a/apps/locales/zh_CN/LC_MESSAGES/django.po +++ b/apps/locales/zh_CN/LC_MESSAGES/django.po @@ -9000,4 +9000,10 @@ msgid "This account has been locked for %s minutes, please try again later" msgstr "该账号已被锁定 %s 分钟,请稍后再试" msgid "User does not have permission to use API Key" -msgstr "用户没有使用 API Key 的权限" \ No newline at end of file +msgstr "用户没有使用 API Key 的权限" + +msgid "Import knowledge workflow" +msgstr "导入知识工作流" + +msgid "Export knowledge workflow" +msgstr "导出知识工作流" diff --git a/apps/locales/zh_Hant/LC_MESSAGES/django.po b/apps/locales/zh_Hant/LC_MESSAGES/django.po index 952bf72f0..fe19bbc6e 100644 --- a/apps/locales/zh_Hant/LC_MESSAGES/django.po +++ b/apps/locales/zh_Hant/LC_MESSAGES/django.po @@ -9001,3 +9001,9 @@ msgstr "該帳號已被鎖定 %s 分鐘,請稍後再試" msgid "User does not have permission to use API Key" msgstr "使用者沒有使用 API Key 的權限" + +msgid "Import knowledge workflow" +msgstr "匯入知識工作流" + +msgid "Export knowledge workflow" +msgstr "匯出知識工作流" diff --git a/ui/src/api/knowledge/knowledge.ts b/ui/src/api/knowledge/knowledge.ts index 06f151526..7e880210a 100644 --- a/ui/src/api/knowledge/knowledge.ts +++ b/ui/src/api/knowledge/knowledge.ts @@ -5,6 +5,7 @@ import type { Dict, pageRequest } from '@/api/type/common' import type { knowledgeData } from '@/api/type/knowledge' import useStore from '@/stores' +import knowledge from '../system-shared/knowledge' const prefix: any = { _value: '/workspace/' } Object.defineProperty(prefix, 'value', { get: function () { @@ -395,6 +396,37 @@ const putKnowledgeWorkflow: ( return put(`${prefix.value}/${knowledge_id}/workflow`, data, undefined, loading) } +/** + * 导出知识库工作流 + * @param knowledge_id + * @param knowledge_name + * @param loading + * @returns + */ +const exportKnowledgeWorkflow = ( + knowledge_id: string, + knowledge_name: string, + loading?: Ref +) => { + return exportFile( + knowledge_name + '.kbwf', + `${prefix.value}/${knowledge_id}/workflow/export`, + undefined, + loading + ) +} +/** + * 导入知识库工作流 + */ +const importKnowledgeWorkflow: ( + knowledge_id: string, + data: any, + loading?:Ref +) => Promise> = (knowledge_id, data, loading)=>{ + return post(`${prefix.value}/${knowledge_id}/workflow/import`,data,undefined,loading) +} + + const listKnowledgeVersion: ( knowledge_id: string, loading?: Ref, @@ -487,4 +519,6 @@ export default { workflowUpload, getWorkflowActionPage, cancelWorkflowAction, + exportKnowledgeWorkflow, + importKnowledgeWorkflow } diff --git a/ui/src/permission/knowledge/system-manage.ts b/ui/src/permission/knowledge/system-manage.ts index 2fc98040f..e5a613f58 100644 --- a/ui/src/permission/knowledge/system-manage.ts +++ b/ui/src/permission/knowledge/system-manage.ts @@ -197,6 +197,12 @@ const systemManage = { PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_EDIT ],'OR' ), + workflow_export: () => + hasPermission([ + RoleConst.ADMIN, + PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_EXPORT + ],'OR' + ), chat_user_edit: () =>false, diff --git a/ui/src/permission/knowledge/system-share.ts b/ui/src/permission/knowledge/system-share.ts index 24cc3ea2e..bea38416d 100644 --- a/ui/src/permission/knowledge/system-share.ts +++ b/ui/src/permission/knowledge/system-share.ts @@ -60,7 +60,8 @@ const share = { hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_READ], 'OR'), workflow_edit: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_EDIT], 'OR'), - + workflow_export: () => + hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_EXPORT], 'OR'), chat_user_edit: () => false, auth: () => false, diff --git a/ui/src/permission/knowledge/workspace-share.ts b/ui/src/permission/knowledge/workspace-share.ts index e0ad36f54..ebb33ff81 100644 --- a/ui/src/permission/knowledge/workspace-share.ts +++ b/ui/src/permission/knowledge/workspace-share.ts @@ -47,8 +47,9 @@ const workspaceShare = { folderAuth: () => false, folderDelete: () => false, hit_test: () => false, - debug: () => true, - workflow_edit: () => true, + debug: () => false, + workflow_edit: () => false, + workflow_export: () => false, } export default workspaceShare diff --git a/ui/src/permission/knowledge/workspace.ts b/ui/src/permission/knowledge/workspace.ts index c944d2cf2..7d403fc70 100644 --- a/ui/src/permission/knowledge/workspace.ts +++ b/ui/src/permission/knowledge/workspace.ts @@ -587,6 +587,21 @@ const workspace = { ], 'OR', ), + workflow_export: (source_id: string) => + hasPermission( + [ + new ComplexPermission( + [RoleConst.USER], + [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)], + [], + 'AND', + ), + RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, + PermissionConst.KNOWLEDGE_WORKFLOW_EXPORT.getKnowledgeWorkspaceResourcePermission(source_id), + PermissionConst.KNOWLEDGE_WORKFLOW_EXPORT.getWorkspacePermissionWorkspaceManageRole, + ], + 'OR', + ), hit_test: () => false, } diff --git a/ui/src/utils/permission/data.ts b/ui/src/utils/permission/data.ts index 3b2cd1af3..689f28e09 100644 --- a/ui/src/utils/permission/data.ts +++ b/ui/src/utils/permission/data.ts @@ -112,6 +112,7 @@ const PermissionConst = { KNOWLEDGE_WORKFLOW_READ: new Permission('KNOWLEDGE_WORKFLOW:READ'), KNOWLEDGE_WORKFLOW_EDIT: new Permission('KNOWLEDGE_WORKFLOW:READ+EDIT'), + KNOWLEDGE_WORKFLOW_EXPORT: new Permission('KNOWLEDGE_WORKFLOW:READ+EXPORT'), KNOWLEDGE_DOCUMENT_READ: new Permission('KNOWLEDGE_DOCUMENT:READ'), KNOWLEDGE_DOCUMENT_CREATE: new Permission('KNOWLEDGE_DOCUMENT:READ+CREATE'), @@ -196,6 +197,7 @@ const PermissionConst = { SHARED_KNOWLEDGE_WORKFLOW_READ: new Permission('SYSTEM_KNOWLEDGE_WORKFLOW:READ'), SHARED_KNOWLEDGE_WORKFLOW_EDIT: new Permission('SYSTEM_KNOWLEDGE_WORKFLOW:READ+EDIT'), + SHARED_KNOWLEDGE_WORKFLOW_EXPORT: new Permission('SYSTEM_KNOWLEDGE_WORKFLOW:READ+EXPORT'), SHARED_KNOWLEDGE_DOCUMENT_READ: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ'), SHARED_KNOWLEDGE_DOCUMENT_CREATE: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+CREATE'), @@ -252,6 +254,7 @@ const PermissionConst = { RESOURCE_KNOWLEDGE_WORKFLOW_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW:READ'), RESOURCE_KNOWLEDGE_WORKFLOW_EDIT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW:READ+EDIT'), + RESOURCE_KNOWLEDGE_WORKFLOW_EXPORT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW:READ+EXPORT'), RESOURCE_KNOWLEDGE_DOCUMENT_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ'), RESOURCE_KNOWLEDGE_DOCUMENT_CREATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+CREATE'), diff --git a/ui/src/views/knowledge-workflow/index.vue b/ui/src/views/knowledge-workflow/index.vue index 3b897b5f9..d1d8dcf4e 100644 --- a/ui/src/views/knowledge-workflow/index.vue +++ b/ui/src/views/knowledge-workflow/index.vue @@ -52,6 +52,30 @@ {{ $t('workflow.operation.toImportDoc') }} + + + {{ $t('common.export') }} + + + + + {{ $t('common.import', '导入') }} + + {{ $t('workflow.ExecutionRecord') }} @@ -366,6 +390,40 @@ const publish = () => { }) } +const elUploadRef = ref() +const importKnowledgeWorkflow = (file: any) => { + const formData = new FormData() + formData.append('file', file.raw) + elUploadRef.value.clearFiles() + loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value }) + .importKnowledgeWorkflow(id, formData, loading) + .then(() => { + getDetail() + }) + .catch((error: any) => { + if (error.code === 400) { + MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), { + cancelButtonText: t('common.confirm'), + confirmButtonText: t('common.professional'), + }).then(() => { + window.open('https://maxkb.cn/pricing.html', '_blank') + }) + } + }) +} + +function exportKnowledgeWorkflow(name: string, id: string) { + loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value }) + .exportKnowledgeWorkflow(id, name, loading) + .catch((error: any) => { + if (error.response.status !== 403) { + error.response.data.text().then((res: string) => { + MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`) + }) + } + }) +} + const clickShowDebug = () => { workflowRef.value ?.validate()