From 93dbe11014cf967690727c25e89d9d1075008c24 Mon Sep 17 00:00:00 2001
From: blackhao <13851610112@163.com>
Date: Sat, 6 Dec 2025 01:30:57 -0600
Subject: UX

---
 frontend/src/components/LeftSidebar.tsx | 134 ++++++++++++++++++++++++++++++++
 1 file changed, 134 insertions(+)
 create mode 100644 frontend/src/components/LeftSidebar.tsx

(limited to 'frontend/src/components/LeftSidebar.tsx')

diff --git a/frontend/src/components/LeftSidebar.tsx b/frontend/src/components/LeftSidebar.tsx
new file mode 100644
index 0000000..fa8b471
--- /dev/null
+++ b/frontend/src/components/LeftSidebar.tsx
@@ -0,0 +1,134 @@
+import React, { useState } from 'react';
+import { Folder, FileText, Archive, ChevronLeft, ChevronRight, Trash2, MessageSquare } from 'lucide-react';
+import useFlowStore from '../store/flowStore';
+
+interface LeftSidebarProps {
+  isOpen: boolean;
+  onToggle: () => void;
+}
+
+const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => {
+  const [activeTab, setActiveTab] = useState<'project' | 'files' | 'archive'>('project');
+  const { archivedNodes, removeFromArchive, createNodeFromArchive } = useFlowStore();
+  
+  const handleDragStart = (e: React.DragEvent, archiveId: string) => {
+    e.dataTransfer.setData('archiveId', archiveId);
+    e.dataTransfer.effectAllowed = 'copy';
+  };
+
+  if (!isOpen) {
+    return (
+      <div className="border-r border-gray-200 h-screen bg-white flex flex-col items-center py-4 w-12 z-10 transition-all duration-300">
+        <button 
+          onClick={onToggle}
+          className="p-2 hover:bg-gray-100 rounded mb-4"
+          title="Expand"
+        >
+          <ChevronRight size={20} className="text-gray-500" />
+        </button>
+        {/* Icons when collapsed */}
+        <div className="flex flex-col gap-4">
+           <Folder size={20} className={activeTab === 'project' ? "text-blue-500" : "text-gray-400"} />
+           <FileText size={20} className={activeTab === 'files' ? "text-blue-500" : "text-gray-400"} />
+           <Archive size={20} className={activeTab === 'archive' ? "text-blue-500" : "text-gray-400"} />
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="w-64 border-r border-gray-200 h-screen flex flex-col bg-white shadow-xl z-10 transition-all duration-300">
+      {/* Header */}
+      <div className="p-3 border-b border-gray-200 flex justify-between items-center bg-gray-50">
+        <h2 className="font-bold text-sm text-gray-700 uppercase">Workspace</h2>
+        <button 
+          onClick={onToggle}
+          className="p-1 hover:bg-gray-200 rounded"
+        >
+          <ChevronLeft size={16} className="text-gray-500" />
+        </button>
+      </div>
+
+      {/* Tabs */}
+      <div className="flex border-b border-gray-200">
+        <button 
+          onClick={() => setActiveTab('project')}
+          className={`flex-1 p-3 text-xs flex justify-center items-center gap-2 ${activeTab === 'project' ? 'border-b-2 border-blue-500 text-blue-600 font-medium' : 'text-gray-600 hover:bg-gray-50'}`}
+        >
+          <Folder size={14} /> Project
+        </button>
+        <button 
+          onClick={() => setActiveTab('files')}
+          className={`flex-1 p-3 text-xs flex justify-center items-center gap-2 ${activeTab === 'files' ? 'border-b-2 border-blue-500 text-blue-600 font-medium' : 'text-gray-600 hover:bg-gray-50'}`}
+        >
+          <FileText size={14} /> Files
+        </button>
+        <button 
+          onClick={() => setActiveTab('archive')}
+          className={`flex-1 p-3 text-xs flex justify-center items-center gap-2 ${activeTab === 'archive' ? 'border-b-2 border-blue-500 text-blue-600 font-medium' : 'text-gray-600 hover:bg-gray-50'}`}
+        >
+          <Archive size={14} /> Archive
+        </button>
+      </div>
+
+      {/* Content Area */}
+      <div className="flex-1 overflow-y-auto p-4 text-sm text-gray-500">
+        {activeTab === 'project' && (
+          <div className="flex flex-col items-center justify-center h-full opacity-50">
+            <Folder size={48} className="mb-2" />
+            <p>Project settings coming soon</p>
+          </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>
+        )}
+        {activeTab === 'archive' && (
+          <div className="space-y-2">
+            {archivedNodes.length === 0 ? (
+              <div className="flex flex-col items-center justify-center h-40 opacity-50">
+                <Archive size={32} className="mb-2" />
+                <p className="text-xs text-center">
+                  No archived nodes.<br/>
+                  Right-click a node → "Add to Archive"
+                </p>
+              </div>
+            ) : (
+              <>
+                <p className="text-xs text-gray-400 mb-2">Drag to canvas to create a copy</p>
+                {archivedNodes.map((archived) => (
+                  <div
+                    key={archived.id}
+                    draggable
+                    onDragStart={(e) => handleDragStart(e, archived.id)}
+                    className="p-2 bg-gray-50 border border-gray-200 rounded-md cursor-grab hover:bg-gray-100 hover:border-gray-300 transition-colors group"
+                  >
+                    <div className="flex items-center justify-between">
+                      <div className="flex items-center gap-2">
+                        <MessageSquare size={14} className="text-gray-500" />
+                        <span className="text-sm font-medium truncate max-w-[140px]">{archived.label}</span>
+                      </div>
+                      <button
+                        onClick={() => removeFromArchive(archived.id)}
+                        className="opacity-0 group-hover:opacity-100 p-1 hover:bg-red-100 rounded text-gray-400 hover:text-red-500 transition-all"
+                        title="Remove from archive"
+                      >
+                        <Trash2 size={12} />
+                      </button>
+                    </div>
+                    <div className="text-[10px] text-gray-400 mt-1">{archived.model}</div>
+                  </div>
+                ))}
+              </>
+            )}
+          </div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default LeftSidebar;
+
-- 
cgit v1.2.3

