diff --git a/apps/common/constants/permission_constants.py b/apps/common/constants/permission_constants.py index d33a836fe..592174114 100644 --- a/apps/common/constants/permission_constants.py +++ b/apps/common/constants/permission_constants.py @@ -153,8 +153,11 @@ class PermissionConstants(Enum): KNOWLEDGE_MODULE_EDIT = Permission(group=Group.KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER]) KNOWLEDGE_MODULE_DELETE = Permission(group=Group.KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, - RoleConstants.USER]) - + RoleConstants.USER]) + KNOWLEDGE_READ = Permission(group=Group.KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) + KNOWLEDGE_CREATE = Permission(group=Group.KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, + RoleConstants.USER]) def get_workspace_application_permission(self): return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, resource_path= diff --git a/apps/knowledge/api/__init__.py b/apps/knowledge/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/knowledge/api/knowledge.py b/apps/knowledge/api/knowledge.py new file mode 100644 index 000000000..02c198227 --- /dev/null +++ b/apps/knowledge/api/knowledge.py @@ -0,0 +1,54 @@ +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter + +from common.mixins.api_mixin import APIMixin +from common.result import ResultSerializer +from knowledge.serializers.knowledge import KnowledgeBaseCreateRequest, KnowledgeModelSerializer + + +class KnowledgeCreateResponse(ResultSerializer): + def get_data(self): + return KnowledgeModelSerializer() + + +class KnowledgeCreateAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ) + ] + + @staticmethod + def get_request(): + return KnowledgeBaseCreateRequest + + @staticmethod + def get_response(): + return KnowledgeCreateResponse + + +class KnowledgeTreeReadAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="module_id", + description="模块id", + type=OpenApiTypes.STR, + location='query', + required=False, + ) + ] diff --git a/apps/knowledge/migrations/0001_initial.py b/apps/knowledge/migrations/0001_initial.py index 5dd16abcf..bec7d6646 100644 --- a/apps/knowledge/migrations/0001_initial.py +++ b/apps/knowledge/migrations/0001_initial.py @@ -14,9 +14,7 @@ def insert_default_data(apps, schema_editor): KnowledgeModule.objects.create(id='root', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab') - class Migration(migrations.Migration): - initial = True dependencies = [ @@ -30,8 +28,12 @@ class Migration(migrations.Migration): fields=[ ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), - ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), + ('id', + models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, + verbose_name='主键id')), ('file_name', models.CharField(default='', max_length=256, verbose_name='文件名称')), + ('workspace_id', + models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('loid', models.IntegerField(verbose_name='loid')), ('meta', models.JSONField(default=dict, verbose_name='文件关联数据')), ], @@ -39,41 +41,62 @@ class Migration(migrations.Migration): 'db_table': 'file', }, ), - migrations.CreateModel( - name='Knowledge', - fields=[ - ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), - ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), - ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), - ('name', models.CharField(max_length=150, verbose_name='知识库名称')), - ('desc', models.CharField(max_length=256, verbose_name='描述')), - ('type', models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')], default=0, verbose_name='类型')), - ('meta', models.JSONField(default=dict, verbose_name='元数据')), - ('embedding_mode', models.ForeignKey(default=knowledge.models.knowledge.default_model, on_delete=django.db.models.deletion.DO_NOTHING, to='models_provider.model', verbose_name='向量模型')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user', verbose_name='所属用户')), - ], - options={ - 'db_table': 'knowledge', - }, - ), migrations.CreateModel( name='KnowledgeModule', fields=[ ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), - ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')), + ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, + verbose_name='主键id')), ('name', models.CharField(max_length=64, verbose_name='文件夹名称')), - ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), + ('workspace_id', + models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), ('lft', models.PositiveIntegerField(editable=False)), ('rght', models.PositiveIntegerField(editable=False)), ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), ('level', models.PositiveIntegerField(editable=False)), - ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='knowledge.knowledgemodule')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')), + ('parent', + mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, + related_name='children', to='knowledge.knowledgemodule')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user', + verbose_name='用户id')), ], options={ 'db_table': 'knowledge_module', }, ), + migrations.CreateModel( + name='Knowledge', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), + ('id', + models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, + verbose_name='主键id')), + ('name', models.CharField(max_length=150, verbose_name='知识库名称')), + ('workspace_id', + models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), + ('desc', models.CharField(max_length=256, verbose_name='描述')), + ('type', + models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')], + default=0, verbose_name='类型')), + ('meta', models.JSONField(default=dict, verbose_name='元数据')), + ('scope', + models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用')], default='WORKSPACE', + max_length=20, verbose_name='可用范围')), + ('module', + models.ForeignKey(default='root', on_delete=django.db.models.deletion.CASCADE, + to='knowledge.knowledgemodule', + verbose_name='模块id')), + ('embedding_model', models.ForeignKey(default=knowledge.models.knowledge.default_model, + on_delete=django.db.models.deletion.DO_NOTHING, + to='models_provider.model', verbose_name='向量模型')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user', + verbose_name='所属用户')), + ], + options={ + 'db_table': 'knowledge', + }, + ), migrations.RunPython(insert_default_data), ] diff --git a/apps/knowledge/models/knowledge.py b/apps/knowledge/models/knowledge.py index 49b586033..2facd2956 100644 --- a/apps/knowledge/models/knowledge.py +++ b/apps/knowledge/models/knowledge.py @@ -12,10 +12,15 @@ from users.models import User class KnowledgeType(models.IntegerChoices): - base = 0, '通用类型' - web = 1, 'web站点类型' - lark = 2, '飞书类型' - yuque = 3, '语雀类型' + BASE = 0, '通用类型' + WEB = 1, 'web站点类型' + LARK = 2, '飞书类型' + YUQUE = 3, '语雀类型' + + +class KnowledgeScope(models.TextChoices): + SHARED = "SHARED", '共享' + WORKSPACE = "WORKSPACE", "工作空间可用" def default_model(): @@ -26,7 +31,7 @@ def default_model(): class KnowledgeModule(MPTTModel, AppModelMixin): id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name="主键id") name = models.CharField(max_length=64, verbose_name="文件夹名称") - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id") + user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name="用户id") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') @@ -37,13 +42,17 @@ class KnowledgeModule(MPTTModel, AppModelMixin): order_insertion_by = ['name'] + class Knowledge(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") name = models.CharField(max_length=150, verbose_name="知识库名称") + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) desc = models.CharField(max_length=256, verbose_name="描述") user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name="所属用户") - type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.base) - embedding_mode = models.ForeignKey(Model, on_delete=models.DO_NOTHING, verbose_name="向量模型", + type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.BASE) + scope = models.CharField(max_length=20, verbose_name='可用范围', choices=KnowledgeScope.choices, default=KnowledgeScope.WORKSPACE) + module = models.ForeignKey(KnowledgeModule, on_delete=models.CASCADE, verbose_name="模块id", default='root') + embedding_model = models.ForeignKey(Model, on_delete=models.DO_NOTHING, verbose_name="向量模型", default=default_model) meta = models.JSONField(verbose_name="元数据", default=dict) @@ -54,6 +63,7 @@ class Knowledge(AppModelMixin): class File(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") file_name = models.CharField(max_length=256, verbose_name="文件名称", default="") + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) loid = models.IntegerField(verbose_name="loid") meta = models.JSONField(verbose_name="文件关联数据", default=dict) diff --git a/apps/knowledge/serializers/__init__.py b/apps/knowledge/serializers/__init__.py new file mode 100644 index 000000000..9bad5790a --- /dev/null +++ b/apps/knowledge/serializers/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/apps/knowledge/serializers/knowledge.py b/apps/knowledge/serializers/knowledge.py new file mode 100644 index 000000000..cfacccef8 --- /dev/null +++ b/apps/knowledge/serializers/knowledge.py @@ -0,0 +1,52 @@ +import uuid_utils as uuid +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from knowledge.models import Knowledge, KnowledgeScope, KnowledgeType + + +class KnowledgeModelSerializer(serializers.ModelSerializer): + class Meta: + model = Knowledge + fields = ['id', 'name', 'desc', 'meta', 'module_id', 'type', 'workspace_id', 'create_time', 'update_time'] + + +class KnowledgeBaseCreateRequest(serializers.Serializer): + name = serializers.CharField(required=True, label=_('knowledge name')) + desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description')) + embedding = serializers.CharField(required=True, label=_('knowledge embedding')) + +class KnowledgeWebCreateRequest(serializers.Serializer): + name = serializers.CharField(required=True, label=_('knowledge name')) + desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description')) + embedding = serializers.CharField(required=True, label=_('knowledge embedding')) + + +class KnowledgeSerializer(serializers.Serializer): + class Create(serializers.Serializer): + user_id = serializers.UUIDField(required=True, label=_('user id')) + workspace_id = serializers.CharField(required=True, label=_('workspace id')) + + def insert(self, instance, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + KnowledgeBaseCreateRequest(data=instance).is_valid(raise_exception=True) + knowledge = Knowledge( + id=uuid.uuid7(), + name=instance.get('name'), + workspace_id=self.data.get('workspace_id'), + desc=instance.get('desc'), + type=instance.get('type', KnowledgeType.BASE), + user_id=self.data.get('user_id'), + scope=KnowledgeScope.WORKSPACE, + module_id=instance.get('module_id', 'root'), + embedding_model_id=instance.get('embedding'), + meta=instance.get('meta', {}), + ) + knowledge.save() + return KnowledgeModelSerializer(knowledge).data + + +class KnowledgeTreeSerializer(serializers.Serializer): + def get_knowledge_list(self, param): + pass diff --git a/apps/knowledge/urls.py b/apps/knowledge/urls.py index 91ea44a21..c5b527e0d 100644 --- a/apps/knowledge/urls.py +++ b/apps/knowledge/urls.py @@ -1,3 +1,8 @@ -from django.shortcuts import render +from django.urls import path -# Create your views here. +from . import views + +app_name = "knowledge" +urlpatterns = [ + path('workspace//knowledge', views.KnowledgeView.as_view()), +] diff --git a/apps/knowledge/views/__init__.py b/apps/knowledge/views/__init__.py index e69de29bb..4a43fce73 100644 --- a/apps/knowledge/views/__init__.py +++ b/apps/knowledge/views/__init__.py @@ -0,0 +1 @@ +from .knowledge import * diff --git a/apps/knowledge/views/knowledge.py b/apps/knowledge/views/knowledge.py new file mode 100644 index 000000000..cc64372a2 --- /dev/null +++ b/apps/knowledge/views/knowledge.py @@ -0,0 +1,44 @@ +from django.utils.translation import gettext_lazy as _ +from drf_spectacular.utils import extend_schema +from rest_framework.request import Request +from rest_framework.views import APIView + +from common.auth import TokenAuth +from common.auth.authentication import has_permissions +from common.constants.permission_constants import PermissionConstants +from common.result import result +from knowledge.api.knowledge import KnowledgeCreateAPI, KnowledgeTreeReadAPI +from knowledge.serializers.knowledge import KnowledgeSerializer, KnowledgeTreeSerializer + + +class KnowledgeView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['POST'], + description=_('Create knowledge'), + operation_id=_('Create knowledge'), + parameters=KnowledgeCreateAPI.get_parameters(), + request=KnowledgeCreateAPI.get_request(), + responses=KnowledgeCreateAPI.get_response(), + tags=[_('Knowledge Base')] + ) + @has_permissions(PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission()) + def post(self, request: Request, workspace_id: str): + return result.success(KnowledgeSerializer.Create( + data={'user_id': request.user.id, 'workspace_id': workspace_id} + ).insert(request.data)) + + @extend_schema( + methods=['GET'], + description=_('Get knowledge by module'), + operation_id=_('Get knowledge by module'), + parameters=KnowledgeTreeReadAPI.get_parameters(), + responses=KnowledgeTreeReadAPI.get_response(), + tags=[_('Knowledge Base')] + ) + @has_permissions(PermissionConstants.KNOWLEDGE_READ.get_workspace_permission()) + def get(self, request: Request, workspace_id: str): + return result.success(KnowledgeTreeSerializer( + data={'workspace_id': workspace_id} + ).get_knowledge_list(request.query_params.get('module_id'))) diff --git a/apps/maxkb/urls.py b/apps/maxkb/urls.py index 133e259a2..8ea7ece3f 100644 --- a/apps/maxkb/urls.py +++ b/apps/maxkb/urls.py @@ -24,7 +24,8 @@ urlpatterns = [ path("api/", include("users.urls")), path("api/", include("tools.urls")), path("api/", include("models_provider.urls")), - path("api/", include("modules.urls")) + path("api/", include("modules.urls")), + path("api/", include("knowledge.urls")), ] urlpatterns += [ path('schema/', SpectacularAPIView.as_view(), name='schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的 diff --git a/apps/tools/migrations/0001_initial.py b/apps/tools/migrations/0001_initial.py index dff684212..7f71eb782 100644 --- a/apps/tools/migrations/0001_initial.py +++ b/apps/tools/migrations/0001_initial.py @@ -38,7 +38,7 @@ class Migration(migrations.Migration): ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='tools.toolmodule')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', + ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user', verbose_name='用户id')), ], options={ @@ -70,7 +70,7 @@ class Migration(migrations.Migration): ('template_id', models.UUIDField(default=None, null=True, verbose_name='模版id')), ('workspace_id', models.CharField(default='default', max_length=64, verbose_name='工作空间id', db_index=True)), ('init_params', models.CharField(max_length=102400, null=True, verbose_name='初始化参数')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', + ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user', verbose_name='用户id')), ('module', models.ForeignKey(default='root', on_delete=django.db.models.deletion.CASCADE, to='tools.toolmodule', diff --git a/apps/tools/models/tool.py b/apps/tools/models/tool.py index 22b0d451c..f8c98a5a0 100644 --- a/apps/tools/models/tool.py +++ b/apps/tools/models/tool.py @@ -10,7 +10,7 @@ from users.models import User class ToolModule(MPTTModel, AppModelMixin): id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name="主键id") name = models.CharField(max_length=64, verbose_name="文件夹名称") - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id") + user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name="用户id") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') @@ -33,7 +33,7 @@ class ToolType(models.TextChoices): class Tool(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id") + user = models.ForeignKey(User, on_delete=models.DO_NOTHING, verbose_name="用户id") name = models.CharField(max_length=64, verbose_name="工具名称") desc = models.CharField(max_length=128, verbose_name="描述") code = models.CharField(max_length=102400, verbose_name="python代码")