Daniel.y commited on
Commit
5931898
·
unverified ·
2 Parent(s): 29c5d74 14d6097

Merge pull request #1198 from danielaskdd/main

Browse files

Improve document TAB layout and tooltips handling logic

lightrag/api/webui/assets/index-CP4Boz-Y.css ADDED
Binary file (55.4 kB). View file
 
lightrag/api/webui/assets/{index-DTDDxtXc.js → index-CZkfsko8.js} RENAMED
Binary files a/lightrag/api/webui/assets/index-DTDDxtXc.js and b/lightrag/api/webui/assets/index-CZkfsko8.js differ
 
lightrag/api/webui/assets/index-CbzkrOyx.css DELETED
Binary file (54.9 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
@@ -47,6 +47,18 @@ const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): str
47
  };
48
 
49
  const pulseStyle = `
 
 
 
 
 
 
 
 
 
 
 
 
50
  @keyframes pulse {
51
  0% {
52
  background-color: rgb(255 0 0 / 0.1);
@@ -115,6 +127,70 @@ export default function DocumentManager() {
115
  }
116
  }, [])
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  const fetchDocuments = useCallback(async () => {
119
  try {
120
  const docs = await getDocuments()
@@ -193,12 +269,12 @@ export default function DocumentManager() {
193
  }, [health, fetchDocuments, t, currentTab])
194
 
195
  return (
196
- <Card className="!size-full !rounded-none !border-none">
197
- <CardHeader>
198
  <CardTitle className="text-lg">{t('documentPanel.documentManager.title')}</CardTitle>
199
  </CardHeader>
200
- <CardContent className="space-y-4">
201
- <div className="flex gap-2">
202
  <div className="flex gap-2">
203
  <Button
204
  variant="outline"
@@ -231,8 +307,8 @@ export default function DocumentManager() {
231
  />
232
  </div>
233
 
234
- <Card>
235
- <CardHeader>
236
  <div className="flex justify-between items-center">
237
  <CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
238
  <div className="flex items-center gap-2">
@@ -250,95 +326,101 @@ export default function DocumentManager() {
250
  </Button>
251
  </div>
252
  </div>
253
- <CardDescription>{t('documentPanel.documentManager.uploadedDescription')}</CardDescription>
254
  </CardHeader>
255
 
256
- <CardContent>
257
  {!docs && (
258
- <EmptyCard
259
- title={t('documentPanel.documentManager.emptyTitle')}
260
- description={t('documentPanel.documentManager.emptyDescription')}
261
- />
 
 
262
  )}
263
  {docs && (
264
- <Table>
265
- <TableHeader>
266
- <TableRow>
267
- <TableHead>{t('documentPanel.documentManager.columns.id')}</TableHead>
268
- <TableHead>{t('documentPanel.documentManager.columns.summary')}</TableHead>
269
- <TableHead>{t('documentPanel.documentManager.columns.status')}</TableHead>
270
- <TableHead>{t('documentPanel.documentManager.columns.length')}</TableHead>
271
- <TableHead>{t('documentPanel.documentManager.columns.chunks')}</TableHead>
272
- <TableHead>{t('documentPanel.documentManager.columns.created')}</TableHead>
273
- <TableHead>{t('documentPanel.documentManager.columns.updated')}</TableHead>
274
- </TableRow>
275
- </TableHeader>
276
- <TableBody className="text-sm">
277
- {Object.entries(docs.statuses).map(([status, documents]) =>
278
- documents.map((doc) => (
279
- <TableRow key={doc.id}>
280
- <TableCell className="truncate font-mono overflow-visible">
281
- {showFileName ? (
282
- <>
283
- <div className="group relative overflow-visible">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  <div className="truncate">
285
- {getDisplayFileName(doc, 35)}
286
  </div>
287
- <div className="invisible group-hover:visible absolute z-[9999] mt-1 max-w-[800px] 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">
288
- {doc.file_path}
289
  </div>
290
  </div>
291
- <div className="text-xs text-gray-500">{doc.id}</div>
292
- </>
293
- ) : (
294
- <div className="group relative overflow-visible">
295
- <div className="truncate">
296
- {doc.id}
297
- </div>
298
- <div className="invisible group-hover:visible absolute z-[9999] mt-1 max-w-[800px] 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">
299
- {doc.file_path}
300
- </div>
301
- </div>
302
- )}
303
- </TableCell>
304
- <TableCell className="max-w-xs min-w-24 truncate overflow-visible">
305
- <div className="group relative overflow-visible">
306
- <div className="truncate">
307
- {doc.content_summary}
308
- </div>
309
- <div className="invisible group-hover:visible absolute z-[9999] mt-1 max-w-[800px] 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">
310
- {doc.content_summary}
311
- </div>
312
- </div>
313
- </TableCell>
314
- <TableCell>
315
- {status === 'processed' && (
316
- <span className="text-green-600">{t('documentPanel.documentManager.status.completed')}</span>
317
- )}
318
- {status === 'processing' && (
319
- <span className="text-blue-600">{t('documentPanel.documentManager.status.processing')}</span>
320
- )}
321
- {status === 'pending' && <span className="text-yellow-600">{t('documentPanel.documentManager.status.pending')}</span>}
322
- {status === 'failed' && <span className="text-red-600">{t('documentPanel.documentManager.status.failed')}</span>}
323
- {doc.error && (
324
- <span className="ml-2 text-red-500" title={doc.error}>
325
- ⚠️
326
- </span>
327
- )}
328
- </TableCell>
329
- <TableCell>{doc.content_length ?? '-'}</TableCell>
330
- <TableCell>{doc.chunks_count ?? '-'}</TableCell>
331
- <TableCell className="truncate">
332
- {new Date(doc.created_at).toLocaleString()}
333
- </TableCell>
334
- <TableCell className="truncate">
335
- {new Date(doc.updated_at).toLocaleString()}
336
- </TableCell>
337
- </TableRow>
338
- ))
339
- )}
340
- </TableBody>
341
- </Table>
342
  )}
343
  </CardContent>
344
  </Card>
 
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);
 
127
  }
128
  }, [])
129
 
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
+
194
  const fetchDocuments = useCallback(async () => {
195
  try {
196
  const docs = await getDocuments()
 
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">
274
  <CardTitle className="text-lg">{t('documentPanel.documentManager.title')}</CardTitle>
275
  </CardHeader>
276
+ <CardContent className="flex-1 flex flex-col min-h-0 overflow-auto">
277
+ <div className="flex gap-2 mb-2">
278
  <div className="flex gap-2">
279
  <Button
280
  variant="outline"
 
307
  />
308
  </div>
309
 
310
+ <Card className="flex-1 flex flex-col border rounded-md min-h-0 mb-0">
311
+ <CardHeader className="flex-none py-2 px-4">
312
  <div className="flex justify-between items-center">
313
  <CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
314
  <div className="flex items-center gap-2">
 
326
  </Button>
327
  </div>
328
  </div>
329
+ <CardDescription aria-hidden="true" className="hidden">{t('documentPanel.documentManager.uploadedDescription')}</CardDescription>
330
  </CardHeader>
331
 
332
+ <CardContent className="flex-1 relative p-0" ref={cardContentRef}>
333
  {!docs && (
334
+ <div className="absolute inset-0 p-0">
335
+ <EmptyCard
336
+ title={t('documentPanel.documentManager.emptyTitle')}
337
+ description={t('documentPanel.documentManager.emptyDescription')}
338
+ />
339
+ </div>
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 ? (
362
+ <>
363
+ <div className="group relative overflow-visible tooltip-container">
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>
371
+ <div className="text-xs text-gray-500">{doc.id}</div>
372
+ </>
373
+ ) : (
374
+ <div className="group relative overflow-visible tooltip-container">
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>
382
+ )}
383
+ </TableCell>
384
+ <TableCell className="max-w-xs min-w-45 truncate overflow-visible">
385
+ <div className="group relative overflow-visible tooltip-container">
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>
393
+ </TableCell>
394
+ <TableCell>
395
+ {status === 'processed' && (
396
+ <span className="text-green-600">{t('documentPanel.documentManager.status.completed')}</span>
397
+ )}
398
+ {status === 'processing' && (
399
+ <span className="text-blue-600">{t('documentPanel.documentManager.status.processing')}</span>
400
+ )}
401
+ {status === 'pending' && <span className="text-yellow-600">{t('documentPanel.documentManager.status.pending')}</span>}
402
+ {status === 'failed' && <span className="text-red-600">{t('documentPanel.documentManager.status.failed')}</span>}
403
+ {doc.error && (
404
+ <span className="ml-2 text-red-500" title={doc.error}>
405
+ ⚠️
406
+ </span>
407
+ )}
408
+ </TableCell>
409
+ <TableCell>{doc.content_length ?? '-'}</TableCell>
410
+ <TableCell>{doc.chunks_count ?? '-'}</TableCell>
411
+ <TableCell className="truncate">
412
+ {new Date(doc.created_at).toLocaleString()}
413
+ </TableCell>
414
+ <TableCell className="truncate">
415
+ {new Date(doc.updated_at).toLocaleString()}
416
+ </TableCell>
417
+ </TableRow>
418
+ ))
419
+ )}
420
+ </TableBody>
421
+ </Table>
422
+ </div>
423
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  )}
425
  </CardContent>
426
  </Card>