feat: Add file name display in WebUI

Backend:
- Add file_path field to DocStatusResponse
- Update document status response creation

Frontend:
- Remove metadata column
- Improve filename display with truncation and tooltips
- Add show/hide filename toggle with proper styling
- Update translations for all supported languages"
This commit is contained in:
yangdx
2025-03-25 22:42:46 +08:00
parent e36cc87633
commit d456269718
7 changed files with 109 additions and 16 deletions

View File

@@ -91,6 +91,7 @@ class DocStatusResponse(BaseModel):
chunks_count: Optional[int] = None chunks_count: Optional[int] = None
error: Optional[str] = None error: Optional[str] = None
metadata: Optional[dict[str, Any]] = None metadata: Optional[dict[str, Any]] = None
file_path: str
class DocsStatusesResponse(BaseModel): class DocsStatusesResponse(BaseModel):
@@ -890,6 +891,7 @@ def create_document_routes(
chunks_count=doc_status.chunks_count, chunks_count=doc_status.chunks_count,
error=doc_status.error, error=doc_status.error,
metadata=doc_status.metadata, metadata=doc_status.metadata,
file_path=doc_status.file_path,
) )
) )
return response return response

View File

@@ -124,6 +124,7 @@ export type DocStatusResponse = {
chunks_count?: number chunks_count?: number
error?: string error?: string
metadata?: Record<string, any> metadata?: Record<string, any>
file_path: string
} }
export type DocsStatusesResponse = { export type DocsStatusesResponse = {

View File

@@ -12,7 +12,6 @@ import {
} from '@/components/ui/Table' } from '@/components/ui/Table'
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card' import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card'
import EmptyCard from '@/components/ui/EmptyCard' import EmptyCard from '@/components/ui/EmptyCard'
import Text from '@/components/ui/Text'
import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog' import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog' import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
@@ -22,12 +21,36 @@ import { toast } from 'sonner'
import { useBackendState } from '@/stores/state' import { useBackendState } from '@/stores/state'
import { RefreshCwIcon } from 'lucide-react' import { RefreshCwIcon } from 'lucide-react'
import { DocStatusResponse } from '@/api/lightrag'
const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): string => {
// Check if file_path exists and is a non-empty string
if (!doc.file_path || typeof doc.file_path !== 'string' || doc.file_path.trim() === '') {
return doc.id;
}
// Try to extract filename from path
const parts = doc.file_path.split('/');
const fileName = parts[parts.length - 1];
// Ensure extracted filename is valid
if (!fileName || fileName.trim() === '') {
return doc.id;
}
// If filename is longer than maxLength, truncate it and add ellipsis
return fileName.length > maxLength
? fileName.slice(0, maxLength) + '...'
: fileName;
};
export default function DocumentManager() { export default function DocumentManager() {
const { t } = useTranslation() const { t } = useTranslation()
const health = useBackendState.use.health() const health = useBackendState.use.health()
const [docs, setDocs] = useState<DocsStatusesResponse | null>(null) const [docs, setDocs] = useState<DocsStatusesResponse | null>(null)
const currentTab = useSettingsStore.use.currentTab() const currentTab = useSettingsStore.use.currentTab()
const showFileName = useSettingsStore.use.showFileName()
const setShowFileName = useSettingsStore.use.setShowFileName()
const fetchDocuments = useCallback(async () => { const fetchDocuments = useCallback(async () => {
try { try {
@@ -107,7 +130,23 @@ export default function DocumentManager() {
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex justify-between items-center">
<CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle> <CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">{t('documentPanel.documentManager.fileNameLabel')}</span>
<Button
variant="outline"
size="sm"
onClick={() => setShowFileName(!showFileName)}
className="border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800"
>
{showFileName
? t('documentPanel.documentManager.hideButton')
: t('documentPanel.documentManager.showButton')
}
</Button>
</div>
</div>
<CardDescription>{t('documentPanel.documentManager.uploadedDescription')}</CardDescription> <CardDescription>{t('documentPanel.documentManager.uploadedDescription')}</CardDescription>
</CardHeader> </CardHeader>
@@ -135,13 +174,39 @@ export default function DocumentManager() {
{Object.entries(docs.statuses).map(([status, documents]) => {Object.entries(docs.statuses).map(([status, documents]) =>
documents.map((doc) => ( documents.map((doc) => (
<TableRow key={doc.id}> <TableRow key={doc.id}>
<TableCell className="truncate font-mono">{doc.id}</TableCell> <TableCell className="truncate font-mono overflow-visible">
<TableCell className="max-w-xs min-w-24 truncate"> {showFileName ? (
<Text <>
text={doc.content_summary} <div className="group relative overflow-visible">
tooltip={doc.content_summary} <div className="truncate">
tooltipClassName="max-w-none overflow-visible block" {getDisplayFileName(doc, 35)}
/> </div>
<div className="invisible group-hover:visible fixed 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">
{doc.file_path}
</div>
</div>
<div className="text-xs text-gray-500">{doc.id}</div>
</>
) : (
<div className="group relative overflow-visible">
<div className="truncate">
{doc.id}
</div>
<div className="invisible group-hover:visible fixed 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">
{doc.file_path}
</div>
</div>
)}
</TableCell>
<TableCell className="max-w-xs min-w-24 truncate overflow-visible">
<div className="group relative overflow-visible">
<div className="truncate">
{doc.content_summary}
</div>
<div className="invisible group-hover:visible fixed 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">
{doc.content_summary}
</div>
</div>
</TableCell> </TableCell>
<TableCell> <TableCell>
{status === 'processed' && ( {status === 'processed' && (

View File

@@ -81,9 +81,14 @@
}, },
"errors": { "errors": {
"loadFailed": "فشل تحميل المستندات\n{{error}}", "loadFailed": "فشل تحميل المستندات\n{{error}}",
"scanFailed": "فشل المسح الضوئي للمستندات\n{{error}}", "scanFailed": "فشل مسح المستندات\n{{error}}",
"scanProgressFailed": "فشل الحصول على تقدم المسح الضوئي\n{{error}}" "scanProgressFailed": "فشل الحصول على تقدم المسح\n{{error}}"
} },
"fileNameLabel": "اسم الملف",
"showButton": "عرض",
"hideButton": "إخفاء",
"showFileNameTooltip": "عرض اسم الملف",
"hideFileNameTooltip": "إخفاء اسم الملف"
} }
}, },
"graphPanel": { "graphPanel": {

View File

@@ -83,7 +83,12 @@
"loadFailed": "Failed to load documents\n{{error}}", "loadFailed": "Failed to load documents\n{{error}}",
"scanFailed": "Failed to scan documents\n{{error}}", "scanFailed": "Failed to scan documents\n{{error}}",
"scanProgressFailed": "Failed to get scan progress\n{{error}}" "scanProgressFailed": "Failed to get scan progress\n{{error}}"
} },
"fileNameLabel": "File Name",
"showButton": "Show",
"hideButton": "Hide",
"showFileNameTooltip": "Show file name",
"hideFileNameTooltip": "Hide file name"
} }
}, },
"graphPanel": { "graphPanel": {

View File

@@ -83,7 +83,12 @@
"loadFailed": "加载文档失败\n{{error}}", "loadFailed": "加载文档失败\n{{error}}",
"scanFailed": "扫描文档失败\n{{error}}", "scanFailed": "扫描文档失败\n{{error}}",
"scanProgressFailed": "获取扫描进度失败\n{{error}}" "scanProgressFailed": "获取扫描进度失败\n{{error}}"
} },
"fileNameLabel": "文件名",
"showButton": "显示",
"hideButton": "隐藏",
"showFileNameTooltip": "显示文件名",
"hideFileNameTooltip": "隐藏文件名"
} }
}, },
"graphPanel": { "graphPanel": {

View File

@@ -9,6 +9,10 @@ type Language = 'en' | 'zh' | 'fr' | 'ar'
type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api' type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
interface SettingsState { interface SettingsState {
// Document manager settings
showFileName: boolean
setShowFileName: (show: boolean) => void
// Graph viewer settings // Graph viewer settings
showPropertyPanel: boolean showPropertyPanel: boolean
showNodeSearchBar: boolean showNodeSearchBar: boolean
@@ -83,6 +87,7 @@ const useSettingsStoreBase = create<SettingsState>()(
apiKey: null, apiKey: null,
currentTab: 'documents', currentTab: 'documents',
showFileName: false,
retrievalHistory: [], retrievalHistory: [],
@@ -138,12 +143,14 @@ const useSettingsStoreBase = create<SettingsState>()(
updateQuerySettings: (settings: Partial<QueryRequest>) => updateQuerySettings: (settings: Partial<QueryRequest>) =>
set((state) => ({ set((state) => ({
querySettings: { ...state.querySettings, ...settings } querySettings: { ...state.querySettings, ...settings }
})) })),
setShowFileName: (show: boolean) => set({ showFileName: show })
}), }),
{ {
name: 'settings-storage', name: 'settings-storage',
storage: createJSONStorage(() => localStorage), storage: createJSONStorage(() => localStorage),
version: 8, version: 9,
migrate: (state: any, version: number) => { migrate: (state: any, version: number) => {
if (version < 2) { if (version < 2) {
state.showEdgeLabel = false state.showEdgeLabel = false
@@ -186,6 +193,9 @@ const useSettingsStoreBase = create<SettingsState>()(
state.graphMinDegree = 0 state.graphMinDegree = 0
state.language = 'en' state.language = 'en'
} }
if (version < 9) {
state.showFileName = false
}
return state return state
} }
} }