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/LeftSidebar.tsx')

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<LeftSidebarProps> = ({ 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<LeftSidebarProps> = ({ isOpen, onToggle }) => {
   const { setViewport, getViewport } = useReactFlow();
   const isDark = theme === 'dark';
   const fileInputRef = useRef<HTMLInputElement | null>(null);
+  const fileUploadRef = useRef<HTMLInputElement | null>(null);
   const [contextMenu, setContextMenu] = useState<{ x: number; y: number; item?: FSItem } | null>(null);
   const [currentFolder, setCurrentFolder] = useState<string>('.');
   const [dragItem, setDragItem] = useState<FSItem | null>(null);
@@ -93,6 +98,13 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ 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<LeftSidebarProps> = ({ 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<HTMLInputElement>) => {
+    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<LeftSidebarProps> = ({ isOpen, onToggle }) => {
           </div>
         )}
         {activeTab === 'files' && (
-          <div className="flex flex-col items-center justify-center h-full opacity-50">
-            <FileText size={48} className="mb-2" />
-            <p>File manager coming soon</p>
+          <div 
+            className="flex flex-col h-full gap-2"
+            onDragOver={(e) => { if (e.dataTransfer.types.includes('Files')) e.preventDefault(); }}
+            onDrop={async (e) => {
+              if (e.dataTransfer.files?.length) {
+                e.preventDefault();
+                await handleFilesUpload(e.dataTransfer.files);
+              }
+            }}
+          >
+            <div className="flex items-center gap-2">
+              <button
+                onClick={() => refreshFiles()}
+                className={`p-1 rounded ${isDark ? 'hover:bg-gray-700' : 'hover:bg-gray-200'}`}
+                title="Refresh files"
+              >
+                <RefreshCw size={14} />
+              </button>
+              <button
+                onClick={() => fileUploadRef.current?.click()}
+                className={`p-1 rounded ${isDark ? 'hover:bg-gray-700' : 'hover:bg-gray-200'}`}
+                title="Upload files (drag & drop supported)"
+              >
+                <Upload size={14} />
+              </button>
+              <input
+                ref={fileUploadRef}
+                type="file"
+                multiple
+                className="hidden"
+                onChange={handleFilesInputChange}
+              />
+              <span className={`text-xs ${isDark ? 'text-gray-500' : 'text-gray-500'}`}>Drag files here or click upload</span>
+            </div>
+
+            {files.length === 0 ? (
+              <div className="flex flex-col items-center justify-center h-full opacity-50 border border-dashed border-gray-300 dark:border-gray-700 rounded">
+                <FileText size={32} className="mb-2" />
+                <p className="text-xs text-center">No files uploaded yet.</p>
+              </div>
+            ) : (
+              <div className="flex-1 overflow-y-auto space-y-1">
+                {files.map(f => (
+                  <div
+                    key={f.id}
+                    className={`flex items-center justify-between px-2 py-1 rounded border ${isDark ? 'border-gray-700 hover:bg-gray-800' : 'border-gray-200 hover:bg-gray-100'}`}
+                  >
+                    <div className="flex flex-col">
+                      <span className="text-sm font-medium">{f.name}</span>
+                      <span className={`text-[11px] ${isDark ? 'text-gray-500' : 'text-gray-500'}`}>
+                        {formatSize(f.size)} • {new Date(f.created_at * 1000).toLocaleString()}
+                      </span>
+                    </div>
+                    <div className="flex items-center gap-2">
+                      <button
+                        onClick={() => handleDownloadFile(f)}
+                        className={`p-1 rounded ${isDark ? 'hover:bg-gray-700' : 'hover:bg-gray-200'}`}
+                        title="Download"
+                      >
+                        <Download size={14} />
+                      </button>
+                      <button
+                        onClick={async () => { if (confirm('Delete this file?')) { await deleteFile(f.id); } }}
+                        className={`p-1 rounded ${isDark ? 'hover:bg-red-900 text-red-300' : 'hover:bg-red-50 text-red-600'}`}
+                        title="Delete"
+                      >
+                        <Trash2 size={14} />
+                      </button>
+                    </div>
+                  </div>
+                ))}
+              </div>
+            )}
           </div>
         )}
         {activeTab === 'archive' && (
-- 
cgit v1.2.3

