yangdx commited on
Commit
23f8583
·
1 Parent(s): 7ff0d05

Merge branch 'feat-document-filter'

Browse files
lightrag/api/webui/assets/{index-CldiPPHk.js → index-B0dMyyzE.js} RENAMED
Binary files a/lightrag/api/webui/assets/index-CldiPPHk.js and b/lightrag/api/webui/assets/index-B0dMyyzE.js differ
 
lightrag/api/webui/index.html CHANGED
Binary files a/lightrag/api/webui/index.html and b/lightrag/api/webui/index.html differ
 
lightrag_webui/src/features/DocumentManager.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useCallback, useRef } from 'react'
2
  import { useTranslation } from 'react-i18next'
3
  import { useSettingsStore } from '@/stores/settings'
4
  import Button from '@/components/ui/Button'
@@ -16,15 +16,17 @@ import EmptyCard from '@/components/ui/EmptyCard'
16
  import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
17
  import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
18
 
19
- import { getDocuments, scanNewDocuments, DocsStatusesResponse } from '@/api/lightrag'
20
  import { errorMessage } from '@/lib/utils'
21
  import { toast } from 'sonner'
22
  import { useBackendState } from '@/stores/state'
23
 
24
- import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react'
25
- import { DocStatusResponse } from '@/api/lightrag'
26
  import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
27
 
 
 
 
28
  const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): string => {
29
  // Check if file_path exists and is a non-empty string
30
  if (!doc.file_path || typeof doc.file_path !== 'string' || doc.file_path.trim() === '') {
@@ -148,6 +150,10 @@ export default function DocumentManager() {
148
  const [sortField, setSortField] = useState<SortField>('updated_at')
149
  const [sortDirection, setSortDirection] = useState<SortDirection>('desc')
150
 
 
 
 
 
151
  // Handle sort column click
152
  const handleSort = (field: SortField) => {
153
  if (sortField === field) {
@@ -190,6 +196,49 @@ export default function DocumentManager() {
190
  });
191
  }
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  // Store previous status counts
194
  const prevStatusCounts = useRef({
195
  processed: 0,
@@ -398,6 +447,50 @@ export default function DocumentManager() {
398
  <CardHeader className="flex-none py-2 px-4">
399
  <div className="flex justify-between items-center">
400
  <CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  <div className="flex items-center gap-2">
402
  <span className="text-sm text-gray-500">{t('documentPanel.documentManager.fileNameLabel')}</span>
403
  <Button
@@ -477,11 +570,8 @@ export default function DocumentManager() {
477
  </TableRow>
478
  </TableHeader>
479
  <TableBody className="text-sm overflow-auto">
480
- {Object.entries(docs.statuses).flatMap(([status, documents]) => {
481
- // Apply sorting to documents
482
- const sortedDocuments = sortDocuments(documents);
483
-
484
- return sortedDocuments.map(doc => (
485
  <TableRow key={doc.id}>
486
  <TableCell className="truncate font-mono overflow-visible max-w-[250px]">
487
  {showFileName ? (
@@ -541,8 +631,8 @@ export default function DocumentManager() {
541
  {new Date(doc.updated_at).toLocaleString()}
542
  </TableCell>
543
  </TableRow>
544
- ));
545
- })}
546
  </TableBody>
547
  </Table>
548
  </div>
 
1
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
2
  import { useTranslation } from 'react-i18next'
3
  import { useSettingsStore } from '@/stores/settings'
4
  import Button from '@/components/ui/Button'
 
16
  import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
17
  import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
18
 
19
+ import { getDocuments, scanNewDocuments, DocsStatusesResponse, DocStatus, DocStatusResponse } from '@/api/lightrag'
20
  import { errorMessage } from '@/lib/utils'
21
  import { toast } from 'sonner'
22
  import { useBackendState } from '@/stores/state'
23
 
24
+ import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon, FilterIcon } from 'lucide-react'
 
25
  import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
26
 
27
+ type StatusFilter = DocStatus | 'all';
28
+
29
+
30
  const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): string => {
31
  // Check if file_path exists and is a non-empty string
32
  if (!doc.file_path || typeof doc.file_path !== 'string' || doc.file_path.trim() === '') {
 
150
  const [sortField, setSortField] = useState<SortField>('updated_at')
151
  const [sortDirection, setSortDirection] = useState<SortDirection>('desc')
152
 
153
+ // State for document status filter
154
+ const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
155
+
156
+
157
  // Handle sort column click
158
  const handleSort = (field: SortField) => {
159
  if (sortField === field) {
 
196
  });
197
  }
198
 
199
+ const filteredAndSortedDocs = useMemo(() => {
200
+ if (!docs) return null;
201
+
202
+ let filteredDocs = { ...docs };
203
+
204
+ if (statusFilter !== 'all') {
205
+ filteredDocs = {
206
+ ...docs,
207
+ statuses: {
208
+ pending: [],
209
+ processing: [],
210
+ processed: [],
211
+ failed: [],
212
+ [statusFilter]: docs.statuses[statusFilter] || []
213
+ }
214
+ };
215
+ }
216
+
217
+ if (!sortField || !sortDirection) return filteredDocs;
218
+
219
+ const sortedStatuses = Object.entries(filteredDocs.statuses).reduce((acc, [status, documents]) => {
220
+ const sortedDocuments = sortDocuments(documents);
221
+ acc[status as DocStatus] = sortedDocuments;
222
+ return acc;
223
+ }, {} as DocsStatusesResponse['statuses']);
224
+
225
+ return { ...filteredDocs, statuses: sortedStatuses };
226
+ }, [docs, sortField, sortDirection, statusFilter]);
227
+
228
+ // Calculate document counts for each status
229
+ const documentCounts = useMemo(() => {
230
+ if (!docs) return { all: 0 } as Record<string, number>;
231
+
232
+ const counts: Record<string, number> = { all: 0 };
233
+
234
+ Object.entries(docs.statuses).forEach(([status, documents]) => {
235
+ counts[status as DocStatus] = documents.length;
236
+ counts.all += documents.length;
237
+ });
238
+
239
+ return counts;
240
+ }, [docs]);
241
+
242
  // Store previous status counts
243
  const prevStatusCounts = useRef({
244
  processed: 0,
 
447
  <CardHeader className="flex-none py-2 px-4">
448
  <div className="flex justify-between items-center">
449
  <CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
450
+ <div className="flex items-center gap-2">
451
+ <FilterIcon className="h-4 w-4" />
452
+ <div className="flex gap-1">
453
+ <Button
454
+ size="sm"
455
+ variant={statusFilter === 'all' ? 'secondary' : 'outline'}
456
+ onClick={() => setStatusFilter('all')}
457
+ >
458
+ {t('documentPanel.documentManager.status.all')} ({documentCounts.all})
459
+ </Button>
460
+ <Button
461
+ size="sm"
462
+ variant={statusFilter === 'processed' ? 'secondary' : 'outline'}
463
+ onClick={() => setStatusFilter('processed')}
464
+ className={documentCounts.processed > 0 ? 'text-green-600' : 'text-gray-500'}
465
+ >
466
+ {t('documentPanel.documentManager.status.completed')} ({documentCounts.processed || 0})
467
+ </Button>
468
+ <Button
469
+ size="sm"
470
+ variant={statusFilter === 'processing' ? 'secondary' : 'outline'}
471
+ onClick={() => setStatusFilter('processing')}
472
+ className={documentCounts.processing > 0 ? 'text-blue-600' : 'text-gray-500'}
473
+ >
474
+ {t('documentPanel.documentManager.status.processing')} ({documentCounts.processing || 0})
475
+ </Button>
476
+ <Button
477
+ size="sm"
478
+ variant={statusFilter === 'pending' ? 'secondary' : 'outline'}
479
+ onClick={() => setStatusFilter('pending')}
480
+ className={documentCounts.pending > 0 ? 'text-yellow-600' : 'text-gray-500'}
481
+ >
482
+ {t('documentPanel.documentManager.status.pending')} ({documentCounts.pending || 0})
483
+ </Button>
484
+ <Button
485
+ size="sm"
486
+ variant={statusFilter === 'failed' ? 'secondary' : 'outline'}
487
+ onClick={() => setStatusFilter('failed')}
488
+ className={documentCounts.failed > 0 ? 'text-red-600' : 'text-gray-500'}
489
+ >
490
+ {t('documentPanel.documentManager.status.failed')} ({documentCounts.failed || 0})
491
+ </Button>
492
+ </div>
493
+ </div>
494
  <div className="flex items-center gap-2">
495
  <span className="text-sm text-gray-500">{t('documentPanel.documentManager.fileNameLabel')}</span>
496
  <Button
 
570
  </TableRow>
571
  </TableHeader>
572
  <TableBody className="text-sm overflow-auto">
573
+ {filteredAndSortedDocs?.statuses && Object.entries(filteredAndSortedDocs.statuses).flatMap(([status, documents]) =>
574
+ documents.map((doc) => (
 
 
 
575
  <TableRow key={doc.id}>
576
  <TableCell className="truncate font-mono overflow-visible max-w-[250px]">
577
  {showFileName ? (
 
631
  {new Date(doc.updated_at).toLocaleString()}
632
  </TableCell>
633
  </TableRow>
634
+ )))
635
+ }
636
  </TableBody>
637
  </Table>
638
  </div>
lightrag_webui/src/locales/ar.json CHANGED
@@ -104,6 +104,7 @@
104
  "metadata": "البيانات الوصفية"
105
  },
106
  "status": {
 
107
  "completed": "مكتمل",
108
  "processing": "قيد المعالجة",
109
  "pending": "معلق",
 
104
  "metadata": "البيانات الوصفية"
105
  },
106
  "status": {
107
+ "all": "الكل",
108
  "completed": "مكتمل",
109
  "processing": "قيد المعالجة",
110
  "pending": "معلق",
lightrag_webui/src/locales/en.json CHANGED
@@ -104,6 +104,7 @@
104
  "metadata": "Metadata"
105
  },
106
  "status": {
 
107
  "completed": "Completed",
108
  "processing": "Processing",
109
  "pending": "Pending",
 
104
  "metadata": "Metadata"
105
  },
106
  "status": {
107
+ "all": "All",
108
  "completed": "Completed",
109
  "processing": "Processing",
110
  "pending": "Pending",
lightrag_webui/src/locales/fr.json CHANGED
@@ -104,6 +104,7 @@
104
  "metadata": "Métadonnées"
105
  },
106
  "status": {
 
107
  "completed": "Terminé",
108
  "processing": "En traitement",
109
  "pending": "En attente",
 
104
  "metadata": "Métadonnées"
105
  },
106
  "status": {
107
+ "all": "Tous",
108
  "completed": "Terminé",
109
  "processing": "En traitement",
110
  "pending": "En attente",
lightrag_webui/src/locales/zh.json CHANGED
@@ -104,6 +104,7 @@
104
  "metadata": "元数据"
105
  },
106
  "status": {
 
107
  "completed": "已完成",
108
  "processing": "处理中",
109
  "pending": "等待中",
 
104
  "metadata": "元数据"
105
  },
106
  "status": {
107
+ "all": "全部",
108
  "completed": "已完成",
109
  "processing": "处理中",
110
  "pending": "等待中",