diff --git a/apps/application/models/application.py b/apps/application/models/application.py index 71d345164cc..28a8f402ed4 100644 --- a/apps/application/models/application.py +++ b/apps/application/models/application.py @@ -1,11 +1,12 @@ # coding=utf-8 """ - @project: MaxKB - @Author:虎虎 - @file: application.py - @date:2025/5/7 15:29 - @desc: +@project: MaxKB +@Author:虎虎 +@file: application.py +@date:2025/5/7 15:29 +@desc: """ + import uuid_utils.compat as uuid from django.db import models from mptt.fields import TreeForeignKey @@ -23,45 +24,50 @@ class ApplicationFolder(MPTTModel, AppModelMixin): desc = models.CharField(max_length=200, null=True, blank=True, verbose_name="描述") user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) - parent = TreeForeignKey('self', on_delete=models.DO_NOTHING, null=True, blank=True, related_name='children') + parent = TreeForeignKey("self", on_delete=models.DO_NOTHING, null=True, blank=True, related_name="children") class Meta: db_table = "application_folder" class MPTTMeta: - order_insertion_by = ['name'] + order_insertion_by = ["name"] class ApplicationTypeChoices(models.TextChoices): """订单类型""" - SIMPLE = 'SIMPLE', '简易' - WORK_FLOW = 'WORK_FLOW', '工作流' + + SIMPLE = "SIMPLE", "简易" + WORK_FLOW = "WORK_FLOW", "工作流" def get_dataset_setting_dict(): - return {'top_n': 3, 'similarity': 0.6, 'max_paragraph_char_number': 5000, 'search_mode': 'embedding', - 'no_references_setting': { - 'status': 'ai_questioning', - 'value': '{question}' - }} + return { + "top_n": 3, + "similarity": 0.6, + "max_paragraph_char_number": 5000, + "search_mode": "embedding", + "no_references_setting": {"status": "ai_questioning", "value": "{question}"}, + } def get_model_setting_dict(): return { - 'prompt': Application.get_default_model_prompt(), - 'no_references_prompt': '{question}', - 'reasoning_content_start': '', - 'reasoning_content_end': '', - 'reasoning_content_enable': False, + "prompt": Application.get_default_model_prompt(), + "no_references_prompt": "{question}", + "reasoning_content_start": "", + "reasoning_content_end": "", + "reasoning_content_enable": False, } 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='default') + folder = models.ForeignKey( + ApplicationFolder, on_delete=models.DO_NOTHING, verbose_name="文件夹id", default="default" + ) is_publish = models.BooleanField(verbose_name="是否发布", default=False) + is_portal = models.BooleanField(verbose_name="是否在门户上架", default=False) name = models.CharField(max_length=128, verbose_name="应用名称", db_index=True) desc = models.CharField(max_length=512, verbose_name="引用描述", default="") prologue = models.CharField(max_length=40960, verbose_name="开场白", default="") @@ -76,15 +82,25 @@ class Application(AppModelMixin): problem_optimization = models.BooleanField(verbose_name="问题优化", default=False) icon = models.CharField(max_length=256, verbose_name="应用icon", default="./favicon.ico") work_flow = models.JSONField(verbose_name="工作流数据", default=dict) - type = models.CharField(verbose_name="应用类型", choices=ApplicationTypeChoices.choices, - default=ApplicationTypeChoices.SIMPLE, max_length=256) - problem_optimization_prompt = models.CharField(verbose_name="问题优化提示词", max_length=102400, blank=True, - null=True, - default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中") - tts_model = models.ForeignKey(Model, related_name='tts_model_id', on_delete=models.SET_NULL, db_constraint=False, - blank=True, null=True) - stt_model = models.ForeignKey(Model, related_name='stt_model_id', on_delete=models.SET_NULL, db_constraint=False, - blank=True, null=True) + type = models.CharField( + verbose_name="应用类型", + choices=ApplicationTypeChoices.choices, + default=ApplicationTypeChoices.SIMPLE, + max_length=256, + ) + problem_optimization_prompt = models.CharField( + verbose_name="问题优化提示词", + max_length=102400, + blank=True, + null=True, + default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中", + ) + tts_model = models.ForeignKey( + Model, related_name="tts_model_id", on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True + ) + stt_model = models.ForeignKey( + Model, related_name="stt_model_id", on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True + ) tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False) stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False) tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER") @@ -105,26 +121,29 @@ class Application(AppModelMixin): skill_tool_ids = models.JSONField(verbose_name="技能ID列表", default=list) mcp_output_enable = models.BooleanField(verbose_name="MCP输出是否启用", default=True) file_clean_time = models.IntegerField(verbose_name="文件清理时间", default=180) - long_term_enable = models.BooleanField(verbose_name='长期记忆是否开启', default=False) - long_term_model = models.ForeignKey(Model, related_name='long_term_model_id', on_delete=models.SET_NULL, - db_constraint=False, blank=True, null=True) + long_term_enable = models.BooleanField(verbose_name="长期记忆是否开启", default=False) + long_term_model = models.ForeignKey( + Model, related_name="long_term_model_id", on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True + ) long_term_model_params_setting = models.JSONField(verbose_name="长期记忆模型参数相关设置", default=dict) - long_term_trigger_type = models.CharField(verbose_name='长期记忆触发类型', default='ROUND') - long_term_trigger_setting = models.JSONField(verbose_name='长期记忆触发配置', default=dict) + long_term_trigger_type = models.CharField(verbose_name="长期记忆触发类型", default="ROUND") + long_term_trigger_setting = models.JSONField(verbose_name="长期记忆触发配置", default=dict) @staticmethod def get_default_model_prompt(): - return ('已知信息:' - '\n{data}' - '\n回答要求:' - '\n- 如果你不知道答案或者没有从获取答案,请回答“没有在知识库中查找到相关信息,建议咨询相关技术支持或参考官方文档进行操作”。' - '\n- 避免提及你是从中获得的知识。' - '\n- 请保持答案与中描述的一致。' - '\n- 请使用markdown 语法优化答案的格式。' - '\n- 中的图片链接、链接地址和脚本语言请完整返回。' - '\n- 请使用与问题相同的语言来回答。' - '\n问题:' - '\n{question}') + return ( + "已知信息:" + "\n{data}" + "\n回答要求:" + "\n- 如果你不知道答案或者没有从获取答案,请回答“没有在知识库中查找到相关信息,建议咨询相关技术支持或参考官方文档进行操作”。" + "\n- 避免提及你是从中获得的知识。" + "\n- 请保持答案与中描述的一致。" + "\n- 请使用markdown 语法优化答案的格式。" + "\n- 中的图片链接、链接地址和脚本语言请完整返回。" + "\n- 请使用与问题相同的语言来回答。" + "\n问题:" + "\n{question}" + ) class Meta: db_table = "application" @@ -160,15 +179,21 @@ class ApplicationVersion(AppModelMixin): problem_optimization = models.BooleanField(verbose_name="问题优化", default=False) icon = models.CharField(max_length=256, verbose_name="应用icon", default="./favicon.ico") work_flow = models.JSONField(verbose_name="工作流数据", default=dict) - type = models.CharField(verbose_name="应用类型", choices=ApplicationTypeChoices.choices, - default=ApplicationTypeChoices.SIMPLE, max_length=256) - problem_optimization_prompt = models.CharField(verbose_name="问题优化提示词", max_length=102400, blank=True, - null=True, - default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中") - tts_model_id = models.UUIDField(verbose_name="文本转语音模型id", - blank=True, null=True) - stt_model_id = models.UUIDField(verbose_name="语音转文本模型id", - blank=True, null=True) + type = models.CharField( + verbose_name="应用类型", + choices=ApplicationTypeChoices.choices, + default=ApplicationTypeChoices.SIMPLE, + max_length=256, + ) + problem_optimization_prompt = models.CharField( + verbose_name="问题优化提示词", + max_length=102400, + blank=True, + null=True, + default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中", + ) + tts_model_id = models.UUIDField(verbose_name="文本转语音模型id", blank=True, null=True) + stt_model_id = models.UUIDField(verbose_name="语音转文本模型id", blank=True, null=True) tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False) stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False) tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER") @@ -187,11 +212,11 @@ class ApplicationVersion(AppModelMixin): application_ids = models.JSONField(verbose_name="应用ID列表", default=list) skill_tool_ids = models.JSONField(verbose_name="技能ID列表", default=list) mcp_output_enable = models.BooleanField(verbose_name="MCP输出是否启用", default=True) - long_term_enable = models.BooleanField(verbose_name='长期记忆是否开启', default=False) + long_term_enable = models.BooleanField(verbose_name="长期记忆是否开启", default=False) long_term_model_id = models.UUIDField(verbose_name="长期记忆模型id", blank=True, null=True) long_term_model_params_setting = models.JSONField(verbose_name="长期记忆模型参数相关设置", default=dict) - long_term_trigger_type = models.CharField(verbose_name='长期记忆触发类型', default='ROUND') - long_term_trigger_setting = models.JSONField(verbose_name='长期记忆触发配置', default=dict) + long_term_trigger_type = models.CharField(verbose_name="长期记忆触发类型", default="ROUND") + long_term_trigger_setting = models.JSONField(verbose_name="长期记忆触发配置", default=dict) knowledge_ids = models.JSONField(verbose_name="数据集id列表", default=list) class Meta: diff --git a/apps/application/serializers/application.py b/apps/application/serializers/application.py index f239f6b6a57..9b6c3bf5fed 100644 --- a/apps/application/serializers/application.py +++ b/apps/application/serializers/application.py @@ -557,6 +557,9 @@ class ApplicationListResponse(serializers.Serializer): required=True, label=_("Application Description"), help_text=_("Application Description") ) is_publish = serializers.BooleanField(required=True, label=_("Model id"), help_text=_("Model id")) + is_portal = serializers.BooleanField( + required=True, label=_("Whether to publish on portal"), help_text=_("Whether to publish on portal") + ) type = serializers.CharField(required=True, label=_("Application type"), help_text=_("Application type")) resource_type = serializers.CharField(required=True, label=_("Resource type"), help_text=_("Resource type")) user_id = serializers.CharField(required=True, label=_("Affiliation user"), help_text=_("Affiliation user")) @@ -690,6 +693,7 @@ class ApplicationImportRequest(serializers.Serializer): class ApplicationEditSerializer(serializers.Serializer): name = serializers.CharField(required=False, max_length=64, min_length=1, label=_("Application Name")) + is_portal = serializers.BooleanField(required=False, label=_("Whether to publish on portal")) desc = serializers.CharField( required=False, max_length=256, @@ -1422,6 +1426,7 @@ def edit(self, instance: Dict, with_valid=True): ToolExecutor().validate_mcp_transport(json.dumps(instance.get("mcp_servers"))) update_keys = [ "name", + "is_portal", "desc", "model_id", "multiple_rounds_dialogue", diff --git a/apps/application/sql/list_application.sql b/apps/application/sql/list_application.sql index 3b6e863fd97..c26218223d7 100644 --- a/apps/application/sql/list_application.sql +++ b/apps/application/sql/list_application.sql @@ -2,6 +2,7 @@ select * from (select application."id"::text, application."name", application."desc", application."is_publish", + application."is_portal", application."type", 'application' as "resource_type", application."workspace_id", diff --git a/apps/application/sql/list_application_user.sql b/apps/application/sql/list_application_user.sql index 777010fdb18..cc25dab288f 100644 --- a/apps/application/sql/list_application_user.sql +++ b/apps/application/sql/list_application_user.sql @@ -2,6 +2,7 @@ select * from (select application."id"::text, application."name", application."desc", application."is_publish", + application."is_portal", application."type", 'application' as "resource_type", application."workspace_id", diff --git a/apps/application/sql/list_application_user_ee.sql b/apps/application/sql/list_application_user_ee.sql index a0b21ff281d..da5c356d5e6 100644 --- a/apps/application/sql/list_application_user_ee.sql +++ b/apps/application/sql/list_application_user_ee.sql @@ -2,6 +2,7 @@ select * from (select application."id"::text, application."name", application."desc", application."is_publish", + application."is_portal", application."type", 'application' as "resource_type", application."workspace_id", diff --git a/apps/chat/serializers/portal.py b/apps/chat/serializers/portal.py index 08a4b1dc008..c58d3450e6e 100644 --- a/apps/chat/serializers/portal.py +++ b/apps/chat/serializers/portal.py @@ -30,7 +30,7 @@ class PortalApplicationAuthMixin: def get_authorized_application_queryset(user_id): public_apps = ApplicationAccessToken.objects.filter(application_id=OuterRef("id"), authentication=False) if not ChatUser.objects.filter(id=user_id).exists(): - return Application.objects.filter(is_publish=True).filter(Exists(public_apps)) + return Application.objects.filter(is_publish=True, is_portal=True).filter(Exists(public_apps)) authed_token_exists = ApplicationAccessToken.objects.filter(application_id=OuterRef("id"), authentication=True) direct_auth = ResourceChatUserAuthorize.objects.filter( resource_id=OuterRef("id"), resource_type=ResourceType.APPLICATION.value, is_auth=True, user_id=user_id @@ -42,7 +42,7 @@ def get_authorized_application_queryset(user_id): is_auth=True, user_group_id__in=user_groups, ) - return Application.objects.filter(is_publish=True).filter( + return Application.objects.filter(is_publish=True, is_portal=True).filter( Exists(public_apps) | (Exists(authed_token_exists) & (Exists(direct_auth) | Exists(group_auth))) ) @@ -56,6 +56,7 @@ class ApplicationResponseSerializer(serializers.Serializer): dialogue_number = serializers.IntegerField(required=True) prologue = serializers.CharField(required=True) is_publish = serializers.BooleanField(required=True) + is_portal = serializers.BooleanField(required=True) class PortalApplicationSerializer(serializers.Serializer): @@ -65,7 +66,7 @@ class Query(PortalApplicationAuthMixin, serializers.Serializer): ) def get_query_set(self): - queryset = Application.objects.filter(is_publish=True) + queryset = Application.objects.filter(is_publish=True, is_portal=True) name = self.data.get("name") if name: queryset = queryset.filter(name__icontains=name)