yangdx commited on
Commit
c3d99d0
·
1 Parent(s): 321edf2

Refactor doc list layout to fix table head from scrolling out

Browse files
lightrag_webui/src/features/DocumentManager.tsx CHANGED
@@ -47,6 +47,14 @@ 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 +123,39 @@ export default function DocumentManager() {
115
  }
116
  }, [])
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  const fetchDocuments = useCallback(async () => {
119
  try {
120
  const docs = await getDocuments()
@@ -193,12 +234,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-2">
201
- <div className="flex gap-2">
202
  <div className="flex gap-2">
203
  <Button
204
  variant="outline"
@@ -231,8 +272,8 @@ export default function DocumentManager() {
231
  />
232
  </div>
233
 
234
- <Card>
235
- <CardHeader className="!pb-0">
236
  <div className="flex justify-between items-center">
237
  <CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
238
  <div className="flex items-center gap-2">
@@ -253,92 +294,100 @@ export default function DocumentManager() {
253
  <CardDescription aria-hidden="true" className="hidden">{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
+ /* Custom tooltip styles */
51
+ .tooltip-top {
52
+ bottom: 100% !important;
53
+ top: auto !important;
54
+ margin-bottom: 0.25rem !important;
55
+ margin-top: 0 !important;
56
+ }
57
+
58
  @keyframes pulse {
59
  0% {
60
  background-color: rgb(255 0 0 / 0.1);
 
123
  }
124
  }, [])
125
 
126
+ // Add tooltip position adjustment based on mouse position
127
+ useEffect(() => {
128
+ if (!docs) return;
129
+
130
+ // Function to handle mouse movement
131
+ const handleMouseMove = (event: MouseEvent) => {
132
+ const cardContent = document.querySelector<HTMLElement>('.flex-1.relative.p-0');
133
+ if (!cardContent) return;
134
+
135
+ const cardRect = cardContent.getBoundingClientRect();
136
+ const cardMiddleY = cardRect.top + cardRect.height / 2;
137
+
138
+ // Get all visible tooltips
139
+ const visibleTooltips = document.querySelectorAll<HTMLElement>('.group:hover > div[class*="invisible group-hover:visible absolute"]');
140
+
141
+ visibleTooltips.forEach(tooltip => {
142
+ // If mouse is in the bottom half of the card, show tooltip above
143
+ if (event.clientY > cardMiddleY) {
144
+ tooltip.classList.add('tooltip-top');
145
+ } else {
146
+ tooltip.classList.remove('tooltip-top');
147
+ }
148
+ });
149
+ };
150
+
151
+ // Add mouse move listener to the document
152
+ document.addEventListener('mousemove', handleMouseMove);
153
+
154
+ return () => {
155
+ document.removeEventListener('mousemove', handleMouseMove);
156
+ };
157
+ }, [docs]);
158
+
159
  const fetchDocuments = useCallback(async () => {
160
  try {
161
  const docs = await getDocuments()
 
234
  }, [health, fetchDocuments, t, currentTab])
235
 
236
  return (
237
+ <Card className="!rounded-none !overflow-hidden flex flex-col h-full min-h-0">
238
+ <CardHeader className="py-2 px-6">
239
  <CardTitle className="text-lg">{t('documentPanel.documentManager.title')}</CardTitle>
240
  </CardHeader>
241
+ <CardContent className="flex-1 flex flex-col min-h-0 overflow-hidden">
242
+ <div className="flex gap-2 mb-2">
243
  <div className="flex gap-2">
244
  <Button
245
  variant="outline"
 
272
  />
273
  </div>
274
 
275
+ <Card className="flex-1 flex flex-col border rounded-md min-h-0 mb-0">
276
+ <CardHeader className="flex-none py-2 px-4">
277
  <div className="flex justify-between items-center">
278
  <CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
279
  <div className="flex items-center gap-2">
 
294
  <CardDescription aria-hidden="true" className="hidden">{t('documentPanel.documentManager.uploadedDescription')}</CardDescription>
295
  </CardHeader>
296
 
297
+ <CardContent className="flex-1 relative p-0">
298
  {!docs && (
299
+ <div className="absolute inset-0 p-0">
300
+ <EmptyCard
301
+ title={t('documentPanel.documentManager.emptyTitle')}
302
+ description={t('documentPanel.documentManager.emptyDescription')}
303
+ />
304
+ </div>
305
  )}
306
  {docs && (
307
+ <div className="absolute inset-0 flex flex-col p-0">
308
+ <div className="w-full h-full flex flex-col rounded-lg border border-gray-200 dark:border-gray-700">
309
+ <div className="flex-1 overflow-hidden flex flex-col">
310
+ <Table className="w-full">
311
+ <TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
312
+ <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)]">
313
+ <TableHead>{t('documentPanel.documentManager.columns.id')}</TableHead>
314
+ <TableHead>{t('documentPanel.documentManager.columns.summary')}</TableHead>
315
+ <TableHead>{t('documentPanel.documentManager.columns.status')}</TableHead>
316
+ <TableHead>{t('documentPanel.documentManager.columns.length')}</TableHead>
317
+ <TableHead>{t('documentPanel.documentManager.columns.chunks')}</TableHead>
318
+ <TableHead>{t('documentPanel.documentManager.columns.created')}</TableHead>
319
+ <TableHead>{t('documentPanel.documentManager.columns.updated')}</TableHead>
320
+ </TableRow>
321
+ </TableHeader>
322
+ <TableBody className="text-sm overflow-auto">
323
+ {Object.entries(docs.statuses).map(([status, documents]) =>
324
+ documents.map((doc) => (
325
+ <TableRow key={doc.id}>
326
+ <TableCell className="truncate font-mono overflow-visible">
327
+ {showFileName ? (
328
+ <>
329
+ <div className="group relative overflow-visible">
330
+ <div className="truncate">
331
+ {getDisplayFileName(doc, 35)}
332
+ </div>
333
+ <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">
334
+ {doc.file_path}
335
+ </div>
336
+ </div>
337
+ <div className="text-xs text-gray-500">{doc.id}</div>
338
+ </>
339
+ ) : (
340
+ <div className="group relative overflow-visible">
341
+ <div className="truncate">
342
+ {doc.id}
343
+ </div>
344
+ <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">
345
+ {doc.file_path}
346
+ </div>
347
+ </div>
348
+ )}
349
+ </TableCell>
350
+ <TableCell className="max-w-xs min-w-24 truncate overflow-visible">
351
+ <div className="group relative overflow-visible">
352
+ <div className="truncate">
353
+ {doc.content_summary}
354
+ </div>
355
+ <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">
356
+ {doc.content_summary}
357
+ </div>
358
  </div>
359
+ </TableCell>
360
+ <TableCell>
361
+ {status === 'processed' && (
362
+ <span className="text-green-600">{t('documentPanel.documentManager.status.completed')}</span>
363
+ )}
364
+ {status === 'processing' && (
365
+ <span className="text-blue-600">{t('documentPanel.documentManager.status.processing')}</span>
366
+ )}
367
+ {status === 'pending' && <span className="text-yellow-600">{t('documentPanel.documentManager.status.pending')}</span>}
368
+ {status === 'failed' && <span className="text-red-600">{t('documentPanel.documentManager.status.failed')}</span>}
369
+ {doc.error && (
370
+ <span className="ml-2 text-red-500" title={doc.error}>
371
+ ⚠️
372
+ </span>
373
+ )}
374
+ </TableCell>
375
+ <TableCell>{doc.content_length ?? '-'}</TableCell>
376
+ <TableCell>{doc.chunks_count ?? '-'}</TableCell>
377
+ <TableCell className="truncate">
378
+ {new Date(doc.created_at).toLocaleString()}
379
+ </TableCell>
380
+ <TableCell className="truncate">
381
+ {new Date(doc.updated_at).toLocaleString()}
382
+ </TableCell>
383
+ </TableRow>
384
+ ))
385
+ )}
386
+ </TableBody>
387
+ </Table>
388
+ </div>
389
+ </div>
390
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  )}
392
  </CardContent>
393
  </Card>