From 0dcaf9d7da9fa5041fbd5489a60886ceb416b1d4 Mon Sep 17 00:00:00 2001 From: blackhao <13851610112@163.com> Date: Tue, 9 Dec 2025 18:04:33 -0600 Subject: upload files to backend --- frontend/src/components/LeftSidebar.tsx | 136 +++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 4 deletions(-) (limited to 'frontend/src/components') diff --git a/frontend/src/components/LeftSidebar.tsx b/frontend/src/components/LeftSidebar.tsx index 1b7ccb2..a75df39 100644 --- a/frontend/src/components/LeftSidebar.tsx +++ b/frontend/src/components/LeftSidebar.tsx @@ -4,7 +4,7 @@ import { Folder, FileText, Archive, ChevronLeft, ChevronRight, Trash2, MessageSquare, MoreVertical, Download, Upload, Plus, RefreshCw, Edit3 } from 'lucide-react'; -import useFlowStore, { type FSItem, type BlueprintDocument } from '../store/flowStore'; +import useFlowStore, { type FSItem, type BlueprintDocument, type FileMeta } from '../store/flowStore'; interface LeftSidebarProps { isOpen: boolean; @@ -18,11 +18,15 @@ const LeftSidebar: React.FC = ({ isOpen, onToggle }) => { removeFromArchive, createNodeFromArchive, theme, + files, projectTree, currentBlueprintPath, saveStatus, refreshProjectTree, loadArchivedNodes, + refreshFiles, + uploadFile, + deleteFile, readBlueprintFile, loadBlueprint, saveBlueprintFile, @@ -37,6 +41,7 @@ const LeftSidebar: React.FC = ({ isOpen, onToggle }) => { const { setViewport, getViewport } = useReactFlow(); const isDark = theme === 'dark'; const fileInputRef = useRef(null); + const fileUploadRef = useRef(null); const [contextMenu, setContextMenu] = useState<{ x: number; y: number; item?: FSItem } | null>(null); const [currentFolder, setCurrentFolder] = useState('.'); const [dragItem, setDragItem] = useState(null); @@ -93,6 +98,13 @@ const LeftSidebar: React.FC = ({ isOpen, onToggle }) => { loadArchivedNodes().catch(() => {}); }, [loadArchivedNodes]); + // Load files when entering files tab + useEffect(() => { + if (activeTab === 'files') { + refreshFiles().catch(() => {}); + } + }, [activeTab, refreshFiles]); + // Context menu handlers const openContextMenu = (e: React.MouseEvent, item?: FSItem) => { e.preventDefault(); @@ -217,6 +229,52 @@ const LeftSidebar: React.FC = ({ isOpen, onToggle }) => { await refreshProjectTree(); }; + // Files tab handlers + const handleFilesUpload = async (list: FileList) => { + let ok = 0; + let failed: string[] = []; + for (const f of Array.from(list)) { + try { + await uploadFile(f); + ok += 1; + } catch (e) { + console.error(e); + failed.push(`${f.name}: ${(e as Error).message}`); + } + } + await refreshFiles(); + if (failed.length) { + alert(`Some files failed:\n${failed.join('\n')}`); + } else if (ok > 0) { + // Optional: brief feedback + console.info(`Uploaded ${ok} file(s)`); + } + }; + + const handleFilesInputChange = async (e: React.ChangeEvent) => { + const files = e.target.files; + if (files && files.length > 0) { + await handleFilesUpload(files); + e.target.value = ''; + } + }; + + const handleDownloadFile = (file: FileMeta) => { + const url = `${import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000'}/api/files/download?user=test&file_id=${encodeURIComponent(file.id)}`; + const a = document.createElement('a'); + a.href = url; + a.download = file.name; + a.click(); + }; + + const formatSize = (bytes: number) => { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; + }; + // Fade-out for "Saved" indicator useEffect(() => { if (saveStatus === 'saved') { @@ -517,9 +575,79 @@ const LeftSidebar: React.FC = ({ isOpen, onToggle }) => { )} {activeTab === 'files' && ( -
- -

File manager coming soon

+
{ if (e.dataTransfer.types.includes('Files')) e.preventDefault(); }} + onDrop={async (e) => { + if (e.dataTransfer.files?.length) { + e.preventDefault(); + await handleFilesUpload(e.dataTransfer.files); + } + }} + > +
+ + + + Drag files here or click upload +
+ + {files.length === 0 ? ( +
+ +

No files uploaded yet.

+
+ ) : ( +
+ {files.map(f => ( +
+
+ {f.name} + + {formatSize(f.size)} • {new Date(f.created_at * 1000).toLocaleString()} + +
+
+ + +
+
+ ))} +
+ )}
)} {activeTab === 'archive' && ( -- cgit v1.2.3