From d7c0b420b9b9df7013323465cea3a4b23315e2e8 Mon Sep 17 00:00:00 2001 From: yangdx Date: Wed, 26 Mar 2025 12:05:54 +0800 Subject: [PATCH] feat: add pipeline status monitoring dialog - Add pipeline status API and types - Create PipelineStatusDialog component with position control - Unify modal overlay style across components --- lightrag_webui/src/api/lightrag.ts | 19 ++ lightrag_webui/src/components/ApiKeyAlert.tsx | 4 +- .../documents/PipelineStatusDialog.tsx | 196 ++++++++++++++++++ .../src/components/ui/AlertDialog.tsx | 1 - .../src/features/DocumentManager.tsx | 37 +++- 5 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 lightrag_webui/src/components/documents/PipelineStatusDialog.tsx diff --git a/lightrag_webui/src/api/lightrag.ts b/lightrag_webui/src/api/lightrag.ts index e647285e..b55a62a3 100644 --- a/lightrag_webui/src/api/lightrag.ts +++ b/lightrag_webui/src/api/lightrag.ts @@ -141,6 +141,20 @@ export type AuthStatusResponse = { api_version?: string } +export type PipelineStatusResponse = { + autoscanned: boolean + busy: boolean + job_name: string + job_start?: string + docs: number + batchs: number + cur_batch: number + request_pending: boolean + latest_message: string + history_messages?: string[] + update_status?: Record +} + export type LoginResponse = { access_token: string token_type: string @@ -424,6 +438,11 @@ export const getAuthStatus = async (): Promise => { } } +export const getPipelineStatus = async (): Promise => { + const response = await axiosInstance.get('/documents/pipeline_status') + return response.data +} + export const loginToServer = async (username: string, password: string): Promise => { const formData = new FormData(); formData.append('username', username); diff --git a/lightrag_webui/src/components/ApiKeyAlert.tsx b/lightrag_webui/src/components/ApiKeyAlert.tsx index bbbce1bb..07b3230e 100644 --- a/lightrag_webui/src/components/ApiKeyAlert.tsx +++ b/lightrag_webui/src/components/ApiKeyAlert.tsx @@ -5,7 +5,8 @@ import { AlertDialogContent, AlertDialogDescription, AlertDialogHeader, - AlertDialogTitle + AlertDialogTitle, + AlertDialogOverlay } from '@/components/ui/AlertDialog' import Button from '@/components/ui/Button' import Input from '@/components/ui/Input' @@ -50,6 +51,7 @@ const ApiKeyAlert = ({ open: opened, onOpenChange: setOpened }: ApiKeyAlertProps return ( + {t('apiKeyAlert.title')} diff --git a/lightrag_webui/src/components/documents/PipelineStatusDialog.tsx b/lightrag_webui/src/components/documents/PipelineStatusDialog.tsx new file mode 100644 index 00000000..a8807592 --- /dev/null +++ b/lightrag_webui/src/components/documents/PipelineStatusDialog.tsx @@ -0,0 +1,196 @@ +import { useState, useEffect, useRef } from 'react' +import { useTranslation } from 'react-i18next' +import { toast } from 'sonner' +import { X, AlignLeft, AlignCenter, AlignRight } from 'lucide-react' + +import { + AlertDialog, + AlertDialogContent, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogOverlay +} from '@/components/ui/AlertDialog' +import Button from '@/components/ui/Button' +import { getPipelineStatus, PipelineStatusResponse } from '@/api/lightrag' +import { errorMessage } from '@/lib/utils' +import { cn } from '@/lib/utils' + +type DialogPosition = 'left' | 'center' | 'right' + +interface PipelineStatusDialogProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +export default function PipelineStatusDialog({ + open, + onOpenChange +}: PipelineStatusDialogProps) { + const { t } = useTranslation() + const [status, setStatus] = useState(null) + const [position, setPosition] = useState('center') + const [isUserScrolled, setIsUserScrolled] = useState(false) + const historyRef = useRef(null) + + // Reset position when dialog opens + useEffect(() => { + if (open) { + setPosition('center') + setIsUserScrolled(false) + } + }, [open]) + + // Handle scroll position + useEffect(() => { + const container = historyRef.current + if (!container || isUserScrolled) return + + container.scrollTop = container.scrollHeight + }, [status?.history_messages, isUserScrolled]) + + const handleScroll = () => { + const container = historyRef.current + if (!container) return + + const isAtBottom = Math.abs( + (container.scrollHeight - container.scrollTop) - container.clientHeight + ) < 1 + + if (isAtBottom) { + setIsUserScrolled(false) + } else { + setIsUserScrolled(true) + } + } + + // Refresh status every 2 seconds + useEffect(() => { + if (!open) return + + const fetchStatus = async () => { + try { + const data = await getPipelineStatus() + setStatus(data) + } catch (err) { + toast.error(t('documentPanel.pipelineStatus.errors.fetchFailed', { error: errorMessage(err) })) + } + } + + fetchStatus() + const interval = setInterval(fetchStatus, 2000) + return () => clearInterval(interval) + }, [open, t]) + + return ( + + + + + + {t('documentPanel.pipelineStatus.title')} + + + {/* Position control buttons and close button */} +
+
+ + + +
+ +
+
+ + {/* Status Content */} +
+ {/* Pipeline Status */} +
+
+
Busy:
+
+
+
+
Request Pending:
+
+
+
+ + {/* Job Information */} +
+
Job Name: {status?.job_name || '-'}
+
+ Start Time: {status?.job_start ? new Date(status.job_start).toLocaleString() : '-'} + Progress: {status ? `${status.cur_batch}/${status.batchs}` : '-'} +
+
+ + {/* Latest Message */} +
+
Latest Message:
+
+ {status?.latest_message || '-'} +
+
+ + {/* History Messages */} +
+
History Messages:
+
+ {status?.history_messages?.map((msg, idx) => ( +
{msg}
+ )) || '-'} +
+
+
+ + + ) +} diff --git a/lightrag_webui/src/components/ui/AlertDialog.tsx b/lightrag_webui/src/components/ui/AlertDialog.tsx index 36868fcb..072bdff7 100644 --- a/lightrag_webui/src/components/ui/AlertDialog.tsx +++ b/lightrag_webui/src/components/ui/AlertDialog.tsx @@ -30,7 +30,6 @@ const AlertDialogContent = React.forwardRef< React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - { // Check if file_path exists and is a non-empty string @@ -45,6 +46,7 @@ const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): str }; export default function DocumentManager() { + const [showPipelineStatus, setShowPipelineStatus] = useState(false) const { t } = useTranslation() const health = useBackendState.use.health() const [docs, setDocs] = useState(null) @@ -114,18 +116,33 @@ export default function DocumentManager() {
- +
+ + +
+