diff --git a/apps/application/api/application_chat.py b/apps/application/api/application_chat.py index e8e57c65362..5ace76513aa 100644 --- a/apps/application/api/application_chat.py +++ b/apps/application/api/application_chat.py @@ -1,17 +1,21 @@ # coding=utf-8 """ - @project: MaxKB - @Author:虎虎 - @file: application_chat.py - @date:2025/6/10 13:54 - @desc: +@project: MaxKB +@Author:虎虎 +@file: application_chat.py +@date:2025/6/10 13:54 +@desc: """ + from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter -from application.serializers.application_chat import ApplicationChatQuerySerializers, \ - ApplicationChatResponseSerializers, ApplicationChatRecordExportRequest +from application.serializers.application_chat import ( + ApplicationChatQuerySerializers, + ApplicationChatResponseSerializers, + ApplicationChatRecordExportRequest, +) from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, ResultPageSerializer @@ -38,16 +42,17 @@ def get_parameters(): name="workspace_id", description="工作空间id", type=OpenApiTypes.STR, - location='path', + location="path", required=True, ), OpenApiParameter( name="application_id", description="application ID", type=OpenApiTypes.STR, - location='path', + location="path", required=True, - ), OpenApiParameter( + ), + OpenApiParameter( name="start_time", description="start Time", type=OpenApiTypes.STR, @@ -71,6 +76,12 @@ def get_parameters(): type=OpenApiTypes.STR, required=False, ), + OpenApiParameter( + name="source", + description="source", + type=OpenApiTypes.STR, + required=False, + ), OpenApiParameter( name="min_star", description=_("Minimum number of likes"), @@ -109,17 +120,16 @@ def get_parameters(): name="current_page", description=_("Current page"), type=OpenApiTypes.INT, - location='path', + location="path", required=True, ), OpenApiParameter( name="page_size", description=_("Page size"), type=OpenApiTypes.INT, - location='path', + location="path", required=True, ), - ] @staticmethod diff --git a/apps/application/serializers/application.py b/apps/application/serializers/application.py index e0edf3e8a70..e78a21f5d12 100644 --- a/apps/application/serializers/application.py +++ b/apps/application/serializers/application.py @@ -547,6 +547,16 @@ class ApplicationQueryRequest(serializers.Serializer): label=_("Publish status"), choices=[("published", _("Published")), ("unpublished", _("Unpublished"))], ) + status = serializers.ChoiceField( + required=False, + label=_("Publish status"), + choices=[ + ("published", _("Published")), + ("unpublished", _("Unpublished")), + ("on_portal", _("On portal")), + ], + ) + type = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Application type")) user_id = serializers.UUIDField(required=False, label=_("User ID")) create_user = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("create user")) @@ -584,6 +594,14 @@ def get_query_set(self, instance: Dict, workspace_manage: bool, is_x_pack_ee: bo if publish_status is not None: is_publish = True if publish_status == "published" else False application_query_set = application_query_set.filter(is_publish=is_publish) + status = instance.get("status") + if status is not None: + if status == "unpublished": + application_query_set = application_query_set.filter(is_publish=False) + elif status == "published": + application_query_set = application_query_set.filter(is_publish=True) + elif status == "on_portal": + application_query_set = application_query_set.filter(is_portal=True) if workspace_id is not None: folder_query_set = folder_query_set.filter(workspace_id=workspace_id) application_query_set = application_query_set.filter(workspace_id=workspace_id) @@ -599,6 +617,9 @@ def get_query_set(self, instance: Dict, workspace_manage: bool, is_x_pack_ee: bo application_query_set = application_query_set.filter(desc__contains=desc) if create_user is not None: application_query_set = application_query_set.filter(user_id=create_user) + application_type = instance.get("type") + if application_type: + application_query_set = application_query_set.filter(type=application_type) application_custom_sql_query_set = application_query_set application_query_set = application_query_set.order_by("-create_time") diff --git a/apps/application/serializers/application_chat.py b/apps/application/serializers/application_chat.py index 0236b178eb5..28f01096796 100644 --- a/apps/application/serializers/application_chat.py +++ b/apps/application/serializers/application_chat.py @@ -58,6 +58,7 @@ class ApplicationChatQuerySerializers(serializers.Serializer): start_time = serializers.DateField(format="%Y-%m-%d", label=_("Start time")) end_time = serializers.DateField(format="%Y-%m-%d", label=_("End time")) application_id = serializers.UUIDField(required=True, label=_("Application ID")) + source = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("Source")) min_star = serializers.IntegerField(required=False, min_value=0, label=_("Minimum number of likes")) min_trample = serializers.IntegerField(required=False, min_value=0, label=_("Minimum number of clicks")) comparer = serializers.CharField( @@ -96,6 +97,7 @@ def get_query_set(self, select_ids=None): "application_chat.application_id": models.CharField(), "application_chat.abstract": models.CharField(), "application_chat.asker": models.JSONField(), + "application_chat.source": models.JSONField(), "star_num": models.IntegerField(), "trample_num": models.IntegerField(), "comparer": models.CharField(), @@ -113,6 +115,8 @@ def get_query_set(self, select_ids=None): } if "abstract" in self.data and self.data.get("abstract") is not None: base_query_dict["application_chat.abstract__icontains"] = self.data.get("abstract") + if "source" in self.data and self.data.get("source") is not None: + base_query_dict["application_chat.source__type"] = self.data.get("source") if select_ids is not None and len(select_ids) > 0: base_query_dict["application_chat.id__in"] = select_ids base_condition = Q(**base_query_dict) diff --git a/apps/knowledge/serializers/knowledge.py b/apps/knowledge/serializers/knowledge.py index 0ab20ed9a6b..93e1c37cdd4 100644 --- a/apps/knowledge/serializers/knowledge.py +++ b/apps/knowledge/serializers/knowledge.py @@ -231,6 +231,7 @@ class Query(serializers.Serializer): max_length=256, min_length=1, ) + type = serializers.IntegerField(required=False, label=_("knowledge type"), allow_null=True) user_id = serializers.UUIDField(required=False, label=_("user id"), allow_null=True) scope = serializers.CharField(required=False, label=_("knowledge scope"), allow_null=True) create_user = serializers.UUIDField(required=False, label=_("create user"), allow_null=True) @@ -250,6 +251,7 @@ def get_query_set(self, workspace_manage, is_x_pack_ee): { "temp.name": models.CharField(), "temp.desc": models.CharField(), + "temp.type": models.IntegerField(), "document_temp.char_length": models.IntegerField(), "temp.create_time": models.DateTimeField(), "temp.user_id": models.CharField(), @@ -268,6 +270,8 @@ def get_query_set(self, workspace_manage, is_x_pack_ee): if "name" in self.data and self.data.get("name") is not None: query_set = query_set.filter(**{"temp.name__icontains": self.data.get("name")}) folder_query_set = folder_query_set.filter(**{"name__icontains": self.data.get("name")}) + if "type" in self.data and self.data.get("type") is not None: + query_set = query_set.filter(**{"temp.type": self.data.get("type")}) if "workspace_id" in self.data and self.data.get("workspace_id") is not None: query_set = query_set.filter(**{"temp.workspace_id": self.data.get("workspace_id")}) folder_query_set = folder_query_set.filter(**{"workspace_id": self.data.get("workspace_id")}) diff --git a/apps/knowledge/views/knowledge.py b/apps/knowledge/views/knowledge.py index 3dc79210ec2..19b6668982d 100644 --- a/apps/knowledge/views/knowledge.py +++ b/apps/knowledge/views/knowledge.py @@ -301,6 +301,7 @@ def get(self, request: Request, workspace_id: str, current_page: int, page_size: "folder_id": request.query_params.get("folder_id"), "name": request.query_params.get("name"), "desc": request.query_params.get("desc"), + "type": request.query_params.get("type"), "scope": KnowledgeScope.WORKSPACE, "user_id": request.user.id, "create_user": request.query_params.get("create_user"), diff --git a/apps/tools/serializers/tool.py b/apps/tools/serializers/tool.py index 095170ea508..946f70be341 100644 --- a/apps/tools/serializers/tool.py +++ b/apps/tools/serializers/tool.py @@ -623,7 +623,7 @@ def convert_value(name: str, value: str, _type: str, is_required: bool): return None try: return common_convert_value(_type, value) - except Exception as e: + except Exception: raise AppApiException( 500, _("Field: {name} Type: {type} Value: {value} Type conversion error").format( @@ -1087,7 +1087,7 @@ def update_template_workflow(self, tool_id: str): tool_instance_bytes = self.data.get("file").read() try: tool_instance = RestrictedUnpickler(io.BytesIO(tool_instance_bytes)).load() - except Exception as e: + except Exception: raise AppApiException(1001, _("Unsupported file format")) tool = tool_instance.tool tool["id"] = tool_id @@ -1109,7 +1109,7 @@ def import_(self, scope=ToolScope.WORKSPACE, name=None, source=None): tool_instance_bytes = self.data.get("file").read() try: tool_instance = RestrictedUnpickler(io.BytesIO(tool_instance_bytes)).load() - except Exception as e: + except Exception: raise AppApiException(1001, _("Unsupported file format")) if self.data.get("folder_id") is None: folder_id = self.data.get("workspace_id") @@ -1731,6 +1731,7 @@ class Query(serializers.Serializer): child=serializers.CharField(), required=False, label=_("tool type list"), allow_null=True, allow_empty=True ) create_user = serializers.UUIDField(required=False, label=_("create user"), allow_null=True) + is_active = serializers.BooleanField(required=False, label=_("is active"), allow_null=True) def page_tool(self, current_page: int, page_size: int): self.is_valid(raise_exception=True) @@ -1792,6 +1793,10 @@ def get_query_set(self, workspace_manage, is_x_pack_ee): if scope is not None: tool_query_set = tool_query_set.filter(scope=scope) + is_active = self.data.get("is_active") + if is_active is not None: + tool_query_set = tool_query_set.filter(is_active=is_active) + tool_type_list = self.data.get("tool_type_list") if tool_type_list: tool_query_set = tool_query_set.filter(tool_type__in=tool_type_list) diff --git a/apps/tools/views/tool.py b/apps/tools/views/tool.py index 346ee7eeb31..3417c94335e 100644 --- a/apps/tools/views/tool.py +++ b/apps/tools/views/tool.py @@ -347,6 +347,7 @@ def get(self, request: Request, workspace_id: str, current_page: int, page_size: "tool_type": request.query_params.get("tool_type"), "user_id": request.user.id, "create_user": request.query_params.get("create_user"), + "is_active": request.query_params.get("is_active"), } ).page_tool_with_folders(current_page, page_size) ) diff --git a/ui/src/components/business/related-resources-drawer/index.vue b/ui/src/components/business/related-resources-drawer/index.vue index 80025286c12..ae7cbb46d0f 100644 --- a/ui/src/components/business/related-resources-drawer/index.vue +++ b/ui/src/components/business/related-resources-drawer/index.vue @@ -33,7 +33,6 @@ const resourceTypeOptions = Object.entries(resourceLabels).map(([value, label]) const searchFields = [ { label: '名称', value: 'resource_name' }, { label: '创建者', value: 'user_name' }, - { label: '类型', value: 'resource_type', multiple: true, options: resourceTypeOptions }, ] const relationTabs = [ { value: 'dependency', label: '依赖' }, @@ -53,12 +52,13 @@ const resourceRows = computed(() => ) const pagination = ref({ currentPage: 1, pageSize: 20, total: 0 }) const searchQuery = ref>({}) +const selectedResourceType = ref(null) function loadResources() { if (!visible.value || !targetResource.value) return const isDependency = relationDirection.value === 'dependency' - const { resource_type, ...query } = searchQuery.value - if (resource_type) query[isDependency ? 'target_type' : 'source_type'] = resource_type + const query: Dict = { ...searchQuery.value } + if (selectedResourceType.value) query[isDependency ? 'target_type' : 'source_type'] = [selectedResourceType.value] if (showWorkspace.value && selectedWorkspaceIds.value.length) query.workspace_ids = JSON.stringify(selectedWorkspaceIds.value) const request = isDependency ? props.api.getUsingResources : props.api.getUsedByResources loading.value = true @@ -80,16 +80,17 @@ function searchResources(query?: Dict) { function changeDirection() { searchQuery.value = {} + selectedResourceType.value = null selectedWorkspaceIds.value = [] relatedResources.value = [] pagination.value = { currentPage: 1, pageSize: 20, total: 0 } loadResources() } -/* 工作空间筛选:确认或重置后从第一页查询。 */ +/* 表头筛选:确认或重置后从第一页查询。 */ const workspaceOptions = ref[]>([]) const selectedWorkspaceIds = ref([]) -function handleWorkspaceFilterChange() { +function handleFilterChange() { pagination.value.currentPage = 1 loadResources() } @@ -130,6 +131,7 @@ function resetData() { relatedResources.value = [] pagination.value = { currentPage: 1, pageSize: 20, total: 0 } searchQuery.value = {} + selectedResourceType.value = null relationDirection.value = 'dependency' workspaceOptions.value = [] selectedWorkspaceIds.value = [] @@ -183,6 +185,9 @@ defineExpose({ open, close }) + @@ -193,7 +198,7 @@ defineExpose({ open, close }) v-model="selectedWorkspaceIds" label="工作空间" :options="workspaceOptions" - @change="handleWorkspaceFilterChange" + @change="handleFilterChange" /> diff --git a/ui/src/views/application-detail/chat-log/index.vue b/ui/src/views/application-detail/chat-log/index.vue index 4890adaced9..225be333af1 100644 --- a/ui/src/views/application-detail/chat-log/index.vue +++ b/ui/src/views/application-detail/chat-log/index.vue @@ -25,9 +25,11 @@ const { application } = useApplicationDetailContext() const loading = ref(false) const chatLogs = ref([]) const paginationConfig = ref({ currentPage: 1, pageSize: 20, total: 0 }) +const sourceOptions: OptionItem[] = Object.entries(CHAT_LOG_SOURCE_LABELS).map(([value, label]) => ({ value, label })) const searchFields: OptionItem[] = [ { label: '摘要', value: 'abstract' }, { label: '用户', value: 'username' }, + { label: '来源', value: 'source', options: sourceOptions }, ] const chatLogSearchQuery = ref>() const chatLogDateQuery = ref({ start_time: beforeDay(7), end_time: beforeDay(0) }) @@ -44,9 +46,12 @@ function handleDateFilterChange({ startTime, endTime }: MkDateRangeValue) { return loadChatLogs() } -// 反馈筛选的草稿、重置和确认由 MkTableFilter 管理 -const feedbackResetValue = { min_star: 0, min_trample: 0 } -const feedbackFilter = ref({ ...feedbackResetValue }) +// 表头用户反馈筛选,单选复用 min_star/min_trample 参数 +const selectedFeedback = ref(null) +const feedbackOptions: OptionItem[] = [ + { label: '赞同', value: 'star' }, + { label: '反对', value: 'trample' }, +] function handleFeedbackChange() { paginationConfig.value.currentPage = 1 @@ -56,7 +61,8 @@ function handleFeedbackChange() { const chatLogQuery = computed>(() => ({ ...chatLogSearchQuery.value, ...chatLogDateQuery.value, - ...feedbackFilter.value, + ...(selectedFeedback.value === 'star' ? { min_star: 1 } : {}), + ...(selectedFeedback.value === 'trample' ? { min_trample: 1 } : {}), comparer: 'and', })) @@ -184,45 +190,7 @@ onMounted(() => loadChatLogs()) + + diff --git a/ui/src/views/workflow/tool/execution-record/ExecutionRecordDrawer.vue b/ui/src/views/workflow/tool/execution-record/ExecutionRecordDrawer.vue index b69e0239a5b..fb7eadfe852 100644 --- a/ui/src/views/workflow/tool/execution-record/ExecutionRecordDrawer.vue +++ b/ui/src/views/workflow/tool/execution-record/ExecutionRecordDrawer.vue @@ -16,33 +16,30 @@ const visible = ref(false) const loading = ref(false) const recordData = ref([]) const pagination = ref({ currentPage: 1, pageSize: 20, total: 0 }) -const searchQuery = ref>() +const searchInput = ref>({}) const sourceLabels = { [TOOL_RECORD_SOURCE.APPLICATION]: '智能体', [TOOL_RECORD_SOURCE.KNOWLEDGE]: '知识库', [TOOL_RECORD_SOURCE.TRIGGER]: '触发器', } -const searchFields = [ - { label: '触发来源', value: 'source_name' }, - { - label: '状态', - value: 'state', - options: [ - { label: EXECUTION_STATUS_OPTIONS[STATE_TYPES.SUCCESS].label, value: STATE_TYPES.SUCCESS }, - { label: EXECUTION_STATUS_OPTIONS[STATE_TYPES.STARTED].label, value: STATE_TYPES.STARTED }, - { label: EXECUTION_STATUS_OPTIONS[STATE_TYPES.FAILURE].label, value: STATE_TYPES.FAILURE }, - ], - }, - { - label: '资源类型', - value: 'source_type', - options: [ - { label: sourceLabels[TOOL_RECORD_SOURCE.APPLICATION], value: TOOL_RECORD_SOURCE.APPLICATION }, - { label: sourceLabels[TOOL_RECORD_SOURCE.KNOWLEDGE], value: TOOL_RECORD_SOURCE.KNOWLEDGE }, - { label: sourceLabels[TOOL_RECORD_SOURCE.TRIGGER], value: TOOL_RECORD_SOURCE.TRIGGER }, - ], - }, +const sourceTypeOptions = [ + { label: sourceLabels[TOOL_RECORD_SOURCE.APPLICATION], value: TOOL_RECORD_SOURCE.APPLICATION }, + { label: sourceLabels[TOOL_RECORD_SOURCE.KNOWLEDGE], value: TOOL_RECORD_SOURCE.KNOWLEDGE }, + { label: sourceLabels[TOOL_RECORD_SOURCE.TRIGGER], value: TOOL_RECORD_SOURCE.TRIGGER }, ] +const stateOptions = [ + { label: EXECUTION_STATUS_OPTIONS[STATE_TYPES.SUCCESS].label, value: STATE_TYPES.SUCCESS }, + { label: EXECUTION_STATUS_OPTIONS[STATE_TYPES.STARTED].label, value: STATE_TYPES.STARTED }, + { label: EXECUTION_STATUS_OPTIONS[STATE_TYPES.FAILURE].label, value: STATE_TYPES.FAILURE }, +] +const searchFields = [{ label: '触发来源', value: 'source_name' }] +const selectedSourceType = ref(null) +const selectedState = ref(null) +const searchQuery = computed>(() => ({ + ...searchInput.value, + ...(selectedSourceType.value ? { source_type: selectedSourceType.value } : {}), + ...(selectedState.value ? { state: selectedState.value } : {}), +})) function loadRecords(page = pagination.value.currentPage): Promise { loading.value = true return props.api @@ -58,13 +55,20 @@ function loadRecords(page = pagination.value.currentPage): Promise) { - searchQuery.value = query ?? {} + searchInput.value = query ?? {} + pagination.value.currentPage = 1 + loadRecords() +} + +function handleFilterChange() { pagination.value.currentPage = 1 loadRecords() } function open() { - searchQuery.value = undefined + searchInput.value = {} + selectedSourceType.value = null + selectedState.value = null closeDetails() recordData.value = [] pagination.value = { currentPage: 1, pageSize: 20, total: 0 } @@ -140,9 +144,27 @@ defineExpose({ open }) + +