feat: Knowledge Base Workflow Execution Record (#4435)

This commit is contained in:
shaohuzhang1 2025-12-04 14:22:46 +08:00 committed by GitHub
parent 0db2622663
commit c4dd09ca1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 188 additions and 21 deletions

View File

@ -15,6 +15,7 @@ from application.flow.i_step_node import KnowledgeWorkflowPostHandler
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
@ -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,
@ -59,9 +90,9 @@ class KnowledgeWorkflowActionSerializer(serializers.Serializer):
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()
@ -71,7 +102,10 @@ class KnowledgeWorkflowActionSerializer(serializers.Serializer):
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,
@ -80,7 +114,7 @@ class KnowledgeWorkflowActionSerializer(serializers.Serializer):
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'))
@ -94,7 +128,8 @@ class KnowledgeWorkflowActionSerializer(serializers.Serializer):
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):

View File

@ -73,6 +73,7 @@ urlpatterns = [
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()),

View File

@ -78,14 +78,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))
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
@ -106,7 +107,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]

View File

@ -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>,
@ -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,
@ -468,4 +479,5 @@ export default {
publish,
putKnowledgeWorkflow,
workflowUpload,
pageWorkflowAction,
}

View File

@ -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>

View File

@ -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>
@ -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') }}
@ -127,6 +129,7 @@
</div>
</el-collapse-transition>
<DebugVue ref="DebugRef"></DebugVue>
<ListAction ref="ListActionRef"></ListAction>
<!-- 发布历史 -->
<PublishHistory
v-if="showHistory"
@ -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'
@ -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()
@ -235,6 +239,9 @@ function back() {
go()
}
}
const openListAction = () => {
ListActionRef.value?.open(id)
}
function clickoutsideHistory() {
if (!disablePublic.value) {
showHistory.value = false