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="!
|
197 |
-
<CardHeader>
|
198 |
<CardTitle className="text-lg">{t('documentPanel.documentManager.title')}</CardTitle>
|
199 |
</CardHeader>
|
200 |
-
<CardContent className="
|
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="
|
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 |
-
<
|
259 |
-
|
260 |
-
|
261 |
-
|
|
|
|
|
262 |
)}
|
263 |
{docs && (
|
264 |
-
<
|
265 |
-
<
|
266 |
-
<
|
267 |
-
<
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
<
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
<
|
284 |
-
|
285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
</div>
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
{
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
</
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
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>
|