diff --git a/frontend/src/components/DocumentManager.jsx b/frontend/src/components/DocumentManager.jsx
index f7a865c..2e0c742 100644
--- a/frontend/src/components/DocumentManager.jsx
+++ b/frontend/src/components/DocumentManager.jsx
@@ -1,7 +1,7 @@
-import React, { useState, useEffect, useCallback } from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
import axios from 'axios'
import { useDropzone } from 'react-dropzone'
-import { Upload, Trash2, Eye, RefreshCw, FileText, X } from 'lucide-react'
+import { Upload, Trash2, Eye, RefreshCw, FileText, FileArchive, X, CheckCircle, AlertCircle, Loader } from 'lucide-react'
const STAGES = ['input', 'parsed', 'generated', 'curated', 'final']
@@ -11,13 +11,19 @@ function formatBytes(bytes) {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}
+function FileIcon({ name }) {
+ return name.endsWith('.zip')
+ ?
+ :
+}
+
export default function DocumentManager({ connected }) {
- const [stage, setStage] = useState('input')
- const [files, setFiles] = useState([])
- const [loading, setLoading] = useState(false)
- const [uploading, setUploading] = useState(false)
- const [preview, setPreview] = useState(null)
- const [error, setError] = useState('')
+ const [stage, setStage] = useState('input')
+ const [files, setFiles] = useState([])
+ const [loading, setLoading] = useState(false)
+ const [uploadQueue, setUploadQueue] = useState([])
+ const [preview, setPreview] = useState(null)
+ const [error, setError] = useState('')
const fetchFiles = useCallback(async () => {
if (!connected) return
@@ -36,23 +42,36 @@ export default function DocumentManager({ connected }) {
const onDrop = useCallback(async accepted => {
if (!accepted.length || !connected) return
- setUploading(true)
+ const entries = accepted.map(f => ({ name: f.name, status: 'uploading', error: null }))
+ setUploadQueue(entries)
const formData = new FormData()
- formData.append('file', accepted[0])
+ accepted.forEach(f => formData.append('files', f))
try {
- await axios.post('/api/upload', formData)
+ const { data } = await axios.post('/api/upload', formData)
+ setUploadQueue(data.results.map(r => ({ name: r.file, status: 'done', action: r.action, error: null })))
fetchFiles()
} catch (err) {
- setError(err.response?.data?.detail || err.message)
- } finally {
- setUploading(false)
+ const msg = err.response?.data?.detail || err.message
+ setUploadQueue(entries.map(e => ({ ...e, status: 'error', error: msg })))
+ setError(msg)
}
+ setTimeout(() => setUploadQueue([]), 4000)
}, [connected, fetchFiles])
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
disabled: !connected || stage !== 'input',
- multiple: false,
+ multiple: true,
+ accept: {
+ 'application/zip': ['.zip'],
+ 'application/x-zip-compressed': ['.zip'],
+ 'text/plain': ['.txt'],
+ 'text/markdown': ['.md'],
+ 'application/pdf': ['.pdf'],
+ 'application/json': ['.json'],
+ 'text/csv': ['.csv'],
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
+ },
})
const deleteFile = async name => {
@@ -66,26 +85,29 @@ export default function DocumentManager({ connected }) {
setPreview({ name, content: data.content })
}
+ const statusIcon = item => {
+ if (item.status === 'uploading') return
+ if (item.status === 'done') return
+ if (item.status === 'error') return
+ return null
+ }
+
return (
- {/* Stage tabs */}
{STAGES.map(s => (
))}
- {/* Upload drop zone (input stage only) */}
{stage === 'input' && (
- {uploading
- ?
Uploading…
- :
- {isDragActive ? 'Drop file here' : 'Drag & drop or click to upload to /input'}
-
- }
+
+ {isDragActive ? 'Drop files here' : 'Drag & drop files or click — multiple files and .zip archives supported'}
+
+
txt · md · pdf · json · csv · docx · zip
+
+ )}
+
+ {uploadQueue.length > 0 && (
+
+
Uploading
+
+ {uploadQueue.map((item, i) => (
+ -
+ {statusIcon(item)}
+ {item.name}
+ {item.status === 'done' && {item.action}}
+ {item.error && {item.error}}
+
+ ))}
+
)}
- {/* File table */}
{files.length} file(s)
-
- {error && (
-
{error}
- )}
+ {error &&
{error}
}
{loading ? (
Loading…
@@ -138,23 +168,17 @@ export default function DocumentManager({ connected }) {
{files.map(f => (
|
-
+
{f.name}
|
{formatBytes(f.size)} |
{f.modified} |
- previewFile(f.name)}
- className="p-1 rounded hover:bg-blue-600/20 text-slate-500 hover:text-blue-400 transition-colors"
- >
+ previewFile(f.name)} className="p-1 rounded hover:bg-blue-600/20 text-slate-500 hover:text-blue-400 transition-colors">
- deleteFile(f.name)}
- className="p-1 rounded hover:bg-red-600/20 text-slate-500 hover:text-red-400 transition-colors"
- >
+ deleteFile(f.name)} className="p-1 rounded hover:bg-red-600/20 text-slate-500 hover:text-red-400 transition-colors">
@@ -166,22 +190,17 @@ export default function DocumentManager({ connected }) {
)}
- {/* Preview modal */}
{preview && (
{preview.name}
- setPreview(null)} className="text-slate-500 hover:text-slate-200">
-
-
+ setPreview(null)} className="text-slate-500 hover:text-slate-200">
-
- {preview.content}
-
+ {preview.content}
)}
)
-}
+}
\ No newline at end of file
|