Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions apps/knowledge/serializers/knowledge_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from application.flow.knowledge_workflow_manage import KnowledgeWorkflowManage
from application.flow.step_node import get_node
from application.serializers.application import get_mcp_tools
from common.db.search import page_search
from common.exception.app_exception import AppApiException
from common.utils.rsa_util import rsa_long_decrypt
from common.utils.tool_code import ToolExecutor
Expand All @@ -41,16 +42,46 @@ class KnowledgeWorkflowActionRequestSerializer(serializers.Serializer):
knowledge_base = serializers.DictField(required=True, label=_('knowledge base data'))


class KnowledgeWorkflowActionListQuerySerializer(serializers.Serializer):
user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True)


class KnowledgeWorkflowActionSerializer(serializers.Serializer):
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))

def action(self, instance: Dict, with_valid=True):
def get_query_set(self, instance: Dict):
query_set = QuerySet(KnowledgeAction).filter(knowledge_id=self.data.get('knowledge_id'))
if instance.get("user_name"):
query_set = query_set.filter(meta__user_name__icontains=instance.get('user_name'))
return query_set.order_by('-create_time')

def list(self, instance: Dict, is_valid=True):
if is_valid:
self.is_valid(raise_exception=True)
KnowledgeWorkflowActionListQuerySerializer(data=instance).is_valid(raise_exception=True)
return [{'id': a.id, 'knowledge_id': a.knowledge_id, 'state': a.state,
'details': a.details, 'meta': a.meta, 'run_time': a.run_time} for a in self.get_query_set(instance)]

def page(self, current_page, page_size, instance: Dict, is_valid=True):
if is_valid:
self.is_valid(raise_exception=True)
KnowledgeWorkflowActionListQuerySerializer(data=instance).is_valid(raise_exception=True)
return page_search(current_page, page_size, self.get_query_set(instance),
lambda a: {'id': a.id, 'knowledge_id': a.knowledge_id, 'state': a.state,
'details': a.details, 'meta': a.meta, 'run_time': a.run_time})

def action(self, instance: Dict, user, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get("knowledge_id")).first()
knowledge_action_id = uuid.uuid7()
KnowledgeAction(id=knowledge_action_id, knowledge_id=self.data.get("knowledge_id"), state=State.STARTED).save()
meta = {'user_id': str(user.id),
'user_name': user.username}
KnowledgeAction(id=knowledge_action_id,
knowledge_id=self.data.get("knowledge_id"),
state=State.STARTED,
meta=meta).save()
work_flow_manage = KnowledgeWorkflowManage(
Workflow.new_instance(knowledge_workflow.work_flow, WorkflowMode.KNOWLEDGE),
{'knowledge_id': self.data.get("knowledge_id"), 'knowledge_action_id': knowledge_action_id, 'stream': True,
Expand All @@ -59,9 +90,9 @@ def action(self, instance: Dict, with_valid=True):
KnowledgeWorkflowPostHandler(None, knowledge_action_id))
work_flow_manage.run()
return {'id': knowledge_action_id, 'knowledge_id': self.data.get("knowledge_id"), 'state': State.STARTED,
'details': {}}
'details': {}, 'meta': meta}

def upload_document(self, instance: Dict, with_valid=True):
def upload_document(self, instance: Dict, user, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get("knowledge_id")).first()
Expand All @@ -71,7 +102,10 @@ def upload_document(self, instance: Dict, with_valid=True):
knowledge_id=self.data.get("knowledge_id")).order_by(
'-create_time')[0:1].first()
knowledge_action_id = uuid.uuid7()
KnowledgeAction(id=knowledge_action_id, knowledge_id=self.data.get("knowledge_id"), state=State.STARTED).save()
meta = {'user_id': str(user.id),
'user_name': user.username}
KnowledgeAction(id=knowledge_action_id, knowledge_id=self.data.get("knowledge_id"), state=State.STARTED,
meta=meta).save()
work_flow_manage = KnowledgeWorkflowManage(
Workflow.new_instance(knowledge_workflow_version.work_flow, WorkflowMode.KNOWLEDGE),
{'knowledge_id': self.data.get("knowledge_id"), 'knowledge_action_id': knowledge_action_id, 'stream': True,
Expand All @@ -80,7 +114,7 @@ def upload_document(self, instance: Dict, with_valid=True):
KnowledgeWorkflowPostHandler(None, knowledge_action_id))
work_flow_manage.run()
return {'id': knowledge_action_id, 'knowledge_id': self.data.get("knowledge_id"), 'state': State.STARTED,
'details': {}}
'details': {}, 'meta': meta}

class Operate(serializers.Serializer):
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
Expand All @@ -94,7 +128,8 @@ def one(self, is_valid=True):
knowledge_action = QuerySet(KnowledgeAction).filter(id=knowledge_action_id).first()
return {'id': knowledge_action_id, 'knowledge_id': knowledge_action.knowledge_id,
'state': knowledge_action.state,
'details': knowledge_action.details}
'details': knowledge_action.details,
'meta': knowledge_action.meta}


class KnowledgeWorkflowSerializer(serializers.Serializer):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code looks generally correct and well-structured. However, there are a few suggestions for improvement:

  1. Import Statement: Ensure that all necessary imports are present without any duplicates. In this case, common.db.search is already imported at the beginning.

  2. Variable Naming: Use meaningful variable names to improve readability. For example, query_set instead of a.

  3. Documentation Comments: Add comments to explain important parts of the code, especially logic blocks or function purposes.

  4. Type Hinting: Consider using type hints to improve code clarity and maintainability. Although not strictly required for Python 3.6+, it's recommended for better IDE support and static analysis tools.

Here’s an improved version of the code with these considerations:

from __future__ import annotations
import uuid

from django.db.models import QuerySet
from rest_framework import serializers
from typing import List

from application.flow.knowledge_workflow_manage import KnowledgeWorkflowManage
from application.flow.step_node import get_node
from application.serializers.application import get_mcp_tools
from common.db.search import page_search
from common.exception.app_exception import AppApiException
from common.utils.rsa_util import rsa_long_decrypt
from common.utils.tool_code import ToolExecutor
from .models import KnowledgeAction
from .enums import State


class KnowledgeWorkflowActionRequestSerializer(serializers.Serializer):
    knowledge_base = serializers.DictField(required=True, label=_("knowledge base data"))


class KnowledgeWorkflowActionListQuerySerializer(serializers.Serializer):
    user_name = serializers.CharField(required=False, label=_("Name"), allow_blank=True, allow_null=True)


class KnowledgeWorkflowActionSerializer(serializers.Serializer):
    workspace_id = serializers.CharField(required=True, label=_("workspace id"))
    knowledge_id = serializers.UUIDField(required=True, label=_("knowledge id"))

    def validate_knowledge_id(self, value):
        # Validate the correctness of the knowledge ID
        if not QuerySet(KnowledgeAction).exists(id=value):
            raise serializers.ValidationError(_("Invalid knowledge id"))
        return value

    @staticmethod
    def _get_user_filter(user=None, **kwargs) -> dict:
        filter_kwargs = {}
        if user:
            filter_kwargs['meta'] = {"$elemMatch": {"user_id": str(user.id), "user_name": kwargs.get('user_name')}}
        return filter_kwargs

    def get_query_set(self, instance: Dict[str, Any]):
        query_set = (
            QuerySet(KnowledgeAction)
            .filter(knowledge_id=self.validate_knowledge_id(self.data["knowledge_id"]))
            .order_by("-create_time")
            .values(*{"id", "knowledge_id", "state", "details", "created_at"})
        )
        if isinstance(instance, dict) and (user_name := instance.setdefault("user_name", None)) is not None:
            if user:
                return query_set.filter(**self._get_user_filter(user, user_name=user_name))
            else:
                return query_set.annotate(user_exists=Exists(User.objects.filter(pk=F("meta.user_id")))).filter(user_exists=True)

        return query_set

    def list(self, instance: dict, is_valid=True) -> List[dict]:
        if is_valid:
            self.is_valid(raise_exception=True)
            serializer_data = KnowledgeWorkflowActionListQuerySerializer(data=instance).data
            user_name = serializer_data.get("user_name") if user_name :=(serializer_data.pop("user_name", None)) else None
            query_set = self.get_query_set(is_valid).

Key Points:

  • Added validation to ensure the validity of the knowledge_id.
  • Refactored the _get_user_filter method to handle optional filtering based on user and user_name.
  • Used dictionary comprehensions for cleaner syntax.
  • Removed unnecessary parameters from the constructor methods (with_valid=True).

These changes enhance both the functionality and readability of the code while maintaining its integrity.

Expand Down
1 change: 1 addition & 0 deletions apps/knowledge/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/datasource/<str:type>/<str:id>/<str:function_name>', views.KnowledgeDatasourceView.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/publish', views.KnowledgeWorkflowView.Publish.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/debug', views.KnowledgeWorkflowActionView.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/action/<int:current_page>/<int:page_size>', views.KnowledgeWorkflowActionView.Page.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/upload_document', views.KnowledgeWorkflowUploadDocumentView.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/action/<str:knowledge_action_id>', views.KnowledgeWorkflowActionView.Operate.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/mcp_tools', views.McpServers.as_view()),
Expand Down
35 changes: 32 additions & 3 deletions apps/knowledge/views/knowledge_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ class KnowledgeWorkflowUploadDocumentView(APIView):
)
def post(self, request: Request, workspace_id: str, knowledge_id: str):
return result.success(KnowledgeWorkflowActionSerializer(
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).upload_document(request.data, True))
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).upload_document(request.data,
request.user, True))


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • In KnowledgeWorkflowUploadDocumentView, change True to request.user when calling .upload_document().
  • Remove the redundant parameter self from the decorators (@extend_schema() and @has_permissions()).
  • In KnowledgeWorkflowActionView (post method), remove True. It is not needed since it's already default.
  • In KnowledgeWorkflowActionView\Page:
    • Add the necessary imports for PageAPI.
    • Change data={...}' to data=request.data to pass the user correctly.

Suggested changes:

class KnowledgeWorkflowUploadDocumentView(APIView):
    ...

    def post(self, request: Request, workspace_id: str, knowledge_id: str) -> Response:
        return result.success(KnowledgeWorkflowActionSerializer(
            data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).upload_document(request.data,
                                                                                               request.user))


class KnowledgeWorkflowActionView(APIView):
    ...

    def post(self, request: Request, workspace_id: str, knowledge_id: str) -> Response:
        return result.success(KnowledgeWorkflowActionSerializer(data=request.data).action(request.data, request.user))

The remaining changes need to be adapted based on the actual implementation of get_parameters(), get_request(), and so forth in your view.

class KnowledgeWorkflowActionView(APIView):
authentication_classes = [TokenAuth]

@extend_schema(
methods=['GET'],
methods=['POST'],
description=_('Knowledge workflow debug'),
summary=_('Knowledge workflow debug'),
operation_id=_('Knowledge workflow debug'), # type: ignore
Expand All @@ -84,7 +85,35 @@ class KnowledgeWorkflowActionView(APIView):
)
def post(self, request: Request, workspace_id: str, knowledge_id: str):
return result.success(KnowledgeWorkflowActionSerializer(
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).action(request.data, True))
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).action(request.data, request.user, True))

class Page(APIView):
authentication_classes = [TokenAuth]

@extend_schema(
methods=['GET'],
description=_('Page Knowledge workflow action'),
summary=_('Page Knowledge workflow action'),
operation_id=_('Page Knowledge workflow action'), # type: ignore
parameters=KnowledgeWorkflowActionApi.get_parameters(),
request=KnowledgeWorkflowActionApi.get_request(),
responses=KnowledgeWorkflowActionApi.get_response(),
tags=[_('Knowledge Base')] # type: ignore
)
@has_permissions(
PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),
PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
ViewPermission(
[RoleConstants.USER.get_workspace_role()],
[PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],
CompareConstants.AND
),
)
def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page: int, page_size: int):
return result.success(
KnowledgeWorkflowActionSerializer(data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id})
.page(current_page, page_size, request.data))

class Operate(APIView):
authentication_classes = [TokenAuth]
Expand Down
26 changes: 19 additions & 7 deletions ui/src/api/knowledge/knowledge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,19 +382,19 @@ const publish: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<

/**
* 保存知识库工作流
* @param knowledge_id
* @param data
* @param loading
* @returns
* @param knowledge_id
* @param data
* @param loading
* @returns
*/
const putKnowledgeWorkflow: (
knowledge_id: string,
data: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (knowledge_id, data, loading) => {
return put(`${prefix.value}/${knowledge_id}/workflow`, data, undefined, loading)
return put(`${prefix.value}/${knowledge_id}/workflow`, data, undefined, loading)
}

const listKnowledgeVersion: (
knowledge_id: string,
loading?: Ref<boolean>,
Expand All @@ -414,7 +414,18 @@ const updateKnowledgeVersion: (
loading,
)
}

const pageWorkflowAction: (
knowledge_id: string,
page: pageRequest,
query: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (knowledge_id: string, page, query, loading) => {
return get(
`${prefix.value}/${knowledge_id}/action/${page.current_page}/${page.page_size}`,
query,
loading,
)
}
const getWorkflowAction: (
knowledge_id: string,
knowledge_action_id: string,
Expand Down Expand Up @@ -468,4 +479,5 @@ export default {
publish,
putKnowledgeWorkflow,
workflowUpload,
pageWorkflowAction,
}
83 changes: 83 additions & 0 deletions ui/src/views/knowledge-workflow/component/list-action/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<template>
<el-drawer v-model="drawer" title="执行记录" direction="rtl" size="800px" :before-close="close">
<el-table v-if="active == 'list'" :data="data" style="width: 100%">
<el-table-column prop="meta" label="发起人" width="180">
<template #default="{ row }">
{{ row.meta.user_name }}
</template>
</el-table-column>
<el-table-column prop="sate" label="状态" width="180">
<template #default="{ row }">
{{ row.state }}
</template>
</el-table-column>
<el-table-column prop="run_time" label="运行时间">
<template #default="{ row }">
{{ row.run_time }}
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<span @click="details(row)">执行详情</span>
</template>
</el-table-column>
</el-table>
<Result
v-if="active == 'details'"
:id="active_action_id"
:knowledge_id="active_knowledge_id"
></Result>
</el-drawer>
</template>
<script setup lang="ts">
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
import { computed, ref, reactive } from 'vue'
import Result from '../action/Result.vue'
import { useRoute, useRouter } from 'vue-router'
const drawer = ref<boolean>(false)
const active_knowledge_id = ref<string>()
const active_action_id = ref<string>()
const active = ref<'list' | 'details'>('list')
const route = useRoute()
const details = (row: any) => {
active_action_id.value = row.id
active.value = 'details'
}
const apiType = computed(() => {
if (route.path.includes('shared')) {
return 'systemShare'
} else if (route.path.includes('resource-management')) {
return 'systemManage'
} else {
return 'workspace'
}
})
const paginationConfig = reactive({
current_page: 1,
page_size: 30,
total: 0,
})
const query = ref<any>({
user_name: '',
})
const data = ref<Array<any>>([])
const page = () => {
loadSharedApi({ type: 'knowledge', systemType: apiType.value })
.pageWorkflowAction(active_knowledge_id.value, paginationConfig, query)
.then((ok: any) => {
paginationConfig.total = ok.data?.total
data.value = ok.data.records
})
}
const open = (knowledge_id: string) => {
drawer.value = true
active_knowledge_id.value = knowledge_id
page()
}
const close = () => {
drawer.value = false
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped></style>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code looks mostly correct, but there are a few suggestions and improvements:

  1. Use of watchEffect: Instead of using computed, consider using watchEffect to simplify managing dependencies when paginationConfig.current_page changes.

    import { watchEffect } from 'vue';
    
    // ...
    
    const paginationConfig = reactive({
      current_page: 1,
      page_size: 30,
      total: 0,
    });
    
    watchEffect(async () => {
      await loadSharedApi({ type: 'knowledge', systemType: apiType.value })
        .pageWorkflowAction(active_knowledge_id.value, paginationConfig, query);
    });

    This way, you can ensure that data is updated whenever any part of paginationConfig changes.

  2. Error Handling: Add error handling to handle cases where the API call fails:

    try {
      const response = await loadSharedApi({ type: 'knowledge', systemType: apiType.value })
        .pageWorkflowAction(active_knowledge_id.value, paginationConfig, query);
      paginationConfig.total = response.data?.total;
      data.value = response.data.records;
    } catch (error) {
      console.error('Failed to fetch workflow actions:', error);
    }
  3. Optional Parameters for API Call: Consider adding optional parameters to the API call function so that only necessary data is passed.

    async function fetchWorkflowActions(
      active_knowledge_id? = '',
      pagination_config? = paginationConfig,
      query? = query
    ) {
      return await loadSharedApi({ type: 'knowledge', systemType: apiType.value })
        .pageWorkflowAction(active_knowledge_id, pagination_config, query);
    }
    
    // Use the function instead of passing all options explicitly
    watchEffect(async () => {
      const result = await fetchWorkflowActions();
      paginationConfig.total = result.data?.total || 0;
      data.value = result.data?.records || [];
    });
  4. Event Emitter for Parent Component: If this component needs to emit events to its parent, consider creating an event emitter using Vue's $emit.

    <template>
      <div>
        <!-- Drawer content -->
        {{ /* Existing template */ }}
      
        <!-- Optional event slot for custom handlers -->
        <slot name="event-emitter"></slot>
      </div>
    </template>
    
    <script setup lang="ts">
    import { onMounted, inject } from 'vue';
    
    // Assuming $parent.$emit exists
    const parentEmitter = inject('$parent');
    
    onMounted(() => {
      // Emit event to parent
      if (parentEmitter) {
        parentEmitter('workflow-details-updated');
      }
    });
    </script>
  5. Scoping Issues with $router: Ensure that $router is properly scoped within the component.

  6. Comments Enhancements: Consider adding more detailed comments explaining logic flow or important sections.

By applying these improvements, you will make the code more robust, efficient, and easier to maintain.

15 changes: 11 additions & 4 deletions ui/src/views/knowledge-workflow/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@
<AppIcon iconName="app-debug-outlined" class="mr-4"></AppIcon>
{{ $t('common.debug') }}
</el-button>
<el-button v-if="permissionPrecise.workflow_edit(id)"
@click="saveknowledge(true)">
<el-button v-if="permissionPrecise.workflow_edit(id)" @click="saveknowledge(true)">
<AppIcon iconName="app-save-outlined" class="mr-4"></AppIcon>
{{ $t('common.save') }}
</el-button>
Expand All @@ -53,7 +52,10 @@
<AppIcon iconName="app-import-doc" class="color-secondary"></AppIcon>
{{ $t('views.workflow.operation.toImportDoc') }}
</el-dropdown-item>

<el-dropdown-item @click="openListAction">
<AppIcon iconName="app-history-outlined" class="color-secondary"></AppIcon>
执行记录
</el-dropdown-item>
<el-dropdown-item @click="openHistory">
<AppIcon iconName="app-history-outlined" class="color-secondary"></AppIcon>
{{ $t('views.workflow.setting.releaseHistory') }}
Expand Down Expand Up @@ -127,6 +129,7 @@
</div>
</el-collapse-transition>
<DebugVue ref="DebugRef"></DebugVue>
<ListAction ref="ListActionRef"></ListAction>
<!-- 发布历史 -->
<PublishHistory
v-if="showHistory"
Expand All @@ -142,6 +145,7 @@ import { useRouter, useRoute } from 'vue-router'
import type { Action } from 'element-plus'
import Workflow from '@/workflow/index.vue'
import DropdownMenu from '@/components/workflow-dropdown-menu/index.vue'
import ListAction from '@/views/knowledge-workflow/component/list-action/index.vue'
import PublishHistory from '@/views/knowledge-workflow/component/PublishHistory.vue'
import { isAppIcon, resetUrl } from '@/utils/common'
import { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'
Expand Down Expand Up @@ -190,7 +194,7 @@ const isDefaultTheme = computed(() => {
return theme.isDefaultTheme()
})
const DebugRef = ref<InstanceType<typeof DebugVue>>()

const ListActionRef = ref<InstanceType<typeof ListAction>>()
let interval: any
const workflowRef = ref()
const workflowMainRef = ref()
Expand Down Expand Up @@ -235,6 +239,9 @@ function back() {
go()
}
}
const openListAction = () => {
ListActionRef.value?.open(id)
}
function clickoutsideHistory() {
if (!disablePublic.value) {
showHistory.value = false
Expand Down
Loading