ArnoChen commited on
Commit
598911f
·
1 Parent(s): 93cbd1b

finish document manager

Browse files
lightrag_webui/src/api/lightrag.ts CHANGED
@@ -27,8 +27,6 @@ export type LightragStatus = {
27
  status: 'healthy'
28
  working_directory: string
29
  input_directory: string
30
- indexed_files: string[]
31
- indexed_files_count: number
32
  configuration: {
33
  llm_binding: string
34
  llm_binding_host: string
@@ -104,11 +102,29 @@ export type QueryResponse = {
104
  response: string
105
  }
106
 
107
- export type DocumentActionResponse = {
108
  status: 'success' | 'partial_success' | 'failure'
109
  message: string
110
  }
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  export const InvalidApiKeyError = 'Invalid API Key'
113
  export const RequireApiKeError = 'API Key required'
114
 
@@ -169,7 +185,7 @@ export const checkHealth = async (): Promise<
169
  }
170
  }
171
 
172
- export const getDocuments = async (): Promise<string[]> => {
173
  const response = await axiosInstance.get('/documents')
174
  return response.data
175
  }
@@ -253,7 +269,7 @@ export const queryTextStream = async (
253
  export const insertText = async (
254
  text: string,
255
  description?: string
256
- ): Promise<DocumentActionResponse> => {
257
  const response = await axiosInstance.post('/documents/text', { text, description })
258
  return response.data
259
  }
@@ -261,7 +277,7 @@ export const insertText = async (
261
  export const uploadDocument = async (
262
  file: File,
263
  onUploadProgress?: (percentCompleted: number) => void
264
- ): Promise<DocumentActionResponse> => {
265
  const formData = new FormData()
266
  formData.append('file', file)
267
 
@@ -284,7 +300,7 @@ export const uploadDocument = async (
284
  export const batchUploadDocuments = async (
285
  files: File[],
286
  onUploadProgress?: (fileName: string, percentCompleted: number) => void
287
- ): Promise<DocumentActionResponse[]> => {
288
  return await Promise.all(
289
  files.map(async (file) => {
290
  return await uploadDocument(file, (percentCompleted) => {
@@ -294,7 +310,7 @@ export const batchUploadDocuments = async (
294
  )
295
  }
296
 
297
- export const clearDocuments = async (): Promise<DocumentActionResponse> => {
298
  const response = await axiosInstance.delete('/documents')
299
  return response.data
300
  }
 
27
  status: 'healthy'
28
  working_directory: string
29
  input_directory: string
 
 
30
  configuration: {
31
  llm_binding: string
32
  llm_binding_host: string
 
102
  response: string
103
  }
104
 
105
+ export type DocActionResponse = {
106
  status: 'success' | 'partial_success' | 'failure'
107
  message: string
108
  }
109
 
110
+ export type DocStatus = 'pending' | 'processing' | 'processed' | 'failed'
111
+
112
+ export type DocStatusResponse = {
113
+ id: string
114
+ content_summary: string
115
+ content_length: number
116
+ status: DocStatus
117
+ created_at: string
118
+ updated_at: string
119
+ chunks_count?: number
120
+ error?: string
121
+ metadata?: Record<string, any>
122
+ }
123
+
124
+ export type DocsStatusesResponse = {
125
+ statuses: Record<DocStatus, DocStatusResponse[]>
126
+ }
127
+
128
  export const InvalidApiKeyError = 'Invalid API Key'
129
  export const RequireApiKeError = 'API Key required'
130
 
 
185
  }
186
  }
187
 
188
+ export const getDocuments = async (): Promise<DocsStatusesResponse> => {
189
  const response = await axiosInstance.get('/documents')
190
  return response.data
191
  }
 
269
  export const insertText = async (
270
  text: string,
271
  description?: string
272
+ ): Promise<DocActionResponse> => {
273
  const response = await axiosInstance.post('/documents/text', { text, description })
274
  return response.data
275
  }
 
277
  export const uploadDocument = async (
278
  file: File,
279
  onUploadProgress?: (percentCompleted: number) => void
280
+ ): Promise<DocActionResponse> => {
281
  const formData = new FormData()
282
  formData.append('file', file)
283
 
 
300
  export const batchUploadDocuments = async (
301
  files: File[],
302
  onUploadProgress?: (fileName: string, percentCompleted: number) => void
303
+ ): Promise<DocActionResponse[]> => {
304
  return await Promise.all(
305
  files.map(async (file) => {
306
  return await uploadDocument(file, (percentCompleted) => {
 
310
  )
311
  }
312
 
313
+ export const clearDocuments = async (): Promise<DocActionResponse> => {
314
  const response = await axiosInstance.delete('/documents')
315
  return response.data
316
  }
lightrag_webui/src/components/StatusCard.tsx CHANGED
@@ -14,8 +14,6 @@ const StatusCard = ({ status }: { status: LightragStatus | null }) => {
14
  <span className="truncate">{status.working_directory}</span>
15
  <span>Input Directory:</span>
16
  <span className="truncate">{status.input_directory}</span>
17
- <span>Indexed Files:</span>
18
- <span>{status.indexed_files_count}</span>
19
  </div>
20
  </div>
21
 
 
14
  <span className="truncate">{status.working_directory}</span>
15
  <span>Input Directory:</span>
16
  <span className="truncate">{status.input_directory}</span>
 
 
17
  </div>
18
  </div>
19
 
lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx CHANGED
@@ -49,10 +49,10 @@ export default function UploadDocumentsDialog() {
49
  toast.error('Upload Failed\n' + errorMessage(err))
50
  } finally {
51
  setIsUploading(false)
52
- setOpen(false)
53
  }
54
  },
55
- [setIsUploading, setProgresses, setOpen]
56
  )
57
 
58
  return (
@@ -66,7 +66,7 @@ export default function UploadDocumentsDialog() {
66
  }}
67
  >
68
  <DialogTrigger asChild>
69
- <Button variant="default" side="bottom" tooltip='Upload documents' size="sm">
70
  <UploadIcon /> Upload
71
  </Button>
72
  </DialogTrigger>
 
49
  toast.error('Upload Failed\n' + errorMessage(err))
50
  } finally {
51
  setIsUploading(false)
52
+ // setOpen(false)
53
  }
54
  },
55
+ [setIsUploading, setProgresses]
56
  )
57
 
58
  return (
 
66
  }}
67
  >
68
  <DialogTrigger asChild>
69
+ <Button variant="default" side="bottom" tooltip="Upload documents" size="sm">
70
  <UploadIcon /> Upload
71
  </Button>
72
  </DialogTrigger>
lightrag_webui/src/features/DocumentManager.tsx CHANGED
@@ -9,39 +9,35 @@ import {
9
  TableRow
10
  } from '@/components/ui/Table'
11
  import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card'
12
- import Progress from '@/components/ui/Progress'
13
  import EmptyCard from '@/components/ui/EmptyCard'
 
14
  import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
15
  import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
16
 
17
- import {
18
- getDocuments,
19
- // getDocumentsScanProgress,
20
- scanNewDocuments
21
- // LightragDocumentsScanProgress
22
- } from '@/api/lightrag'
23
  import { errorMessage } from '@/lib/utils'
24
  import { toast } from 'sonner'
25
- // import { useBackendState } from '@/stores/state'
26
-
27
- import { RefreshCwIcon, TrashIcon } from 'lucide-react'
28
 
29
- // type DocumentStatus = 'indexed' | 'pending' | 'indexing' | 'error'
30
 
31
  export default function DocumentManager() {
32
- // const health = useBackendState.use.health()
33
- const [files, setFiles] = useState<string[]>([])
34
- const [indexedFiles, setIndexedFiles] = useState<string[]>([])
35
- // const [scanProgress, setScanProgress] = useState<LightragDocumentsScanProgress | null>(null)
36
 
37
  const fetchDocuments = useCallback(async () => {
38
  try {
39
  const docs = await getDocuments()
40
- setFiles(docs)
 
 
 
 
 
41
  } catch (err) {
42
  toast.error('Failed to load documents\n' + errorMessage(err))
43
  }
44
- }, [setFiles])
45
 
46
  useEffect(() => {
47
  fetchDocuments()
@@ -56,28 +52,19 @@ export default function DocumentManager() {
56
  }
57
  }, [])
58
 
59
- // useEffect(() => {
60
- // const interval = setInterval(async () => {
61
- // try {
62
- // if (!health) return
63
- // const progress = await getDocumentsScanProgress()
64
- // setScanProgress((pre) => {
65
- // if (pre?.is_scanning === progress.is_scanning && progress.is_scanning === false) {
66
- // return pre
67
- // }
68
- // return progress
69
- // })
70
- // console.log(progress)
71
- // } catch (err) {
72
- // toast.error('Failed to get scan progress\n' + errorMessage(err))
73
- // }
74
- // }, 2000)
75
- // return () => clearInterval(interval)
76
- // }, [health])
77
-
78
- const handleDelete = async (fileName: string) => {
79
- console.log(`deleting ${fileName}`)
80
- }
81
 
82
  return (
83
  <Card className="!size-full !rounded-none !border-none">
@@ -100,16 +87,6 @@ export default function DocumentManager() {
100
  <UploadDocumentsDialog />
101
  </div>
102
 
103
- {/* {scanProgress?.is_scanning && (
104
- <div className="space-y-2">
105
- <div className="flex justify-between text-sm">
106
- <span>Indexing {scanProgress.current_file}</span>
107
- <span>{scanProgress.progress}%</span>
108
- </div>
109
- <Progress value={scanProgress.progress} />
110
- </div>
111
- )} */}
112
-
113
  <Card>
114
  <CardHeader>
115
  <CardTitle>Uploaded documents</CardTitle>
@@ -117,44 +94,67 @@ export default function DocumentManager() {
117
  </CardHeader>
118
 
119
  <CardContent>
120
- {files.length == 0 && (
121
  <EmptyCard
122
- title="No documents uploades"
123
  description="upload documents to see them here"
124
  />
125
  )}
126
- {files.length > 0 && (
127
  <Table>
128
  <TableHeader>
129
  <TableRow>
130
- <TableHead>Filename</TableHead>
 
131
  <TableHead>Status</TableHead>
132
- <TableHead>Actions</TableHead>
 
 
 
 
133
  </TableRow>
134
  </TableHeader>
135
- <TableBody>
136
- {files.map((file) => (
137
- <TableRow key={file}>
138
- <TableCell>{file}</TableCell>
139
- <TableCell>
140
- {indexedFiles.includes(file) ? (
141
- <span className="text-green-600">Indexed</span>
142
- ) : (
143
- <span className="text-yellow-600">Pending</span>
144
- )}
145
- </TableCell>
146
- <TableCell>
147
- <Button
148
- variant="ghost"
149
- size="sm"
150
- onClick={() => handleDelete(file)}
151
- // disabled={isUploading}
152
- >
153
- <TrashIcon />
154
- </Button>
155
- </TableCell>
156
- </TableRow>
157
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  </TableBody>
159
  </Table>
160
  )}
 
9
  TableRow
10
  } from '@/components/ui/Table'
11
  import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card'
 
12
  import EmptyCard from '@/components/ui/EmptyCard'
13
+ import Text from '@/components/ui/Text'
14
  import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
15
  import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
16
 
17
+ import { getDocuments, scanNewDocuments, DocsStatusesResponse } from '@/api/lightrag'
 
 
 
 
 
18
  import { errorMessage } from '@/lib/utils'
19
  import { toast } from 'sonner'
20
+ import { useBackendState } from '@/stores/state'
 
 
21
 
22
+ import { RefreshCwIcon } from 'lucide-react'
23
 
24
  export default function DocumentManager() {
25
+ const health = useBackendState.use.health()
26
+ const [docs, setDocs] = useState<DocsStatusesResponse | null>(null)
 
 
27
 
28
  const fetchDocuments = useCallback(async () => {
29
  try {
30
  const docs = await getDocuments()
31
+ if (docs && docs.statuses) {
32
+ setDocs(docs)
33
+ // console.log(docs)
34
+ } else {
35
+ setDocs(null)
36
+ }
37
  } catch (err) {
38
  toast.error('Failed to load documents\n' + errorMessage(err))
39
  }
40
+ }, [setDocs])
41
 
42
  useEffect(() => {
43
  fetchDocuments()
 
52
  }
53
  }, [])
54
 
55
+ useEffect(() => {
56
+ const interval = setInterval(async () => {
57
+ if (!health) {
58
+ return
59
+ }
60
+ try {
61
+ await fetchDocuments()
62
+ } catch (err) {
63
+ toast.error('Failed to get scan progress\n' + errorMessage(err))
64
+ }
65
+ }, 5000)
66
+ return () => clearInterval(interval)
67
+ }, [health, fetchDocuments])
 
 
 
 
 
 
 
 
 
68
 
69
  return (
70
  <Card className="!size-full !rounded-none !border-none">
 
87
  <UploadDocumentsDialog />
88
  </div>
89
 
 
 
 
 
 
 
 
 
 
 
90
  <Card>
91
  <CardHeader>
92
  <CardTitle>Uploaded documents</CardTitle>
 
94
  </CardHeader>
95
 
96
  <CardContent>
97
+ {!docs && (
98
  <EmptyCard
99
+ title="No documents uploaded"
100
  description="upload documents to see them here"
101
  />
102
  )}
103
+ {docs && (
104
  <Table>
105
  <TableHeader>
106
  <TableRow>
107
+ <TableHead>ID</TableHead>
108
+ <TableHead>Summary</TableHead>
109
  <TableHead>Status</TableHead>
110
+ <TableHead>Length</TableHead>
111
+ <TableHead>Chunks</TableHead>
112
+ <TableHead>Created</TableHead>
113
+ <TableHead>Updated</TableHead>
114
+ <TableHead>Metadata</TableHead>
115
  </TableRow>
116
  </TableHeader>
117
+ <TableBody className="text-sm">
118
+ {Object.entries(docs.statuses).map(([status, documents]) =>
119
+ documents.map((doc) => (
120
+ <TableRow key={doc.id}>
121
+ <TableCell className="truncate font-mono">{doc.id}</TableCell>
122
+ <TableCell className="max-w-xs min-w-24 truncate">
123
+ <Text
124
+ text={doc.content_summary}
125
+ tooltip={doc.content_summary}
126
+ tooltipClassName="max-w-none overflow-visible block"
127
+ />
128
+ </TableCell>
129
+ <TableCell>
130
+ {status === 'processed' && (
131
+ <span className="text-green-600">Completed</span>
132
+ )}
133
+ {status === 'processing' && (
134
+ <span className="text-blue-600">Processing</span>
135
+ )}
136
+ {status === 'pending' && <span className="text-yellow-600">Pending</span>}
137
+ {status === 'failed' && <span className="text-red-600">Failed</span>}
138
+ {doc.error && (
139
+ <span className="ml-2 text-red-500" title={doc.error}>
140
+ ⚠️
141
+ </span>
142
+ )}
143
+ </TableCell>
144
+ <TableCell>{doc.content_length ?? '-'}</TableCell>
145
+ <TableCell>{doc.chunks_count ?? '-'}</TableCell>
146
+ <TableCell className="truncate">
147
+ {new Date(doc.created_at).toLocaleString()}
148
+ </TableCell>
149
+ <TableCell className="truncate">
150
+ {new Date(doc.updated_at).toLocaleString()}
151
+ </TableCell>
152
+ <TableCell className="max-w-xs truncate">
153
+ {doc.metadata ? JSON.stringify(doc.metadata) : '-'}
154
+ </TableCell>
155
+ </TableRow>
156
+ ))
157
+ )}
158
  </TableBody>
159
  </Table>
160
  )}