Daniel.y commited on
Commit
92d15a2
·
unverified ·
2 Parent(s): 79cb7e5 4ebfed0

Merge pull request #1202 from danielaskdd/sort-file

Browse files
lightrag/api/webui/assets/{index-CZkfsko8.js → index-BoPw3HVA.js} RENAMED
Binary files a/lightrag/api/webui/assets/index-CZkfsko8.js and b/lightrag/api/webui/assets/index-BoPw3HVA.js differ
 
lightrag/api/webui/assets/index-Bwboeqcm.css ADDED
Binary file (55 kB). View file
 
lightrag/api/webui/assets/index-CP4Boz-Y.css DELETED
Binary file (55.4 kB)
 
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
@@ -21,7 +21,7 @@ import { errorMessage } from '@/lib/utils'
21
  import { toast } from 'sonner'
22
  import { useBackendState } from '@/stores/state'
23
 
24
- import { RefreshCwIcon, ActivityIcon } from 'lucide-react'
25
  import { DocStatusResponse } from '@/api/lightrag'
26
  import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
27
 
@@ -47,18 +47,41 @@ const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): str
47
  };
48
 
49
  const pulseStyle = `
50
- /* Fixed tooltip styles for small tables */
51
- .tooltip-fixed {
52
- position: fixed !important;
53
- z-index: 9999 !important;
54
- }
55
-
56
- /* Parent container for tooltips */
57
  .tooltip-container {
58
  position: relative;
59
  overflow: visible !important;
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  @keyframes pulse {
63
  0% {
64
  background-color: rgb(255 0 0 / 0.1);
@@ -99,6 +122,10 @@ const pulseStyle = `
99
  }
100
  `;
101
 
 
 
 
 
102
  export default function DocumentManager() {
103
  const [showPipelineStatus, setShowPipelineStatus] = useState(false)
104
  const { t } = useTranslation()
@@ -109,6 +136,52 @@ export default function DocumentManager() {
109
  const showFileName = useSettingsStore.use.showFileName()
110
  const setShowFileName = useSettingsStore.use.setShowFileName()
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  // Store previous status counts
113
  const prevStatusCounts = useRef({
114
  processed: 0,
@@ -130,64 +203,47 @@ export default function DocumentManager() {
130
  // Reference to the card content element
131
  const cardContentRef = useRef<HTMLDivElement>(null);
132
 
133
- // Add tooltip position adjustment based on mouse position
134
  useEffect(() => {
135
  if (!docs) return;
136
 
137
- // Function to handle mouse movement - throttled to reduce layout calculations
138
- let lastExecution = 0;
139
- const throttleInterval = 50; // ms
140
-
141
- const handleMouseMove = () => {
142
- const now = Date.now();
143
- if (now - lastExecution < throttleInterval) return;
144
- lastExecution = now;
145
-
146
- const cardContent = cardContentRef.current;
147
- if (!cardContent) return;
148
-
149
- // Get all visible tooltips
150
- const visibleTooltips = document.querySelectorAll<HTMLElement>('.group:hover > div[class*="invisible group-hover:visible absolute"]');
151
- if (visibleTooltips.length === 0) return;
152
-
153
- visibleTooltips.forEach(tooltip => {
154
- // Get the parent element that triggered the tooltip
155
- const triggerElement = tooltip.parentElement;
156
- if (!triggerElement) return;
157
 
158
- const triggerRect = triggerElement.getBoundingClientRect();
 
 
159
 
160
- // Use fixed positioning for all tooltips
161
- tooltip.classList.add('tooltip-fixed');
162
 
163
- // Calculate position based on trigger element
164
- const tooltipHeight = tooltip.offsetHeight;
165
- const viewportHeight = window.innerHeight;
166
 
167
- // Check if tooltip would go off the bottom of the viewport
168
- const wouldOverflowBottom = triggerRect.bottom + tooltipHeight + 5 > viewportHeight;
 
 
 
 
169
 
170
- if (wouldOverflowBottom) {
171
- // Position above the trigger
172
- tooltip.style.top = `${triggerRect.top - tooltipHeight - 5}px`;
173
- tooltip.style.bottom = 'auto';
174
- } else {
175
- // Position below the trigger
176
- tooltip.style.top = `${triggerRect.bottom + 5}px`;
177
- tooltip.style.bottom = 'auto';
178
- }
179
 
180
- // Horizontal positioning
181
- tooltip.style.left = `${triggerRect.left}px`;
182
- tooltip.style.maxWidth = '600px';
183
- });
184
  };
185
 
186
- // Add mouse move listener to the document
187
- document.addEventListener('mousemove', handleMouseMove);
188
 
189
  return () => {
190
- document.removeEventListener('mousemove', handleMouseMove);
191
  };
192
  }, [docs]);
193
 
@@ -268,6 +324,11 @@ export default function DocumentManager() {
268
  return () => clearInterval(interval)
269
  }, [health, fetchDocuments, t, currentTab])
270
 
 
 
 
 
 
271
  return (
272
  <Card className="!rounded-none !overflow-hidden flex flex-col h-full min-h-0">
273
  <CardHeader className="py-2 px-6">
@@ -340,22 +401,61 @@ export default function DocumentManager() {
340
  )}
341
  {docs && (
342
  <div className="absolute inset-0 flex flex-col p-0">
343
- <div className="w-full h-full flex flex-col rounded-lg border border-gray-200 dark:border-gray-700 overflow-auto">
344
  <Table className="w-full">
345
  <TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
346
  <TableRow className="border-b bg-card/95 backdrop-blur supports-[backdrop-filter]:bg-card/75 shadow-[inset_0_-1px_0_rgba(0,0,0,0.1)]">
347
- <TableHead>{t('documentPanel.documentManager.columns.id')}</TableHead>
 
 
 
 
 
 
 
 
 
 
 
 
348
  <TableHead>{t('documentPanel.documentManager.columns.summary')}</TableHead>
349
  <TableHead>{t('documentPanel.documentManager.columns.status')}</TableHead>
350
  <TableHead>{t('documentPanel.documentManager.columns.length')}</TableHead>
351
  <TableHead>{t('documentPanel.documentManager.columns.chunks')}</TableHead>
352
- <TableHead>{t('documentPanel.documentManager.columns.created')}</TableHead>
353
- <TableHead>{t('documentPanel.documentManager.columns.updated')}</TableHead>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  </TableRow>
355
  </TableHeader>
356
  <TableBody className="text-sm overflow-auto">
357
- {Object.entries(docs.statuses).map(([status, documents]) =>
358
- documents.map((doc) => (
 
 
 
359
  <TableRow key={doc.id}>
360
  <TableCell className="truncate font-mono overflow-visible max-w-[250px]">
361
  {showFileName ? (
@@ -364,7 +464,7 @@ export default function DocumentManager() {
364
  <div className="truncate">
365
  {getDisplayFileName(doc, 30)}
366
  </div>
367
- <div className="invisible group-hover:visible absolute z-[9999] mt-1 max-w-[600px] whitespace-normal break-all rounded-md bg-black/95 px-3 py-2 text-sm text-white shadow-lg dark:bg-white/95 dark:text-black">
368
  {doc.file_path}
369
  </div>
370
  </div>
@@ -375,7 +475,7 @@ export default function DocumentManager() {
375
  <div className="truncate">
376
  {doc.id}
377
  </div>
378
- <div className="invisible group-hover:visible absolute z-[9999] mt-1 max-w-[600px] whitespace-normal break-all rounded-md bg-black/95 px-3 py-2 text-sm text-white shadow-lg dark:bg-white/95 dark:text-black">
379
  {doc.file_path}
380
  </div>
381
  </div>
@@ -386,7 +486,7 @@ export default function DocumentManager() {
386
  <div className="truncate">
387
  {doc.content_summary}
388
  </div>
389
- <div className="invisible group-hover:visible absolute z-[9999] mt-1 max-w-[600px] whitespace-normal break-all rounded-md bg-black/95 px-3 py-2 text-sm text-white shadow-lg dark:bg-white/95 dark:text-black">
390
  {doc.content_summary}
391
  </div>
392
  </div>
@@ -415,8 +515,8 @@ export default function DocumentManager() {
415
  {new Date(doc.updated_at).toLocaleString()}
416
  </TableCell>
417
  </TableRow>
418
- ))
419
- )}
420
  </TableBody>
421
  </Table>
422
  </div>
 
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
 
 
47
  };
48
 
49
  const pulseStyle = `
50
+ /* Tooltip styles */
 
 
 
 
 
 
51
  .tooltip-container {
52
  position: relative;
53
  overflow: visible !important;
54
  }
55
 
56
+ .tooltip {
57
+ position: fixed; /* Use fixed positioning to escape overflow constraints */
58
+ z-index: 9999; /* Ensure tooltip appears above all other elements */
59
+ max-width: 600px;
60
+ white-space: normal;
61
+ border-radius: 0.375rem;
62
+ padding: 0.5rem 0.75rem;
63
+ background-color: rgba(0, 0, 0, 0.95);
64
+ color: white;
65
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
66
+ pointer-events: none; /* Prevent tooltip from interfering with mouse events */
67
+ }
68
+
69
+ .dark .tooltip {
70
+ background-color: rgba(255, 255, 255, 0.95);
71
+ color: black;
72
+ }
73
+
74
+ /* Position tooltip helper class */
75
+ .tooltip-helper {
76
+ position: absolute;
77
+ visibility: hidden;
78
+ pointer-events: none;
79
+ top: 0;
80
+ left: 0;
81
+ width: 100%;
82
+ height: 0;
83
+ }
84
+
85
  @keyframes pulse {
86
  0% {
87
  background-color: rgb(255 0 0 / 0.1);
 
122
  }
123
  `;
124
 
125
+ // Type definitions for sort field and direction
126
+ type SortField = 'created_at' | 'updated_at' | 'id';
127
+ type SortDirection = 'asc' | 'desc';
128
+
129
  export default function DocumentManager() {
130
  const [showPipelineStatus, setShowPipelineStatus] = useState(false)
131
  const { t } = useTranslation()
 
136
  const showFileName = useSettingsStore.use.showFileName()
137
  const setShowFileName = useSettingsStore.use.setShowFileName()
138
 
139
+ // Sort state
140
+ const [sortField, setSortField] = useState<SortField>('updated_at')
141
+ const [sortDirection, setSortDirection] = useState<SortDirection>('desc')
142
+
143
+ // Handle sort column click
144
+ const handleSort = (field: SortField) => {
145
+ if (sortField === field) {
146
+ // Toggle sort direction if clicking the same field
147
+ setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc')
148
+ } else {
149
+ // Set new sort field with default desc direction
150
+ setSortField(field)
151
+ setSortDirection('desc')
152
+ }
153
+ }
154
+
155
+ // Sort documents based on current sort field and direction
156
+ const sortDocuments = (documents: DocStatusResponse[]) => {
157
+ return [...documents].sort((a, b) => {
158
+ let valueA, valueB;
159
+
160
+ // Special handling for ID field based on showFileName setting
161
+ if (sortField === 'id' && showFileName) {
162
+ valueA = getDisplayFileName(a);
163
+ valueB = getDisplayFileName(b);
164
+ } else if (sortField === 'id') {
165
+ valueA = a.id;
166
+ valueB = b.id;
167
+ } else {
168
+ // Date fields
169
+ valueA = new Date(a[sortField]).getTime();
170
+ valueB = new Date(b[sortField]).getTime();
171
+ }
172
+
173
+ // Apply sort direction
174
+ const sortMultiplier = sortDirection === 'asc' ? 1 : -1;
175
+
176
+ // Compare values
177
+ if (typeof valueA === 'string' && typeof valueB === 'string') {
178
+ return sortMultiplier * valueA.localeCompare(valueB);
179
+ } else {
180
+ return sortMultiplier * (valueA > valueB ? 1 : valueA < valueB ? -1 : 0);
181
+ }
182
+ });
183
+ }
184
+
185
  // Store previous status counts
186
  const prevStatusCounts = useRef({
187
  processed: 0,
 
203
  // Reference to the card content element
204
  const cardContentRef = useRef<HTMLDivElement>(null);
205
 
206
+ // Add tooltip position adjustment for fixed positioning
207
  useEffect(() => {
208
  if (!docs) return;
209
 
210
+ // Function to position tooltips
211
+ const positionTooltips = () => {
212
+ // Get all tooltip containers
213
+ const containers = document.querySelectorAll<HTMLElement>('.tooltip-container');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
+ containers.forEach(container => {
216
+ const tooltip = container.querySelector<HTMLElement>('.tooltip');
217
+ if (!tooltip) return;
218
 
219
+ // Only position visible tooltips
220
+ if (getComputedStyle(tooltip).visibility === 'hidden') return;
221
 
222
+ // Get container position
223
+ const rect = container.getBoundingClientRect();
 
224
 
225
+ // Position tooltip above the container
226
+ tooltip.style.left = `${rect.left}px`;
227
+ tooltip.style.top = `${rect.top - 5}px`;
228
+ tooltip.style.transform = 'translateY(-100%)';
229
+ });
230
+ };
231
 
232
+ // Set up event listeners
233
+ const handleMouseOver = (e: MouseEvent) => {
234
+ // Check if target or its parent is a tooltip container
235
+ const target = e.target as HTMLElement;
236
+ const container = target.closest('.tooltip-container');
237
+ if (!container) return;
 
 
 
238
 
239
+ // Small delay to ensure tooltip is visible before positioning
240
+ setTimeout(positionTooltips, 10);
 
 
241
  };
242
 
243
+ document.addEventListener('mouseover', handleMouseOver);
 
244
 
245
  return () => {
246
+ document.removeEventListener('mouseover', handleMouseOver);
247
  };
248
  }, [docs]);
249
 
 
324
  return () => clearInterval(interval)
325
  }, [health, fetchDocuments, t, currentTab])
326
 
327
+ // Add dependency on sort state to re-render when sort changes
328
+ useEffect(() => {
329
+ // This effect ensures the component re-renders when sort state changes
330
+ }, [sortField, sortDirection]);
331
+
332
  return (
333
  <Card className="!rounded-none !overflow-hidden flex flex-col h-full min-h-0">
334
  <CardHeader className="py-2 px-6">
 
401
  )}
402
  {docs && (
403
  <div className="absolute inset-0 flex flex-col p-0">
404
+ <div className="w-full h-full flex flex-col border border-gray-200 dark:border-gray-700 overflow-hidden">
405
  <Table className="w-full">
406
  <TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
407
  <TableRow className="border-b bg-card/95 backdrop-blur supports-[backdrop-filter]:bg-card/75 shadow-[inset_0_-1px_0_rgba(0,0,0,0.1)]">
408
+ <TableHead
409
+ onClick={() => handleSort('id')}
410
+ className="cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800 select-none"
411
+ >
412
+ <div className="flex items-center">
413
+ {t('documentPanel.documentManager.columns.id')}
414
+ {sortField === 'id' && (
415
+ <span className="ml-1">
416
+ {sortDirection === 'asc' ? <ArrowUpIcon size={14} /> : <ArrowDownIcon size={14} />}
417
+ </span>
418
+ )}
419
+ </div>
420
+ </TableHead>
421
  <TableHead>{t('documentPanel.documentManager.columns.summary')}</TableHead>
422
  <TableHead>{t('documentPanel.documentManager.columns.status')}</TableHead>
423
  <TableHead>{t('documentPanel.documentManager.columns.length')}</TableHead>
424
  <TableHead>{t('documentPanel.documentManager.columns.chunks')}</TableHead>
425
+ <TableHead
426
+ onClick={() => handleSort('created_at')}
427
+ className="cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800 select-none"
428
+ >
429
+ <div className="flex items-center">
430
+ {t('documentPanel.documentManager.columns.created')}
431
+ {sortField === 'created_at' && (
432
+ <span className="ml-1">
433
+ {sortDirection === 'asc' ? <ArrowUpIcon size={14} /> : <ArrowDownIcon size={14} />}
434
+ </span>
435
+ )}
436
+ </div>
437
+ </TableHead>
438
+ <TableHead
439
+ onClick={() => handleSort('updated_at')}
440
+ className="cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800 select-none"
441
+ >
442
+ <div className="flex items-center">
443
+ {t('documentPanel.documentManager.columns.updated')}
444
+ {sortField === 'updated_at' && (
445
+ <span className="ml-1">
446
+ {sortDirection === 'asc' ? <ArrowUpIcon size={14} /> : <ArrowDownIcon size={14} />}
447
+ </span>
448
+ )}
449
+ </div>
450
+ </TableHead>
451
  </TableRow>
452
  </TableHeader>
453
  <TableBody className="text-sm overflow-auto">
454
+ {Object.entries(docs.statuses).flatMap(([status, documents]) => {
455
+ // Apply sorting to documents
456
+ const sortedDocuments = sortDocuments(documents);
457
+
458
+ return sortedDocuments.map(doc => (
459
  <TableRow key={doc.id}>
460
  <TableCell className="truncate font-mono overflow-visible max-w-[250px]">
461
  {showFileName ? (
 
464
  <div className="truncate">
465
  {getDisplayFileName(doc, 30)}
466
  </div>
467
+ <div className="invisible group-hover:visible tooltip">
468
  {doc.file_path}
469
  </div>
470
  </div>
 
475
  <div className="truncate">
476
  {doc.id}
477
  </div>
478
+ <div className="invisible group-hover:visible tooltip">
479
  {doc.file_path}
480
  </div>
481
  </div>
 
486
  <div className="truncate">
487
  {doc.content_summary}
488
  </div>
489
+ <div className="invisible group-hover:visible tooltip">
490
  {doc.content_summary}
491
  </div>
492
  </div>
 
515
  {new Date(doc.updated_at).toLocaleString()}
516
  </TableCell>
517
  </TableRow>
518
+ ));
519
+ })}
520
  </TableBody>
521
  </Table>
522
  </div>