yangdx
commited on
Commit
·
7540325
1
Parent(s):
73acfd2
feat: Add file name display in WebUI
Browse filesBackend:
- 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"
- lightrag/api/routers/document_routes.py +2 -0
- lightrag_webui/src/api/lightrag.ts +1 -0
- lightrag_webui/src/features/DocumentManager.tsx +74 -9
- lightrag_webui/src/locales/ar.json +8 -3
- lightrag_webui/src/locales/en.json +6 -1
- lightrag_webui/src/locales/zh.json +6 -1
- lightrag_webui/src/stores/settings.ts +12 -2
lightrag/api/routers/document_routes.py
CHANGED
@@ -91,6 +91,7 @@ class DocStatusResponse(BaseModel):
|
|
91 |
chunks_count: Optional[int] = None
|
92 |
error: Optional[str] = None
|
93 |
metadata: Optional[dict[str, Any]] = None
|
|
|
94 |
|
95 |
|
96 |
class DocsStatusesResponse(BaseModel):
|
@@ -890,6 +891,7 @@ def create_document_routes(
|
|
890 |
chunks_count=doc_status.chunks_count,
|
891 |
error=doc_status.error,
|
892 |
metadata=doc_status.metadata,
|
|
|
893 |
)
|
894 |
)
|
895 |
return response
|
|
|
91 |
chunks_count: Optional[int] = None
|
92 |
error: Optional[str] = None
|
93 |
metadata: Optional[dict[str, Any]] = None
|
94 |
+
file_path: str
|
95 |
|
96 |
|
97 |
class DocsStatusesResponse(BaseModel):
|
|
|
891 |
chunks_count=doc_status.chunks_count,
|
892 |
error=doc_status.error,
|
893 |
metadata=doc_status.metadata,
|
894 |
+
file_path=doc_status.file_path,
|
895 |
)
|
896 |
)
|
897 |
return response
|
lightrag_webui/src/api/lightrag.ts
CHANGED
@@ -124,6 +124,7 @@ export type DocStatusResponse = {
|
|
124 |
chunks_count?: number
|
125 |
error?: string
|
126 |
metadata?: Record<string, any>
|
|
|
127 |
}
|
128 |
|
129 |
export type DocsStatusesResponse = {
|
|
|
124 |
chunks_count?: number
|
125 |
error?: string
|
126 |
metadata?: Record<string, any>
|
127 |
+
file_path: string
|
128 |
}
|
129 |
|
130 |
export type DocsStatusesResponse = {
|
lightrag_webui/src/features/DocumentManager.tsx
CHANGED
@@ -12,7 +12,6 @@ import {
|
|
12 |
} from '@/components/ui/Table'
|
13 |
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card'
|
14 |
import EmptyCard from '@/components/ui/EmptyCard'
|
15 |
-
import Text from '@/components/ui/Text'
|
16 |
import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
|
17 |
import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
|
18 |
|
@@ -22,12 +21,36 @@ import { toast } from 'sonner'
|
|
22 |
import { useBackendState } from '@/stores/state'
|
23 |
|
24 |
import { RefreshCwIcon } from 'lucide-react'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
export default function DocumentManager() {
|
27 |
const { t } = useTranslation()
|
28 |
const health = useBackendState.use.health()
|
29 |
const [docs, setDocs] = useState<DocsStatusesResponse | null>(null)
|
30 |
const currentTab = useSettingsStore.use.currentTab()
|
|
|
|
|
31 |
|
32 |
const fetchDocuments = useCallback(async () => {
|
33 |
try {
|
@@ -107,7 +130,23 @@ export default function DocumentManager() {
|
|
107 |
|
108 |
<Card>
|
109 |
<CardHeader>
|
110 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
<CardDescription>{t('documentPanel.documentManager.uploadedDescription')}</CardDescription>
|
112 |
</CardHeader>
|
113 |
|
@@ -135,13 +174,39 @@ export default function DocumentManager() {
|
|
135 |
{Object.entries(docs.statuses).map(([status, documents]) =>
|
136 |
documents.map((doc) => (
|
137 |
<TableRow key={doc.id}>
|
138 |
-
<TableCell className="truncate font-mono">
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
</TableCell>
|
146 |
<TableCell>
|
147 |
{status === 'processed' && (
|
|
|
12 |
} from '@/components/ui/Table'
|
13 |
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card'
|
14 |
import EmptyCard from '@/components/ui/EmptyCard'
|
|
|
15 |
import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
|
16 |
import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
|
17 |
|
|
|
21 |
import { useBackendState } from '@/stores/state'
|
22 |
|
23 |
import { RefreshCwIcon } from 'lucide-react'
|
24 |
+
import { DocStatusResponse } from '@/api/lightrag'
|
25 |
+
|
26 |
+
const getDisplayFileName = (doc: DocStatusResponse, maxLength: number = 20): string => {
|
27 |
+
// Check if file_path exists and is a non-empty string
|
28 |
+
if (!doc.file_path || typeof doc.file_path !== 'string' || doc.file_path.trim() === '') {
|
29 |
+
return doc.id;
|
30 |
+
}
|
31 |
+
|
32 |
+
// Try to extract filename from path
|
33 |
+
const parts = doc.file_path.split('/');
|
34 |
+
const fileName = parts[parts.length - 1];
|
35 |
+
|
36 |
+
// Ensure extracted filename is valid
|
37 |
+
if (!fileName || fileName.trim() === '') {
|
38 |
+
return doc.id;
|
39 |
+
}
|
40 |
+
|
41 |
+
// If filename is longer than maxLength, truncate it and add ellipsis
|
42 |
+
return fileName.length > maxLength
|
43 |
+
? fileName.slice(0, maxLength) + '...'
|
44 |
+
: fileName;
|
45 |
+
};
|
46 |
|
47 |
export default function DocumentManager() {
|
48 |
const { t } = useTranslation()
|
49 |
const health = useBackendState.use.health()
|
50 |
const [docs, setDocs] = useState<DocsStatusesResponse | null>(null)
|
51 |
const currentTab = useSettingsStore.use.currentTab()
|
52 |
+
const showFileName = useSettingsStore.use.showFileName()
|
53 |
+
const setShowFileName = useSettingsStore.use.setShowFileName()
|
54 |
|
55 |
const fetchDocuments = useCallback(async () => {
|
56 |
try {
|
|
|
130 |
|
131 |
<Card>
|
132 |
<CardHeader>
|
133 |
+
<div className="flex justify-between items-center">
|
134 |
+
<CardTitle>{t('documentPanel.documentManager.uploadedTitle')}</CardTitle>
|
135 |
+
<div className="flex items-center gap-2">
|
136 |
+
<span className="text-sm text-gray-500">{t('documentPanel.documentManager.fileNameLabel')}</span>
|
137 |
+
<Button
|
138 |
+
variant="outline"
|
139 |
+
size="sm"
|
140 |
+
onClick={() => setShowFileName(!showFileName)}
|
141 |
+
className="border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800"
|
142 |
+
>
|
143 |
+
{showFileName
|
144 |
+
? t('documentPanel.documentManager.hideButton')
|
145 |
+
: t('documentPanel.documentManager.showButton')
|
146 |
+
}
|
147 |
+
</Button>
|
148 |
+
</div>
|
149 |
+
</div>
|
150 |
<CardDescription>{t('documentPanel.documentManager.uploadedDescription')}</CardDescription>
|
151 |
</CardHeader>
|
152 |
|
|
|
174 |
{Object.entries(docs.statuses).map(([status, documents]) =>
|
175 |
documents.map((doc) => (
|
176 |
<TableRow key={doc.id}>
|
177 |
+
<TableCell className="truncate font-mono overflow-visible">
|
178 |
+
{showFileName ? (
|
179 |
+
<>
|
180 |
+
<div className="group relative overflow-visible">
|
181 |
+
<div className="truncate">
|
182 |
+
{getDisplayFileName(doc, 35)}
|
183 |
+
</div>
|
184 |
+
<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">
|
185 |
+
{doc.file_path}
|
186 |
+
</div>
|
187 |
+
</div>
|
188 |
+
<div className="text-xs text-gray-500">{doc.id}</div>
|
189 |
+
</>
|
190 |
+
) : (
|
191 |
+
<div className="group relative overflow-visible">
|
192 |
+
<div className="truncate">
|
193 |
+
{doc.id}
|
194 |
+
</div>
|
195 |
+
<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">
|
196 |
+
{doc.file_path}
|
197 |
+
</div>
|
198 |
+
</div>
|
199 |
+
)}
|
200 |
+
</TableCell>
|
201 |
+
<TableCell className="max-w-xs min-w-24 truncate overflow-visible">
|
202 |
+
<div className="group relative overflow-visible">
|
203 |
+
<div className="truncate">
|
204 |
+
{doc.content_summary}
|
205 |
+
</div>
|
206 |
+
<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">
|
207 |
+
{doc.content_summary}
|
208 |
+
</div>
|
209 |
+
</div>
|
210 |
</TableCell>
|
211 |
<TableCell>
|
212 |
{status === 'processed' && (
|
lightrag_webui/src/locales/ar.json
CHANGED
@@ -81,9 +81,14 @@
|
|
81 |
},
|
82 |
"errors": {
|
83 |
"loadFailed": "فشل تحميل المستندات\n{{error}}",
|
84 |
-
"scanFailed": "فشل
|
85 |
-
"scanProgressFailed": "فشل الحصول على تقدم
|
86 |
-
}
|
|
|
|
|
|
|
|
|
|
|
87 |
}
|
88 |
},
|
89 |
"graphPanel": {
|
|
|
81 |
},
|
82 |
"errors": {
|
83 |
"loadFailed": "فشل تحميل المستندات\n{{error}}",
|
84 |
+
"scanFailed": "فشل مسح المستندات\n{{error}}",
|
85 |
+
"scanProgressFailed": "فشل الحصول على تقدم المسح\n{{error}}"
|
86 |
+
},
|
87 |
+
"fileNameLabel": "اسم الملف",
|
88 |
+
"showButton": "عرض",
|
89 |
+
"hideButton": "إخفاء",
|
90 |
+
"showFileNameTooltip": "عرض اسم الملف",
|
91 |
+
"hideFileNameTooltip": "إخفاء اسم الملف"
|
92 |
}
|
93 |
},
|
94 |
"graphPanel": {
|
lightrag_webui/src/locales/en.json
CHANGED
@@ -83,7 +83,12 @@
|
|
83 |
"loadFailed": "Failed to load documents\n{{error}}",
|
84 |
"scanFailed": "Failed to scan documents\n{{error}}",
|
85 |
"scanProgressFailed": "Failed to get scan progress\n{{error}}"
|
86 |
-
}
|
|
|
|
|
|
|
|
|
|
|
87 |
}
|
88 |
},
|
89 |
"graphPanel": {
|
|
|
83 |
"loadFailed": "Failed to load documents\n{{error}}",
|
84 |
"scanFailed": "Failed to scan documents\n{{error}}",
|
85 |
"scanProgressFailed": "Failed to get scan progress\n{{error}}"
|
86 |
+
},
|
87 |
+
"fileNameLabel": "File Name",
|
88 |
+
"showButton": "Show",
|
89 |
+
"hideButton": "Hide",
|
90 |
+
"showFileNameTooltip": "Show file name",
|
91 |
+
"hideFileNameTooltip": "Hide file name"
|
92 |
}
|
93 |
},
|
94 |
"graphPanel": {
|
lightrag_webui/src/locales/zh.json
CHANGED
@@ -83,7 +83,12 @@
|
|
83 |
"loadFailed": "加载文档失败\n{{error}}",
|
84 |
"scanFailed": "扫描文档失败\n{{error}}",
|
85 |
"scanProgressFailed": "获取扫描进度失败\n{{error}}"
|
86 |
-
}
|
|
|
|
|
|
|
|
|
|
|
87 |
}
|
88 |
},
|
89 |
"graphPanel": {
|
|
|
83 |
"loadFailed": "加载文档失败\n{{error}}",
|
84 |
"scanFailed": "扫描文档失败\n{{error}}",
|
85 |
"scanProgressFailed": "获取扫描进度失败\n{{error}}"
|
86 |
+
},
|
87 |
+
"fileNameLabel": "文件名",
|
88 |
+
"showButton": "显示",
|
89 |
+
"hideButton": "隐藏",
|
90 |
+
"showFileNameTooltip": "显示文件名",
|
91 |
+
"hideFileNameTooltip": "隐藏文件名"
|
92 |
}
|
93 |
},
|
94 |
"graphPanel": {
|
lightrag_webui/src/stores/settings.ts
CHANGED
@@ -9,6 +9,10 @@ type Language = 'en' | 'zh' | 'fr' | 'ar'
|
|
9 |
type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
|
10 |
|
11 |
interface SettingsState {
|
|
|
|
|
|
|
|
|
12 |
// Graph viewer settings
|
13 |
showPropertyPanel: boolean
|
14 |
showNodeSearchBar: boolean
|
@@ -83,6 +87,7 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|
83 |
apiKey: null,
|
84 |
|
85 |
currentTab: 'documents',
|
|
|
86 |
|
87 |
retrievalHistory: [],
|
88 |
|
@@ -138,12 +143,14 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|
138 |
updateQuerySettings: (settings: Partial<QueryRequest>) =>
|
139 |
set((state) => ({
|
140 |
querySettings: { ...state.querySettings, ...settings }
|
141 |
-
}))
|
|
|
|
|
142 |
}),
|
143 |
{
|
144 |
name: 'settings-storage',
|
145 |
storage: createJSONStorage(() => localStorage),
|
146 |
-
version:
|
147 |
migrate: (state: any, version: number) => {
|
148 |
if (version < 2) {
|
149 |
state.showEdgeLabel = false
|
@@ -186,6 +193,9 @@ const useSettingsStoreBase = create<SettingsState>()(
|
|
186 |
state.graphMinDegree = 0
|
187 |
state.language = 'en'
|
188 |
}
|
|
|
|
|
|
|
189 |
return state
|
190 |
}
|
191 |
}
|
|
|
9 |
type Tab = 'documents' | 'knowledge-graph' | 'retrieval' | 'api'
|
10 |
|
11 |
interface SettingsState {
|
12 |
+
// Document manager settings
|
13 |
+
showFileName: boolean
|
14 |
+
setShowFileName: (show: boolean) => void
|
15 |
+
|
16 |
// Graph viewer settings
|
17 |
showPropertyPanel: boolean
|
18 |
showNodeSearchBar: boolean
|
|
|
87 |
apiKey: null,
|
88 |
|
89 |
currentTab: 'documents',
|
90 |
+
showFileName: false,
|
91 |
|
92 |
retrievalHistory: [],
|
93 |
|
|
|
143 |
updateQuerySettings: (settings: Partial<QueryRequest>) =>
|
144 |
set((state) => ({
|
145 |
querySettings: { ...state.querySettings, ...settings }
|
146 |
+
})),
|
147 |
+
|
148 |
+
setShowFileName: (show: boolean) => set({ showFileName: show })
|
149 |
}),
|
150 |
{
|
151 |
name: 'settings-storage',
|
152 |
storage: createJSONStorage(() => localStorage),
|
153 |
+
version: 9,
|
154 |
migrate: (state: any, version: number) => {
|
155 |
if (version < 2) {
|
156 |
state.showEdgeLabel = false
|
|
|
193 |
state.graphMinDegree = 0
|
194 |
state.language = 'en'
|
195 |
}
|
196 |
+
if (version < 9) {
|
197 |
+
state.showFileName = false
|
198 |
+
}
|
199 |
return state
|
200 |
}
|
201 |
}
|