Add pipeline cancellation feature with UI and i18n support

- Add cancelPipeline API endpoint
- Add cancel button to status dialog
- Update status response type
- Add cancellation UI translations
- Handle cancellation request states
This commit is contained in:
yangdx 2025-10-24 15:30:27 +08:00
parent 78ad8873b8
commit f89b5ab101
7 changed files with 125 additions and 32 deletions

View file

@ -242,6 +242,7 @@ export type PipelineStatusResponse = {
batchs: number batchs: number
cur_batch: number cur_batch: number
request_pending: boolean request_pending: boolean
cancellation_requested?: boolean
latest_message: string latest_message: string
history_messages?: string[] history_messages?: string[]
update_status?: Record<string, any> update_status?: Record<string, any>
@ -691,6 +692,14 @@ export const getPipelineStatus = async (): Promise<PipelineStatusResponse> => {
return response.data return response.data
} }
export const cancelPipeline = async (): Promise<{
status: 'cancellation_requested' | 'not_busy'
message: string
}> => {
const response = await axiosInstance.post('/documents/cancel_pipeline')
return response.data
}
export const loginToServer = async (username: string, password: string): Promise<LoginResponse> => { export const loginToServer = async (username: string, password: string): Promise<LoginResponse> => {
const formData = new FormData(); const formData = new FormData();
formData.append('username', username); formData.append('username', username);

View file

@ -11,7 +11,7 @@ import {
DialogDescription DialogDescription
} from '@/components/ui/Dialog' } from '@/components/ui/Dialog'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import { getPipelineStatus, PipelineStatusResponse } from '@/api/lightrag' import { getPipelineStatus, cancelPipeline, PipelineStatusResponse } from '@/api/lightrag'
import { errorMessage } from '@/lib/utils' import { errorMessage } from '@/lib/utils'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -81,6 +81,23 @@ export default function PipelineStatusDialog({
return () => clearInterval(interval) return () => clearInterval(interval)
}, [open, t]) }, [open, t])
// Handle cancel pipeline
const handleCancelPipeline = async () => {
try {
const result = await cancelPipeline()
if (result.status === 'cancellation_requested') {
toast.success(t('documentPanel.pipelineStatus.cancelSuccess'))
} else if (result.status === 'not_busy') {
toast.info(t('documentPanel.pipelineStatus.cancelNotBusy'))
}
} catch (err) {
toast.error(t('documentPanel.pipelineStatus.cancelFailed', { error: errorMessage(err) }))
}
}
// Determine if cancel button should be enabled
const canCancel = status?.busy === true && !status?.cancellation_requested
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent <DialogContent
@ -142,16 +159,43 @@ export default function PipelineStatusDialog({
{/* Status Content */} {/* Status Content */}
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
{/* Pipeline Status */} {/* Pipeline Status - with cancel button */}
<div className="flex items-center gap-4"> <div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-2"> {/* Left side: Status indicators */}
<div className="text-sm font-medium">{t('documentPanel.pipelineStatus.busy')}:</div> <div className="flex items-center gap-4">
<div className={`h-2 w-2 rounded-full ${status?.busy ? 'bg-green-500' : 'bg-gray-300'}`} /> <div className="flex items-center gap-2">
</div> <div className="text-sm font-medium">{t('documentPanel.pipelineStatus.busy')}:</div>
<div className="flex items-center gap-2"> <div className={`h-2 w-2 rounded-full ${status?.busy ? 'bg-green-500' : 'bg-gray-300'}`} />
<div className="text-sm font-medium">{t('documentPanel.pipelineStatus.requestPending')}:</div> </div>
<div className={`h-2 w-2 rounded-full ${status?.request_pending ? 'bg-green-500' : 'bg-gray-300'}`} /> <div className="flex items-center gap-2">
<div className="text-sm font-medium">{t('documentPanel.pipelineStatus.requestPending')}:</div>
<div className={`h-2 w-2 rounded-full ${status?.request_pending ? 'bg-green-500' : 'bg-gray-300'}`} />
</div>
{/* Only show cancellation status when it's requested */}
{status?.cancellation_requested && (
<div className="flex items-center gap-2">
<div className="text-sm font-medium">{t('documentPanel.pipelineStatus.cancellationRequested')}:</div>
<div className="h-2 w-2 rounded-full bg-red-500" />
</div>
)}
</div> </div>
{/* Right side: Cancel button - only show when pipeline is busy */}
{status?.busy && (
<Button
variant="destructive"
size="sm"
disabled={!canCancel}
onClick={handleCancelPipeline}
title={
status?.cancellation_requested
? t('documentPanel.pipelineStatus.cancelInProgress')
: t('documentPanel.pipelineStatus.cancelTooltip')
}
>
{t('documentPanel.pipelineStatus.cancelButton')}
</Button>
)}
</div> </div>
{/* Job Information */} {/* Job Information */}

View file

@ -157,17 +157,25 @@
"hideFileNameTooltip": "إخفاء اسم الملف" "hideFileNameTooltip": "إخفاء اسم الملف"
}, },
"pipelineStatus": { "pipelineStatus": {
"title": "حالة خط المعالجة", "title": "حالة خط الأنابيب",
"busy": "خط المعالجة مشغول", "busy": "خط الأنابيب مشغول",
"requestPending": "الطلب معلق", "requestPending": "طلب معلق",
"cancellationRequested": "طلب الإلغاء",
"jobName": "اسم المهمة", "jobName": "اسم المهمة",
"startTime": "وقت البدء", "startTime": "وقت البدء",
"progress": "التقدم", "progress": "التقدم",
"unit": "دفعة", "unit": "دفعة",
"latestMessage": "آخر رسالة", "latestMessage": "آخر رسالة",
"historyMessages": "سجل الرسائل", "historyMessages": "رسائل السجل",
"cancelButton": "إلغاء",
"cancelTooltip": "إلغاء معالجة خط الأنابيب",
"cancelInProgress": "الإلغاء قيد التقدم...",
"pipelineNotRunning": "خط الأنابيب غير قيد التشغيل",
"cancelSuccess": "تم طلب إلغاء خط الأنابيب",
"cancelFailed": "فشل إلغاء خط الأنابيب\n{{error}}",
"cancelNotBusy": "خط الأنابيب غير قيد التشغيل، لا حاجة للإلغاء",
"errors": { "errors": {
"fetchFailed": "فشل في جلب حالة خط المعالجة\n{{error}}" "fetchFailed": "فشل في جلب حالة خط الأنابيب\n{{error}}"
} }
} }
}, },

View file

@ -160,14 +160,22 @@
"title": "Pipeline Status", "title": "Pipeline Status",
"busy": "Pipeline Busy", "busy": "Pipeline Busy",
"requestPending": "Request Pending", "requestPending": "Request Pending",
"cancellationRequested": "Cancellation Requested",
"jobName": "Job Name", "jobName": "Job Name",
"startTime": "Start Time", "startTime": "Start Time",
"progress": "Progress", "progress": "Progress",
"unit": "batch", "unit": "Batch",
"latestMessage": "Latest Message", "latestMessage": "Latest Message",
"historyMessages": "History Messages", "historyMessages": "History Messages",
"cancelButton": "Cancel",
"cancelTooltip": "Cancel pipeline processing",
"cancelInProgress": "Cancellation in progress...",
"pipelineNotRunning": "Pipeline not running",
"cancelSuccess": "Pipeline cancellation requested",
"cancelFailed": "Failed to cancel pipeline\n{{error}}",
"cancelNotBusy": "Pipeline is not running, no need to cancel",
"errors": { "errors": {
"fetchFailed": "Failed to get pipeline status\n{{error}}" "fetchFailed": "Failed to fetch pipeline status\n{{error}}"
} }
} }
}, },

View file

@ -158,14 +158,22 @@
}, },
"pipelineStatus": { "pipelineStatus": {
"title": "État du Pipeline", "title": "État du Pipeline",
"busy": "Pipeline occupé", "busy": "Pipeline Occupé",
"requestPending": "Requête en attente", "requestPending": "Demande en Attente",
"jobName": "Nom du travail", "cancellationRequested": "Annulation Demandée",
"startTime": "Heure de début", "jobName": "Nom du Travail",
"progress": "Progression", "startTime": "Heure de Début",
"unit": "lot", "progress": "Progrès",
"latestMessage": "Dernier message", "unit": "Lot",
"historyMessages": "Historique des messages", "latestMessage": "Dernier Message",
"historyMessages": "Messages d'Historique",
"cancelButton": "Annuler",
"cancelTooltip": "Annuler le traitement du pipeline",
"cancelInProgress": "Annulation en cours...",
"pipelineNotRunning": "Le pipeline n'est pas en cours d'exécution",
"cancelSuccess": "Annulation du pipeline demandée",
"cancelFailed": "Échec de l'annulation du pipeline\n{{error}}",
"cancelNotBusy": "Le pipeline n'est pas en cours d'exécution, pas besoin d'annuler",
"errors": { "errors": {
"fetchFailed": "Échec de la récupération de l'état du pipeline\n{{error}}" "fetchFailed": "Échec de la récupération de l'état du pipeline\n{{error}}"
} }

View file

@ -160,12 +160,20 @@
"title": "流水线状态", "title": "流水线状态",
"busy": "流水线忙碌", "busy": "流水线忙碌",
"requestPending": "待处理请求", "requestPending": "待处理请求",
"cancellationRequested": "取消请求",
"jobName": "作业名称", "jobName": "作业名称",
"startTime": "开始时间", "startTime": "开始时间",
"progress": "进度", "progress": "进度",
"unit": "批", "unit": "批",
"latestMessage": "最新消息", "latestMessage": "最新消息",
"historyMessages": "历史消息", "historyMessages": "历史消息",
"cancelButton": "中断",
"cancelTooltip": "中断流水线处理",
"cancelInProgress": "取消请求进行中...",
"pipelineNotRunning": "流水线未运行",
"cancelSuccess": "流水线中断请求已发送",
"cancelFailed": "中断流水线失败\n{{error}}",
"cancelNotBusy": "流水线未运行,无需中断",
"errors": { "errors": {
"fetchFailed": "获取流水线状态失败\n{{error}}" "fetchFailed": "获取流水线状态失败\n{{error}}"
} }

View file

@ -157,17 +157,25 @@
"hideFileNameTooltip": "隱藏檔案名稱" "hideFileNameTooltip": "隱藏檔案名稱"
}, },
"pipelineStatus": { "pipelineStatus": {
"title": "pipeline 狀態", "title": "流水線狀態",
"busy": "pipeline 忙碌中", "busy": "流水線忙碌",
"requestPending": "待處理請求", "requestPending": "待處理請求",
"jobName": "工作名稱", "cancellationRequested": "取消請求",
"jobName": "作業名稱",
"startTime": "開始時間", "startTime": "開始時間",
"progress": "進度", "progress": "進度",
"unit": "梯次", "unit": "批",
"latestMessage": "最新訊息", "latestMessage": "最新消息",
"historyMessages": "歷史訊息", "historyMessages": "歷史消息",
"cancelButton": "中斷",
"cancelTooltip": "中斷流水線處理",
"cancelInProgress": "取消請求進行中...",
"pipelineNotRunning": "流水線未運行",
"cancelSuccess": "流水線中斷請求已發送",
"cancelFailed": "中斷流水線失敗\n{{error}}",
"cancelNotBusy": "流水線未運行,無需中斷",
"errors": { "errors": {
"fetchFailed": "取得pipeline 狀態失敗\n{{error}}" "fetchFailed": "獲取流水線狀態失敗\n{{error}}"
} }
} }
}, },