import { useState, useEffect, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useSettingsStore } from '@/stores/settings' import Button from '@/components/ui/Button' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/Table' import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card' import EmptyCard from '@/components/ui/EmptyCard' import Text from '@/components/ui/Text' import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog' import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog' import { getDocuments, scanNewDocuments, DocsStatusesResponse } from '@/api/lightrag' import { errorMessage } from '@/lib/utils' import { toast } from 'sonner' import { useBackendState } from '@/stores/state' import { RefreshCwIcon } from 'lucide-react' export default function DocumentManager() { const { t } = useTranslation() const health = useBackendState.use.health() const [docs, setDocs] = useState(null) const currentTab = useSettingsStore.use.currentTab() const fetchDocuments = useCallback(async () => { try { const docs = await getDocuments() if (docs && docs.statuses) { // compose all documents count const numDocuments = Object.values(docs.statuses).reduce( (acc, status) => acc + status.length, 0 ) if (numDocuments > 0) { setDocs(docs) } else { setDocs(null) } } else { setDocs(null) } } catch (err) { toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) })) } }, [setDocs, t]) // Fetch documents when the tab becomes visible useEffect(() => { if (currentTab === 'documents') { fetchDocuments() } }, [currentTab, fetchDocuments]) const scanDocuments = useCallback(async () => { try { const { status } = await scanNewDocuments() toast.message(status) } catch (err) { toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) })) } }, [t]) // Set up polling when the documents tab is active and health is good useEffect(() => { if (currentTab !== 'documents' || !health) { return } const interval = setInterval(async () => { try { await fetchDocuments() } catch (err) { toast.error(t('documentPanel.documentManager.errors.scanProgressFailed', { error: errorMessage(err) })) } }, 5000) return () => clearInterval(interval) }, [health, fetchDocuments, t, currentTab]) return ( {t('documentPanel.documentManager.title')}
{t('documentPanel.documentManager.uploadedTitle')} {t('documentPanel.documentManager.uploadedDescription')} {!docs && ( )} {docs && ( {t('documentPanel.documentManager.columns.id')} {t('documentPanel.documentManager.columns.summary')} {t('documentPanel.documentManager.columns.status')} {t('documentPanel.documentManager.columns.length')} {t('documentPanel.documentManager.columns.chunks')} {t('documentPanel.documentManager.columns.created')} {t('documentPanel.documentManager.columns.updated')} {Object.entries(docs.statuses).map(([status, documents]) => documents.map((doc) => ( {doc.id} {status === 'processed' && ( {t('documentPanel.documentManager.status.completed')} )} {status === 'processing' && ( {t('documentPanel.documentManager.status.processing')} )} {status === 'pending' && {t('documentPanel.documentManager.status.pending')}} {status === 'failed' && {t('documentPanel.documentManager.status.failed')}} {doc.error && ( ⚠️ )} {doc.content_length ?? '-'} {doc.chunks_count ?? '-'} {new Date(doc.created_at).toLocaleString()} {new Date(doc.updated_at).toLocaleString()} )) )}
)}
) }