From e21d53b7468e3b861c0b89027f7712aae90b9060 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:42:31 +0800 Subject: [PATCH] feat: Simple application version (#3479) --- apps/application/api/application_version.py | 2 +- ...ublish_time_applicationversion_and_more.py | 66 +++++++++++++++++++ apps/application/models/application.py | 36 +++++++++- apps/application/serializers/application.py | 62 ++++++++++++----- .../serializers/application_version.py | 34 +++++----- apps/application/serializers/common.py | 27 +++++--- apps/application/sql/list_application.sql | 6 +- .../application/sql/list_application_user.sql | 6 +- .../sql/list_application_user_ee.sql | 6 +- apps/application/urls.py | 6 +- apps/application/views/application.py | 5 +- apps/application/views/application_version.py | 8 +-- apps/chat/serializers/chat.py | 35 +++++----- apps/chat/serializers/chat_authentication.py | 31 +++++++-- ui/src/api/application/workflow-version.ts | 6 +- .../locales/lang/zh-CN/views/application.ts | 3 +- ui/src/views/application-workflow/index.vue | 5 +- .../views/application/ApplicationSetting.vue | 41 +++++++++--- 18 files changed, 282 insertions(+), 103 deletions(-) create mode 100644 apps/application/migrations/0004_application_publish_time_applicationversion_and_more.py diff --git a/apps/application/api/application_version.py b/apps/application/api/application_version.py index e1f4a3d3..9afd5c7e 100644 --- a/apps/application/api/application_version.py +++ b/apps/application/api/application_version.py @@ -55,7 +55,7 @@ class ApplicationVersionOperateAPI(APIMixin): def get_parameters(): return [ OpenApiParameter( - name="work_flow_version_id", + name="application_version_id", description="工作流版本id", type=OpenApiTypes.STR, location='path', diff --git a/apps/application/migrations/0004_application_publish_time_applicationversion_and_more.py b/apps/application/migrations/0004_application_publish_time_applicationversion_and_more.py new file mode 100644 index 00000000..778b097e --- /dev/null +++ b/apps/application/migrations/0004_application_publish_time_applicationversion_and_more.py @@ -0,0 +1,66 @@ +# Generated by Django 5.2.3 on 2025-07-04 03:18 + +import application.models.application +import django.db.models.deletion +import uuid_utils.compat +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0003_chat_asker_chat_meta_and_more'), + ('users', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='application', + name='publish_time', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='发布时间'), + ), + migrations.CreateModel( + name='ApplicationVersion', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), + ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), + ('name', models.CharField(default='', max_length=128, verbose_name='版本名称')), + ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')), + ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')), + ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), + ('application_name', models.CharField(max_length=128, verbose_name='应用名称')), + ('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')), + ('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')), + ('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')), + ('model_id', models.UUIDField(blank=True, null=True, verbose_name='大语言模型')), + ('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')), + ('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')), + ('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), + ('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), + ('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')), + ('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')), + ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), + ('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')), + ('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中', max_length=102400, null=True, verbose_name='问题优化提示词')), + ('tts_model_id', models.UUIDField(blank=True, null=True, verbose_name='文本转语音模型id')), + ('stt_model_id', models.UUIDField(blank=True, null=True, verbose_name='语音转文本模型id')), + ('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')), + ('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')), + ('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')), + ('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')), + ('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')), + ('clean_time', models.IntegerField(default=180, verbose_name='清理时间')), + ('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')), + ('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')), + ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')), + ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')), + ], + options={ + 'db_table': 'application_version', + }, + ), + migrations.DeleteModel( + name='WorkFlowVersion', + ), + ] diff --git a/apps/application/models/application.py b/apps/application/models/application.py index a20f76fe..67bff2b5 100644 --- a/apps/application/models/application.py +++ b/apps/application/models/application.py @@ -90,6 +90,7 @@ class Application(AppModelMixin): tts_autoplay = models.BooleanField(verbose_name="自动播放", default=False) stt_autosend = models.BooleanField(verbose_name="自动发送", default=False) clean_time = models.IntegerField(verbose_name="清理时间", default=180) + publish_time = models.DateTimeField(verbose_name="发布时间", default=None, null=True, blank=True) file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False) file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict) @@ -120,14 +121,43 @@ class ApplicationKnowledgeMapping(AppModelMixin): db_table = "application_knowledge_mapping" -class WorkFlowVersion(AppModelMixin): +class ApplicationVersion(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) - 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="") + workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) + application_name = models.CharField(max_length=128, verbose_name="应用名称") + desc = models.CharField(max_length=512, verbose_name="引用描述", default="") + prologue = models.CharField(max_length=40960, verbose_name="开场白", default="") + dialogue_number = models.IntegerField(default=0, verbose_name="会话数量") + user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) + model_id = models.UUIDField(verbose_name="大语言模型", blank=True, null=True) + knowledge_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict) + model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict) + model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict) + tts_model_params_setting = models.JSONField(verbose_name="模型参数相关设置", default=dict) + problem_optimization = models.BooleanField(verbose_name="问题优化", default=False) + icon = models.CharField(max_length=256, verbose_name="应用icon", default="./favicon.ico") work_flow = models.JSONField(verbose_name="工作流数据", default=dict) + type = models.CharField(verbose_name="应用类型", choices=ApplicationTypeChoices.choices, + default=ApplicationTypeChoices.SIMPLE, max_length=256) + problem_optimization_prompt = models.CharField(verbose_name="问题优化提示词", max_length=102400, blank=True, + null=True, + default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在标签中") + tts_model_id = models.UUIDField(verbose_name="文本转语音模型id", + blank=True, null=True) + stt_model_id = models.UUIDField(verbose_name="语音转文本模型id", + blank=True, null=True) + tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False) + stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False) + tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER") + tts_autoplay = models.BooleanField(verbose_name="自动播放", default=False) + stt_autosend = models.BooleanField(verbose_name="自动发送", default=False) + clean_time = models.IntegerField(verbose_name="清理时间", default=180) + file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False) + file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict) class Meta: - db_table = "application_work_flow_version" + db_table = "application_version" diff --git a/apps/application/serializers/application.py b/apps/application/serializers/application.py index d260c9df..9dada48a 100644 --- a/apps/application/serializers/application.py +++ b/apps/application/serializers/application.py @@ -27,7 +27,7 @@ from rest_framework.utils.formatting import lazy_format from application.flow.common import Workflow from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \ - ApplicationFolder, WorkFlowVersion + ApplicationFolder, ApplicationVersion from application.models.application_access_token import ApplicationAccessToken from common import result from common.database_model_manage.database_model_manage import DatabaseModelManage @@ -614,6 +614,8 @@ class ApplicationOperateSerializer(serializers.Serializer): def delete(self, with_valid=True): if with_valid: self.is_valid() + QuerySet(ApplicationVersion).filter(application_id=self.data.get('application_id')).delete() + QuerySet(ApplicationKnowledgeMapping).filter(application_id=self.data.get('application_id')).delete() QuerySet(Application).filter(id=self.data.get('application_id')).delete() return True @@ -644,6 +646,27 @@ class ApplicationOperateSerializer(serializers.Serializer): except Exception as e: return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) + @staticmethod + def reset_application_version(application_version, application): + update_field_dict = { + 'application_name': 'name', 'desc': 'desc', 'prologue': 'prologue', 'dialogue_number': 'dialogue_number', + 'user_id': 'user_id', 'model_id': 'model_id', 'knowledge_setting': 'knowledge_setting', + 'model_setting': 'model_setting', 'model_params_setting': 'model_params_setting', + 'tts_model_params_setting': 'tts_model_params_setting', + 'problem_optimization': 'problem_optimization', 'icon': 'icon', 'work_flow': 'work_flow', + 'problem_optimization_prompt': 'problem_optimization_prompt', 'tts_model_id': 'tts_model_id', + 'stt_model_id': 'stt_model_id', 'tts_model_enable': 'tts_model_enable', + 'stt_model_enable': 'stt_model_enable', 'tts_type': 'tts_type', + 'tts_autoplay': 'tts_autoplay', 'stt_autosend': 'stt_autosend', 'file_upload_enable': 'file_upload_enable', + 'file_upload_setting': 'file_upload_setting', + 'type': 'type' + } + + for (version_field, app_field) in update_field_dict.items(): + _v = getattr(application, app_field) + if _v: + setattr(application_version, version_field, _v) + @transaction.atomic def publish(self, instance, with_valid=True): if with_valid: @@ -653,25 +676,28 @@ class ApplicationOperateSerializer(serializers.Serializer): 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")) - Workflow.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 + if application.type == ApplicationTypeChoices.WORK_FLOW: + work_flow = application.work_flow + if work_flow is None: + raise AppApiException(500, _("work_flow is a required field")) + Workflow.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.publish_time = datetime.datetime.now() application.is_publish = True 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 = ApplicationVersion(work_flow=application.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) + self.reset_application_version(work_flow_version, application) work_flow_version.save() return self.one(with_valid=False) diff --git a/apps/application/serializers/application_version.py b/apps/application/serializers/application_version.py index b3c80eeb..58562203 100644 --- a/apps/application/serializers/application_version.py +++ b/apps/application/serializers/application_version.py @@ -12,7 +12,7 @@ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from application.models import WorkFlowVersion, Application +from application.models import Application, ApplicationVersion from common.db.search import page_search from common.exception.app_exception import AppApiException @@ -25,7 +25,7 @@ class ApplicationVersionQuerySerializer(serializers.Serializer): class ApplicationVersionModelSerializer(serializers.ModelSerializer): class Meta: - model = WorkFlowVersion + model = ApplicationVersion fields = ['id', 'name', 'workspace_id', 'application_id', 'work_flow', 'publish_user_id', 'publish_user_name', 'create_time', 'update_time'] @@ -43,11 +43,11 @@ class ApplicationVersionSerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) def get_query_set(self, query): - query_set = QuerySet(WorkFlowVersion).filter(application_id=query.get('application_id')) + query_set = QuerySet(ApplicationVersion).filter(application_id=query.get('application_id')) if 'name' in query and query.get('name') is not None: query_set = query_set.filter(name__contains=query.get('name')) if 'workspace_id' in self.data and self.data.get('workspace_id') is not None: - query_set = query_set.filter(workspace_id=self.data.get('workspace_id').get('name')) + query_set = query_set.filter(workspace_id=self.data.get('workspace_id')) return query_set.order_by("-create_time") def list(self, query, with_valid=True): @@ -67,8 +67,8 @@ class ApplicationVersionSerializer(serializers.Serializer): class Operate(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) - work_flow_version_id = serializers.UUIDField(required=True, - label=_("Workflow version id")) + application_version_id = serializers.UUIDField(required=True, + label=_("Application version ID")) def is_valid(self, *, raise_exception=False): super().is_valid(raise_exception=True) @@ -82,10 +82,11 @@ class ApplicationVersionSerializer(serializers.Serializer): def one(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=self.data.get('application_id'), - id=self.data.get('work_flow_version_id')).first() - if work_flow_version is not None: - return ApplicationVersionModelSerializer(work_flow_version).data + application_version = QuerySet(ApplicationVersion).filter(application_id=self.data.get('application_id'), + id=self.data.get( + 'application_version_id')).first() + if application_version is not None: + return ApplicationVersionModelSerializer(application_version).data else: raise AppApiException(500, _('Workflow version does not exist')) @@ -93,13 +94,14 @@ class ApplicationVersionSerializer(serializers.Serializer): if with_valid: self.is_valid(raise_exception=True) ApplicationVersionEditSerializer(data=instance).is_valid(raise_exception=True) - work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=self.data.get('application_id'), - id=self.data.get('work_flow_version_id')).first() - if work_flow_version is not None: + application_version = QuerySet(ApplicationVersion).filter(application_id=self.data.get('application_id'), + id=self.data.get( + 'application_version_id')).first() + if application_version is not None: name = instance.get('name', None) if name is not None and len(name) > 0: - work_flow_version.name = name - work_flow_version.save() - return ApplicationVersionModelSerializer(work_flow_version).data + application_version.name = name + application_version.save() + return ApplicationVersionModelSerializer(application_version).data else: raise AppApiException(500, _('Workflow version does not exist')) diff --git a/apps/application/serializers/common.py b/apps/application/serializers/common.py index acb20b0a..f602c78b 100644 --- a/apps/application/serializers/common.py +++ b/apps/application/serializers/common.py @@ -14,8 +14,9 @@ from django.db.models import QuerySet from django.utils.translation import gettext_lazy as _ from application.chat_pipeline.step.chat_step.i_chat_step import PostResponseHandler -from application.models import Application, WorkFlowVersion, ChatRecord, Chat +from application.models import Application, ChatRecord, Chat, ApplicationVersion from common.constants.cache_version import Cache_Version +from common.exception.app_exception import ChatException from models_provider.models import Model from models_provider.tools import get_model_credential @@ -28,8 +29,6 @@ class ChatInfo: knowledge_id_list: List[str], exclude_document_id_list: list[str], application_id: str, - application: Application, - work_flow_version: WorkFlowVersion = None, debug=False): """ :param chat_id: 对话id @@ -38,18 +37,16 @@ class ChatInfo: :param knowledge_id_list: 知识库列表 :param exclude_document_id_list: 排除的文档 :param application_id 应用id - :param application: 应用信息 :param debug 是否是调试 """ self.chat_id = chat_id self.chat_user_id = chat_user_id self.chat_user_type = chat_user_type - self.application = application self.knowledge_id_list = knowledge_id_list self.exclude_document_id_list = exclude_document_id_list self.application_id = application_id self.chat_record_list: List[ChatRecord] = [] - self.work_flow_version = work_flow_version + self.application = None self.debug = debug @staticmethod @@ -63,10 +60,24 @@ class ChatInfo: no_references_setting['value'] = no_references_prompt if len(no_references_prompt) > 0 else "{question}" return no_references_setting + def get_application(self): + if self.debug: + application = QuerySet(Application).filter(id=self.application_id).first() + if not application: + raise ChatException(500, _('The application does not exist')) + else: + application = QuerySet(ApplicationVersion).filter(application_id=self.application_id).order_by( + '-create_time')[0:1].first() + if not application: + raise ChatException(500, _("The application has not been published. Please use it after publishing.")) + self.application = application + return application + def to_base_pipeline_manage_params(self): + self.get_application() knowledge_setting = self.application.knowledge_setting model_setting = self.application.model_setting - model_id = self.application.model.id if self.application.model is not None else None + model_id = self.application.model_id model_params_setting = None if model_id is not None: model = QuerySet(Model).filter(id=model_id).first() @@ -127,7 +138,7 @@ class ChatInfo: self.chat_record_list.append(chat_record) if not self.debug: if not QuerySet(Chat).filter(id=self.chat_id).exists(): - Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text[0:1024], + Chat(id=self.chat_id, application_id=self.application_id, abstract=chat_record.problem_text[0:1024], chat_user_id=self.chat_user_id, chat_user_type=self.chat_user_type).save() else: QuerySet(Chat).filter(id=self.chat_id).update(update_time=datetime.now()) diff --git a/apps/application/sql/list_application.sql b/apps/application/sql/list_application.sql index 1e53e688..e9a87f54 100644 --- a/apps/application/sql/list_application.sql +++ b/apps/application/sql/list_application.sql @@ -10,7 +10,8 @@ from (select application."id"::text, application."user_id", "user"."nick_name" as "nick_name", application."create_time", - application."update_time" + application."update_time", + application."publish_time" from application left join "user" on user_id = "user".id ${application_custom_sql} UNION @@ -25,6 +26,7 @@ from (select application."id"::text, application_folder."user_id", "user"."nick_name" as "nick_name", application_folder."create_time", - application_folder."update_time" + application_folder."update_time", + null as "publish_time" from application_folder left join "user" on user_id = "user".id ${folder_query_set}) temp ${application_query_set} \ No newline at end of file diff --git a/apps/application/sql/list_application_user.sql b/apps/application/sql/list_application_user.sql index 25761605..377ba2a5 100644 --- a/apps/application/sql/list_application_user.sql +++ b/apps/application/sql/list_application_user.sql @@ -10,7 +10,8 @@ from (select application."id"::text, application."user_id", "user"."nick_name" as "nick_name", application."create_time", - application."update_time" + application."update_time", + application."publish_time" from application left join "user" on user_id = "user".id where application."id" in (select target from workspace_user_resource_permission @@ -28,6 +29,7 @@ from (select application."id"::text, application_folder."user_id", "user"."nick_name" as "nick_name", application_folder."create_time", - application_folder."update_time" + application_folder."update_time", + null as "publish_time" from application_folder left join "user" on user_id = "user".id ${folder_query_set}) temp ${application_query_set} \ No newline at end of file diff --git a/apps/application/sql/list_application_user_ee.sql b/apps/application/sql/list_application_user_ee.sql index d6a653cb..fe44b1fa 100644 --- a/apps/application/sql/list_application_user_ee.sql +++ b/apps/application/sql/list_application_user_ee.sql @@ -10,7 +10,8 @@ from (select application."id"::text, application."user_id", "user"."nick_name" as "nick_name", application."create_time", - application."update_time" + application."update_time", + application."publish_time" from application left join "user" on user_id = "user".id where "application".id in (select target from workspace_user_resource_permission @@ -41,6 +42,7 @@ from (select application."id"::text, application_folder."user_id", "user"."nick_name" as "nick_name", application_folder."create_time", - application_folder."update_time" + application_folder."update_time", + null as "publish_time" from application_folder left join "user" on user_id = "user".id ${folder_query_set}) temp ${application_query_set} \ No newline at end of file diff --git a/apps/application/urls.py b/apps/application/urls.py index b27c0acd..0a4e44d9 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -15,7 +15,7 @@ urlpatterns = [ path('workspace//application//application_stats', views.ApplicationStats.as_view()), path('workspace//application//application_key/', views.ApplicationKey.Operate.as_view()), path('workspace//application//export', views.ApplicationAPI.Export.as_view()), - path('workspace//application//work_flow_version', views.ApplicationVersionView.as_view()), + path('workspace//application//application_version', views.ApplicationVersionView.as_view()), path('workspace//application//access_token', views.AccessToken.as_view()), path('workspace//application//add_knowledge', views.ApplicationChatRecordAddKnowledge.as_view()), path('workspace//application//chat', views.ApplicationChat.as_view()), @@ -27,8 +27,8 @@ urlpatterns = [ path('workspace//application//chat//chat_record//improve', views.ApplicationChatRecordImprove.as_view()), path('workspace//application//chat//chat_record//knowledge//document//improve', views.ApplicationChatRecordImproveParagraph.as_view()), path('workspace//application//chat//chat_record//knowledge//document//paragraph//improve', views.ApplicationChatRecordImproveParagraph.Operate.as_view()), - path('workspace//application//work_flow_version//', views.ApplicationVersionView.Page.as_view()), - path('workspace//application//work_flow_version/', views.ApplicationVersionView.Operate.as_view()), + path('workspace//application//application_version//', views.ApplicationVersionView.Page.as_view()), + path('workspace//application//application_version/', views.ApplicationVersionView.Operate.as_view()), path('workspace//application//open', views.OpenView.as_view()), path('workspace//application//text_to_speech', views.TextToSpeech.as_view()), path('workspace//application//speech_to_text', views.SpeechToText.as_view()), diff --git a/apps/application/views/application.py b/apps/application/views/application.py index a1b20e34..8b032bf6 100644 --- a/apps/application/views/application.py +++ b/apps/application/views/application.py @@ -132,7 +132,8 @@ class ApplicationAPI(APIView): @has_permissions(PermissionConstants.APPLICATION_EXPORT.get_workspace_application_permission(), PermissionConstants.APPLICATION_EXPORT.get_workspace_permission_workspace_manage_role(), ViewPermission([RoleConstants.USER.get_workspace_role()], - [PermissionConstants.APPLICATION.get_workspace_application_permission()],CompareConstants.AND), + [PermissionConstants.APPLICATION.get_workspace_application_permission()], + CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) @log(menu='Application', operate="Export Application", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), @@ -227,7 +228,7 @@ class ApplicationAPI(APIView): summary=_("Publishing an application"), operation_id=_("Publishing an application"), # type: ignore parameters=ApplicationOperateAPI.get_parameters(), - request=ApplicationEditAPI.get_request(), + request=None, responses=result.DefaultResultSerializer, tags=[_('Application')] # type: ignore ) diff --git a/apps/application/views/application_version.py b/apps/application/views/application_version.py index 248a1c68..3a87a153 100644 --- a/apps/application/views/application_version.py +++ b/apps/application/views/application_version.py @@ -89,11 +89,11 @@ class ApplicationVersionView(APIView): [PermissionConstants.APPLICATION.get_workspace_application_permission()], CompareConstants.AND), RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) - def get(self, request: Request, workspace_id: str, application_id: str, work_flow_version_id: str): + def get(self, request: Request, workspace_id: str, application_id: str, application_version_id: str): return result.success( ApplicationVersionSerializer.Operate( data={'user_id': request.user, 'workspace_id': workspace_id, - 'application_id': application_id, 'work_flow_version_id': work_flow_version_id}).one()) + 'application_id': application_id, 'application_version_id': application_version_id}).one()) @extend_schema( methods=['PUT'], @@ -114,10 +114,10 @@ class ApplicationVersionView(APIView): @log(menu='Application', operate="Modify application version information", get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')), ) - def put(self, request: Request, workspace_id: str, application_id: str, work_flow_version_id: str): + def put(self, request: Request, workspace_id: str, application_id: str, application_version_id: str): return result.success( ApplicationVersionSerializer.Operate( data={'application_id': application_id, 'workspace_id': workspace_id, - 'work_flow_version_id': work_flow_version_id, + 'application_version_id': application_version_id, 'user_id': request.user.id}).edit( request.data)) diff --git a/apps/chat/serializers/chat.py b/apps/chat/serializers/chat.py index 4ca06651..f0b5bff3 100644 --- a/apps/chat/serializers/chat.py +++ b/apps/chat/serializers/chat.py @@ -25,8 +25,8 @@ from application.chat_pipeline.step.search_dataset_step.impl.base_search_dataset from application.flow.common import Answer, Workflow from application.flow.i_step_node import WorkFlowPostHandler from application.flow.workflow_manage import WorkflowManage -from application.models import Application, ApplicationTypeChoices, WorkFlowVersion, ApplicationKnowledgeMapping, \ - ChatUserType, ApplicationChatUserStats, ApplicationAccessToken, ChatRecord, Chat +from application.models import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \ + ChatUserType, ApplicationChatUserStats, ApplicationAccessToken, ChatRecord, Chat, ApplicationVersion from application.serializers.application import ApplicationOperateSerializer from application.serializers.common import ChatInfo from common.exception.app_exception import AppApiException, AppChatNumOutOfBoundsFailed, ChatException @@ -124,7 +124,7 @@ class ChatSerializers(serializers.Serializer): def is_valid_chat_id(self, chat_info: ChatInfo): if self.data.get('application_id') is not None and self.data.get('application_id') != str( - chat_info.application.id): + chat_info.application_id): raise ChatException(500, _("Conversation does not exist")) def is_valid_intraday_access_num(self): @@ -148,10 +148,10 @@ class ChatSerializers(serializers.Serializer): def is_valid_application_simple(self, *, chat_info: ChatInfo, raise_exception=False): self.is_valid_intraday_access_num() - model = chat_info.application.model - if model is None: + model_id = chat_info.application.model_id + if model_id is None: return chat_info - model = QuerySet(Model).filter(id=model.id).first() + model = QuerySet(Model).filter(id=model_id).first() if model is None: return chat_info if model.status == Status.ERROR: @@ -226,10 +226,7 @@ class ChatSerializers(serializers.Serializer): if chat_record_id is not None: chat_record = self.get_chat_record(chat_info, chat_record_id) history_chat_record = [r for r in chat_info.chat_record_list if str(r.id) != chat_record_id] - if not debug: - work_flow = chat_info.work_flow_version.work_flow - else: - work_flow = chat_info.application.work_flow + work_flow = chat_info.application.work_flow work_flow_manage = WorkflowManage(Workflow.new_instance(work_flow), {'history_chat_record': history_chat_record, 'question': message, 'chat_id': chat_info.chat_id, 'chat_record_id': str( @@ -252,6 +249,7 @@ class ChatSerializers(serializers.Serializer): super().is_valid(raise_exception=True) ChatMessageSerializers(data=instance).is_valid(raise_exception=True) chat_info = self.get_chat_info() + chat_info.get_application() self.is_valid_chat_id(chat_info) if chat_info.application.type == ApplicationTypeChoices.SIMPLE: self.is_valid_application_simple(raise_exception=True, chat_info=chat_info) @@ -301,14 +299,13 @@ class ChatSerializers(serializers.Serializer): return chat_info def re_open_chat_work_flow(self, chat_id, application): - work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application.id).order_by( + application_version = QuerySet(ApplicationVersion).filter(application_id=application.id).order_by( '-create_time')[0:1].first() - if work_flow_version is None: + if application_version is None: raise ChatException(500, _("The application has not been published. Please use it after publishing.")) chat_info = ChatInfo(chat_id, self.data.get('chat_user_id'), self.data.get('chat_user_type'), [], [], - application.id, - application, work_flow_version) + application.id) chat_record_list = list(QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')[0:5]) chat_record_list.sort(key=lambda r: r.create_time) for chat_record in chat_record_list: @@ -349,18 +346,16 @@ class OpenChatSerializers(serializers.Serializer): chat_user_type = self.data.get("chat_user_type") debug = self.data.get("debug") chat_id = str(uuid.uuid7()) - work_flow_version = None if not debug: - work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application_id).order_by( + application_version = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by( '-create_time')[0:1].first() - if work_flow_version is None: + if application_version is None: raise AppApiException(500, gettext( "The application has not been published. Please use it after publishing.")) ChatInfo(chat_id, chat_user_id, chat_user_type, [], [], - application_id, - application, work_flow_version, debug).set_cache() + application_id, debug).set_cache() return chat_id def open_simple(self, application): @@ -378,7 +373,7 @@ class OpenChatSerializers(serializers.Serializer): knowledge_id__in=knowledge_id_list, is_active=False)], application_id, - application, debug=debug).set_cache() + debug=debug).set_cache() return chat_id diff --git a/apps/chat/serializers/chat_authentication.py b/apps/chat/serializers/chat_authentication.py index f12f4843..b16fda35 100644 --- a/apps/chat/serializers/chat_authentication.py +++ b/apps/chat/serializers/chat_authentication.py @@ -15,7 +15,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationTypeChoices, \ - WorkFlowVersion + ApplicationVersion from application.serializers.application import ApplicationSerializerModel from common.auth.common import ChatUserToken, ChatAuthentication from common.constants.authentication_type import AuthenticationType @@ -79,6 +79,25 @@ class AuthProfileSerializer(serializers.Serializer): class ApplicationProfileSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, label=_("Application ID")) + @staticmethod + def reset_application(application, application_version): + update_field_dict = { + 'application_name': 'name', 'desc': 'desc', 'prologue': 'prologue', 'dialogue_number': 'dialogue_number', + 'user_id': 'user_id', 'model_id': 'model_id', 'knowledge_setting': 'knowledge_setting', + 'model_setting': 'model_setting', 'model_params_setting': 'model_params_setting', + 'tts_model_params_setting': 'tts_model_params_setting', + 'problem_optimization': 'problem_optimization', 'icon': 'icon', 'work_flow': 'work_flow', + 'problem_optimization_prompt': 'problem_optimization_prompt', 'tts_model_id': 'tts_model_id', + 'stt_model_id': 'stt_model_id', 'tts_model_enable': 'tts_model_enable', + 'stt_model_enable': 'stt_model_enable', 'tts_type': 'tts_type', + 'tts_autoplay': 'tts_autoplay', 'stt_autosend': 'stt_autosend', 'file_upload_enable': 'file_upload_enable', + 'file_upload_setting': 'file_upload_setting' + } + for (version_field, app_field) in update_field_dict.items(): + _v = getattr(application_version, version_field) + if _v: + setattr(application, app_field, _v) + def profile(self, with_valid=True): if with_valid: self.is_valid() @@ -88,12 +107,10 @@ class ApplicationProfileSerializer(serializers.Serializer): if application_access_token is None: raise AppUnauthorizedFailed(500, _("Illegal User")) application_setting_model = DatabaseModelManage.get_model('application_setting') - if application.type == ApplicationTypeChoices.WORK_FLOW: - work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application.id).order_by( - '-create_time')[0:1].first() - if work_flow_version is not None: - application.work_flow = work_flow_version.work_flow - + application_version = QuerySet(ApplicationVersion).filter(application_id=application.id).order_by( + '-create_time')[0:1].first() + if application_version is not None: + self.reset_application(application, application_version) license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'), version=Cache_Version.SYSTEM.get_version()) application_setting_dict = {} diff --git a/ui/src/api/application/workflow-version.ts b/ui/src/api/application/workflow-version.ts index aa6d46a3..d644d1f0 100644 --- a/ui/src/api/application/workflow-version.ts +++ b/ui/src/api/application/workflow-version.ts @@ -18,7 +18,7 @@ const getWorkFlowVersion: ( application_id: string, loading?: Ref, ) => Promise> = (application_id, loading) => { - return get(`${prefix.value}/${application_id}/work_flow_version`, undefined, loading) + return get(`${prefix.value}/${application_id}/application_version`, undefined, loading) } /** @@ -30,7 +30,7 @@ const getWorkFlowVersionDetail: ( loading?: Ref, ) => Promise> = (application_id, application_version_id, loading) => { return get( - `${prefix.value}/${application_id}/work_flow_version/${application_version_id}`, + `${prefix.value}/${application_id}/application_version/${application_version_id}`, undefined, loading, ) @@ -45,7 +45,7 @@ const putWorkFlowVersion: ( loading?: Ref, ) => Promise> = (application_id, application_version_id, data, loading) => { return put( - `${prefix.value}/${application_id}/work_flow_version/${application_version_id}`, + `${prefix.value}/${application_id}/application_version/${application_version_id}`, data, undefined, loading, diff --git a/ui/src/locales/lang/zh-CN/views/application.ts b/ui/src/locales/lang/zh-CN/views/application.ts index b55771bd..b577dbf3 100644 --- a/ui/src/locales/lang/zh-CN/views/application.ts +++ b/ui/src/locales/lang/zh-CN/views/application.ts @@ -114,7 +114,8 @@ export default { }, }, buttons: { - publish: '保存并发布', + save: '保存', + publish: '发布', addModel: '添加模型', }, diff --git a/ui/src/views/application-workflow/index.vue b/ui/src/views/application-workflow/index.vue index d86c3372..26926a5b 100644 --- a/ui/src/views/application-workflow/index.vue +++ b/ui/src/views/application-workflow/index.vue @@ -281,7 +281,10 @@ const publish = () => { return } applicationApi - .publish(id, { work_flow: workflow }, loading) + .putApplication(id, { work_flow: workflow }, loading) + .then((ok) => { + return applicationApi.publish(id, {}, loading) + }) .then((ok: any) => { detail.value.name = ok.data.name MsgSuccess(t('views.applicationWorkflow.tip.publicSuccess')) diff --git a/ui/src/views/application/ApplicationSetting.vue b/ui/src/views/application/ApplicationSetting.vue index 797c490c..1af9b563 100644 --- a/ui/src/views/application/ApplicationSetting.vue +++ b/ui/src/views/application/ApplicationSetting.vue @@ -4,14 +4,24 @@

{{ $t('common.setting') }}

- - {{ $t('views.application.buttons.publish') }} - +
+ + {{ $t('views.application.buttons.save') }} + + + {{ $t('views.application.buttons.publish') }} + +
@@ -444,7 +454,7 @@ import { t } from '@/locales' import TTSModeParamSettingDialog from './component/TTSModeParamSettingDialog.vue' import ReasoningParamSettingDialog from './component/ReasoningParamSettingDialog.vue' import permissionMap from '@/permission' - +import ApplicationAPI from '@/api/application/application' const route = useRoute() const apiType = computed<'workspace'>(() => { @@ -549,7 +559,18 @@ function submitReasoningDialog(val: any) { ...val, } } - +const publish = (formEl: FormInstance | undefined) => { + if (!formEl) return + formEl.validate().then(() => { + return ApplicationAPI.putApplication(id, applicationForm.value, loading) + .then((ok) => { + return ApplicationAPI.publish(id, {}, loading) + }) + .then((res) => { + MsgSuccess(t('common.saveSuccess')) + }) + }) +} const submit = async (formEl: FormInstance | undefined) => { if (!formEl) return await formEl.validate((valid, fields) => {