diff --git a/apps/application/api/application_api.py b/apps/application/api/application_api.py index 677e7ad99..b4d1f83a4 100644 --- a/apps/application/api/application_api.py +++ b/apps/application/api/application_api.py @@ -13,12 +13,18 @@ from rest_framework import serializers from application.serializers.application import ApplicationCreateSerializer from common.mixins.api_mixin import APIMixin +from common.result import ResultSerializer class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest): work_flow = serializers.DictField(required=True, label=_("Workflow Objects")) +class ApplicationCreateResponse(ResultSerializer): + def get_data(self): + return ApplicationCreateSerializer.ApplicationResponse() + + class ApplicationCreateAPI(APIMixin): @staticmethod def get_parameters(): @@ -38,4 +44,4 @@ class ApplicationCreateAPI(APIMixin): @staticmethod def get_response(): - return FolderCreateResponse + return ApplicationCreateResponse diff --git a/apps/application/migrations/0003_applicationaccesstoken_application_is_publish_and_more.py b/apps/application/migrations/0003_applicationaccesstoken_application_is_publish_and_more.py new file mode 100644 index 000000000..d25859aff --- /dev/null +++ b/apps/application/migrations/0003_applicationaccesstoken_application_is_publish_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.2 on 2025-05-27 03:05 + +import django.contrib.postgres.fields +import django.db.models.deletion +import uuid_utils.compat +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0002_applicationapikey'), + ('knowledge', '0007_alter_document_status_alter_paragraph_status_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ApplicationAccessToken', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), + ('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='application.application', verbose_name='应用id')), + ('access_token', models.CharField(max_length=128, unique=True, verbose_name='用户公开访问 认证token')), + ('is_active', models.BooleanField(default=True, verbose_name='是否开启公开访问')), + ('access_num', models.IntegerField(default=100, verbose_name='访问次数')), + ('white_active', models.BooleanField(default=False, verbose_name='是否开启白名单')), + ('white_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='白名单列表')), + ('show_source', models.BooleanField(default=False, verbose_name='是否显示知识来源')), + ('language', models.CharField(default=None, max_length=10, null=True, verbose_name='语言')), + ], + options={ + 'db_table': 'application_access_token', + }, + ), + migrations.AddField( + model_name='application', + name='is_publish', + field=models.BooleanField(default=False, verbose_name='是否发布'), + ), + migrations.CreateModel( + name='ApplicationKnowledgeMapping', + 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')), + ('application', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='application.application')), + ('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')), + ], + options={ + 'db_table': 'application_knowledge_mapping', + }, + ), + ] diff --git a/apps/application/models/__init__.py b/apps/application/models/__init__.py index 898bb79df..405945b54 100644 --- a/apps/application/models/__init__.py +++ b/apps/application/models/__init__.py @@ -6,3 +6,5 @@ @date:2025/5/7 15:14 @desc: """ +from .application import * +from .application_access_token import * diff --git a/apps/application/models/application.py b/apps/application/models/application.py index a5a71328c..50ee76087 100644 --- a/apps/application/models/application.py +++ b/apps/application/models/application.py @@ -12,6 +12,7 @@ from mptt.fields import TreeForeignKey from mptt.models import MPTTModel from common.mixins.app_model_mixin import AppModelMixin +from knowledge.models import Knowledge from models_provider.models import Model from users.models import User @@ -59,6 +60,7 @@ class Application(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) folder = models.ForeignKey(ApplicationFolder, on_delete=models.DO_NOTHING, verbose_name="文件夹id", default='root') + is_publish = models.BooleanField(verbose_name="是否发布", default=False) name = models.CharField(max_length=128, verbose_name="应用名称") desc = models.CharField(max_length=512, verbose_name="引用描述", default="") prologue = models.CharField(max_length=40960, verbose_name="开场白", default="") @@ -106,3 +108,12 @@ class Application(AppModelMixin): class Meta: db_table = "application" + + +class ApplicationKnowledgeMapping(AppModelMixin): + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") + application = models.ForeignKey(Application, on_delete=models.DO_NOTHING) + knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING) + + class Meta: + db_table = "application_knowledge_mapping" diff --git a/apps/application/models/application_access_token.py b/apps/application/models/application_access_token.py new file mode 100644 index 000000000..ecf7da630 --- /dev/null +++ b/apps/application/models/application_access_token.py @@ -0,0 +1,33 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: application_access_token.py + @date:2025/5/27 9:55 + @desc: +""" +from django.contrib.postgres.fields import ArrayField +from django.db import models + +from application.models.application import Application +from common.mixins.app_model_mixin import AppModelMixin + + +class ApplicationAccessToken(AppModelMixin): + """ + 应用认证token + """ + application = models.OneToOneField(Application, primary_key=True, on_delete=models.CASCADE, verbose_name="应用id") + access_token = models.CharField(max_length=128, verbose_name="用户公开访问 认证token", unique=True) + is_active = models.BooleanField(default=True, verbose_name="是否开启公开访问") + access_num = models.IntegerField(default=100, verbose_name="访问次数") + white_active = models.BooleanField(default=False, verbose_name="是否开启白名单") + white_list = ArrayField(verbose_name="白名单列表", + base_field=models.CharField(max_length=128, blank=True) + , default=list) + show_source = models.BooleanField(default=False, verbose_name="是否显示知识来源") + + language = models.CharField(max_length=10, verbose_name="语言", default=None, null=True) + + class Meta: + db_table = "application_access_token" diff --git a/apps/application/serializers/application.py b/apps/application/serializers/application.py index f9f3c661d..879962290 100644 --- a/apps/application/serializers/application.py +++ b/apps/application/serializers/application.py @@ -6,6 +6,7 @@ @date:2025/5/26 17:03 @desc: """ +import hashlib import re from typing import Dict @@ -16,7 +17,8 @@ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from application.models.application import Application, ApplicationTypeChoices +from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping +from application.models.application_access_token import ApplicationAccessToken from common.exception.app_exception import AppApiException from knowledge.models import Knowledge from models_provider.models import Model @@ -94,6 +96,11 @@ class ModelSettingSerializer(serializers.Serializer): class ApplicationCreateSerializer(serializers.Serializer): + class ApplicationResponse(serializers.ModelSerializer): + class Meta: + model = Application + fields = "__all__" + class WorkflowRequest(serializers.Serializer): name = serializers.CharField(required=True, max_length=64, min_length=1, label=_("Application Name")) @@ -105,7 +112,7 @@ class ApplicationCreateSerializer(serializers.Serializer): label=_("Opening remarks")) @staticmethod - def to_application_model(user_id: str, application: Dict): + def to_application_model(user_id: str, workspace_id: str, application: Dict): default_workflow = application.get('work_flow') for node in default_workflow.get('nodes'): if node.get('id') == 'base-node': @@ -115,6 +122,7 @@ class ApplicationCreateSerializer(serializers.Serializer): return Application(id=uuid.uuid7(), name=application.get('name'), desc=application.get('desc'), + workspace_id=workspace_id, prologue="", dialogue_number=0, user_id=user_id, model_id=None, @@ -176,7 +184,70 @@ class ApplicationCreateSerializer(serializers.Serializer): ModelKnowledgeAssociation(data={'user_id': user_id, 'model_id': self.data.get('model_id'), 'knowledge_id_list': self.data.get('knowledge_id_list')}).is_valid() + @staticmethod + def to_application_model(user_id: str, application: Dict): + return Application(id=uuid.uuid1(), name=application.get('name'), desc=application.get('desc'), + prologue=application.get('prologue'), + dialogue_number=application.get('dialogue_number', 0), + user_id=user_id, model_id=application.get('model_id'), + dataset_setting=application.get('dataset_setting'), + model_setting=application.get('model_setting'), + problem_optimization=application.get('problem_optimization'), + type=ApplicationTypeChoices.SIMPLE, + model_params_setting=application.get('model_params_setting', {}), + problem_optimization_prompt=application.get('problem_optimization_prompt', None), + stt_model_enable=application.get('stt_model_enable', False), + stt_model_id=application.get('stt_model', None), + tts_model_id=application.get('tts_model', None), + tts_model_enable=application.get('tts_model_enable', False), + tts_model_params_setting=application.get('tts_model_params_setting', {}), + tts_type=application.get('tts_type', None), + file_upload_enable=application.get('file_upload_enable', False), + file_upload_setting=application.get('file_upload_setting', {}), + work_flow={} + ) + class ApplicationSerializer(serializers.Serializer): - def insert(self): - pass + workspace_id = serializers.CharField(required=True, label=_('workspace id')) + user_id = serializers.UUIDField(required=True, label=_("User ID")) + + def insert(self, instance: Dict, with_valid=True): + application_type = instance.get('type') + if 'WORK_FLOW' == application_type: + return self.insert_workflow(instance) + else: + return self.insert_simple(instance) + + def insert_workflow(self, instance: Dict): + self.is_valid(raise_exception=True) + user_id = self.data.get('user_id') + ApplicationCreateSerializer.WorkflowRequest(data=instance).is_valid(raise_exception=True) + application_model = ApplicationCreateSerializer.WorkflowRequest.to_application_model(user_id, instance) + application_model.save() + # 插入认证信息 + ApplicationAccessToken(application_id=application_model.id, + access_token=hashlib.md5(str(uuid.uuid1()).encode()).hexdigest()[8:24]).save() + return ApplicationCreateSerializer.ApplicationResponse(application_model).data + + @staticmethod + def to_application_knowledge_mapping(application_id: str, dataset_id: str): + return ApplicationKnowledgeMapping(id=uuid.uuid1(), application_id=application_id, dataset_id=dataset_id) + + def insert_simple(self, instance: Dict): + self.is_valid(raise_exception=True) + user_id = self.data.get('user_id') + ApplicationCreateSerializer.SimplateRequest(data=instance).is_valid(user_id=user_id, raise_exception=True) + application_model = ApplicationCreateSerializer.SimplateRequest.to_application_model(user_id, instance) + dataset_id_list = instance.get('knowledge_id_list', []) + application_knowledge_mapping_model_list = [ + self.to_application_knowledge_mapping(application_model.id, dataset_id) for + dataset_id in dataset_id_list] + # 插入应用 + application_model.save() + # 插入认证信息 + ApplicationAccessToken(application_id=application_model.id, + access_token=hashlib.md5(str(uuid.uuid1()).encode()).hexdigest()[8:24]).save() + # 插入关联数据 + QuerySet(ApplicationKnowledgeMapping).bulk_create(application_knowledge_mapping_model_list) + return ApplicationCreateSerializer.ApplicationResponse(application_model).data diff --git a/apps/application/urls.py b/apps/application/urls.py index b4c63f627..a7b0c8ef2 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -5,5 +5,6 @@ from . import views app_name = 'application' urlpatterns = [ - path('workspace//application//application_key', views.ApplicationKey.as_view()), -] + path('workspace//application', views.Application.as_view(), name='application'), + path('workspace//application//application_key', + views.ApplicationKey.as_view())] diff --git a/apps/application/views/__init__.py b/apps/application/views/__init__.py index c168364ef..478281e50 100644 --- a/apps/application/views/__init__.py +++ b/apps/application/views/__init__.py @@ -6,4 +6,5 @@ @date:2025/5/9 18:51 @desc: """ -from .application_api_key import * \ No newline at end of file +from .application_api_key import * +from .application import * diff --git a/apps/application/views/application.py b/apps/application/views/application.py index eae8044a5..a381d6b52 100644 --- a/apps/application/views/application.py +++ b/apps/application/views/application.py @@ -14,9 +14,11 @@ from rest_framework.views import APIView from application.api.application_api import ApplicationCreateAPI from application.serializers.application import ApplicationSerializer from common import result +from common.auth import TokenAuth class Application(APIView): + authentication_classes = [TokenAuth] @extend_schema( methods=['POST'], @@ -28,5 +30,6 @@ class Application(APIView): responses=ApplicationCreateAPI.get_response(), tags=[_('Application')] # type: ignore ) - def post(self, request: Request): - return result.success(ApplicationSerializer.insert(request.data)) + def post(self, request: Request, workspace_id: str): + return result.success( + ApplicationSerializer(data={'workspace_id': workspace_id, 'user_id': request.user.id}).insert(request.data)) diff --git a/apps/knowledge/migrations/0007_alter_document_status_alter_paragraph_status_and_more.py b/apps/knowledge/migrations/0007_alter_document_status_alter_paragraph_status_and_more.py new file mode 100644 index 000000000..023794628 --- /dev/null +++ b/apps/knowledge/migrations/0007_alter_document_status_alter_paragraph_status_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2 on 2025-05-27 03:05 + +import knowledge.models.knowledge +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('knowledge', '0006_knowledgefolder_desc_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='document', + name='status', + field=models.CharField(default=knowledge.models.knowledge.Status.__str__, max_length=20, verbose_name='状态'), + ), + migrations.AlterField( + model_name='paragraph', + name='status', + field=models.CharField(default=knowledge.models.knowledge.Status.__str__, max_length=20, verbose_name='状态'), + ), + migrations.DeleteModel( + name='ApplicationKnowledgeMapping', + ), + ] diff --git a/apps/knowledge/models/knowledge.py b/apps/knowledge/models/knowledge.py index 2a374c214..e8b600c0d 100644 --- a/apps/knowledge/models/knowledge.py +++ b/apps/knowledge/models/knowledge.py @@ -202,15 +202,6 @@ class ProblemParagraphMapping(AppModelMixin): db_table = "problem_paragraph_mapping" -class ApplicationKnowledgeMapping(AppModelMixin): - id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") - # application = models.ForeignKey(Application, on_delete=models.DO_NOTHING) - knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING) - - class Meta: - db_table = "application_knowledge_mapping" - - class SourceType(models.IntegerChoices): """订单类型""" PROBLEM = 0, '问题' diff --git a/apps/knowledge/serializers/knowledge.py b/apps/knowledge/serializers/knowledge.py index bd96f0c5f..aa73f78a7 100644 --- a/apps/knowledge/serializers/knowledge.py +++ b/apps/knowledge/serializers/knowledge.py @@ -14,6 +14,7 @@ from django.db.models.functions import Reverse, Substr from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from application.models import ApplicationKnowledgeMapping from common.config.embedding_config import VectorStore from common.db.search import native_search, get_dynamics_model, native_page_search from common.db.sql_execute import select_list @@ -23,7 +24,7 @@ from common.utils.common import valid_license, post, get_file_content from common.utils.fork import Fork, ChildLink from common.utils.split_model import get_split_model from knowledge.models import Knowledge, KnowledgeScope, KnowledgeType, Document, Paragraph, Problem, \ - ProblemParagraphMapping, ApplicationKnowledgeMapping, TaskType, State, SearchMode, KnowledgeFolder + ProblemParagraphMapping, TaskType, State, SearchMode, KnowledgeFolder from knowledge.serializers.common import ProblemParagraphManage, get_embedding_model_id_by_knowledge_id, MetaSerializer, \ GenerateRelatedSerializer, get_embedding_model_by_knowledge_id, list_paragraph from knowledge.serializers.document import DocumentSerializers diff --git a/apps/maxkb/settings/base.py b/apps/maxkb/settings/base.py index d44a5b6c2..f0e50d0f3 100644 --- a/apps/maxkb/settings/base.py +++ b/apps/maxkb/settings/base.py @@ -40,7 +40,7 @@ INSTALLED_APPS = [ 'drf_spectacular_sidecar', 'users.apps.UsersConfig', 'tools.apps.ToolConfig', - 'knowledge.apps.KnowledgeConfig', + 'knowledge', 'common', 'system_manage', 'models_provider', diff --git a/apps/maxkb/urls.py b/apps/maxkb/urls.py index 8f5b1fbe4..85d38a582 100644 --- a/apps/maxkb/urls.py +++ b/apps/maxkb/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ path("api/", include("folders.urls")), path("api/", include("knowledge.urls")), path("api/", include("system_manage.urls")), + path("api/", include("application.urls")) ] urlpatterns += [ path('schema/', SpectacularAPIView.as_view(), name='schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的 diff --git a/apps/users/migrations/0002_alter_user_nick_name.py b/apps/users/migrations/0002_alter_user_nick_name.py new file mode 100644 index 000000000..bb73db3c4 --- /dev/null +++ b/apps/users/migrations/0002_alter_user_nick_name.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-05-27 02:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='nick_name', + field=models.CharField(max_length=150, unique=True, verbose_name='昵称'), + ), + ]