From 43654bddaf926ee9f8f736437c690c389b631eaf Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Fri, 30 May 2025 20:02:39 +0800 Subject: [PATCH] feat: application operate api (#3176) --- apps/application/api/application_api.py | 51 ++- apps/application/models/__init__.py | 2 + apps/application/models/application.py | 13 + .../application/models/application_api_key.py | 19 +- apps/application/models/application_chat.py | 83 ++++ apps/application/serializers/application.py | 373 +++++++++++++++++- .../serializers/application_api_key.py | 17 +- apps/application/urls.py | 6 +- apps/application/views/application.py | 96 ++++- apps/chat/__init__.py | 0 apps/chat/admin.py | 3 + apps/chat/api/chat_embed_api.py | 47 +++ apps/chat/apps.py | 6 + .../chat_pipeline/I_base_chat_pipeline.py | 0 .../chat_pipeline/__init__.py | 0 .../chat_pipeline/pipeline_manage.py | 0 .../chat_pipeline/step/__init__.py | 0 .../chat_pipeline/step/chat_step/__init__.py | 0 .../step/chat_step/i_chat_step.py | 0 .../step/chat_step/impl/base_chat_step.py | 4 +- .../generate_human_message_step/__init__.py | 0 .../i_generate_human_message_step.py | 0 .../impl/base_generate_human_message_step.py | 0 .../step/reset_problem_step/__init__.py | 0 .../i_reset_problem_step.py | 0 .../impl/base_reset_problem_step.py | 0 .../step/search_dataset_step/__init__.py | 0 .../i_search_dataset_step.py | 0 .../impl/base_search_dataset_step.py | 0 apps/{application => chat}/flow/__init__.py | 0 apps/{application => chat}/flow/common.py | 0 .../flow/default_workflow.json | 0 .../flow/default_workflow_en.json | 0 .../flow/default_workflow_zh.json | 0 .../flow/default_workflow_zh_Hant.json | 0 .../{application => chat}/flow/i_step_node.py | 31 +- .../flow/step_node/__init__.py | 0 .../step_node/ai_chat_step_node/__init__.py | 0 .../ai_chat_step_node/i_chat_node.py | 24 +- .../ai_chat_step_node/impl/__init__.py | 0 .../ai_chat_step_node/impl/base_chat_node.py | 11 +- .../step_node/application_node/__init__.py | 0 .../application_node/i_application_node.py | 21 +- .../application_node/impl/__init__.py | 0 .../impl/base_application_node.py | 6 +- .../flow/step_node/condition_node/__init__.py | 0 .../condition_node/compare/__init__.py | 0 .../condition_node/compare/compare.py | 0 .../condition_node/compare/contain_compare.py | 2 +- .../condition_node/compare/equal_compare.py | 2 +- .../condition_node/compare/ge_compare.py | 2 +- .../condition_node/compare/gt_compare.py | 2 +- .../compare/is_not_null_compare.py | 2 +- .../condition_node/compare/is_not_true.py | 2 +- .../condition_node/compare/is_null_compare.py | 2 +- .../condition_node/compare/is_true.py | 2 +- .../condition_node/compare/le_compare.py | 2 +- .../compare/len_equal_compare.py | 2 +- .../condition_node/compare/len_ge_compare.py | 2 +- .../condition_node/compare/len_gt_compare.py | 2 +- .../condition_node/compare/len_le_compare.py | 2 +- .../condition_node/compare/len_lt_compare.py | 2 +- .../condition_node/compare/lt_compare.py | 2 +- .../compare/not_contain_compare.py | 2 +- .../condition_node/i_condition_node.py | 15 +- .../step_node/condition_node/impl/__init__.py | 0 .../impl/base_condition_node.py | 6 +- .../step_node/direct_reply_node/__init__.py | 0 .../direct_reply_node/i_reply_node.py | 13 +- .../direct_reply_node/impl/__init__.py | 0 .../direct_reply_node/impl/base_reply_node.py | 4 +- .../document_extract_node/__init__.py | 0 .../i_document_extract_node.py | 5 +- .../document_extract_node/impl/__init__.py | 0 .../impl/base_document_extract_node.py | 12 +- .../flow/step_node/form_node/__init__.py | 0 .../flow/step_node/form_node/i_form_node.py | 10 +- .../flow/step_node/form_node/impl/__init__.py | 0 .../form_node/impl/base_form_node.py | 6 +- .../step_node/function_lib_node/__init__.py | 0 .../function_lib_node/i_function_lib_node.py | 17 +- .../function_lib_node/impl/__init__.py | 0 .../impl/base_function_lib_node.py | 14 +- .../flow/step_node/function_node/__init__.py | 0 .../function_node/i_function_node.py | 19 +- .../step_node/function_node/impl/__init__.py | 0 .../function_node/impl/base_function_node.py | 10 +- .../image_generate_step_node/__init__.py | 0 .../i_image_generate_node.py | 20 +- .../image_generate_step_node/impl/__init__.py | 0 .../impl/base_image_generate_node.py | 10 +- .../image_understand_step_node/__init__.py | 0 .../i_image_understand_node.py | 21 +- .../impl/__init__.py | 0 .../impl/base_image_understand_node.py | 20 +- .../flow/step_node/mcp_node/__init__.py | 0 .../flow/step_node/mcp_node/i_mcp_node.py | 13 +- .../flow/step_node/mcp_node/impl/__init__.py | 0 .../step_node/mcp_node/impl/base_mcp_node.py | 4 +- .../flow/step_node/question_node/__init__.py | 0 .../question_node/i_question_node.py | 20 +- .../step_node/question_node/impl/__init__.py | 0 .../question_node/impl/base_question_node.py | 9 +- .../flow/step_node/reranker_node/__init__.py | 0 .../reranker_node/i_reranker_node.py | 10 +- .../step_node/reranker_node/impl/__init__.py | 0 .../reranker_node/impl/base_reranker_node.py | 6 +- .../step_node/search_dataset_node/__init__.py | 0 .../i_search_dataset_node.py | 16 +- .../search_dataset_node/impl/__init__.py | 0 .../impl/base_search_dataset_node.py | 18 +- .../speech_to_text_step_node/__init__.py | 0 .../i_speech_to_text_node.py | 14 +- .../speech_to_text_step_node/impl/__init__.py | 0 .../impl/base_speech_to_text_node.py | 18 +- .../flow/step_node/start_node/__init__.py | 0 .../flow/step_node/start_node/i_start_node.py | 2 +- .../step_node/start_node/impl/__init__.py | 0 .../start_node/impl/base_start_node.py | 4 +- .../text_to_speech_step_node/__init__.py | 0 .../i_text_to_speech_node.py | 13 +- .../text_to_speech_step_node/impl/__init__.py | 0 .../impl/base_text_to_speech_node.py | 10 +- .../variable_assign_node/__init__.py | 0 .../i_variable_assign_node.py | 5 +- .../variable_assign_node/impl/__init__.py | 0 .../impl/base_variable_assign_node.py | 4 +- apps/{application => chat}/flow/tools.py | 4 +- .../flow/workflow_manage.py | 14 +- apps/chat/migrations/__init__.py | 0 apps/chat/models/__init__.py | 8 + apps/chat/serializers/__init__.py | 8 + .../serializers/chat_embed_serializers.py | 103 +++++ apps/chat/template/embed.js | 325 +++++++++++++++ apps/chat/tests.py | 3 + apps/chat/urls.py | 9 + apps/chat/views/__init__.py | 9 + apps/chat/views/chat_embed.py | 32 ++ apps/common/constants/authentication_type.py | 4 + apps/common/constants/permission_constants.py | 16 +- apps/common/encoder/encoder.py | 30 ++ apps/common/utils/common.py | 38 +- apps/common/utils/function_code.py | 99 +++++ apps/maxkb/urls.py | 3 +- 144 files changed, 1628 insertions(+), 290 deletions(-) create mode 100644 apps/application/models/application_chat.py create mode 100644 apps/chat/__init__.py create mode 100644 apps/chat/admin.py create mode 100644 apps/chat/api/chat_embed_api.py create mode 100644 apps/chat/apps.py rename apps/{application => chat}/chat_pipeline/I_base_chat_pipeline.py (100%) rename apps/{application => chat}/chat_pipeline/__init__.py (100%) rename apps/{application => chat}/chat_pipeline/pipeline_manage.py (100%) rename apps/{application => chat}/chat_pipeline/step/__init__.py (100%) rename apps/{application => chat}/chat_pipeline/step/chat_step/__init__.py (100%) rename apps/{application => chat}/chat_pipeline/step/chat_step/i_chat_step.py (100%) rename apps/{application => chat}/chat_pipeline/step/chat_step/impl/base_chat_step.py (99%) rename apps/{application => chat}/chat_pipeline/step/generate_human_message_step/__init__.py (100%) rename apps/{application => chat}/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py (100%) rename apps/{application => chat}/chat_pipeline/step/generate_human_message_step/impl/base_generate_human_message_step.py (100%) rename apps/{application => chat}/chat_pipeline/step/reset_problem_step/__init__.py (100%) rename apps/{application => chat}/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py (100%) rename apps/{application => chat}/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py (100%) rename apps/{application => chat}/chat_pipeline/step/search_dataset_step/__init__.py (100%) rename apps/{application => chat}/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py (100%) rename apps/{application => chat}/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py (100%) rename apps/{application => chat}/flow/__init__.py (100%) rename apps/{application => chat}/flow/common.py (100%) rename apps/{application => chat}/flow/default_workflow.json (100%) rename apps/{application => chat}/flow/default_workflow_en.json (100%) rename apps/{application => chat}/flow/default_workflow_zh.json (100%) rename apps/{application => chat}/flow/default_workflow_zh_Hant.json (100%) rename apps/{application => chat}/flow/i_step_node.py (87%) rename apps/{application => chat}/flow/step_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/ai_chat_step_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/ai_chat_step_node/i_chat_node.py (54%) rename apps/{application => chat}/flow/step_node/ai_chat_step_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/ai_chat_step_node/impl/base_chat_node.py (97%) rename apps/{application => chat}/flow/step_node/application_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/application_node/i_application_node.py (81%) rename apps/{application => chat}/flow/step_node/application_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/application_node/impl/base_application_node.py (98%) rename apps/{application => chat}/flow/step_node/condition_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/condition_node/compare/__init__.py (100%) rename apps/{application => chat}/flow/step_node/condition_node/compare/compare.py (100%) rename apps/{application => chat}/flow/step_node/condition_node/compare/contain_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/equal_compare.py (84%) rename apps/{application => chat}/flow/step_node/condition_node/compare/ge_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/gt_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/is_not_null_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/is_not_true.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/is_null_compare.py (86%) rename apps/{application => chat}/flow/step_node/condition_node/compare/is_true.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/le_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/len_equal_compare.py (86%) rename apps/{application => chat}/flow/step_node/condition_node/compare/len_ge_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/len_gt_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/len_le_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/len_lt_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/lt_compare.py (87%) rename apps/{application => chat}/flow/step_node/condition_node/compare/not_contain_compare.py (88%) rename apps/{application => chat}/flow/step_node/condition_node/i_condition_node.py (50%) rename apps/{application => chat}/flow/step_node/condition_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/condition_node/impl/base_condition_node.py (90%) rename apps/{application => chat}/flow/step_node/direct_reply_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/direct_reply_node/i_reply_node.py (72%) rename apps/{application => chat}/flow/step_node/direct_reply_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/direct_reply_node/impl/base_reply_node.py (91%) rename apps/{application => chat}/flow/step_node/document_extract_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/document_extract_node/i_document_extract_node.py (79%) rename apps/{application => chat}/flow/step_node/document_extract_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/document_extract_node/impl/base_document_extract_node.py (89%) rename apps/{application => chat}/flow/step_node/form_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/form_node/i_form_node.py (66%) rename apps/{application => chat}/flow/step_node/form_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/form_node/impl/base_form_node.py (97%) rename apps/{application => chat}/flow/step_node/function_lib_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/function_lib_node/i_function_lib_node.py (61%) rename apps/{application => chat}/flow/step_node/function_lib_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/function_lib_node/impl/base_function_lib_node.py (93%) rename apps/{application => chat}/flow/step_node/function_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/function_node/i_function_node.py (67%) rename apps/{application => chat}/flow/step_node/function_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/function_node/impl/base_function_node.py (92%) rename apps/{application => chat}/flow/step_node/image_generate_step_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/image_generate_step_node/i_image_generate_node.py (54%) rename apps/{application => chat}/flow/step_node/image_generate_step_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py (94%) rename apps/{application => chat}/flow/step_node/image_understand_step_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/image_understand_step_node/i_image_understand_node.py (55%) rename apps/{application => chat}/flow/step_node/image_understand_step_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py (93%) rename apps/{application => chat}/flow/step_node/mcp_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/mcp_node/i_mcp_node.py (60%) rename apps/{application => chat}/flow/step_node/mcp_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/mcp_node/impl/base_mcp_node.py (95%) rename apps/{application => chat}/flow/step_node/question_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/question_node/i_question_node.py (57%) rename apps/{application => chat}/flow/step_node/question_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/question_node/impl/base_question_node.py (95%) rename apps/{application => chat}/flow/step_node/reranker_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/reranker_node/i_reranker_node.py (81%) rename apps/{application => chat}/flow/step_node/reranker_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/reranker_node/impl/base_reranker_node.py (95%) rename apps/{application => chat}/flow/step_node/search_dataset_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/search_dataset_node/i_search_dataset_node.py (82%) rename apps/{application => chat}/flow/step_node/search_dataset_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/search_dataset_node/impl/base_search_dataset_node.py (93%) rename apps/{application => chat}/flow/step_node/speech_to_text_step_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py (61%) rename apps/{application => chat}/flow/step_node/speech_to_text_step_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py (86%) rename apps/{application => chat}/flow/step_node/start_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/start_node/i_start_node.py (85%) rename apps/{application => chat}/flow/step_node/start_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/start_node/impl/base_start_node.py (96%) rename apps/{application => chat}/flow/step_node/text_to_speech_step_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py (63%) rename apps/{application => chat}/flow/step_node/text_to_speech_step_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py (86%) rename apps/{application => chat}/flow/step_node/variable_assign_node/__init__.py (100%) rename apps/{application => chat}/flow/step_node/variable_assign_node/i_variable_assign_node.py (76%) rename apps/{application => chat}/flow/step_node/variable_assign_node/impl/__init__.py (100%) rename apps/{application => chat}/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py (94%) rename apps/{application => chat}/flow/tools.py (98%) rename apps/{application => chat}/flow/workflow_manage.py (98%) create mode 100644 apps/chat/migrations/__init__.py create mode 100644 apps/chat/models/__init__.py create mode 100644 apps/chat/serializers/__init__.py create mode 100644 apps/chat/serializers/chat_embed_serializers.py create mode 100644 apps/chat/template/embed.js create mode 100644 apps/chat/tests.py create mode 100644 apps/chat/urls.py create mode 100644 apps/chat/views/__init__.py create mode 100644 apps/chat/views/chat_embed.py create mode 100644 apps/common/encoder/encoder.py create mode 100644 apps/common/utils/function_code.py diff --git a/apps/application/api/application_api.py b/apps/application/api/application_api.py index d0e1fdfa8..51852aa74 100644 --- a/apps/application/api/application_api.py +++ b/apps/application/api/application_api.py @@ -12,9 +12,9 @@ from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers from application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \ - ApplicationQueryRequest + ApplicationImportRequest, ApplicationEditSerializer from common.mixins.api_mixin import APIMixin -from common.result import ResultSerializer, ResultPageSerializer +from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest): @@ -120,3 +120,50 @@ class ApplicationCreateAPI(APIMixin): @staticmethod def get_response(): return ApplicationCreateResponse + + +class ApplicationImportAPI(APIMixin): + @staticmethod + def get_parameters(): + ApplicationCreateAPI.get_parameters() + + @staticmethod + def get_request(): + return ApplicationImportRequest + + +class ApplicationOperateAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="application_id", + description="应用id", + type=OpenApiTypes.STR, + location='path', + required=True, + ) + ] + + +class ApplicationExportAPI(APIMixin): + @staticmethod + def get_parameters(): + return ApplicationOperateAPI.get_parameters() + + @staticmethod + def get_response(): + return DefaultResultSerializer + + +class ApplicationEditAPI(APIMixin): + @staticmethod + def get_request(): + return ApplicationEditSerializer diff --git a/apps/application/models/__init__.py b/apps/application/models/__init__.py index 405945b54..ffa532f05 100644 --- a/apps/application/models/__init__.py +++ b/apps/application/models/__init__.py @@ -8,3 +8,5 @@ """ from .application import * from .application_access_token import * +from .application_chat import * +from .application_api_key import * diff --git a/apps/application/models/application.py b/apps/application/models/application.py index 243c14652..69bc9cb1c 100644 --- a/apps/application/models/application.py +++ b/apps/application/models/application.py @@ -117,3 +117,16 @@ class ApplicationKnowledgeMapping(AppModelMixin): class Meta: db_table = "application_knowledge_mapping" + + +class WorkFlowVersion(AppModelMixin): + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") + application = models.ForeignKey(Application, on_delete=models.CASCADE) + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) + name = models.CharField(verbose_name="版本名称", max_length=128, default="") + publish_user_id = models.UUIDField(verbose_name="发布者id", max_length=128, default=None, null=True) + publish_user_name = models.CharField(verbose_name="发布者名称", max_length=128, default="") + work_flow = models.JSONField(verbose_name="工作流数据", default=dict) + + class Meta: + db_table = "application_work_flow_version" diff --git a/apps/application/models/application_api_key.py b/apps/application/models/application_api_key.py index 69fad9ba6..8970ba322 100644 --- a/apps/application/models/application_api_key.py +++ b/apps/application/models/application_api_key.py @@ -1,4 +1,3 @@ - import uuid from django.contrib.postgres.fields import ArrayField @@ -6,7 +5,6 @@ from django.db import models from application.models import Application from common.mixins.app_model_mixin import AppModelMixin - from users.models import User @@ -23,4 +21,19 @@ class ApplicationApiKey(AppModelMixin): , default=list) class Meta: - db_table = "application_api_key" \ No newline at end of file + db_table = "application_api_key" + + +class ApplicationPublicAccessClient(AppModelMixin): + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") + client_id = models.UUIDField(max_length=128, default=uuid.uuid1, verbose_name="公共访问链接客户端id") + client_type = models.CharField(max_length=64, verbose_name="客户端类型") + application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用id") + access_num = models.IntegerField(default=0, verbose_name="访问总次数次数") + intraday_access_num = models.IntegerField(default=0, verbose_name="当日访问次数") + + class Meta: + db_table = "application_public_access_client" + indexes = [ + models.Index(fields=['application_id', 'client_id']), + ] diff --git a/apps/application/models/application_chat.py b/apps/application/models/application_chat.py new file mode 100644 index 000000000..1c3184e13 --- /dev/null +++ b/apps/application/models/application_chat.py @@ -0,0 +1,83 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: application_chat_log.py + @date:2025/5/29 17:12 + @desc: +""" +import uuid_utils.compat as uuid +from django.contrib.postgres.fields import ArrayField +from django.db import models +from django.utils.translation import gettext_lazy as _ +from langchain_core.messages import HumanMessage, AIMessage + +from application.models import Application +from common.encoder.encoder import SystemEncoder +from common.mixins.app_model_mixin import AppModelMixin + + +def default_asker(): + return {'user_name': '游客'} + + +class Chat(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.CASCADE) + abstract = models.CharField(max_length=1024, verbose_name="摘要") + asker = models.JSONField(verbose_name="访问者", default=default_asker, encoder=SystemEncoder) + client_id = models.UUIDField(verbose_name="客户端id", default=None, null=True) + is_deleted = models.BooleanField(verbose_name="", default=False) + + class Meta: + db_table = "application_chat" + + +class VoteChoices(models.TextChoices): + """订单类型""" + UN_VOTE = "-1", '未投票' + STAR = "0", '赞同' + TRAMPLE = "1", '反对' + + + +class ChatRecord(AppModelMixin): + """ + 对话日志 详情 + """ + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") + chat = models.ForeignKey(Chat, on_delete=models.CASCADE) + vote_status = models.CharField(verbose_name='投票', max_length=10, choices=VoteChoices.choices, + default=VoteChoices.UN_VOTE) + problem_text = models.CharField(max_length=10240, verbose_name="问题") + answer_text = models.CharField(max_length=40960, verbose_name="答案") + answer_text_list = ArrayField(verbose_name="改进标注列表", + base_field=models.JSONField() + , default=list) + message_tokens = models.IntegerField(verbose_name="请求token数量", default=0) + answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0) + const = models.IntegerField(verbose_name="总费用", default=0) + details = models.JSONField(verbose_name="对话详情", default=dict, encoder=SystemEncoder) + improve_paragraph_id_list = ArrayField(verbose_name="改进标注列表", + base_field=models.UUIDField(max_length=128, blank=True) + , default=list) + run_time = models.FloatField(verbose_name="运行时长", default=0) + index = models.IntegerField(verbose_name="对话下标") + + def get_human_message(self): + if 'problem_padding' in self.details: + return HumanMessage(content=self.details.get('problem_padding').get('padding_problem_text')) + return HumanMessage(content=self.problem_text) + + def get_ai_message(self): + answer_text = self.answer_text + if answer_text is None or len(str(answer_text).strip()) == 0: + answer_text = _( + 'Sorry, no relevant content was found. Please re-describe your problem or provide more information. ') + return AIMessage(content=answer_text) + + def get_node_details_runtime_node_id(self, runtime_node_id): + return self.details.get(runtime_node_id, None) + + class Meta: + db_table = "application_chat_record" diff --git a/apps/application/serializers/application.py b/apps/application/serializers/application.py index fe979b83d..75b9432ae 100644 --- a/apps/application/serializers/application.py +++ b/apps/application/serializers/application.py @@ -6,28 +6,64 @@ @date:2025/5/26 17:03 @desc: """ +import datetime import hashlib import os +import pickle import re -from typing import Dict +from typing import Dict, List import uuid_utils.compat as uuid from django.core import validators -from django.db import models +from django.db import models, transaction from django.db.models import QuerySet +from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers +from rest_framework import serializers, status +from rest_framework.utils.formatting import lazy_format from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \ - ApplicationFolder + ApplicationFolder, WorkFlowVersion from application.models.application_access_token import ApplicationAccessToken +from chat.flow.workflow_manage import Flow +from common import result from common.database_model_manage.database_model_manage import DatabaseModelManage from common.db.search import native_search, native_page_search from common.exception.app_exception import AppApiException -from common.utils.common import get_file_content +from common.field.common import UploadedFileField +from common.utils.common import get_file_content, valid_license, restricted_loads from knowledge.models import Knowledge from maxkb.conf import PROJECT_DIR from models_provider.models import Model +from tools.models import Tool, ToolScope +from tools.serializers.tool import ToolModelSerializer +from users.models import User + + +def get_base_node_work_flow(work_flow): + node_list = work_flow.get('nodes') + base_node_list = [node for node in node_list if node.get('id') == 'base-node'] + if len(base_node_list) > 0: + return base_node_list[-1] + return None + + +class MKInstance: + + def __init__(self, application: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]): + self.application = application + 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 ApplicationSerializerModel(serializers.ModelSerializer): + class Meta: + model = Application + fields = "__all__" class NoReferencesChoices(models.TextChoices): @@ -307,6 +343,55 @@ class Query(serializers.Serializer): ) +class ApplicationImportRequest(serializers.Serializer): + file = UploadedFileField(required=True, label=_("file")) + + +class ApplicationEditSerializer(serializers.Serializer): + name = serializers.CharField(required=False, max_length=64, min_length=1, + label=_("Application Name")) + desc = serializers.CharField(required=False, max_length=256, min_length=1, allow_null=True, allow_blank=True, + label=_("Application Description")) + model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, + label=_("Model")) + dialogue_number = serializers.IntegerField(required=False, + min_value=0, + max_value=1024, + label=_("Historical chat records")) + prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, + label=_("Opening remarks")) + dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), + label=_("Related Knowledge Base") + ) + # 数据集相关设置 + knowledge_setting = KnowledgeSettingSerializer(required=False, allow_null=True, + label=_("Dataset settings")) + # 模型相关设置 + model_setting = ModelSettingSerializer(required=False, allow_null=True, + label=_("Model setup")) + # 问题补全 + problem_optimization = serializers.BooleanField(required=False, allow_null=True, + label=_("Question completion")) + icon = serializers.CharField(required=False, allow_null=True, label=_("Icon")) + + model_params_setting = serializers.DictField(required=False, + label=_('Model parameters')) + + tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled')) + + tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Voice playback model ID")) + + tts_type = serializers.CharField(required=False, label=_('Voice playback type')) + + tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay')) + + stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled')) + + stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID')) + + stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission')) + + class ApplicationSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) user_id = serializers.UUIDField(required=True, label=_("User ID")) @@ -352,3 +437,281 @@ class ApplicationSerializer(serializers.Serializer): # 插入关联数据 QuerySet(ApplicationKnowledgeMapping).bulk_create(application_knowledge_mapping_model_list) return ApplicationCreateSerializer.ApplicationResponse(application_model).data + + @valid_license(model=Application, count=5, + message=_( + 'The community version supports up to 5 applications. If you need more applications, please contact us (https://fit2cloud.com/).')) + @transaction.atomic + def import_(self, instance: dict, with_valid=True): + if with_valid: + self.is_valid() + ApplicationImportRequest(data=instance).is_valid(raise_exception=True) + user_id = self.data.get('user_id') + workspace_id = self.data.get("workspace_id") + mk_instance_bytes = instance.get('file').read() + try: + mk_instance = restricted_loads(mk_instance_bytes) + except Exception as e: + raise AppApiException(1001, _("Unsupported file format")) + application = mk_instance.application + + tool_list = mk_instance.get_tool_list() + if len(tool_list) > 0: + tool_id_list = [tool.get('id') for tool in tool_list] + exits_tool_id_list = [str(tool.id) for tool in + QuerySet(Tool).filter(id__in=tool_id_list)] + # 获取到需要插入的函数 + tool_list = [tool for tool in tool_id_list if + not exits_tool_id_list.__contains__(tool.get('id'))] + application_model = self.to_application(application, workspace_id, user_id) + tool_model_list = [self.to_tool(f, workspace_id, user_id) for f in tool_list] + application_model.save() + # 插入认证信息 + ApplicationAccessToken(application_id=application_model.id, + access_token=hashlib.md5(str(uuid.uuid1()).encode()).hexdigest()[8:24]).save() + QuerySet(Tool).bulk_create(tool_model_list) if len(tool_model_list) > 0 else None + return True + + @staticmethod + def to_tool(tool, workspace_id, user_id): + """ + @param workspace_id: + @param user_id: 用户id + @param tool: 工具 + @return: + """ + return Tool(id=tool.get('id'), + user_id=user_id, + name=tool.get('name'), + code=tool.get('code'), + input_field_list=tool.get('input_field_list'), + is_active=tool.get('is_active'), + scope=ToolScope.WORKSPACE, + workspace_id=workspace_id) + + @staticmethod + def to_application(application, workspace_id, user_id): + work_flow = application.get('work_flow') + for node in work_flow.get('nodes', []): + if node.get('type') == 'search-dataset-node': + node.get('properties', {}).get('node_data', {})['dataset_id_list'] = [] + return Application(id=uuid.uuid1(), + user_id=user_id, + name=application.get('name'), + workspace_id=workspace_id, + desc=application.get('desc'), + prologue=application.get('prologue'), dialogue_number=application.get('dialogue_number'), + dataset_setting=application.get('dataset_setting'), + model_setting=application.get('model_setting'), + model_params_setting=application.get('model_params_setting'), + tts_model_params_setting=application.get('tts_model_params_setting'), + problem_optimization=application.get('problem_optimization'), + icon="/ui/favicon.ico", + work_flow=work_flow, + type=application.get('type'), + problem_optimization_prompt=application.get('problem_optimization_prompt'), + tts_model_enable=application.get('tts_model_enable'), + stt_model_enable=application.get('stt_model_enable'), + tts_type=application.get('tts_type'), + clean_time=application.get('clean_time'), + file_upload_enable=application.get('file_upload_enable'), + file_upload_setting=application.get('file_upload_setting'), + ) + + +class ApplicationOperateSerializer(serializers.Serializer): + application_id = serializers.UUIDField(required=True, label=_("Application ID")) + user_id = serializers.UUIDField(required=True, label=_("User ID")) + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=True) + if not QuerySet(Application).filter(id=self.data.get('application_id')).exists(): + raise AppApiException(500, _('Application id does not exist')) + + def delete(self, with_valid=True): + if with_valid: + self.is_valid() + QuerySet(Application).filter(id=self.data.get('application_id')).delete() + return True + + def export(self, with_valid=True): + try: + if with_valid: + self.is_valid() + application_id = self.data.get('application_id') + application = QuerySet(Application).filter(id=application_id).first() + tool_id_list = [node.get('properties', {}).get('node_data', {}).get('tool_id') for node + in + application.work_flow.get('nodes', []) if + node.get('type') == 'tool-node'] + tool_list = [] + if len(tool_id_list) > 0: + tool_list = QuerySet(Tool).filter(id__in=tool_id_list) + application_dict = ApplicationSerializerModel(application).data + + mk_instance = MKInstance(application_dict, + [], + 'v2', + [ToolModelSerializer(tool).data for tool in + tool_list]) + application_pickle = pickle.dumps(mk_instance) + response = HttpResponse(content_type='text/plain', content=application_pickle) + response['Content-Disposition'] = f'attachment; filename="{application.name}.mk"' + return response + except Exception as e: + return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @transaction.atomic + def publish(self, instance, with_valid=True): + if with_valid: + self.is_valid() + user_id = self.data.get('user_id') + workspace_id = self.data.get("workspace_id") + user = QuerySet(User).filter(id=user_id).first() + application = QuerySet(Application).filter(id=self.data.get("application_id"), + workspace_id=workspace_id).first() + work_flow = instance.get('work_flow') + if work_flow is None: + raise AppApiException(500, _("work_flow is a required field")) + Flow.new_instance(work_flow).is_valid() + base_node = get_base_node_work_flow(work_flow) + if base_node is not None: + node_data = base_node.get('properties').get('node_data') + if node_data is not None: + application.name = node_data.get('name') + application.desc = node_data.get('desc') + application.prologue = node_data.get('prologue') + application.work_flow = work_flow + application.save() + work_flow_version = WorkFlowVersion(work_flow=work_flow, application=application, + name=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + publish_user_id=user_id, + publish_user_name=user.username, + workspace_id=workspace_id) + work_flow_version.save() + return True + + @staticmethod + def update_work_flow_model(instance): + if 'nodes' not in instance.get('work_flow'): + return + nodes = instance.get('work_flow')['nodes'] + for node in nodes: + if node['id'] == 'base-node': + node_data = node['properties']['node_data'] + if 'stt_model_id' in node_data: + instance['stt_model_id'] = node_data['stt_model_id'] + if 'tts_model_id' in node_data: + instance['tts_model_id'] = node_data['tts_model_id'] + if 'stt_model_enable' in node_data: + instance['stt_model_enable'] = node_data['stt_model_enable'] + if 'tts_model_enable' in node_data: + instance['tts_model_enable'] = node_data['tts_model_enable'] + if 'tts_type' in node_data: + instance['tts_type'] = node_data['tts_type'] + if 'tts_autoplay' in node_data: + instance['tts_autoplay'] = node_data['tts_autoplay'] + if 'stt_autosend' in node_data: + instance['stt_autosend'] = node_data['stt_autosend'] + if 'tts_model_params_setting' in node_data: + instance['tts_model_params_setting'] = node_data['tts_model_params_setting'] + if 'file_upload_enable' in node_data: + instance['file_upload_enable'] = node_data['file_upload_enable'] + if 'file_upload_setting' in node_data: + instance['file_upload_setting'] = node_data['file_upload_setting'] + if 'name' in node_data: + instance['name'] = node_data['name'] + break + + @transaction.atomic + def edit(self, instance: Dict, with_valid=True): + if with_valid: + self.is_valid() + ApplicationEditSerializer(data=instance).is_valid( + raise_exception=True) + application_id = self.data.get("application_id") + + application = QuerySet(Application).get(id=application_id) + if instance.get('model_id') is None or len(instance.get('model_id')) == 0: + application.model_id = None + else: + model = QuerySet(Model).filter( + id=instance.get('model_id')).first() + if model is None: + raise AppApiException(500, _("Model does not exist")) + if instance.get('stt_model_id') is None or len(instance.get('stt_model_id')) == 0: + application.stt_model_id = None + else: + model = QuerySet(Model).filter( + id=instance.get('stt_model_id')).first() + if model is None: + raise AppApiException(500, _("Model does not exist")) + if instance.get('tts_model_id') is None or len(instance.get('tts_model_id')) == 0: + application.tts_model_id = None + else: + model = QuerySet(Model).filter( + id=instance.get('tts_model_id')).first() + if model is None: + raise AppApiException(500, _("Model does not exist")) + if 'work_flow' in instance: + # 修改语音配置相关 + self.update_work_flow_model(instance) + + update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status', + 'dataset_setting', 'model_setting', 'problem_optimization', 'dialogue_number', + 'stt_model_id', 'tts_model_id', 'tts_model_enable', 'stt_model_enable', 'tts_type', + 'tts_autoplay', 'stt_autosend', 'file_upload_enable', 'file_upload_setting', + 'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting', + 'problem_optimization_prompt', 'clean_time'] + for update_key in update_keys: + if update_key in instance and instance.get(update_key) is not None: + application.__setattr__(update_key, instance.get(update_key)) + print(application.name) + application.save() + + if 'knowledge_id_list' in instance: + knowledge_id_list = instance.get('knowledge_id_list') + # 当前用户可修改关联的知识库列表 + application_knowledge_id_list = [str(knowledge.id) for knowledge in + self.list_knowledge(with_valid=False)] + for dataset_id in knowledge_id_list: + if not application_knowledge_id_list.__contains__(dataset_id): + message = lazy_format(_('Unknown knowledge base id {dataset_id}, unable to associate'), + dataset_id=dataset_id) + raise AppApiException(500, message) + + self.save_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id) + return self.one(with_valid=False) + + def one(self, with_valid=True): + if with_valid: + self.is_valid() + application_id = self.data.get("application_id") + application = QuerySet(Application).get(id=application_id) + dataset_list = self.list_knowledge(with_valid=False) + mapping_knowledge_id_list = [akm.knowledge_id for akm in + QuerySet(ApplicationKnowledgeMapping).filter(application_id=application_id)] + knowledge_id_list = [d.get('id') for d in + list(filter(lambda row: mapping_knowledge_id_list.__contains__(row.get('id')), + dataset_list))] + return {**ApplicationSerializerModel(application).data, + 'dataset_id_list': knowledge_id_list} + + def list_knowledge(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + workspace_id = self.data.get("workspace_id") + knowledge_list = QuerySet(Knowledge).filter(workspace_id=workspace_id) + return knowledge_list + + @staticmethod + def save_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id): + # 需要排除已删除的数据集 + knowledge_id_list = [knowledge.id for knowledge in QuerySet(Knowledge).filter(id__in=knowledge_id_list)] + # 删除已经关联的id + QuerySet(ApplicationKnowledgeMapping).filter(knowledge_id__in=application_knowledge_id_list, + application_id=application_id).delete() + # 插入 + QuerySet(ApplicationKnowledgeMapping).bulk_create( + [ApplicationKnowledgeMapping(application_id=application_id, dataset_id=dataset_id) for dataset_id in + knowledge_id_list]) if len(knowledge_id_list) > 0 else None diff --git a/apps/application/serializers/application_api_key.py b/apps/application/serializers/application_api_key.py index dc352169f..430785867 100644 --- a/apps/application/serializers/application_api_key.py +++ b/apps/application/serializers/application_api_key.py @@ -1,10 +1,9 @@ import hashlib -import uuid_utils.compat as uuid -from baidubce.services.bmr.bmr_client import application +import uuid_utils.compat as uuid from django.db.models import QuerySet -from rest_framework import serializers from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers from application.models import Application from application.models.application_api_key import ApplicationApiKey @@ -16,6 +15,7 @@ class ApplicationKeySerializerModel(serializers.ModelSerializer): model = ApplicationApiKey fields = "__all__" + class Edit(serializers.Serializer): pass @@ -25,10 +25,6 @@ class ApplicationKeySerializer(serializers.Serializer): workspace_id = serializers.CharField(required=True, label=_('workspace id')) application_id = serializers.UUIDField(required=True, label=_('application id')) - - - - def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) application_id = self.data.get("application_id") @@ -49,21 +45,18 @@ class ApplicationKeySerializer(serializers.Serializer): application_api_key.save() return ApplicationKeySerializerModel(application_api_key).data - def list(self,with_valid=True): + def list(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) application_id = self.data.get("application_id") return [ApplicationKeySerializerModel(application_api_key).data for application_api_key in - QuerySet(ApplicationApiKey).filter(application_id = application_id)] + QuerySet(ApplicationApiKey).filter(application_id=application_id)] class Operate(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) workspace_id = serializers.CharField(required=True, label=_('workspace id')) application_id = serializers.UUIDField(required=True, label=_('application id')) - - def edit(self, instance, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - diff --git a/apps/application/urls.py b/apps/application/urls.py index dfe44cf08..96cf40cb4 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -5,8 +5,12 @@ from . import views app_name = 'application' urlpatterns = [ + path('workspace//application', views.Application.as_view(), name='application'), + path('workspace//application/import', views.Application.Import.as_view()), path('workspace//application//', views.Application.Page.as_view(), name='application_page'), path('workspace//application//application_key', - views.ApplicationKey.as_view())] + views.ApplicationKey.as_view()), + path('workspace//application//export', views.Application.Export.as_view()), +] diff --git a/apps/application/views/application.py b/apps/application/views/application.py index 9ab96aa93..89244b105 100644 --- a/apps/application/views/application.py +++ b/apps/application/views/application.py @@ -8,11 +8,14 @@ """ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema +from rest_framework.decorators import action +from rest_framework.parsers import MultiPartParser from rest_framework.request import Request from rest_framework.views import APIView -from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI -from application.serializers.application import ApplicationSerializer, Query +from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI, ApplicationImportAPI, \ + ApplicationExportAPI, ApplicationOperateAPI, ApplicationEditAPI +from application.serializers.application import ApplicationSerializer, Query, ApplicationOperateSerializer from common import result from common.auth import TokenAuth from common.auth.authentication import has_permissions @@ -67,3 +70,92 @@ class Application(APIView): return result.success( Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).page(current_page, page_size, request.query_params)) + + class Import(APIView): + authentication_classes = [TokenAuth] + parser_classes = [MultiPartParser] + + @extend_schema( + methods=['POST'], + description=_('Import Application'), + summary=_('Import Application'), + operation_id=_('Import Application'), # type: ignore + parameters=ApplicationImportAPI.get_parameters(), + request=ApplicationImportAPI.get_request(), + responses=result.DefaultResultSerializer, + tags=[_('Application')] # type: ignore + ) + @has_permissions(PermissionConstants.APPLICATION_READ) + def post(self, request: Request, workspace_id: str): + return result.success(ApplicationSerializer( + data={'user_id': request.user.id, 'workspace_id': workspace_id, + }).import_({'file': request.FILES.get('file')})) + + class Export(APIView): + authentication_classes = [TokenAuth] + + @action(methods=['POST'], detail=False) + @extend_schema( + methods=['POST'], + description=_('Export conversation'), + summary=_('Export conversation'), + operation_id=_('Export conversation'), # type: ignore + parameters=ApplicationExportAPI.get_parameters(), + responses=ApplicationExportAPI.get_response(), + tags=[_('Application')] # type: ignore + ) + @has_permissions(PermissionConstants.APPLICATION_EXPORT.get_workspace_application_permission()) + def post(self, request: Request, workspace_id: str, application_id: str): + return ApplicationOperateSerializer( + data={'application_id': application_id, + 'user_id': request.user.id}).export(request.data) + + class Operate(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['DELETE'], + description=_('Deleting application'), + summary=_('Deleting application'), + operation_id=_('Deleting application'), # type: ignore + parameters=ApplicationOperateAPI.get_parameters(), + responses=result.DefaultResultSerializer, + tags=[_('Application')] # type: ignore + ) + @has_permissions(PermissionConstants.APPLICATION_DELETE.get_workspace_application_permission()) + def delete(self, request: Request, application_id: str): + return result.success(ApplicationOperateSerializer( + data={'application_id': application_id, 'user_id': request.user.id}).delete( + with_valid=True)) + + @extend_schema( + methods=['PUT'], + description=_('Modify the application'), + summary=_('Modify the application'), + operation_id=_('Modify the application'), # type: ignore + parameters=ApplicationOperateAPI.get_parameters(), + request=ApplicationEditAPI.get_request(), + responses=ApplicationCreateAPI.get_response(), + tags=[_('Application')] # type: ignore + ) + @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission()) + def put(self, request: Request, application_id: str): + return result.success( + ApplicationOperateSerializer( + data={'application_id': application_id, 'user_id': request.user.id}).edit( + request.data)) + + @extend_schema( + methods=['PUT'], + description=_('Get application details'), + summary=_('Get application details'), + operation_id=_('Get application details'), # type: ignore + parameters=ApplicationOperateAPI.get_parameters(), + request=ApplicationEditAPI.get_request(), + responses=result.DefaultResultSerializer, + tags=[_('Application')] # type: ignore + ) + @has_permissions(PermissionConstants.WORKSPACE_READ.get_workspace_application_permission()) + def get(self, request: Request, application_id: str): + return result.success(ApplicationOperateSerializer( + data={'application_id': application_id, 'user_id': request.user.id}).one()) diff --git a/apps/chat/__init__.py b/apps/chat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/chat/admin.py b/apps/chat/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/apps/chat/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/chat/api/chat_embed_api.py b/apps/chat/api/chat_embed_api.py new file mode 100644 index 000000000..9b3c2d0dd --- /dev/null +++ b/apps/chat/api/chat_embed_api.py @@ -0,0 +1,47 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: chat_embed_api.py + @date:2025/5/30 15:25 + @desc: +""" +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter + +from common.mixins.api_mixin import APIMixin +from django.utils.translation import gettext_lazy as _ + +from common.result import DefaultResultSerializer + + +class ChatEmbedAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="host", + description=_("host"), + type=OpenApiTypes.STR, + location='query', + required=False, + ), + OpenApiParameter( + name="protocol", + description=_("protocol"), + type=OpenApiTypes.STR, + location='query', + required=False, + ), + OpenApiParameter( + name="token", + description=_("token"), + type=OpenApiTypes.STR, + location='query', + required=False, + ) + ] + + @staticmethod + def get_response(): + return DefaultResultSerializer diff --git a/apps/chat/apps.py b/apps/chat/apps.py new file mode 100644 index 000000000..2fe899ad4 --- /dev/null +++ b/apps/chat/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ChatConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'chat' diff --git a/apps/application/chat_pipeline/I_base_chat_pipeline.py b/apps/chat/chat_pipeline/I_base_chat_pipeline.py similarity index 100% rename from apps/application/chat_pipeline/I_base_chat_pipeline.py rename to apps/chat/chat_pipeline/I_base_chat_pipeline.py diff --git a/apps/application/chat_pipeline/__init__.py b/apps/chat/chat_pipeline/__init__.py similarity index 100% rename from apps/application/chat_pipeline/__init__.py rename to apps/chat/chat_pipeline/__init__.py diff --git a/apps/application/chat_pipeline/pipeline_manage.py b/apps/chat/chat_pipeline/pipeline_manage.py similarity index 100% rename from apps/application/chat_pipeline/pipeline_manage.py rename to apps/chat/chat_pipeline/pipeline_manage.py diff --git a/apps/application/chat_pipeline/step/__init__.py b/apps/chat/chat_pipeline/step/__init__.py similarity index 100% rename from apps/application/chat_pipeline/step/__init__.py rename to apps/chat/chat_pipeline/step/__init__.py diff --git a/apps/application/chat_pipeline/step/chat_step/__init__.py b/apps/chat/chat_pipeline/step/chat_step/__init__.py similarity index 100% rename from apps/application/chat_pipeline/step/chat_step/__init__.py rename to apps/chat/chat_pipeline/step/chat_step/__init__.py diff --git a/apps/application/chat_pipeline/step/chat_step/i_chat_step.py b/apps/chat/chat_pipeline/step/chat_step/i_chat_step.py similarity index 100% rename from apps/application/chat_pipeline/step/chat_step/i_chat_step.py rename to apps/chat/chat_pipeline/step/chat_step/i_chat_step.py diff --git a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py b/apps/chat/chat_pipeline/step/chat_step/impl/base_chat_step.py similarity index 99% rename from apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py rename to apps/chat/chat_pipeline/step/chat_step/impl/base_chat_step.py index b03f06d80..4e5142ac2 100644 --- a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py +++ b/apps/chat/chat_pipeline/step/chat_step/impl/base_chat_step.py @@ -25,9 +25,9 @@ from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineMode from application.chat_pipeline.pipeline_manage import PipelineManage from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler from application.flow.tools import Reasoning -from application.models.api_key_model import ApplicationPublicAccessClient +from application.models.application_api_key import ApplicationPublicAccessClient from common.constants.authentication_type import AuthenticationType -from setting.models_provider.tools import get_model_instance_by_model_user_id +from models_provider.tools import get_model_instance_by_model_user_id def add_access_num(client_id=None, client_type=None, application_id=None): diff --git a/apps/application/chat_pipeline/step/generate_human_message_step/__init__.py b/apps/chat/chat_pipeline/step/generate_human_message_step/__init__.py similarity index 100% rename from apps/application/chat_pipeline/step/generate_human_message_step/__init__.py rename to apps/chat/chat_pipeline/step/generate_human_message_step/__init__.py diff --git a/apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py b/apps/chat/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py similarity index 100% rename from apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py rename to apps/chat/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py diff --git a/apps/application/chat_pipeline/step/generate_human_message_step/impl/base_generate_human_message_step.py b/apps/chat/chat_pipeline/step/generate_human_message_step/impl/base_generate_human_message_step.py similarity index 100% rename from apps/application/chat_pipeline/step/generate_human_message_step/impl/base_generate_human_message_step.py rename to apps/chat/chat_pipeline/step/generate_human_message_step/impl/base_generate_human_message_step.py diff --git a/apps/application/chat_pipeline/step/reset_problem_step/__init__.py b/apps/chat/chat_pipeline/step/reset_problem_step/__init__.py similarity index 100% rename from apps/application/chat_pipeline/step/reset_problem_step/__init__.py rename to apps/chat/chat_pipeline/step/reset_problem_step/__init__.py diff --git a/apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py b/apps/chat/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py similarity index 100% rename from apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py rename to apps/chat/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py diff --git a/apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py b/apps/chat/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py similarity index 100% rename from apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py rename to apps/chat/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py diff --git a/apps/application/chat_pipeline/step/search_dataset_step/__init__.py b/apps/chat/chat_pipeline/step/search_dataset_step/__init__.py similarity index 100% rename from apps/application/chat_pipeline/step/search_dataset_step/__init__.py rename to apps/chat/chat_pipeline/step/search_dataset_step/__init__.py diff --git a/apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py b/apps/chat/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py similarity index 100% rename from apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py rename to apps/chat/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py diff --git a/apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py b/apps/chat/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py similarity index 100% rename from apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py rename to apps/chat/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py diff --git a/apps/application/flow/__init__.py b/apps/chat/flow/__init__.py similarity index 100% rename from apps/application/flow/__init__.py rename to apps/chat/flow/__init__.py diff --git a/apps/application/flow/common.py b/apps/chat/flow/common.py similarity index 100% rename from apps/application/flow/common.py rename to apps/chat/flow/common.py diff --git a/apps/application/flow/default_workflow.json b/apps/chat/flow/default_workflow.json similarity index 100% rename from apps/application/flow/default_workflow.json rename to apps/chat/flow/default_workflow.json diff --git a/apps/application/flow/default_workflow_en.json b/apps/chat/flow/default_workflow_en.json similarity index 100% rename from apps/application/flow/default_workflow_en.json rename to apps/chat/flow/default_workflow_en.json diff --git a/apps/application/flow/default_workflow_zh.json b/apps/chat/flow/default_workflow_zh.json similarity index 100% rename from apps/application/flow/default_workflow_zh.json rename to apps/chat/flow/default_workflow_zh.json diff --git a/apps/application/flow/default_workflow_zh_Hant.json b/apps/chat/flow/default_workflow_zh_Hant.json similarity index 100% rename from apps/application/flow/default_workflow_zh_Hant.json rename to apps/chat/flow/default_workflow_zh_Hant.json diff --git a/apps/application/flow/i_step_node.py b/apps/chat/flow/i_step_node.py similarity index 87% rename from apps/application/flow/i_step_node.py rename to apps/chat/flow/i_step_node.py index fcead7a40..ddfd11b28 100644 --- a/apps/application/flow/i_step_node.py +++ b/apps/chat/flow/i_step_node.py @@ -17,14 +17,13 @@ from django.db.models import QuerySet from rest_framework import serializers from rest_framework.exceptions import ValidationError, ErrorDetail -from application.flow.common import Answer, NodeChunk +from chat.flow.common import Answer, NodeChunk from application.models import ChatRecord -from application.models.api_key_model import ApplicationPublicAccessClient +from application.models import ApplicationPublicAccessClient from common.constants.authentication_type import AuthenticationType from common.field.common import InstanceField -from common.util.field_message import ErrMessage -chat_cache = cache.caches['chat_cache'] +chat_cache = cache def write_context(step_variable: Dict, global_variable: Dict, node, workflow): @@ -123,31 +122,31 @@ class NodeResult: class ReferenceAddressSerializer(serializers.Serializer): - node_id = serializers.CharField(required=True, error_messages=ErrMessage.char("节点id")) + node_id = serializers.CharField(required=True, label="节点id") fields = serializers.ListField( - child=serializers.CharField(required=True, error_messages=ErrMessage.char("节点字段")), required=True, - error_messages=ErrMessage.list("节点字段数组")) + child=serializers.CharField(required=True, label="节点字段"), required=True, + label="节点字段数组") class FlowParamsSerializer(serializers.Serializer): # 历史对答 history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True), - error_messages=ErrMessage.list("历史对答")) + label="历史对答") - question = serializers.CharField(required=True, error_messages=ErrMessage.list("用户问题")) + question = serializers.CharField(required=True, label="用户问题") - chat_id = serializers.CharField(required=True, error_messages=ErrMessage.list("对话id")) + chat_id = serializers.CharField(required=True, label="对话id") - chat_record_id = serializers.CharField(required=True, error_messages=ErrMessage.char("对话记录id")) + chat_record_id = serializers.CharField(required=True, label="对话记录id") - stream = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("流式输出")) + stream = serializers.BooleanField(required=True, label="流式输出") - client_id = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端id")) + client_id = serializers.CharField(required=False, label="客户端id") - client_type = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端类型")) + client_type = serializers.CharField(required=False, label="客户端类型") - user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id")) - re_chat = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("换个答案")) + user_id = serializers.UUIDField(required=True, label="用户id") + re_chat = serializers.BooleanField(required=True, label="换个答案") class INode: diff --git a/apps/application/flow/step_node/__init__.py b/apps/chat/flow/step_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/__init__.py rename to apps/chat/flow/step_node/__init__.py diff --git a/apps/application/flow/step_node/ai_chat_step_node/__init__.py b/apps/chat/flow/step_node/ai_chat_step_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/ai_chat_step_node/__init__.py rename to apps/chat/flow/step_node/ai_chat_step_node/__init__.py diff --git a/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py b/apps/chat/flow/step_node/ai_chat_step_node/i_chat_node.py similarity index 54% rename from apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py rename to apps/chat/flow/step_node/ai_chat_step_node/i_chat_node.py index a83d2ef57..dc6df2c9f 100644 --- a/apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py +++ b/apps/chat/flow/step_node/ai_chat_step_node/i_chat_node.py @@ -11,31 +11,29 @@ 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 -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult class ChatNodeSerializer(serializers.Serializer): - model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) + model_id = serializers.CharField(required=True, label=_("Model id")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, - error_messages=ErrMessage.char(_("Role Setting"))) - prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) + label=_("Role Setting")) + prompt = serializers.CharField(required=True, label=_("Prompt word")) # 多轮对话数量 - dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer( - _("Number of multi-round conversations"))) + dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations")) is_result = serializers.BooleanField(required=False, - error_messages=ErrMessage.boolean(_('Whether to return content'))) + label=_('Whether to return content')) model_params_setting = serializers.DictField(required=False, - error_messages=ErrMessage.dict(_("Model parameter settings"))) + label=_("Model parameter settings")) model_setting = serializers.DictField(required=False, - error_messages=ErrMessage.dict('Model settings')) + label='Model settings') dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True, - error_messages=ErrMessage.char(_("Context Type"))) + label=_("Context Type")) mcp_enable = serializers.BooleanField(required=False, - error_messages=ErrMessage.boolean(_("Whether to enable MCP"))) - mcp_servers = serializers.JSONField(required=False, error_messages=ErrMessage.list(_("MCP Server"))) + label=_("Whether to enable MCP")) + mcp_servers = serializers.JSONField(required=False, label=_("MCP Server")) class IChatNode(INode): diff --git a/apps/application/flow/step_node/ai_chat_step_node/impl/__init__.py b/apps/chat/flow/step_node/ai_chat_step_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/ai_chat_step_node/impl/__init__.py rename to apps/chat/flow/step_node/ai_chat_step_node/impl/__init__.py diff --git a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py b/apps/chat/flow/step_node/ai_chat_step_node/impl/base_chat_node.py similarity index 97% rename from apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py rename to apps/chat/flow/step_node/ai_chat_step_node/impl/base_chat_node.py index 8d576d416..b80d059d6 100644 --- a/apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py +++ b/apps/chat/flow/step_node/ai_chat_step_node/impl/base_chat_node.py @@ -20,12 +20,11 @@ from langchain_core.messages import BaseMessage, AIMessage, AIMessageChunk, Tool from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent -from application.flow.i_step_node import NodeResult, INode -from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode -from application.flow.tools import Reasoning -from setting.models import Model -from setting.models_provider import get_model_credential -from setting.models_provider.tools import get_model_instance_by_model_user_id +from chat.flow.i_step_node import NodeResult, INode +from chat.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode +from chat.flow.tools import Reasoning +from models_provider.models import Model +from models_provider.tools import get_model_credential, get_model_instance_by_model_user_id tool_message_template = """
diff --git a/apps/application/flow/step_node/application_node/__init__.py b/apps/chat/flow/step_node/application_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/application_node/__init__.py rename to apps/chat/flow/step_node/application_node/__init__.py diff --git a/apps/application/flow/step_node/application_node/i_application_node.py b/apps/chat/flow/step_node/application_node/i_application_node.py similarity index 81% rename from apps/application/flow/step_node/application_node/i_application_node.py rename to apps/chat/flow/step_node/application_node/i_application_node.py index 6394fa49c..2ee9c9552 100644 --- a/apps/application/flow/step_node/application_node/i_application_node.py +++ b/apps/chat/flow/step_node/application_node/i_application_node.py @@ -3,25 +3,24 @@ from typing import Type from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult from django.utils.translation import gettext_lazy as _ class ApplicationNodeSerializer(serializers.Serializer): - application_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Application ID"))) + application_id = serializers.CharField(required=True, label=_("Application ID")) question_reference_address = serializers.ListField(required=True, - error_messages=ErrMessage.list(_("User Questions"))) - api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("API Input Fields"))) + label=_("User Questions")) + api_input_field_list = serializers.ListField(required=False, label=_("API Input Fields")) user_input_field_list = serializers.ListField(required=False, - error_messages=ErrMessage.uuid(_("User Input Fields"))) - image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture"))) - document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document"))) - audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio"))) + label=_("User Input Fields")) + image_list = serializers.ListField(required=False, label=_("picture")) + document_list = serializers.ListField(required=False, label=_("document")) + audio_list = serializers.ListField(required=False, label=_("Audio")) child_node = serializers.DictField(required=False, allow_null=True, - error_messages=ErrMessage.dict(_("Child Nodes"))) - node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data"))) + label=_("Child Nodes")) + node_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data")) class IApplicationNode(INode): diff --git a/apps/application/flow/step_node/application_node/impl/__init__.py b/apps/chat/flow/step_node/application_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/application_node/impl/__init__.py rename to apps/chat/flow/step_node/application_node/impl/__init__.py diff --git a/apps/application/flow/step_node/application_node/impl/base_application_node.py b/apps/chat/flow/step_node/application_node/impl/base_application_node.py similarity index 98% rename from apps/application/flow/step_node/application_node/impl/base_application_node.py rename to apps/chat/flow/step_node/application_node/impl/base_application_node.py index 95445f456..c32e00678 100644 --- a/apps/application/flow/step_node/application_node/impl/base_application_node.py +++ b/apps/chat/flow/step_node/application_node/impl/base_application_node.py @@ -5,9 +5,9 @@ import time import uuid from typing import Dict, List -from application.flow.common import Answer -from application.flow.i_step_node import NodeResult, INode -from application.flow.step_node.application_node.i_application_node import IApplicationNode +from chat.flow.common import Answer +from chat.flow.i_step_node import NodeResult, INode +from chat.flow.step_node.application_node.i_application_node import IApplicationNode from application.models import Chat diff --git a/apps/application/flow/step_node/condition_node/__init__.py b/apps/chat/flow/step_node/condition_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/condition_node/__init__.py rename to apps/chat/flow/step_node/condition_node/__init__.py diff --git a/apps/application/flow/step_node/condition_node/compare/__init__.py b/apps/chat/flow/step_node/condition_node/compare/__init__.py similarity index 100% rename from apps/application/flow/step_node/condition_node/compare/__init__.py rename to apps/chat/flow/step_node/condition_node/compare/__init__.py diff --git a/apps/application/flow/step_node/condition_node/compare/compare.py b/apps/chat/flow/step_node/condition_node/compare/compare.py similarity index 100% rename from apps/application/flow/step_node/condition_node/compare/compare.py rename to apps/chat/flow/step_node/condition_node/compare/compare.py diff --git a/apps/application/flow/step_node/condition_node/compare/contain_compare.py b/apps/chat/flow/step_node/condition_node/compare/contain_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/contain_compare.py rename to apps/chat/flow/step_node/condition_node/compare/contain_compare.py index 6073131a5..5a2e88e28 100644 --- a/apps/application/flow/step_node/condition_node/compare/contain_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/contain_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class ContainCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/equal_compare.py b/apps/chat/flow/step_node/condition_node/compare/equal_compare.py similarity index 84% rename from apps/application/flow/step_node/condition_node/compare/equal_compare.py rename to apps/chat/flow/step_node/condition_node/compare/equal_compare.py index 0061a82f6..846c65dec 100644 --- a/apps/application/flow/step_node/condition_node/compare/equal_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/equal_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class EqualCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/ge_compare.py b/apps/chat/flow/step_node/condition_node/compare/ge_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/ge_compare.py rename to apps/chat/flow/step_node/condition_node/compare/ge_compare.py index d4e22cbd6..d473a7559 100644 --- a/apps/application/flow/step_node/condition_node/compare/ge_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/ge_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class GECompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/gt_compare.py b/apps/chat/flow/step_node/condition_node/compare/gt_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/gt_compare.py rename to apps/chat/flow/step_node/condition_node/compare/gt_compare.py index 80942abb2..c1d86eb8c 100644 --- a/apps/application/flow/step_node/condition_node/compare/gt_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/gt_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class GTCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/is_not_null_compare.py b/apps/chat/flow/step_node/condition_node/compare/is_not_null_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/is_not_null_compare.py rename to apps/chat/flow/step_node/condition_node/compare/is_not_null_compare.py index 5dec26713..ce82dddc9 100644 --- a/apps/application/flow/step_node/condition_node/compare/is_not_null_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/is_not_null_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare import Compare +from chat.flow.step_node.condition_node.compare import Compare class IsNotNullCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/is_not_true.py b/apps/chat/flow/step_node/condition_node/compare/is_not_true.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/is_not_true.py rename to apps/chat/flow/step_node/condition_node/compare/is_not_true.py index f8a29f5a1..6995ece34 100644 --- a/apps/application/flow/step_node/condition_node/compare/is_not_true.py +++ b/apps/chat/flow/step_node/condition_node/compare/is_not_true.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare import Compare +from chat.flow.step_node.condition_node.compare import Compare class IsNotTrueCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/is_null_compare.py b/apps/chat/flow/step_node/condition_node/compare/is_null_compare.py similarity index 86% rename from apps/application/flow/step_node/condition_node/compare/is_null_compare.py rename to apps/chat/flow/step_node/condition_node/compare/is_null_compare.py index c463f3fda..9376340ce 100644 --- a/apps/application/flow/step_node/condition_node/compare/is_null_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/is_null_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare import Compare +from chat.flow.step_node.condition_node.compare import Compare class IsNullCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/is_true.py b/apps/chat/flow/step_node/condition_node/compare/is_true.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/is_true.py rename to apps/chat/flow/step_node/condition_node/compare/is_true.py index 166e0993a..cdd0e4683 100644 --- a/apps/application/flow/step_node/condition_node/compare/is_true.py +++ b/apps/chat/flow/step_node/condition_node/compare/is_true.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare import Compare +from chat.flow.step_node.condition_node.compare import Compare class IsTrueCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/le_compare.py b/apps/chat/flow/step_node/condition_node/compare/le_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/le_compare.py rename to apps/chat/flow/step_node/condition_node/compare/le_compare.py index 77a0bca0f..bc68483ce 100644 --- a/apps/application/flow/step_node/condition_node/compare/le_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/le_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class LECompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/len_equal_compare.py b/apps/chat/flow/step_node/condition_node/compare/len_equal_compare.py similarity index 86% rename from apps/application/flow/step_node/condition_node/compare/len_equal_compare.py rename to apps/chat/flow/step_node/condition_node/compare/len_equal_compare.py index f2b0764c5..7ec2d8b7b 100644 --- a/apps/application/flow/step_node/condition_node/compare/len_equal_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/len_equal_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class LenEqualCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/len_ge_compare.py b/apps/chat/flow/step_node/condition_node/compare/len_ge_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/len_ge_compare.py rename to apps/chat/flow/step_node/condition_node/compare/len_ge_compare.py index 87f11eb2c..b8f738c7b 100644 --- a/apps/application/flow/step_node/condition_node/compare/len_ge_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/len_ge_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class LenGECompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/len_gt_compare.py b/apps/chat/flow/step_node/condition_node/compare/len_gt_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/len_gt_compare.py rename to apps/chat/flow/step_node/condition_node/compare/len_gt_compare.py index 0532d353d..b894e9564 100644 --- a/apps/application/flow/step_node/condition_node/compare/len_gt_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/len_gt_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class LenGTCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/len_le_compare.py b/apps/chat/flow/step_node/condition_node/compare/len_le_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/len_le_compare.py rename to apps/chat/flow/step_node/condition_node/compare/len_le_compare.py index d315a754a..d5c552af5 100644 --- a/apps/application/flow/step_node/condition_node/compare/len_le_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/len_le_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class LenLECompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/len_lt_compare.py b/apps/chat/flow/step_node/condition_node/compare/len_lt_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/len_lt_compare.py rename to apps/chat/flow/step_node/condition_node/compare/len_lt_compare.py index c89638cd7..702342f50 100644 --- a/apps/application/flow/step_node/condition_node/compare/len_lt_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/len_lt_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class LenLTCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/lt_compare.py b/apps/chat/flow/step_node/condition_node/compare/lt_compare.py similarity index 87% rename from apps/application/flow/step_node/condition_node/compare/lt_compare.py rename to apps/chat/flow/step_node/condition_node/compare/lt_compare.py index d2d5be748..6dbd67f37 100644 --- a/apps/application/flow/step_node/condition_node/compare/lt_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/lt_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class LTCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/compare/not_contain_compare.py b/apps/chat/flow/step_node/condition_node/compare/not_contain_compare.py similarity index 88% rename from apps/application/flow/step_node/condition_node/compare/not_contain_compare.py rename to apps/chat/flow/step_node/condition_node/compare/not_contain_compare.py index f95b237dd..da8fb51fa 100644 --- a/apps/application/flow/step_node/condition_node/compare/not_contain_compare.py +++ b/apps/chat/flow/step_node/condition_node/compare/not_contain_compare.py @@ -8,7 +8,7 @@ """ from typing import List -from application.flow.step_node.condition_node.compare.compare import Compare +from chat.flow.step_node.condition_node.compare.compare import Compare class NotContainCompare(Compare): diff --git a/apps/application/flow/step_node/condition_node/i_condition_node.py b/apps/chat/flow/step_node/condition_node/i_condition_node.py similarity index 50% rename from apps/application/flow/step_node/condition_node/i_condition_node.py rename to apps/chat/flow/step_node/condition_node/i_condition_node.py index a0e9814ff..6aefd3f8c 100644 --- a/apps/application/flow/step_node/condition_node/i_condition_node.py +++ b/apps/chat/flow/step_node/condition_node/i_condition_node.py @@ -11,20 +11,19 @@ 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 -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode class ConditionSerializer(serializers.Serializer): - compare = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Comparator"))) - value = serializers.CharField(required=True, error_messages=ErrMessage.char(_("value"))) - field = serializers.ListField(required=True, error_messages=ErrMessage.char(_("Fields"))) + compare = serializers.CharField(required=True, label=_("Comparator")) + value = serializers.CharField(required=True, label=_("value")) + field = serializers.ListField(required=True, label=_("Fields")) class ConditionBranchSerializer(serializers.Serializer): - id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch id"))) - type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch Type"))) - condition = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Condition or|and"))) + id = serializers.CharField(required=True, label=_("Branch id")) + type = serializers.CharField(required=True, label=_("Branch Type")) + condition = serializers.CharField(required=True, label=_("Condition or|and")) conditions = ConditionSerializer(many=True) diff --git a/apps/application/flow/step_node/condition_node/impl/__init__.py b/apps/chat/flow/step_node/condition_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/condition_node/impl/__init__.py rename to apps/chat/flow/step_node/condition_node/impl/__init__.py diff --git a/apps/application/flow/step_node/condition_node/impl/base_condition_node.py b/apps/chat/flow/step_node/condition_node/impl/base_condition_node.py similarity index 90% rename from apps/application/flow/step_node/condition_node/impl/base_condition_node.py rename to apps/chat/flow/step_node/condition_node/impl/base_condition_node.py index 109029be2..7e85aea29 100644 --- a/apps/application/flow/step_node/condition_node/impl/base_condition_node.py +++ b/apps/chat/flow/step_node/condition_node/impl/base_condition_node.py @@ -8,9 +8,9 @@ """ from typing import List -from application.flow.i_step_node import NodeResult -from application.flow.step_node.condition_node.compare import compare_handle_list -from application.flow.step_node.condition_node.i_condition_node import IConditionNode +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.condition_node.compare import compare_handle_list +from chat.flow.step_node.condition_node.i_condition_node import IConditionNode class BaseConditionNode(IConditionNode): diff --git a/apps/application/flow/step_node/direct_reply_node/__init__.py b/apps/chat/flow/step_node/direct_reply_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/direct_reply_node/__init__.py rename to apps/chat/flow/step_node/direct_reply_node/__init__.py diff --git a/apps/application/flow/step_node/direct_reply_node/i_reply_node.py b/apps/chat/flow/step_node/direct_reply_node/i_reply_node.py similarity index 72% rename from apps/application/flow/step_node/direct_reply_node/i_reply_node.py rename to apps/chat/flow/step_node/direct_reply_node/i_reply_node.py index d60541b18..f6d43f4e1 100644 --- a/apps/application/flow/step_node/direct_reply_node/i_reply_node.py +++ b/apps/chat/flow/step_node/direct_reply_node/i_reply_node.py @@ -10,18 +10,19 @@ from typing import Type from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult +from chat.flow.i_step_node import INode, NodeResult from common.exception.app_exception import AppApiException -from common.util.field_message import ErrMessage + from django.utils.translation import gettext_lazy as _ class ReplyNodeParamsSerializer(serializers.Serializer): - reply_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Response Type"))) - fields = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Reference Field"))) + reply_type = serializers.CharField(required=True, label=_("Response Type")) + fields = serializers.ListField(required=False, label=_("Reference Field")) content = serializers.CharField(required=False, allow_blank=True, allow_null=True, - error_messages=ErrMessage.char(_("Direct answer content"))) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) + label=_("Direct answer content")) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) diff --git a/apps/application/flow/step_node/direct_reply_node/impl/__init__.py b/apps/chat/flow/step_node/direct_reply_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/direct_reply_node/impl/__init__.py rename to apps/chat/flow/step_node/direct_reply_node/impl/__init__.py diff --git a/apps/application/flow/step_node/direct_reply_node/impl/base_reply_node.py b/apps/chat/flow/step_node/direct_reply_node/impl/base_reply_node.py similarity index 91% rename from apps/application/flow/step_node/direct_reply_node/impl/base_reply_node.py rename to apps/chat/flow/step_node/direct_reply_node/impl/base_reply_node.py index 1d3115e4c..dd36aa34e 100644 --- a/apps/application/flow/step_node/direct_reply_node/impl/base_reply_node.py +++ b/apps/chat/flow/step_node/direct_reply_node/impl/base_reply_node.py @@ -8,8 +8,8 @@ """ from typing import List -from application.flow.i_step_node import NodeResult -from application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.direct_reply_node.i_reply_node import IReplyNode class BaseReplyNode(IReplyNode): diff --git a/apps/application/flow/step_node/document_extract_node/__init__.py b/apps/chat/flow/step_node/document_extract_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/document_extract_node/__init__.py rename to apps/chat/flow/step_node/document_extract_node/__init__.py diff --git a/apps/application/flow/step_node/document_extract_node/i_document_extract_node.py b/apps/chat/flow/step_node/document_extract_node/i_document_extract_node.py similarity index 79% rename from apps/application/flow/step_node/document_extract_node/i_document_extract_node.py rename to apps/chat/flow/step_node/document_extract_node/i_document_extract_node.py index 93d2b5b98..8da45509f 100644 --- a/apps/application/flow/step_node/document_extract_node/i_document_extract_node.py +++ b/apps/chat/flow/step_node/document_extract_node/i_document_extract_node.py @@ -5,12 +5,11 @@ 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 -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult class DocumentExtractNodeSerializer(serializers.Serializer): - document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document"))) + document_list = serializers.ListField(required=False, label=_("document")) class IDocumentExtractNode(INode): diff --git a/apps/application/flow/step_node/document_extract_node/impl/__init__.py b/apps/chat/flow/step_node/document_extract_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/document_extract_node/impl/__init__.py rename to apps/chat/flow/step_node/document_extract_node/impl/__init__.py diff --git a/apps/application/flow/step_node/document_extract_node/impl/base_document_extract_node.py b/apps/chat/flow/step_node/document_extract_node/impl/base_document_extract_node.py similarity index 89% rename from apps/application/flow/step_node/document_extract_node/impl/base_document_extract_node.py rename to apps/chat/flow/step_node/document_extract_node/impl/base_document_extract_node.py index 6ddcb6e2f..0b4837eb3 100644 --- a/apps/application/flow/step_node/document_extract_node/impl/base_document_extract_node.py +++ b/apps/chat/flow/step_node/document_extract_node/impl/base_document_extract_node.py @@ -5,11 +5,11 @@ import mimetypes from django.core.files.uploadedfile import InMemoryUploadedFile from django.db.models import QuerySet -from application.flow.i_step_node import NodeResult -from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode -from dataset.models import File -from dataset.serializers.document_serializers import split_handles, parse_table_handle_list, FileBufferHandle -from dataset.serializers.file_serializers import FileSerializer +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode +from knowledge.models import File +from knowledge.serializers.document import split_handles, parse_table_handle_list, FileBufferHandle +from knowledge.serializers.file import FileSerializer def bytes_to_uploaded_file(file_bytes, file_name="file.txt"): @@ -37,11 +37,11 @@ def bytes_to_uploaded_file(file_bytes, file_name="file.txt"): splitter = '\n`-----------------------------------`\n' + class BaseDocumentExtractNode(IDocumentExtractNode): def save_context(self, details, workflow_manage): self.context['content'] = details.get('content') - def execute(self, document, chat_id, **kwargs): get_buffer = FileBufferHandle().get_buffer diff --git a/apps/application/flow/step_node/form_node/__init__.py b/apps/chat/flow/step_node/form_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/form_node/__init__.py rename to apps/chat/flow/step_node/form_node/__init__.py diff --git a/apps/application/flow/step_node/form_node/i_form_node.py b/apps/chat/flow/step_node/form_node/i_form_node.py similarity index 66% rename from apps/application/flow/step_node/form_node/i_form_node.py rename to apps/chat/flow/step_node/form_node/i_form_node.py index 7e8249429..6f2dad1b1 100644 --- a/apps/application/flow/step_node/form_node/i_form_node.py +++ b/apps/chat/flow/step_node/form_node/i_form_node.py @@ -10,15 +10,15 @@ from typing import Type from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult + from django.utils.translation import gettext_lazy as _ class FormNodeParamsSerializer(serializers.Serializer): - form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Form Configuration"))) - form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Form output content'))) - form_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data"))) + form_field_list = serializers.ListField(required=True, label=_("Form Configuration")) + form_content_format = serializers.CharField(required=True, label=_('Form output content')) + form_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data")) class IFormNode(INode): diff --git a/apps/application/flow/step_node/form_node/impl/__init__.py b/apps/chat/flow/step_node/form_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/form_node/impl/__init__.py rename to apps/chat/flow/step_node/form_node/impl/__init__.py diff --git a/apps/application/flow/step_node/form_node/impl/base_form_node.py b/apps/chat/flow/step_node/form_node/impl/base_form_node.py similarity index 97% rename from apps/application/flow/step_node/form_node/impl/base_form_node.py rename to apps/chat/flow/step_node/form_node/impl/base_form_node.py index dcf35dd3c..1ba412147 100644 --- a/apps/application/flow/step_node/form_node/impl/base_form_node.py +++ b/apps/chat/flow/step_node/form_node/impl/base_form_node.py @@ -12,9 +12,9 @@ from typing import Dict, List from langchain_core.prompts import PromptTemplate -from application.flow.common import Answer -from application.flow.i_step_node import NodeResult -from application.flow.step_node.form_node.i_form_node import IFormNode +from chat.flow.common import Answer +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.form_node.i_form_node import IFormNode def write_context(step_variable: Dict, global_variable: Dict, node, workflow): diff --git a/apps/application/flow/step_node/function_lib_node/__init__.py b/apps/chat/flow/step_node/function_lib_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/function_lib_node/__init__.py rename to apps/chat/flow/step_node/function_lib_node/__init__.py diff --git a/apps/application/flow/step_node/function_lib_node/i_function_lib_node.py b/apps/chat/flow/step_node/function_lib_node/i_function_lib_node.py similarity index 61% rename from apps/application/flow/step_node/function_lib_node/i_function_lib_node.py rename to apps/chat/flow/step_node/function_lib_node/i_function_lib_node.py index c84782ff6..d71edf75a 100644 --- a/apps/application/flow/step_node/function_lib_node/i_function_lib_node.py +++ b/apps/chat/flow/step_node/function_lib_node/i_function_lib_node.py @@ -11,26 +11,27 @@ from typing import Type from django.db.models import QuerySet from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult +from chat.flow.i_step_node import INode, NodeResult from common.field.common import ObjectField -from common.util.field_message import ErrMessage -from function_lib.models.function import FunctionLib + +from tools.models.tool import Tool from django.utils.translation import gettext_lazy as _ class InputField(serializers.Serializer): - name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name'))) - value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list]) + name = serializers.CharField(required=True, label=_('Variable Name')) + value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list]) class FunctionLibNodeParamsSerializer(serializers.Serializer): - function_lib_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('Library ID'))) + tool_id = serializers.UUIDField(required=True, label=_('Library ID')) input_field_list = InputField(required=True, many=True) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) - f_lib = QuerySet(FunctionLib).filter(id=self.data.get('function_lib_id')).first() + f_lib = QuerySet(Tool).filter(id=self.data.get('tool_id')).first() if f_lib is None: raise Exception(_('The function has been deleted')) diff --git a/apps/application/flow/step_node/function_lib_node/impl/__init__.py b/apps/chat/flow/step_node/function_lib_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/function_lib_node/impl/__init__.py rename to apps/chat/flow/step_node/function_lib_node/impl/__init__.py diff --git a/apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py b/apps/chat/flow/step_node/function_lib_node/impl/base_function_lib_node.py similarity index 93% rename from apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py rename to apps/chat/flow/step_node/function_lib_node/impl/base_function_lib_node.py index 341bb91da..4f567d818 100644 --- a/apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py +++ b/apps/chat/flow/step_node/function_lib_node/impl/base_function_lib_node.py @@ -13,13 +13,13 @@ from typing import Dict from django.db.models import QuerySet from django.utils.translation import gettext as _ -from application.flow.i_step_node import NodeResult -from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode from common.exception.app_exception import AppApiException -from common.util.function_code import FunctionExecutor -from common.util.rsa_util import rsa_long_decrypt -from function_lib.models.function import FunctionLib -from smartdoc.const import CONFIG +from common.utils.function_code import FunctionExecutor +from common.utils.rsa_util import rsa_long_decrypt +from maxkb.const import CONFIG +from tools.models import Tool function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) @@ -117,7 +117,7 @@ class BaseFunctionLibNodeNode(IFunctionLibNode): self.answer_text = str(details.get('result')) def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult: - function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first() + function_lib = QuerySet(Tool).filter(id=function_lib_id).first() valid_function(function_lib, self.flow_params_serializer.data.get('user_id')) params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'), field.get('is_required'), diff --git a/apps/application/flow/step_node/function_node/__init__.py b/apps/chat/flow/step_node/function_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/function_node/__init__.py rename to apps/chat/flow/step_node/function_node/__init__.py diff --git a/apps/application/flow/step_node/function_node/i_function_node.py b/apps/chat/flow/step_node/function_node/i_function_node.py similarity index 67% rename from apps/application/flow/step_node/function_node/i_function_node.py rename to apps/chat/flow/step_node/function_node/i_function_node.py index bbaae6c73..85722e3d7 100644 --- a/apps/application/flow/step_node/function_node/i_function_node.py +++ b/apps/chat/flow/step_node/function_node/i_function_node.py @@ -12,26 +12,26 @@ from typing import Type from django.core import validators from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult +from chat.flow.i_step_node import INode, NodeResult from common.exception.app_exception import AppApiException from common.field.common import ObjectField -from common.util.field_message import ErrMessage + from django.utils.translation import gettext_lazy as _ from rest_framework.utils.formatting import lazy_format class InputField(serializers.Serializer): - name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name'))) - is_required = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean(_("Is this field required"))) - type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("type")), validators=[ + name = serializers.CharField(required=True, label=_('Variable Name')) + is_required = serializers.BooleanField(required=True, label=_("Is this field required")) + type = serializers.CharField(required=True, label=_("type"), validators=[ validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"), message=_("The field only supports string|int|dict|array|float"), code=500) ]) - source = serializers.CharField(required=True, error_messages=ErrMessage.char(_("source")), validators=[ + source = serializers.CharField(required=True, label=_("source"), validators=[ validators.RegexValidator(regex=re.compile("^custom|reference$"), message=_("The field only supports custom|reference"), code=500) ]) - value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list]) + value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list]) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) @@ -43,8 +43,9 @@ class InputField(serializers.Serializer): class FunctionNodeParamsSerializer(serializers.Serializer): input_field_list = InputField(required=True, many=True) - code = serializers.CharField(required=True, error_messages=ErrMessage.char(_("function"))) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) + code = serializers.CharField(required=True, label=_("function")) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) diff --git a/apps/application/flow/step_node/function_node/impl/__init__.py b/apps/chat/flow/step_node/function_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/function_node/impl/__init__.py rename to apps/chat/flow/step_node/function_node/impl/__init__.py diff --git a/apps/application/flow/step_node/function_node/impl/base_function_node.py b/apps/chat/flow/step_node/function_node/impl/base_function_node.py similarity index 92% rename from apps/application/flow/step_node/function_node/impl/base_function_node.py rename to apps/chat/flow/step_node/function_node/impl/base_function_node.py index d659227f1..be342c5b4 100644 --- a/apps/application/flow/step_node/function_node/impl/base_function_node.py +++ b/apps/chat/flow/step_node/function_node/impl/base_function_node.py @@ -8,14 +8,12 @@ """ import json import time - from typing import Dict -from application.flow.i_step_node import NodeResult -from application.flow.step_node.function_node.i_function_node import IFunctionNode -from common.exception.app_exception import AppApiException -from common.util.function_code import FunctionExecutor -from smartdoc.const import CONFIG +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.function_node.i_function_node import IFunctionNode +from common.utils.function_code import FunctionExecutor +from maxkb.const import CONFIG function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) diff --git a/apps/application/flow/step_node/image_generate_step_node/__init__.py b/apps/chat/flow/step_node/image_generate_step_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/image_generate_step_node/__init__.py rename to apps/chat/flow/step_node/image_generate_step_node/__init__.py diff --git a/apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py b/apps/chat/flow/step_node/image_generate_step_node/i_image_generate_node.py similarity index 54% rename from apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py rename to apps/chat/flow/step_node/image_generate_step_node/i_image_generate_node.py index 56a214cf9..400b3b3b9 100644 --- a/apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py +++ b/apps/chat/flow/step_node/image_generate_step_node/i_image_generate_node.py @@ -2,31 +2,31 @@ 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 -from common.util.field_message import ErrMessage -from django.utils.translation import gettext_lazy as _ +from chat.flow.i_step_node import INode, NodeResult class ImageGenerateNodeSerializer(serializers.Serializer): - model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) + model_id = serializers.CharField(required=True, label=_("Model id")) - prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word (positive)"))) + prompt = serializers.CharField(required=True, label=_("Prompt word (positive)")) - negative_prompt = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Prompt word (negative)")), + negative_prompt = serializers.CharField(required=False, label=_("Prompt word (negative)"), allow_null=True, allow_blank=True, ) # 多轮对话数量 dialogue_number = serializers.IntegerField(required=False, default=0, - error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) + label=_("Number of multi-round conversations")) dialogue_type = serializers.CharField(required=False, default='NODE', - error_messages=ErrMessage.char(_("Conversation storage type"))) + label=_("Conversation storage type")) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) model_params_setting = serializers.JSONField(required=False, default=dict, - error_messages=ErrMessage.json(_("Model parameter settings"))) + label=_("Model parameter settings")) class IImageGenerateNode(INode): diff --git a/apps/application/flow/step_node/image_generate_step_node/impl/__init__.py b/apps/chat/flow/step_node/image_generate_step_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/image_generate_step_node/impl/__init__.py rename to apps/chat/flow/step_node/image_generate_step_node/impl/__init__.py diff --git a/apps/application/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py b/apps/chat/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py similarity index 94% rename from apps/application/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py rename to apps/chat/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py index 16423eafd..6a8d8daa9 100644 --- a/apps/application/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py +++ b/apps/chat/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py @@ -5,11 +5,11 @@ from typing import List import requests from langchain_core.messages import BaseMessage, HumanMessage, AIMessage -from application.flow.i_step_node import NodeResult -from application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode -from common.util.common import bytes_to_uploaded_file -from dataset.serializers.file_serializers import FileSerializer -from setting.models_provider.tools import get_model_instance_by_model_user_id +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode +from common.utils.common import bytes_to_uploaded_file +from knowledge.serializers.file import FileSerializer +from models_provider.tools import get_model_instance_by_model_user_id class BaseImageGenerateNode(IImageGenerateNode): diff --git a/apps/application/flow/step_node/image_understand_step_node/__init__.py b/apps/chat/flow/step_node/image_understand_step_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/image_understand_step_node/__init__.py rename to apps/chat/flow/step_node/image_understand_step_node/__init__.py diff --git a/apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py b/apps/chat/flow/step_node/image_understand_step_node/i_image_understand_node.py similarity index 55% rename from apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py rename to apps/chat/flow/step_node/image_understand_step_node/i_image_understand_node.py index 5ef4c1017..b31cdcc18 100644 --- a/apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py +++ b/apps/chat/flow/step_node/image_understand_step_node/i_image_understand_node.py @@ -4,27 +4,28 @@ from typing import Type from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult + from django.utils.translation import gettext_lazy as _ class ImageUnderstandNodeSerializer(serializers.Serializer): - model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) + model_id = serializers.CharField(required=True, label=_("Model id")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, - error_messages=ErrMessage.char(_("Role Setting"))) - prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) + label=_("Role Setting")) + prompt = serializers.CharField(required=True, label=_("Prompt word")) # 多轮对话数量 - dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) + dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations")) - dialogue_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Conversation storage type"))) + dialogue_type = serializers.CharField(required=True, label=_("Conversation storage type")) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) - image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture"))) + image_list = serializers.ListField(required=False, label=_("picture")) model_params_setting = serializers.JSONField(required=False, default=dict, - error_messages=ErrMessage.json(_("Model parameter settings"))) + label=_("Model parameter settings")) class IImageUnderstandNode(INode): diff --git a/apps/application/flow/step_node/image_understand_step_node/impl/__init__.py b/apps/chat/flow/step_node/image_understand_step_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/image_understand_step_node/impl/__init__.py rename to apps/chat/flow/step_node/image_understand_step_node/impl/__init__.py diff --git a/apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py b/apps/chat/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py similarity index 93% rename from apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py rename to apps/chat/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py index 44765bc4f..b14bda215 100644 --- a/apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py +++ b/apps/chat/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py @@ -1,18 +1,17 @@ # coding=utf-8 import base64 -import os import time from functools import reduce +from imghdr import what from typing import List, Dict from django.db.models import QuerySet from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage -from application.flow.i_step_node import NodeResult, INode -from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode -from dataset.models import File -from setting.models_provider.tools import get_model_instance_by_model_user_id -from imghdr import what +from chat.flow.i_step_node import NodeResult, INode +from chat.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode +from knowledge.models import File +from models_provider.tools import get_model_instance_by_model_user_id def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): @@ -81,7 +80,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode): if image is None or not isinstance(image, list): image = [] print(model_params_setting) - image_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), **model_params_setting) + image_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), + **model_params_setting) # 执行详情中的历史消息不需要图片内容 history_message = self.get_history_message_for_details(history_chat_record, dialogue_number) self.context['history_message'] = history_message @@ -155,7 +155,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode): return HumanMessage( content=[ {'type': 'text', 'text': data['question']}, - *[{'type': 'image_url', 'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for + *[{'type': 'image_url', + 'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for base64_image in image_base64_list] ]) return HumanMessage(content=chat_record.problem_text) @@ -173,7 +174,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode): image_bytes = file.get_byte() base64_image = base64.b64encode(image_bytes).decode("utf-8") image_format = what(None, image_bytes.tobytes()) - images.append({'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}}) + images.append( + {'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}}) messages = [HumanMessage( content=[ {'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)}, diff --git a/apps/application/flow/step_node/mcp_node/__init__.py b/apps/chat/flow/step_node/mcp_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/mcp_node/__init__.py rename to apps/chat/flow/step_node/mcp_node/__init__.py diff --git a/apps/application/flow/step_node/mcp_node/i_mcp_node.py b/apps/chat/flow/step_node/mcp_node/i_mcp_node.py similarity index 60% rename from apps/application/flow/step_node/mcp_node/i_mcp_node.py rename to apps/chat/flow/step_node/mcp_node/i_mcp_node.py index 94cb4da77..598b93582 100644 --- a/apps/application/flow/step_node/mcp_node/i_mcp_node.py +++ b/apps/chat/flow/step_node/mcp_node/i_mcp_node.py @@ -2,24 +2,23 @@ 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 -from common.util.field_message import ErrMessage -from django.utils.translation import gettext_lazy as _ +from chat.flow.i_step_node import INode, NodeResult class McpNodeSerializer(serializers.Serializer): mcp_servers = serializers.JSONField(required=True, - error_messages=ErrMessage.char(_("Mcp servers"))) + label=_("Mcp servers")) mcp_server = serializers.CharField(required=True, - error_messages=ErrMessage.char(_("Mcp server"))) + label=_("Mcp server")) - mcp_tool = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Mcp tool"))) + mcp_tool = serializers.CharField(required=True, label=_("Mcp tool")) tool_params = serializers.DictField(required=True, - error_messages=ErrMessage.char(_("Tool parameters"))) + label=_("Tool parameters")) class IMcpNode(INode): diff --git a/apps/application/flow/step_node/mcp_node/impl/__init__.py b/apps/chat/flow/step_node/mcp_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/mcp_node/impl/__init__.py rename to apps/chat/flow/step_node/mcp_node/impl/__init__.py diff --git a/apps/application/flow/step_node/mcp_node/impl/base_mcp_node.py b/apps/chat/flow/step_node/mcp_node/impl/base_mcp_node.py similarity index 95% rename from apps/application/flow/step_node/mcp_node/impl/base_mcp_node.py rename to apps/chat/flow/step_node/mcp_node/impl/base_mcp_node.py index e49ef7019..408a9dc93 100644 --- a/apps/application/flow/step_node/mcp_node/impl/base_mcp_node.py +++ b/apps/chat/flow/step_node/mcp_node/impl/base_mcp_node.py @@ -5,8 +5,8 @@ from typing import List from langchain_mcp_adapters.client import MultiServerMCPClient -from application.flow.i_step_node import NodeResult -from application.flow.step_node.mcp_node.i_mcp_node import IMcpNode +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.mcp_node.i_mcp_node import IMcpNode class BaseMcpNode(IMcpNode): diff --git a/apps/application/flow/step_node/question_node/__init__.py b/apps/chat/flow/step_node/question_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/question_node/__init__.py rename to apps/chat/flow/step_node/question_node/__init__.py diff --git a/apps/application/flow/step_node/question_node/i_question_node.py b/apps/chat/flow/step_node/question_node/i_question_node.py similarity index 57% rename from apps/application/flow/step_node/question_node/i_question_node.py rename to apps/chat/flow/step_node/question_node/i_question_node.py index 57898bf22..a6a66e158 100644 --- a/apps/application/flow/step_node/question_node/i_question_node.py +++ b/apps/chat/flow/step_node/question_node/i_question_node.py @@ -8,23 +8,25 @@ """ 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 -from common.util.field_message import ErrMessage -from django.utils.translation import gettext_lazy as _ +from chat.flow.i_step_node import INode, NodeResult class QuestionNodeSerializer(serializers.Serializer): - model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) + model_id = serializers.CharField(required=True, label=_("Model id")) system = serializers.CharField(required=False, allow_blank=True, allow_null=True, - error_messages=ErrMessage.char(_("Role Setting"))) - prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) + label=_("Role Setting")) + prompt = serializers.CharField(required=True, label=_("Prompt word")) # 多轮对话数量 - dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) + dialogue_number = serializers.IntegerField(required=True, label= + _("Number of multi-round conversations")) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) - model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.integer(_("Model parameter settings"))) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) + model_params_setting = serializers.DictField(required=False, + label=_("Model parameter settings")) class IQuestionNode(INode): diff --git a/apps/application/flow/step_node/question_node/impl/__init__.py b/apps/chat/flow/step_node/question_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/question_node/impl/__init__.py rename to apps/chat/flow/step_node/question_node/impl/__init__.py diff --git a/apps/application/flow/step_node/question_node/impl/base_question_node.py b/apps/chat/flow/step_node/question_node/impl/base_question_node.py similarity index 95% rename from apps/application/flow/step_node/question_node/impl/base_question_node.py rename to apps/chat/flow/step_node/question_node/impl/base_question_node.py index e1fd5b860..96788cceb 100644 --- a/apps/application/flow/step_node/question_node/impl/base_question_node.py +++ b/apps/chat/flow/step_node/question_node/impl/base_question_node.py @@ -15,11 +15,10 @@ from django.db.models import QuerySet from langchain.schema import HumanMessage, SystemMessage from langchain_core.messages import BaseMessage -from application.flow.i_step_node import NodeResult, INode -from application.flow.step_node.question_node.i_question_node import IQuestionNode -from setting.models import Model -from setting.models_provider import get_model_credential -from setting.models_provider.tools import get_model_instance_by_model_user_id +from chat.flow.i_step_node import NodeResult, INode +from chat.flow.step_node.question_node.i_question_node import IQuestionNode +from models_provider.models import Model +from models_provider.tools import get_model_instance_by_model_user_id, get_model_credential def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): diff --git a/apps/application/flow/step_node/reranker_node/__init__.py b/apps/chat/flow/step_node/reranker_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/reranker_node/__init__.py rename to apps/chat/flow/step_node/reranker_node/__init__.py diff --git a/apps/application/flow/step_node/reranker_node/i_reranker_node.py b/apps/chat/flow/step_node/reranker_node/i_reranker_node.py similarity index 81% rename from apps/application/flow/step_node/reranker_node/i_reranker_node.py rename to apps/chat/flow/step_node/reranker_node/i_reranker_node.py index 3b95e4dd6..e43a319cb 100644 --- a/apps/application/flow/step_node/reranker_node/i_reranker_node.py +++ b/apps/chat/flow/step_node/reranker_node/i_reranker_node.py @@ -10,20 +10,20 @@ from typing import Type from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult + from django.utils.translation import gettext_lazy as _ class RerankerSettingSerializer(serializers.Serializer): # 需要查询的条数 top_n = serializers.IntegerField(required=True, - error_messages=ErrMessage.integer(_("Reference segment number"))) + label=_("Reference segment number")) # 相似度 0-1之间 similarity = serializers.FloatField(required=True, max_value=2, min_value=0, - error_messages=ErrMessage.float(_("Reference segment number"))) + label=_("Reference segment number")) max_paragraph_char_number = serializers.IntegerField(required=True, - error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment"))) + label=_("Maximum number of words in a quoted segment")) class RerankerStepNodeSerializer(serializers.Serializer): diff --git a/apps/application/flow/step_node/reranker_node/impl/__init__.py b/apps/chat/flow/step_node/reranker_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/reranker_node/impl/__init__.py rename to apps/chat/flow/step_node/reranker_node/impl/__init__.py diff --git a/apps/application/flow/step_node/reranker_node/impl/base_reranker_node.py b/apps/chat/flow/step_node/reranker_node/impl/base_reranker_node.py similarity index 95% rename from apps/application/flow/step_node/reranker_node/impl/base_reranker_node.py rename to apps/chat/flow/step_node/reranker_node/impl/base_reranker_node.py index ee92b88a5..b8dd0f722 100644 --- a/apps/application/flow/step_node/reranker_node/impl/base_reranker_node.py +++ b/apps/chat/flow/step_node/reranker_node/impl/base_reranker_node.py @@ -10,9 +10,9 @@ from typing import List from langchain_core.documents import Document -from application.flow.i_step_node import NodeResult -from application.flow.step_node.reranker_node.i_reranker_node import IRerankerNode -from setting.models_provider.tools import get_model_instance_by_model_user_id +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.reranker_node.i_reranker_node import IRerankerNode +from models_provider.tools import get_model_instance_by_model_user_id def merge_reranker_list(reranker_list, result=None): diff --git a/apps/application/flow/step_node/search_dataset_node/__init__.py b/apps/chat/flow/step_node/search_dataset_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/search_dataset_node/__init__.py rename to apps/chat/flow/step_node/search_dataset_node/__init__.py diff --git a/apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py b/apps/chat/flow/step_node/search_dataset_node/i_search_dataset_node.py similarity index 82% rename from apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py rename to apps/chat/flow/step_node/search_dataset_node/i_search_dataset_node.py index 8f15c7a32..341dd6ddf 100644 --- a/apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py +++ b/apps/chat/flow/step_node/search_dataset_node/i_search_dataset_node.py @@ -12,31 +12,31 @@ from typing import Type from django.core import validators from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult -from common.util.common import flat_map -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult +from common.utils.common import flat_map + from django.utils.translation import gettext_lazy as _ class DatasetSettingSerializer(serializers.Serializer): # 需要查询的条数 top_n = serializers.IntegerField(required=True, - error_messages=ErrMessage.integer(_("Reference segment number"))) + label=_("Reference segment number")) # 相似度 0-1之间 similarity = serializers.FloatField(required=True, max_value=2, min_value=0, - error_messages=ErrMessage.float(_('similarity'))) + label=_('similarity')) search_mode = serializers.CharField(required=True, validators=[ validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), message=_("The type only supports embedding|keywords|blend"), code=500) - ], error_messages=ErrMessage.char(_("Retrieval Mode"))) + ], label=_("Retrieval Mode")) max_paragraph_char_number = serializers.IntegerField(required=True, - error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment"))) + label=_("Maximum number of words in a quoted segment")) class SearchDatasetStepNodeSerializer(serializers.Serializer): # 需要查询的数据集id列表 dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), - error_messages=ErrMessage.list(_("Dataset id list"))) + label=_("Dataset id list")) dataset_setting = DatasetSettingSerializer(required=True) question_reference_address = serializers.ListField(required=True) diff --git a/apps/application/flow/step_node/search_dataset_node/impl/__init__.py b/apps/chat/flow/step_node/search_dataset_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/search_dataset_node/impl/__init__.py rename to apps/chat/flow/step_node/search_dataset_node/impl/__init__.py diff --git a/apps/application/flow/step_node/search_dataset_node/impl/base_search_dataset_node.py b/apps/chat/flow/step_node/search_dataset_node/impl/base_search_dataset_node.py similarity index 93% rename from apps/application/flow/step_node/search_dataset_node/impl/base_search_dataset_node.py rename to apps/chat/flow/step_node/search_dataset_node/impl/base_search_dataset_node.py index 5107d4ce2..2859d135f 100644 --- a/apps/application/flow/step_node/search_dataset_node/impl/base_search_dataset_node.py +++ b/apps/chat/flow/step_node/search_dataset_node/impl/base_search_dataset_node.py @@ -11,19 +11,21 @@ from typing import List, Dict from django.db.models import QuerySet from django.db import connection -from application.flow.i_step_node import NodeResult -from application.flow.step_node.search_dataset_node.i_search_dataset_node import ISearchDatasetStepNode +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.search_dataset_node.i_search_dataset_node import ISearchDatasetStepNode from common.config.embedding_config import VectorStore from common.db.search import native_search -from common.util.file_util import get_file_content -from dataset.models import Document, Paragraph, DataSet -from embedding.models import SearchMode -from setting.models_provider.tools import get_model_instance_by_model_user_id -from smartdoc.conf import PROJECT_DIR +from common.utils.common import get_file_content +from knowledge.models import Document, Paragraph, Knowledge + +from models_provider.tools import get_model_instance_by_model_user_id +from maxkb.conf import PROJECT_DIR + +SearchMode = None def get_embedding_id(dataset_id_list): - dataset_list = QuerySet(DataSet).filter(id__in=dataset_id_list) + dataset_list = QuerySet(Knowledge).filter(id__in=dataset_id_list) if len(set([dataset.embedding_mode_id for dataset in dataset_list])) > 1: raise Exception("关联知识库的向量模型不一致,无法召回分段。") if len(dataset_list) == 0: diff --git a/apps/application/flow/step_node/speech_to_text_step_node/__init__.py b/apps/chat/flow/step_node/speech_to_text_step_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/speech_to_text_step_node/__init__.py rename to apps/chat/flow/step_node/speech_to_text_step_node/__init__.py diff --git a/apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py b/apps/chat/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py similarity index 61% rename from apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py rename to apps/chat/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py index 154762dca..c2a25a216 100644 --- a/apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py +++ b/apps/chat/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py @@ -4,17 +4,18 @@ from typing import Type from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult from django.utils.translation import gettext_lazy as _ class SpeechToTextNodeSerializer(serializers.Serializer): - stt_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) + stt_model_id = serializers.CharField(required=True, label=_("Model id")) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) - audio_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("The audio file cannot be empty"))) + audio_list = serializers.ListField(required=True, + label=_("The audio file cannot be empty")) class ISpeechToTextNode(INode): @@ -28,7 +29,8 @@ class ISpeechToTextNode(INode): self.node_params_serializer.data.get('audio_list')[1:]) for audio in res: if 'file_id' not in audio: - raise ValueError(_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails")) + raise ValueError( + _("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails")) return self.execute(audio=res, **self.node_params_serializer.data, **self.flow_params_serializer.data) diff --git a/apps/application/flow/step_node/speech_to_text_step_node/impl/__init__.py b/apps/chat/flow/step_node/speech_to_text_step_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/speech_to_text_step_node/impl/__init__.py rename to apps/chat/flow/step_node/speech_to_text_step_node/impl/__init__.py diff --git a/apps/application/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py b/apps/chat/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py similarity index 86% rename from apps/application/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py rename to apps/chat/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py index 13b954e46..6b14499fb 100644 --- a/apps/application/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py +++ b/apps/chat/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py @@ -1,18 +1,16 @@ # coding=utf-8 import os import tempfile -import time -import io -from typing import List, Dict +from concurrent.futures import ThreadPoolExecutor from django.db.models import QuerySet -from pydub import AudioSegment -from concurrent.futures import ThreadPoolExecutor -from application.flow.i_step_node import NodeResult, INode -from application.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode -from common.util.common import split_and_transcribe, any_to_mp3 -from dataset.models import File -from setting.models_provider.tools import get_model_instance_by_model_user_id + +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode +from common.utils.common import split_and_transcribe, any_to_mp3 +from knowledge.models import File +from models_provider.tools import get_model_instance_by_model_user_id + class BaseSpeechToTextNode(ISpeechToTextNode): diff --git a/apps/application/flow/step_node/start_node/__init__.py b/apps/chat/flow/step_node/start_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/start_node/__init__.py rename to apps/chat/flow/step_node/start_node/__init__.py diff --git a/apps/application/flow/step_node/start_node/i_start_node.py b/apps/chat/flow/step_node/start_node/i_start_node.py similarity index 85% rename from apps/application/flow/step_node/start_node/i_start_node.py rename to apps/chat/flow/step_node/start_node/i_start_node.py index 41d73f218..1a93a27f9 100644 --- a/apps/application/flow/step_node/start_node/i_start_node.py +++ b/apps/chat/flow/step_node/start_node/i_start_node.py @@ -7,7 +7,7 @@ @desc: """ -from application.flow.i_step_node import INode, NodeResult +from chat.flow.i_step_node import INode, NodeResult class IStarNode(INode): diff --git a/apps/application/flow/step_node/start_node/impl/__init__.py b/apps/chat/flow/step_node/start_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/start_node/impl/__init__.py rename to apps/chat/flow/step_node/start_node/impl/__init__.py diff --git a/apps/application/flow/step_node/start_node/impl/base_start_node.py b/apps/chat/flow/step_node/start_node/impl/base_start_node.py similarity index 96% rename from apps/application/flow/step_node/start_node/impl/base_start_node.py rename to apps/chat/flow/step_node/start_node/impl/base_start_node.py index 24b968471..71416fadc 100644 --- a/apps/application/flow/step_node/start_node/impl/base_start_node.py +++ b/apps/chat/flow/step_node/start_node/impl/base_start_node.py @@ -12,8 +12,8 @@ from typing import List, Type from rest_framework import serializers -from application.flow.i_step_node import NodeResult -from application.flow.step_node.start_node.i_start_node import IStarNode +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.start_node.i_start_node import IStarNode def get_default_global_variable(input_field_list: List): diff --git a/apps/application/flow/step_node/text_to_speech_step_node/__init__.py b/apps/chat/flow/step_node/text_to_speech_step_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/text_to_speech_step_node/__init__.py rename to apps/chat/flow/step_node/text_to_speech_step_node/__init__.py diff --git a/apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py b/apps/chat/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py similarity index 63% rename from apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py rename to apps/chat/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py index 68b53ea92..f5d40a711 100644 --- a/apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py +++ b/apps/chat/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py @@ -4,19 +4,20 @@ from typing import Type from rest_framework import serializers -from application.flow.i_step_node import INode, NodeResult -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult + from django.utils.translation import gettext_lazy as _ class TextToSpeechNodeSerializer(serializers.Serializer): - tts_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) + tts_model_id = serializers.CharField(required=True, label=_("Model id")) - is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) + is_result = serializers.BooleanField(required=False, + label=_('Whether to return content')) - content_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Text content"))) + content_list = serializers.ListField(required=True, label=_("Text content")) model_params_setting = serializers.DictField(required=False, - error_messages=ErrMessage.integer(_("Model parameter settings"))) + label=_("Model parameter settings")) class ITextToSpeechNode(INode): diff --git a/apps/application/flow/step_node/text_to_speech_step_node/impl/__init__.py b/apps/chat/flow/step_node/text_to_speech_step_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/text_to_speech_step_node/impl/__init__.py rename to apps/chat/flow/step_node/text_to_speech_step_node/impl/__init__.py diff --git a/apps/application/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py b/apps/chat/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py similarity index 86% rename from apps/application/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py rename to apps/chat/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py index 970447295..41d749919 100644 --- a/apps/application/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py +++ b/apps/chat/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py @@ -4,12 +4,10 @@ import mimetypes from django.core.files.uploadedfile import InMemoryUploadedFile -from application.flow.i_step_node import NodeResult, INode -from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode -from application.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode -from dataset.models import File -from dataset.serializers.file_serializers import FileSerializer -from setting.models_provider.tools import get_model_instance_by_model_user_id +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode +from knowledge.serializers.file import FileSerializer +from models_provider.tools import get_model_instance_by_model_user_id def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"): diff --git a/apps/application/flow/step_node/variable_assign_node/__init__.py b/apps/chat/flow/step_node/variable_assign_node/__init__.py similarity index 100% rename from apps/application/flow/step_node/variable_assign_node/__init__.py rename to apps/chat/flow/step_node/variable_assign_node/__init__.py diff --git a/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py b/apps/chat/flow/step_node/variable_assign_node/i_variable_assign_node.py similarity index 76% rename from apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py rename to apps/chat/flow/step_node/variable_assign_node/i_variable_assign_node.py index e4594183f..06c7d9e95 100644 --- a/apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py +++ b/apps/chat/flow/step_node/variable_assign_node/i_variable_assign_node.py @@ -5,13 +5,12 @@ 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 -from common.util.field_message import ErrMessage +from chat.flow.i_step_node import INode, NodeResult class VariableAssignNodeParamsSerializer(serializers.Serializer): variable_list = serializers.ListField(required=True, - error_messages=ErrMessage.list(_("Reference Field"))) + label=_("Reference Field")) class IVariableAssignNode(INode): diff --git a/apps/application/flow/step_node/variable_assign_node/impl/__init__.py b/apps/chat/flow/step_node/variable_assign_node/impl/__init__.py similarity index 100% rename from apps/application/flow/step_node/variable_assign_node/impl/__init__.py rename to apps/chat/flow/step_node/variable_assign_node/impl/__init__.py diff --git a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py b/apps/chat/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py similarity index 94% rename from apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py rename to apps/chat/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py index ce2906e62..b964715fe 100644 --- a/apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py +++ b/apps/chat/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py @@ -2,8 +2,8 @@ import json from typing import List -from application.flow.i_step_node import NodeResult -from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode +from chat.flow.i_step_node import NodeResult +from chat.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode class BaseVariableAssignNode(IVariableAssignNode): diff --git a/apps/application/flow/tools.py b/apps/chat/flow/tools.py similarity index 98% rename from apps/application/flow/tools.py rename to apps/chat/flow/tools.py index dfbf69b35..31fada47a 100644 --- a/apps/application/flow/tools.py +++ b/apps/chat/flow/tools.py @@ -12,8 +12,8 @@ from typing import Iterator from django.http import StreamingHttpResponse from langchain_core.messages import BaseMessageChunk, BaseMessage -from application.flow.i_step_node import WorkFlowPostHandler -from common.response import result +from chat.flow.i_step_node import WorkFlowPostHandler +from common.result import result class Reasoning: diff --git a/apps/application/flow/workflow_manage.py b/apps/chat/flow/workflow_manage.py similarity index 98% rename from apps/application/flow/workflow_manage.py rename to apps/chat/flow/workflow_manage.py index 0f7bc9c75..3a251b5bd 100644 --- a/apps/application/flow/workflow_manage.py +++ b/apps/chat/flow/workflow_manage.py @@ -23,15 +23,15 @@ from langchain_core.prompts import PromptTemplate from rest_framework import status from rest_framework.exceptions import ErrorDetail, ValidationError -from application.flow import tools -from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult -from application.flow.step_node import get_node +from chat.flow import tools +from chat.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult +from chat.flow.step_node import get_node from common.exception.app_exception import AppApiException from common.handle.base_to_response import BaseToResponse from common.handle.impl.response.system_to_response import SystemToResponse -from function_lib.models.function import FunctionLib -from setting.models import Model -from setting.models_provider import get_model_credential +from tools.models.tool import Tool +from models_provider.models import Model +from models_provider.tools import get_model_credential executor = ThreadPoolExecutor(max_workers=200) @@ -161,7 +161,7 @@ class Flow: if function_lib_id is None: raise ValidationError(ErrorDetail( _('The library ID of node {node} cannot be empty').format(node=node.properties.get("stepName")))) - f_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first() + f_lib = QuerySet(Tool).filter(id=function_lib_id).first() if f_lib is None: raise ValidationError(ErrorDetail(_("The function library for node {node} is not available").format( node=node.properties.get("stepName")))) diff --git a/apps/chat/migrations/__init__.py b/apps/chat/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/chat/models/__init__.py b/apps/chat/models/__init__.py new file mode 100644 index 000000000..be548b1d2 --- /dev/null +++ b/apps/chat/models/__init__.py @@ -0,0 +1,8 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: __init__.py.py + @date:2025/5/29 16:08 + @desc: +""" diff --git a/apps/chat/serializers/__init__.py b/apps/chat/serializers/__init__.py new file mode 100644 index 000000000..be548b1d2 --- /dev/null +++ b/apps/chat/serializers/__init__.py @@ -0,0 +1,8 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: __init__.py.py + @date:2025/5/29 16:08 + @desc: +""" diff --git a/apps/chat/serializers/chat_embed_serializers.py b/apps/chat/serializers/chat_embed_serializers.py new file mode 100644 index 000000000..1622ba103 --- /dev/null +++ b/apps/chat/serializers/chat_embed_serializers.py @@ -0,0 +1,103 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: chat_embed_serializers.py + @date:2025/5/30 14:34 + @desc: +""" +import os +import uuid + +from django.db.models import QuerySet +from django.http import HttpResponse +from django.template import Template, Context +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from application.models import ApplicationAccessToken +from common.database_model_manage.database_model_manage import DatabaseModelManage +from maxkb.conf import PROJECT_DIR + + +class ChatEmbedSerializer(serializers.Serializer): + host = serializers.CharField(required=True, label=_("Host")) + protocol = serializers.CharField(required=True, label=_("protocol")) + token = serializers.CharField(required=True, label=_("token")) + + def get_embed(self, with_valid=True, params=None): + if params is None: + params = {} + if with_valid: + self.is_valid(raise_exception=True) + index_path = os.path.join(PROJECT_DIR, 'apps', "chat", 'template', 'embed.js') + file = open(index_path, "r", encoding='utf-8') + content = file.read() + file.close() + application_access_token = QuerySet(ApplicationAccessToken).filter( + access_token=self.data.get('token')).first() + is_draggable = 'false' + show_guide = 'true' + float_icon = f"{self.data.get('protocol')}://{self.data.get('host')}/ui/MaxKB.gif" + xpack_cache = DatabaseModelManage.get_model('xpack_cache') + X_PACK_LICENSE_IS_VALID = False if xpack_cache is None else xpack_cache.get('XPACK_LICENSE_IS_VALID', False) + # 获取接入的query参数 + query = self.get_query_api_input(application_access_token.application, params) + float_location = {"x": {"type": "right", "value": 0}, "y": {"type": "bottom", "value": 30}} + header_font_color = "rgb(100, 106, 115)" + application_setting_model = DatabaseModelManage.get_model('application_setting') + if application_setting_model is not None and X_PACK_LICENSE_IS_VALID: + application_setting = QuerySet(application_setting_model).filter( + application_id=application_access_token.application_id).first() + if application_setting is not None: + is_draggable = 'true' if application_setting.draggable else 'false' + if application_setting.float_icon is not None and len(application_setting.float_icon) > 0: + float_icon = f"{self.data.get('protocol')}://{self.data.get('host')}{application_setting.float_icon}" + show_guide = 'true' if application_setting.show_guide else 'false' + if application_setting.float_location is not None: + float_location = application_setting.float_location + if application_setting.custom_theme is not None and len( + application_setting.custom_theme.get('header_font_color', 'rgb(100, 106, 115)')) > 0: + header_font_color = application_setting.custom_theme.get('header_font_color', + 'rgb(100, 106, 115)') + + is_auth = 'true' if application_access_token is not None and application_access_token.is_active else 'false' + t = Template(content) + s = t.render( + Context( + {'is_auth': is_auth, 'protocol': self.data.get('protocol'), 'host': self.data.get('host'), + 'token': self.data.get('token'), + 'white_list_str': ",".join( + application_access_token.white_list if application_access_token.white_list is not None else []), + 'white_active': 'true' if application_access_token.white_active else 'false', + 'is_draggable': is_draggable, + 'float_icon': float_icon, + 'query': query, + 'show_guide': show_guide, + 'x_type': float_location.get('x', {}).get('type', 'right'), + 'x_value': float_location.get('x', {}).get('value', 0), + 'y_type': float_location.get('y', {}).get('type', 'bottom'), + 'y_value': float_location.get('y', {}).get('value', 30), + 'max_kb_id': str(uuid.uuid1()).replace('-', ''), + 'header_font_color': header_font_color})) + response = HttpResponse(s, status=200, headers={'Content-Type': 'text/javascript'}) + return response + + @staticmethod + def get_query_api_input(application, params): + query = '' + if application.work_flow is not None: + work_flow = application.work_flow + if work_flow is not None: + for node in work_flow.get('nodes', []): + if node['id'] == 'base-node': + input_field_list = node.get('properties', {}).get('api_input_field_list', + node.get('properties', {}).get( + 'input_field_list', [])) + if input_field_list is not None: + for field in input_field_list: + if field['assignment_method'] == 'api_input' and field['variable'] in params: + query += f"&{field['variable']}={params[field['variable']]}" + if 'asker' in params: + query += f"&asker={params.get('asker')}" + return query diff --git a/apps/chat/template/embed.js b/apps/chat/template/embed.js new file mode 100644 index 000000000..a64e6379c --- /dev/null +++ b/apps/chat/template/embed.js @@ -0,0 +1,325 @@ +(function() { +const guideHtml=` +
+
+
+
+
+ + + +
+ +
🌟 遇见问题,不再有障碍!
+

你好,我是你的智能小助手。
+ 点我,开启高效解答模式,让问题变成过去式。

+
+ +
+ +
+` +const chatButtonHtml= +`
+ +
` + + + +const getChatContainerHtml=(protocol,host,token,query)=>{ + return `
+ +
+ +
+
+ + +
+
+ + +
+` +} +/** + * 初始化引导 + * @param {*} root + */ +const initGuide=(root)=>{ + root.insertAdjacentHTML("beforeend",guideHtml) + const button=root.querySelector(".maxkb-button") + const close_icon=root.querySelector('.maxkb-close') + const close_func=()=>{ + root.removeChild(root.querySelector('.maxkb-tips')) + root.removeChild(root.querySelector('.maxkb-mask')) + localStorage.setItem('maxkbMaskTip',true) + } + button.onclick=close_func + close_icon.onclick=close_func +} +const initChat=(root)=>{ + // 添加对话icon + root.insertAdjacentHTML("beforeend",chatButtonHtml) + // 添加对话框 + root.insertAdjacentHTML('beforeend',getChatContainerHtml('{{protocol}}','{{host}}','{{token}}','{{query}}')) + // 按钮元素 + const chat_button=root.querySelector('.maxkb-chat-button') + const chat_button_img=root.querySelector('.maxkb-chat-button > img') + // 对话框元素 + const chat_container=root.querySelector('#maxkb-chat-container') + // 引导层 + const mask_content = root.querySelector('.maxkb-mask > .maxkb-content') + const mask_tips = root.querySelector('.maxkb-tips') + chat_button_img.onload=(event)=>{ + if(mask_content){ + mask_content.style.width = chat_button_img.width + 'px' + mask_content.style.height = chat_button_img.height + 'px' + if('{{x_type}}'=='left'){ + mask_tips.style.marginLeft = (chat_button_img.naturalWidth>500?500:chat_button_img.naturalWidth)-64 + 'px' + }else{ + mask_tips.style.marginRight = (chat_button_img.naturalWidth>500?500:chat_button_img.naturalWidth)-64 + 'px' + } + } + } + + const viewport=root.querySelector('.maxkb-openviewport') + const closeviewport=root.querySelector('.maxkb-closeviewport') + const close_func=()=>{ + chat_container.style['display']=chat_container.style['display']=='block'?'none':'block' + chat_button.style['display']=chat_container.style['display']=='block'?'none':'block' + } + close_icon=chat_container.querySelector('.maxkb-chat-close') + chat_button.onclick = close_func + close_icon.onclick=close_func + const viewport_func=()=>{ + if(chat_container.classList.contains('maxkb-enlarge')){ + chat_container.classList.remove("maxkb-enlarge"); + viewport.classList.remove('maxkb-viewportnone') + closeviewport.classList.add('maxkb-viewportnone') + }else{ + chat_container.classList.add("maxkb-enlarge"); + viewport.classList.add('maxkb-viewportnone') + closeviewport.classList.remove('maxkb-viewportnone') + } + } + const drag=(e)=>{ + if (['touchmove','touchstart'].includes(e.type)) { + chat_button.style.top=(e.touches[0].clientY-chat_button_img.naturalHeight/2)+'px' + chat_button.style.left=(e.touches[0].clientX-chat_button_img.naturalWidth/2)+'px' + } else { + chat_button.style.top=(e.y-chat_button_img.naturalHeight/2)+'px' + chat_button.style.left=(e.x-chat_button_img.naturalWidth/2)+'px' + } + chat_button.style.width =chat_button_img.naturalWidth+'px' + chat_button.style.height =chat_button_img.naturalHeight+'px' + } + if({{is_draggable}}){ + chat_button.addEventListener("drag",drag) + chat_button.addEventListener("dragover",(e)=>{ + e.preventDefault() + }) + chat_button.addEventListener("dragend",drag) + chat_button.addEventListener("touchstart",drag) + chat_button.addEventListener("touchmove",drag) + } + viewport.onclick=viewport_func + closeviewport.onclick=viewport_func +} +/** + * 第一次进来的引导提示 + */ +function initMaxkb(){ + const maxkb=document.createElement('div') + const root=document.createElement('div') + const maxkbId = 'maxkb-'+'{{max_kb_id}}' + root.id=maxkbId + initMaxkbStyle(maxkb, maxkbId) + maxkb.appendChild(root) + document.body.appendChild(maxkb) + const maxkbMaskTip=localStorage.getItem('maxkbMaskTip') + if(maxkbMaskTip==null && {{show_guide}}){ + initGuide(root) + } + initChat(root) +} + + +// 初始化全局样式 +function initMaxkbStyle(root, maxkbId){ + style=document.createElement('style') + style.type='text/css' + style.innerText= ` + /* 放大 */ + #maxkb .maxkb-enlarge { + width: 50%!important; + height: 100%!important; + bottom: 0!important; + right: 0 !important; + } + @media only screen and (max-width: 768px){ + #maxkb .maxkb-enlarge { + width: 100%!important; + height: 100%!important; + right: 0 !important; + bottom: 0!important; + } + } + + /* 引导 */ + + #maxkb .maxkb-mask { + position: fixed; + z-index: 10001; + background-color: transparent; + height: 100%; + width: 100%; + top: 0; + left: 0; + } + #maxkb .maxkb-mask .maxkb-content { + width: 64px; + height: 64px; + box-shadow: 1px 1px 1px 9999px rgba(0,0,0,.6); + position: absolute; + {{x_type}}: {{x_value}}px; + {{y_type}}: {{y_value}}px; + z-index: 10001; + } + #maxkb .maxkb-tips { + position: fixed; + {{x_type}}:calc({{x_value}}px + 75px); + {{y_type}}: calc({{y_value}}px + 0px); + padding: 22px 24px 24px; + border-radius: 6px; + color: #ffffff; + font-size: 14px; + background: #3370FF; + z-index: 10001; + } + #maxkb .maxkb-tips .maxkb-arrow { + position: absolute; + background: #3370FF; + width: 10px; + height: 10px; + pointer-events: none; + transform: rotate(45deg); + box-sizing: border-box; + /* left */ + {{x_type}}: -5px; + {{y_type}}: 33px; + border-left-color: transparent; + border-bottom-color: transparent + } + #maxkb .maxkb-tips .maxkb-title { + font-size: 20px; + font-weight: 500; + margin-bottom: 8px; + } + #maxkb .maxkb-tips .maxkb-button { + text-align: right; + margin-top: 24px; + } + #maxkb .maxkb-tips .maxkb-button button { + border-radius: 4px; + background: #FFF; + padding: 3px 12px; + color: #3370FF; + cursor: pointer; + outline: none; + border: none; + } + #maxkb .maxkb-tips .maxkb-button button::after{ + border: none; + } + #maxkb .maxkb-tips .maxkb-close { + position: absolute; + right: 20px; + top: 20px; + cursor: pointer; + + } + #maxkb-chat-container { + width: 450px; + height: 600px; + display:none; + } + @media only screen and (max-width: 768px) { + #maxkb-chat-container { + width: 100%; + height: 70%; + right: 0 !important; + } + } + + #maxkb .maxkb-chat-button{ + position: fixed; + {{x_type}}: {{x_value}}px; + {{y_type}}: {{y_value}}px; + cursor: pointer; + z-index:10000; + } + #maxkb #maxkb-chat-container{ + z-index:10000;position: relative; + border-radius: 8px; + border: 1px solid #ffffff; + background: linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%, rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1; + box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.10); + position: fixed;bottom: 16px;right: 16px;overflow: hidden; + } + + #maxkb #maxkb-chat-container .maxkb-operate{ + top: 18px; + right: 15px; + position: absolute; + display: flex; + align-items: center; + line-height: 18px; + } + #maxkb #maxkb-chat-container .maxkb-operate .maxkb-chat-close{ + margin-left:15px; + cursor: pointer; + } + #maxkb #maxkb-chat-container .maxkb-operate .maxkb-openviewport{ + + cursor: pointer; + } + #maxkb #maxkb-chat-container .maxkb-operate .maxkb-closeviewport{ + + cursor: pointer; + } + #maxkb #maxkb-chat-container .maxkb-viewportnone{ + display:none; + } + #maxkb #maxkb-chat-container #maxkb-chat{ + height:100%; + width:100%; + border: none; +} + #maxkb #maxkb-chat-container { + animation: appear .4s ease-in-out; + } + @keyframes appear { + from { + height: 0;; + } + + to { + height: 600px; + } + }` + .replaceAll('#maxkb ',`#${maxkbId} `) + root.appendChild(style) +} + +function embedChatbot() { + white_list_str='{{white_list_str}}' + white_list=white_list_str.split(',') + + if ({{is_auth}}&&({{white_active}}?white_list.includes(window.location.origin):true)) { + // 初始化maxkb智能小助手 + initMaxkb() + } else console.error('invalid parameter') +} +window.addEventListener('load',embedChatbot) +})(); \ No newline at end of file diff --git a/apps/chat/tests.py b/apps/chat/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/apps/chat/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/chat/urls.py b/apps/chat/urls.py new file mode 100644 index 000000000..e7d9314f1 --- /dev/null +++ b/apps/chat/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +app_name = 'chat' + +urlpatterns = [ + path('chat/embed', views.ChatEmbedView.as_view()), +] diff --git a/apps/chat/views/__init__.py b/apps/chat/views/__init__.py new file mode 100644 index 000000000..203186ac9 --- /dev/null +++ b/apps/chat/views/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: __init__.py.py + @date:2025/5/29 16:08 + @desc: +""" +from .chat_embed import * diff --git a/apps/chat/views/chat_embed.py b/apps/chat/views/chat_embed.py new file mode 100644 index 000000000..997ff5e98 --- /dev/null +++ b/apps/chat/views/chat_embed.py @@ -0,0 +1,32 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎虎 + @file: chat_embed.py + @date:2025/5/30 15:22 + @desc: +""" +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 chat.api.chat_embed_api import ChatEmbedAPI +from chat.serializers.chat_embed_serializers import ChatEmbedSerializer + + +class ChatEmbedView(APIView): + + @extend_schema( + methods=['GET'], + description=_('Import Application'), + summary=_('Import Application'), + operation_id=_('Import Application'), # type: ignore + parameters=ChatEmbedAPI.get_parameters(), + responses=ChatEmbedAPI.get_response(), + tags=[_('Application')] # type: ignore + ) + def get(self, request: Request): + return ChatEmbedSerializer( + data={'protocol': request.query_params.get('protocol'), 'token': request.query_params.get('token'), + 'host': request.query_params.get('host'), }).get_embed(params=request.query_params) diff --git a/apps/common/constants/authentication_type.py b/apps/common/constants/authentication_type.py index f9c04ce12..1880fe4d3 100644 --- a/apps/common/constants/authentication_type.py +++ b/apps/common/constants/authentication_type.py @@ -14,3 +14,7 @@ class AuthenticationType(Enum): SYSTEM_USER = "SYSTEM_USER" # 对话用户 CHAT_USER = "CHAT_USER" + # 对话匿名用户 + CHAT_ANONYMOUS_USER = "CHAT_ANONYMOUS_USER" + # APIKEY + API_KEY = "API_KEY" diff --git a/apps/common/constants/permission_constants.py b/apps/common/constants/permission_constants.py index 2ca152f79..3fdd57f9c 100644 --- a/apps/common/constants/permission_constants.py +++ b/apps/common/constants/permission_constants.py @@ -524,6 +524,20 @@ class PermissionConstants(Enum): parent_group=[SystemGroup.APPLICATION], resource_permission_group_list=[ResourcePermissionGroup.VIEW], ) + APPLICATION_EXPORT = Permission(group=Group.APPLICATION, operate=Operate.EXPORT, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[SystemGroup.APPLICATION] + ) + APPLICATION_DELETE = Permission(group=Group.APPLICATION, operate=Operate.DELETE, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[SystemGroup.APPLICATION], + resource_permission_group_list=[ResourcePermissionGroup.VIEW], + ) + APPLICATION_EDIT = Permission(group=Group.APPLICATION, operate=Operate.EDIT, + role_list=[RoleConstants.ADMIN, RoleConstants.USER], + parent_group=[SystemGroup.APPLICATION], + resource_permission_group_list=[ResourcePermissionGroup.VIEW], + ) SYSTEM_API_KEY_READ = Permission(group=Group.SYSTEM_API_KEY, operate=Operate.READ, role_list=[RoleConstants.ADMIN], parent_group=[SystemGroup.SYSTEM_SETTING] @@ -553,8 +567,6 @@ class PermissionConstants(Enum): - - 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/common/encoder/encoder.py b/apps/common/encoder/encoder.py new file mode 100644 index 000000000..02d0ec88c --- /dev/null +++ b/apps/common/encoder/encoder.py @@ -0,0 +1,30 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: SystemEncoder.py + @date:2025/3/17 16:38 + @desc: +""" +import datetime +import decimal +import json +import uuid + +from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile + + +class SystemEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, uuid.UUID): + return str(obj) + if isinstance(obj, datetime.datetime): + return obj.strftime("%Y-%m-%d %H:%M:%S") + if isinstance(obj, decimal.Decimal): + return float(obj) + if isinstance(obj, InMemoryUploadedFile): + return {'name': obj.name, 'size': obj.size} + if isinstance(obj, TemporaryUploadedFile): + return {'name': obj.name, 'size': obj.size} + else: + return json.JSONEncoder.default(self, obj) diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index af0e0ed4f..7f4d88650 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -7,10 +7,10 @@ @desc: """ import hashlib - -import random import io import mimetypes +import pickle +import random import re import shutil from functools import reduce @@ -23,7 +23,6 @@ from pydub import AudioSegment from ..exception.app_exception import AppApiException from ..models.db_model_manage import DBModelManage -import hashlib def password_encrypt(row_password): @@ -287,3 +286,36 @@ def get_sha256_hash(_v: str | bytes): else: sha256.update(_v) return sha256.hexdigest() + + +ALLOWED_CLASSES = { + ("builtins", "dict"), + ('uuid', 'UUID'), + ("application.serializers.application_serializers", "MKInstance"), + ("function_lib.serializers.function_lib_serializer", "FlibInstance") +} + + +class RestrictedUnpickler(pickle.Unpickler): + + def find_class(self, module, name): + if (module, name) in ALLOWED_CLASSES: + return super().find_class(module, name) + raise pickle.UnpicklingError("global '%s.%s' is forbidden" % + (module, name)) + + +def restricted_loads(s): + """Helper function analogous to pickle.loads().""" + return RestrictedUnpickler(io.BytesIO(s)).load() + +def flat_map(array: List[List]): + """ + 将二位数组转为一维数组 + :param array: 二维数组 + :return: 一维数组 + """ + result = [] + for e in array: + result += e + return result \ No newline at end of file diff --git a/apps/common/utils/function_code.py b/apps/common/utils/function_code.py new file mode 100644 index 000000000..03ad0af8e --- /dev/null +++ b/apps/common/utils/function_code.py @@ -0,0 +1,99 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:虎 + @file: function_code.py + @date:2024/8/7 16:11 + @desc: +""" +import os +import pickle +import subprocess +import sys +import uuid +from textwrap import dedent + +from maxkb.const import BASE_DIR +from maxkb.const import PROJECT_DIR + +python_directory = sys.executable + + +class FunctionExecutor: + def __init__(self, sandbox=False): + self.sandbox = sandbox + if sandbox: + self.sandbox_path = '/opt/maxkb/app/sandbox' + self.user = 'sandbox' + else: + self.sandbox_path = os.path.join(PROJECT_DIR, 'data', 'sandbox') + self.user = None + self._createdir() + if self.sandbox: + os.system(f"chown -R {self.user}:root {self.sandbox_path}") + + def _createdir(self): + old_mask = os.umask(0o077) + try: + os.makedirs(self.sandbox_path, 0o700, exist_ok=True) + os.makedirs(os.path.join(self.sandbox_path, 'execute'), 0o700, exist_ok=True) + os.makedirs(os.path.join(self.sandbox_path, 'result'), 0o700, exist_ok=True) + finally: + os.umask(old_mask) + + def exec_code(self, code_str, keywords): + _id = str(uuid.uuid1()) + success = '{"code":200,"msg":"成功","data":exec_result}' + err = '{"code":500,"msg":str(e),"data":None}' + result_path = f'{self.sandbox_path}/result/{_id}.result' + _exec_code = f""" +try: + import os + env = dict(os.environ) + for key in list(env.keys()): + if key in os.environ and (key.startswith('MAXKB') or key.startswith('POSTGRES') or key.startswith('PG')): + del os.environ[key] + locals_v={'{}'} + keywords={keywords} + globals_v=globals() + exec({dedent(code_str)!a}, globals_v, locals_v) + f_name, f = locals_v.popitem() + for local in locals_v: + globals_v[local] = locals_v[local] + exec_result=f(**keywords) + import pickle + with open({result_path!a}, 'wb') as file: + file.write(pickle.dumps({success})) +except Exception as e: + with open({result_path!a}, 'wb') as file: + file.write(pickle.dumps({err})) +""" + if self.sandbox: + subprocess_result = self._exec_sandbox(_exec_code, _id) + else: + subprocess_result = self._exec(_exec_code) + if subprocess_result.returncode == 1: + raise Exception(subprocess_result.stderr) + with open(result_path, 'rb') as file: + result = pickle.loads(file.read()) + os.remove(result_path) + if result.get('code') == 200: + return result.get('data') + raise Exception(result.get('msg')) + + def _exec_sandbox(self, _code, _id): + exec_python_file = f'{self.sandbox_path}/execute/{_id}.py' + with open(exec_python_file, 'w') as file: + file.write(_code) + os.system(f"chown {self.user}:root {exec_python_file}") + kwargs = {'cwd': BASE_DIR} + subprocess_result = subprocess.run( + ['su', '-s', python_directory, '-c', "exec(open('" + exec_python_file + "').read())", self.user], + text=True, + capture_output=True, **kwargs) + os.remove(exec_python_file) + return subprocess_result + + @staticmethod + def _exec(_code): + return subprocess.run([python_directory, '-c', _code], text=True, capture_output=True) diff --git a/apps/maxkb/urls.py b/apps/maxkb/urls.py index 85d38a582..10e5690ca 100644 --- a/apps/maxkb/urls.py +++ b/apps/maxkb/urls.py @@ -27,7 +27,8 @@ urlpatterns = [ path("api/", include("folders.urls")), path("api/", include("knowledge.urls")), path("api/", include("system_manage.urls")), - path("api/", include("application.urls")) + path("api/", include("application.urls")), + path("chat/", include("chat.urls")) ] urlpatterns += [ path('schema/', SpectacularAPIView.as_view(), name='schema'), # schema的配置文件的路由,下面两个ui也是根据这个配置文件来生成的