diff --git a/lightrag_webui/src/api/lightrag.ts b/lightrag_webui/src/api/lightrag.ts index b55a62a3..8b59ccbf 100644 --- a/lightrag_webui/src/api/lightrag.ts +++ b/lightrag_webui/src/api/lightrag.ts @@ -45,6 +45,7 @@ export type LightragStatus = { core_version?: string api_version?: string auth_mode?: 'enabled' | 'disabled' + pipeline_busy: boolean } export type LightragDocumentsScanProgress = { diff --git a/lightrag_webui/src/features/DocumentManager.tsx b/lightrag_webui/src/features/DocumentManager.tsx index dd24ca9c..cee5add3 100644 --- a/lightrag_webui/src/features/DocumentManager.tsx +++ b/lightrag_webui/src/features/DocumentManager.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useSettingsStore } from '@/stores/settings' import Button from '@/components/ui/Button' +import { cn } from '@/lib/utils' import { Table, TableBody, @@ -45,15 +46,67 @@ const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): str : fileName; }; +const pulseStyle = ` +@keyframes pulse { + 0% { + background-color: rgb(255 0 0 / 0.1); + border-color: rgb(255 0 0 / 0.2); + } + 50% { + background-color: rgb(255 0 0 / 0.2); + border-color: rgb(255 0 0 / 0.4); + } + 100% { + background-color: rgb(255 0 0 / 0.1); + border-color: rgb(255 0 0 / 0.2); + } +} + +.dark .pipeline-busy { + animation: dark-pulse 2s infinite; +} + +@keyframes dark-pulse { + 0% { + background-color: rgb(255 0 0 / 0.2); + border-color: rgb(255 0 0 / 0.4); + } + 50% { + background-color: rgb(255 0 0 / 0.3); + border-color: rgb(255 0 0 / 0.6); + } + 100% { + background-color: rgb(255 0 0 / 0.2); + border-color: rgb(255 0 0 / 0.4); + } +} + +.pipeline-busy { + animation: pulse 2s infinite; + border: 1px solid; +} +`; + export default function DocumentManager() { const [showPipelineStatus, setShowPipelineStatus] = useState(false) const { t } = useTranslation() const health = useBackendState.use.health() + const pipelineBusy = useBackendState.use.pipelineBusy() const [docs, setDocs] = useState(null) const currentTab = useSettingsStore.use.currentTab() const showFileName = useSettingsStore.use.showFileName() const setShowFileName = useSettingsStore.use.setShowFileName() + // Add pulse style to document + useEffect(() => { + const style = document.createElement('style') + style.textContent = pulseStyle + document.head.appendChild(style) + return () => { + document.head.removeChild(style) + } + }, []) + const fetchDocuments = useCallback(async () => { try { const docs = await getDocuments() @@ -132,6 +185,9 @@ export default function DocumentManager() { side="bottom" tooltip={t('documentPanel.documentManager.pipelineStatusTooltip')} size="sm" + className={cn( + pipelineBusy && 'pipeline-busy' + )} > {t('documentPanel.documentManager.pipelineStatusButton')} diff --git a/lightrag_webui/src/stores/state.ts b/lightrag_webui/src/stores/state.ts index 20b5ff27..a860f84d 100644 --- a/lightrag_webui/src/stores/state.ts +++ b/lightrag_webui/src/stores/state.ts @@ -6,14 +6,14 @@ interface BackendState { health: boolean message: string | null messageTitle: string | null - status: LightragStatus | null - lastCheckTime: number + pipelineBusy: boolean check: () => Promise clear: () => void setErrorMessage: (message: string, messageTitle: string) => void + setPipelineBusy: (busy: boolean) => void } interface AuthState { @@ -34,6 +34,7 @@ const useBackendStateStoreBase = create()((set) => ({ messageTitle: null, lastCheckTime: Date.now(), status: null, + pipelineBusy: false, check: async () => { const health = await checkHealth() @@ -51,7 +52,8 @@ const useBackendStateStoreBase = create()((set) => ({ message: null, messageTitle: null, lastCheckTime: Date.now(), - status: health + status: health, + pipelineBusy: health.pipeline_busy }) return true } @@ -71,6 +73,10 @@ const useBackendStateStoreBase = create()((set) => ({ setErrorMessage: (message: string, messageTitle: string) => { set({ health: false, message, messageTitle }) + }, + + setPipelineBusy: (busy: boolean) => { + set({ pipelineBusy: busy }) } }))