feat: add pipeline busy status indicator with breathing effect

- Add pipeline_busy field to health check response
- Track pipeline busy state in frontend store
- Add breathing animation for pipeline status button
- Enhance dark mode visibility with stronger contrast
This commit is contained in:
yangdx
2025-03-26 13:11:53 +08:00
parent 51be3fcfa5
commit 814f3b3308
3 changed files with 66 additions and 3 deletions

View File

@@ -45,6 +45,7 @@ export type LightragStatus = {
core_version?: string core_version?: string
api_version?: string api_version?: string
auth_mode?: 'enabled' | 'disabled' auth_mode?: 'enabled' | 'disabled'
pipeline_busy: boolean
} }
export type LightragDocumentsScanProgress = { export type LightragDocumentsScanProgress = {

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import { cn } from '@/lib/utils'
import { import {
Table, Table,
TableBody, TableBody,
@@ -45,15 +46,67 @@ const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): str
: fileName; : 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() { export default function DocumentManager() {
const [showPipelineStatus, setShowPipelineStatus] = useState(false) const [showPipelineStatus, setShowPipelineStatus] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
const health = useBackendState.use.health() const health = useBackendState.use.health()
const pipelineBusy = useBackendState.use.pipelineBusy()
const [docs, setDocs] = useState<DocsStatusesResponse | null>(null) const [docs, setDocs] = useState<DocsStatusesResponse | null>(null)
const currentTab = useSettingsStore.use.currentTab() const currentTab = useSettingsStore.use.currentTab()
const showFileName = useSettingsStore.use.showFileName() const showFileName = useSettingsStore.use.showFileName()
const setShowFileName = useSettingsStore.use.setShowFileName() 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 () => { const fetchDocuments = useCallback(async () => {
try { try {
const docs = await getDocuments() const docs = await getDocuments()
@@ -132,6 +185,9 @@ export default function DocumentManager() {
side="bottom" side="bottom"
tooltip={t('documentPanel.documentManager.pipelineStatusTooltip')} tooltip={t('documentPanel.documentManager.pipelineStatusTooltip')}
size="sm" size="sm"
className={cn(
pipelineBusy && 'pipeline-busy'
)}
> >
<ActivityIcon /> {t('documentPanel.documentManager.pipelineStatusButton')} <ActivityIcon /> {t('documentPanel.documentManager.pipelineStatusButton')}
</Button> </Button>

View File

@@ -6,14 +6,14 @@ interface BackendState {
health: boolean health: boolean
message: string | null message: string | null
messageTitle: string | null messageTitle: string | null
status: LightragStatus | null status: LightragStatus | null
lastCheckTime: number lastCheckTime: number
pipelineBusy: boolean
check: () => Promise<boolean> check: () => Promise<boolean>
clear: () => void clear: () => void
setErrorMessage: (message: string, messageTitle: string) => void setErrorMessage: (message: string, messageTitle: string) => void
setPipelineBusy: (busy: boolean) => void
} }
interface AuthState { interface AuthState {
@@ -34,6 +34,7 @@ const useBackendStateStoreBase = create<BackendState>()((set) => ({
messageTitle: null, messageTitle: null,
lastCheckTime: Date.now(), lastCheckTime: Date.now(),
status: null, status: null,
pipelineBusy: false,
check: async () => { check: async () => {
const health = await checkHealth() const health = await checkHealth()
@@ -51,7 +52,8 @@ const useBackendStateStoreBase = create<BackendState>()((set) => ({
message: null, message: null,
messageTitle: null, messageTitle: null,
lastCheckTime: Date.now(), lastCheckTime: Date.now(),
status: health status: health,
pipelineBusy: health.pipeline_busy
}) })
return true return true
} }
@@ -71,6 +73,10 @@ const useBackendStateStoreBase = create<BackendState>()((set) => ({
setErrorMessage: (message: string, messageTitle: string) => { setErrorMessage: (message: string, messageTitle: string) => {
set({ health: false, message, messageTitle }) set({ health: false, message, messageTitle })
},
setPipelineBusy: (busy: boolean) => {
set({ pipelineBusy: busy })
} }
})) }))