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
2 changes: 2 additions & 0 deletions apps/application/flow/i_step_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ def get_loop_workflow_node(node_list):


def get_workflow_state(workflow):
if workflow.is_the_task_interrupted():
return State.REVOKED
details = workflow.get_runtime_details()
node_list = details.values()
all_node = [*node_list, *get_loop_workflow_node(node_list)]
Expand Down
7 changes: 5 additions & 2 deletions apps/application/flow/knowledge_workflow_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ def __init__(self, flow: Workflow,
work_flow_post_handler: WorkFlowPostHandler,
base_to_response: BaseToResponse = SystemToResponse(),
start_node_id=None,
start_node_data=None, chat_record=None, child_node=None):
start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False):
super().__init__(flow, params, work_flow_post_handler, base_to_response, None, None, None,
None,
None, None, start_node_id, start_node_data, chat_record, child_node)
None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted)

def get_params_serializer_class(self):
return KnowledgeFlowParamsSerializer
Expand Down Expand Up @@ -91,6 +91,9 @@ def hand_node_result(self, current_node, node_result_future):
list(result)
if current_node.status == 500:
return None
if self.is_the_task_interrupted():
current_node.status = 201
return None
return current_result
except Exception as e:
traceback.print_exc()
Expand Down
4 changes: 2 additions & 2 deletions apps/application/flow/loop_workflow_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ def __init__(self, flow: Workflow,
get_loop_context,
base_to_response: BaseToResponse = SystemToResponse(),
start_node_id=None,
start_node_data=None, chat_record=None, child_node=None):
start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False):
self.parentWorkflowManage = parentWorkflowManage
self.loop_params = loop_params
self.get_loop_context = get_loop_context
self.loop_field_list = []
super().__init__(flow, params, work_flow_post_handler, base_to_response, None, None, None,
None,
None, None, start_node_id, start_node_data, chat_record, child_node)
None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted)

def get_node_cls_by_id(self, node_id, up_node_id_list=None,
get_node_params=lambda node: node.properties.get('node_data')):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
response_reasoning_content = False

for chunk in response:
if workflow.is_the_task_interrupted():
break
reasoning_chunk = reasoning.get_reasoning_content(chunk)
content_chunk = reasoning_chunk.get('content')
if 'reasoning_content' in chunk.additional_kwargs:
Expand Down Expand Up @@ -110,7 +112,8 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
if 'reasoning_content' in meta:
reasoning_content = (meta.get('reasoning_content', '') or '')
else:
reasoning_content = (reasoning_result.get('reasoning_content') or '') + (reasoning_result_end.get('reasoning_content') or '')
reasoning_content = (reasoning_result.get('reasoning_content') or '') + (
reasoning_result_end.get('reasoning_content') or '')
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ def workflow_manage_new_instance(loop_data, global_data, start_node_id=None,
start_node_id=start_node_id,
start_node_data=start_node_data,
chat_record=chat_record,
child_node=child_node
child_node=child_node,
is_the_task_interrupted=self.workflow_manage.is_the_task_interrupted
)

return workflow_manage
Expand Down
3 changes: 2 additions & 1 deletion apps/application/flow/workflow_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostH
video_list=None,
other_list=None,
start_node_id=None,
start_node_data=None, chat_record=None, child_node=None):
start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False):
if form_data is None:
form_data = {}
if image_list is None:
Expand Down Expand Up @@ -138,6 +138,7 @@ def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostH
self.global_field_list = []
self.chat_field_list = []
self.init_fields()
self.is_the_task_interrupted = is_the_task_interrupted
if start_node_id is not None:
self.load_node(chat_record, start_node_id, start_node_data)
else:
Expand Down
2 changes: 1 addition & 1 deletion apps/common/constants/cache_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Cache_Version(Enum):
SYSTEM = "SYSTEM", lambda key: key
# 应用对接三方应用的缓存
APPLICATION_THIRD_PARTY = "APPLICATION:THIRD_PARTY", lambda key: key

KNOWLEDGE_WORKFLOW_INTERRUPTED = "KNOWLEDGE_WORKFLOW_INTERRUPTED", lambda action_id: action_id
# 对话
CHAT = "CHAT", lambda key: key

Expand Down
26 changes: 22 additions & 4 deletions apps/knowledge/serializers/knowledge_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
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.constants.cache_version import Cache_Version
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 knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion
from knowledge.models.knowledge_action import KnowledgeAction, State
from knowledge.serializers.knowledge import KnowledgeModelSerializer
from maxkb.const import CONFIG
from django.core.cache import cache
from system_manage.models import AuthTargetType
from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer
from tools.models import Tool
Expand Down Expand Up @@ -52,7 +53,11 @@ class KnowledgeWorkflowActionSerializer(serializers.Serializer):
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))

def get_query_set(self, instance: Dict):
query_set = QuerySet(KnowledgeAction).filter(knowledge_id=self.data.get('knowledge_id')).values('id','knowledge_id',"state",'meta','run_time',"create_time")
query_set = QuerySet(KnowledgeAction).filter(knowledge_id=self.data.get('knowledge_id')).values('id',
'knowledge_id',
"state", 'meta',
'run_time',
"create_time")
if instance.get("user_name"):
query_set = query_set.filter(meta__user_name__icontains=instance.get('user_name'))
if instance.get('state'):
Expand All @@ -73,7 +78,8 @@ def page(self, current_page, page_size, instance: Dict, is_valid=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.get("id"), 'knowledge_id': a.get("knowledge_id"), 'state': a.get("state"),
'meta': a.get("meta"), 'run_time': a.get("run_time"), 'create_time': a.get("create_time")})
'meta': a.get("meta"), 'run_time': a.get("run_time"),
'create_time': a.get("create_time")})

def action(self, instance: Dict, user, with_valid=True):
if with_valid:
Expand All @@ -91,7 +97,10 @@ def action(self, instance: Dict, user, with_valid=True):
{'knowledge_id': self.data.get("knowledge_id"), 'knowledge_action_id': knowledge_action_id, 'stream': True,
'workspace_id': self.data.get("workspace_id"),
**instance},
KnowledgeWorkflowPostHandler(None, knowledge_action_id))
KnowledgeWorkflowPostHandler(None, knowledge_action_id),
is_the_task_interrupted=lambda: cache.get(
Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id),
version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version()) or False)
work_flow_manage.run()
return {'id': knowledge_action_id, 'knowledge_id': self.data.get("knowledge_id"), 'state': State.STARTED,
'details': {}, 'meta': meta}
Expand Down Expand Up @@ -135,6 +144,15 @@ def one(self, is_valid=True):
'details': knowledge_action.details,
'meta': knowledge_action.meta}

def cancel(self, is_valid=True):
if is_valid:
self.is_valid(raise_exception=True)
knowledge_action_id = self.data.get("id")
cache.set(Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id), True,
version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version())
QuerySet(KnowledgeAction).filter(id=knowledge_action_id).update(state=State.REVOKE)
return True


class KnowledgeWorkflowSerializer(serializers.Serializer):
class Datasource(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 code seems mostly correct, but there are a few areas that could be improved:

  1. Redundant Imports:

    from common.db.search import page_search
    from system_manage.models import AuthTargetType
    from tools.models import Tool

    These imports might not be needed at the beginning of KnowledgeWorkflowActionListQuerySerializer.

  2. Useless Comment:

    #       'details': knowledge_action.details,

    The comment doesn't add any value; you can remove it.

  3. Duplicate Code in one Method:
    Both the get_one method and the cancel method have similar logic to retrieve and update data related to knowledge_action. You should consider refactoring this into a single function.

Here's an example of how you could refactor the cancel method to use a helper function:

def _set_knowledge_action_state_to_revoke(knowledge_action_id):
    cache.set(Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id), True,
              version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version())
    QuerySet(KnowledgeAction).filter(id=knowledge_action_id).update(state=State.REVOKE)

class KnowledgeActionListQuerySerializer(serializers.ModelSerializer):
    ...

    def cancel(self, is_valid=True):
        if is_valid:
            self.is_valid(raise_exception=True)
        knowledge_action_id = self.data.get("id")
        _set_knowledge_action_state_to_revoke(knowledge_action_id)
        return {}

This way, you reduce redundancy and make the code cleaner.

Expand Down
1 change: 1 addition & 0 deletions apps/knowledge/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
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>/action/<str:knowledge_action_id>/cancel', views.KnowledgeWorkflowActionView.Cancel.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/mcp_tools', views.McpServers.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version', views.KnowledgeWorkflowVersionView.as_view()),
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version/<int:current_page>/<int:page_size>', views.KnowledgeWorkflowVersionView.Page.as_view()),
Expand Down
27 changes: 27 additions & 0 deletions apps/knowledge/views/knowledge_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,33 @@ def get(self, request, workspace_id: str, knowledge_id: str, knowledge_action_id
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'id': knowledge_action_id})
.one())

class Cancel(APIView):
authentication_classes = [TokenAuth]

@extend_schema(
methods=['POST'],
description=_('Cancel knowledge workflow action'),
summary=_('Cancel knowledge workflow action'),
operation_id=_('Cancel knowledge workflow action'), # type: ignore
parameters=KnowledgeWorkflowActionApi.get_parameters(),
responses=DefaultResultSerializer(),
tags=[_('Knowledge Base')] # type: ignore
)
@has_permissions(
PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(),
PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.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 post(self, request, workspace_id: str, knowledge_id: str, knowledge_action_id: str):
return result.success(KnowledgeWorkflowActionSerializer.Operate(
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'id': knowledge_action_id})
.cancel())


class KnowledgeWorkflowView(APIView):
authentication_classes = [TokenAuth]
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 seems mostly correct, but there are a few improvements and corrections that could be made:

Improvements and Corrections

  1. Type Annotations: The @extend_schema and @has_permissions decorators can cause linting errors if not properly installed in your environment. Ensure these packages are available.

  2. Docstrings and Comments: Enhance comments to explain complex sections of the code, especially those related to permissions and API operations.

  3. Variable Names: Ensure consistent naming conventions, such as using underscores for private instance variables if necessary.

  4. Security Considerations: Check if any security measures should be implemented for sensitive operations like cancelling workflow actions.

  5. Error Handling: Implement error handling around the database queries and response serialization to manage exceptions gracefully.

Here is an updated version with some of these considerations:

from rest_framework.views import APIView
from rest_framework.response import Response
from .models import KnowledgeWorkflowAction
from .serializers import KnowledgeWorkflowActionSerializer, DefaultResultSerializer
from .permissions import has_permissions, PermissionConstants, RoleConstants, ViewPermission
from drf_yasg.utils import extend_schema
import traceback

# Define the CancelAPIView class here
class Cancel(APIView):
    authentication_classes = [TokenAuth]

    @extend_schema(
        methods=['POST'],
        description=_('Cancel knowledge workflow action'),
        summary=_('Cancel knowledge workflow action'),
        operation_id='cancel_knowledge_workflow_action',  # Use kebab case instead of underscores
        parameters=KnowledgeWorkflowActionApi.get_parameters(),
        responses={
            200: DefaultResultSerializer()
        },
        tags=[_('Knowledge Base')]
    )
    @has_permissions(
        PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(),
        PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.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 post(self, request, workspace_id: str, knowledge_id: str, knowledge_action_id: str) -> Response:
        try:
            action_instance = KnowledgeWorkflowAction.objects.filter(
                id=knowledge_action_id
            ).select_related(
                'workflow__stage',
                'workflow'
            ).first()

            if not action_instance:
                return Result.failure(404, "Knowledge workflow action not found")

            cancel_result = action_instance.cancel()
            if cancel_result:
                return Result.success(serializer.instance)
            else:
                return Result.failure("Failed to cancel knowledge workflow action")
        
        except Exception as e:
            print(traceback.format_exc())  # Log exception details
            return Result.failure(str(e))

class KnowledgeWorkflowView(APIView):
    authentication_classes = [TokenAuth]

Key Points:

  • Use Kebab Case (operation_id): Using kebab case for identifiers (e.g., operation_id) helps maintain consistency across different frameworks.
  • Error Handling: Added basic error handling to catch unexpected exceptions and log them.
  • Serialization: Used Response instead of directly returning values from view methods for more control over the HTTP status code and response format.

These changes will make the code cleaner, more robust, maintainable, and secure.

Expand Down
9 changes: 8 additions & 1 deletion ui/src/api/knowledge/knowledge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,13 @@ const getWorkflowAction: (
) => Promise<Result<any>> = (knowledge_id: string, knowledge_action_id, loading) => {
return get(`${prefix.value}/${knowledge_id}/action/${knowledge_action_id}`, {}, loading)
}

const cancelWorkflowAction: (
knowledge_id: string,
knowledge_action_id: string,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (knowledge_id: string, knowledge_action_id, loading) => {
return post(`${prefix.value}/${knowledge_id}/action/${knowledge_action_id}/cancel`, {}, loading)
}
/**
* mcp 节点
*/
Expand Down Expand Up @@ -480,4 +486,5 @@ export default {
putKnowledgeWorkflow,
workflowUpload,
getWorkflowActionPage,
cancelWorkflowAction,
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@
<el-icon class="color-danger"><CircleCloseFilled /></el-icon>
{{ $t('common.status.fail') }}
</el-text>
<el-text
class="color-text-primary"
v-else-if="props.currentContent?.state === 'REVOKED'"
>
<el-icon class="color-danger"><CircleCloseFilled /></el-icon>
{{ $t('common.status.REVOKED', '已取消') }}
</el-text>
<el-text
class="color-text-primary"
v-else-if="props.currentContent?.state === 'REVOKE'"
>
<el-icon class="is-loading color-primary"><Loading /></el-icon>
{{ $t('views.document.fileStatus.REVOKE', '取消中') }}
</el-text>
<el-text class="color-text-primary" v-else>
<el-icon class="is-loading color-primary"><Loading /></el-icon>
{{ $t('common.status.padding') }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@
<el-icon class="color-danger"><CircleCloseFilled /></el-icon>
{{ $t('common.status.fail') }}
</el-text>
<el-text class="color-text-primary" v-else-if="row.state === 'REVOKED'">
<el-icon class="color-danger"><CircleCloseFilled /></el-icon>
{{ $t('common.status.REVOKED', '已取消') }}
</el-text>
<el-text class="color-text-primary" v-else-if="row.state === 'REVOKE'">
<el-icon class="is-loading color-primary"><Loading /></el-icon>
{{ $t('views.document.fileStatus.REVOKE', '取消中') }}
</el-text>
<el-text class="color-text-primary" v-else>
<el-icon class="is-loading color-primary"><Loading /></el-icon>
{{ $t('common.status.padding') }}
Expand All @@ -87,11 +95,22 @@

<el-table-column :label="$t('common.operation')" width="80">
<template #default="{ row }">
<el-tooltip effect="dark" :content="$t('chat.executionDetails.title')" placement="top">
<el-button type="primary" text @click.stop="toDetails(row)">
<AppIcon iconName="app-operate-log"></AppIcon>
</el-button>
</el-tooltip>
<div class="flex">
<el-tooltip effect="dark" :content="$t('chat.executionDetails.title')" placement="top">
<el-button type="primary" text @click.stop="toDetails(row)">
<AppIcon iconName="app-operate-log"></AppIcon>
</el-button>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('chat.executionDetails.cancel', '取消')"
placement="top"
>
<el-button type="primary" text @click.stop="cancel(row)">
<el-icon><CircleCloseFilled /></el-icon>
</el-button>
</el-tooltip>
</div>
</template>
</el-table-column>
</app-table-infinite-scroll>
Expand Down Expand Up @@ -157,6 +176,11 @@ const toDetails = (row: any) => {
ExecutionDetailDrawerRef.value?.open()
}

const cancel = (row: any) => {
loadSharedApi({ type: 'knowledge', systemType: apiType.value })
.cancelWorkflowAction(active_knowledge_id.value, row.id, loading)
.then((ok: any) => {})
}
const changeFilterHandle = () => {
query.value = { user_name: '', status: '' }
}
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 snippet has several areas that could be improved:

Code Improvements

  1. Duplicated Tooltip Content: There is a repeated tooltip content "Operation Details" which can be consolidated to reduce redundancy.

  2. Loading Icons for Actions: The loading icons should use consistent styles across different states.

  3. Functionality Documentation: Inline comments on the cancel function explain its purpose clearly.

  4. Optimize Button Placement: The buttons are placed next to each other without clear spacing adjustments. Consider adding some space between the two tooltips.

Here's an optimized version of the code with these improvements:

<template>
  <!-- Existing template content -->

  <el-table-column :label="$t('common.operation')" width="120">
    <template #default="{ row }">
      <div class="flex gap-2 items-center">
        <el-tooltip effect="dark" :content="$t('chat.executionDetails.title')" placement="top">
          <el-button type="primary" text @click.stop="toDetails(row)">
            <AppIcon iconName="app-operate-log"></AppIcon>
          </el-button>
        </el-tooltip>

        <el-tooltip effect="dark" :content="$t('chat.executionDetails.cancel', '取消')">
          <span style="display: flex; align-items: center;">
            <el-icon><CircleCloseFilled /></el-icon>
            <elButton type="danger" plain size="small" @click.stop="cancel(row)" />
          </span>
        </el-tooltip>
      </div>
    </template>
  </el-table-column>

</template>

<script lang="ts">
import { ref, defineComponent, PropTybe } from 'vue'
// Import necessary components & libraries here

export default defineCompone({
  // Component setup and data

  methods: {
    async cancel ({ id }: Record<string, unknown>) {
      await loadSharedApi({ type: 'knowledge', systemType: this.apiType }).cancelWorkflowAction(
        active_knowledge_id.value,
        id,
        this.loading
      )
    }
    // Other functions remain unchanged
  },
})
</script>

<style scoped>
/* Add any custom styling if needed */
.flex {
  display: flex;
  align-items: center;
}

.items-center {
  justify-content: center;
}
</style>

Summary of Changes

  • Consolidated tooltips.
  • Used <span> and align-items:center combined with inline styling for better layout control within the tooltips.
  • Added a small padding inside the button (size="small").
  • Kept original logic intact while addressing visual inconsistencies and duplication.

Expand Down
Loading