File size: 11,000 Bytes
f226ca1
781de58
3d06096
 
05e0780
66ee2d1
3d06096
 
 
 
536f2ea
3d06096
781de58
 
3d06096
 
536f2ea
781de58
15bdbe0
3d06096
 
 
 
 
 
05e0780
270baa2
05e0780
 
 
 
 
15bdbe0
 
 
 
 
 
 
 
 
 
 
 
 
3d06096
 
781de58
 
 
 
 
 
 
 
15bdbe0
781de58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05e0780
781de58
 
 
 
536f2ea
 
 
 
 
77f5ddc
 
 
 
7da31e5
77f5ddc
536f2ea
 
 
 
 
781de58
 
 
 
 
 
 
 
05e0780
781de58
 
05e0780
781de58
 
05e0780
781de58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05e0780
 
781de58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5b8143b
781de58
 
5b8143b
 
 
781de58
05e0780
3d06096
781de58
 
 
 
3d06096
781de58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5b8143b
781de58
 
5b8143b
 
 
781de58
05e0780
15bdbe0
781de58
 
05e0780
781de58
 
66ee2d1
f226ca1
 
 
 
 
 
 
66ee2d1
781de58
 
 
270baa2
781de58
 
05e0780
781de58
 
3d06096
 
 
536f2ea
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229

import React from 'react';
import { LAYER_DEFINITIONS } from '../constants';
import { LayerType } from '../types';
import { Box, Sparkles, LayoutTemplate, Circle, Search, X, ChevronLeft, ChevronRight } from 'lucide-react';
import GoogleAd from './GoogleAd';

interface SidebarProps {
  onOpenAIBuilder: () => void;
  onSelectTemplate: (templateId: string) => void;
  onBackToHome: () => void;
  isConnected: boolean;
  isOpen: boolean;
  onToggle: () => void;
}

const Sidebar: React.FC<SidebarProps> = ({ onOpenAIBuilder, onSelectTemplate, onBackToHome, isConnected, isOpen, onToggle }) => {
  const [searchQuery, setSearchQuery] = React.useState('');

  const onDragStart = (event: React.DragEvent, layerType: LayerType) => {
    event.dataTransfer.setData('application/reactflow', layerType);
    event.dataTransfer.effectAllowed = 'move';
  };

  const categories = Array.from(new Set(Object.values(LAYER_DEFINITIONS).map(l => l.category)));
  const categoryOrder = [
    'Core', 'Convolution', 'Preprocessing', 'Recurrent', 'Transformer', 'Normalization', 'GenAI', 
    'Video', 'Audio', '3D', 'Detection', 'OCR', 'Robotics', 
    'Graph', 'Physics', 'Spiking', 'RL', 'Advanced', 
    'Utility', 'Merge'
  ];
  
  categories.sort((a, b) => {
    const idxA = categoryOrder.indexOf(a);
    const idxB = categoryOrder.indexOf(b);
    if (idxA === -1) return 1;
    if (idxB === -1) return -1;
    return idxA - idxB;
  });

  const filteredLayers = Object.values(LAYER_DEFINITIONS).filter(layer => 
    layer.label.toLowerCase().includes(searchQuery.toLowerCase()) || 
    layer.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
    layer.type.toLowerCase().includes(searchQuery.toLowerCase())
  );

  return (
    <>
      {/* Mobile Backdrop */}
      {isOpen && (
        <div 
          className="fixed inset-0 bg-black/60 z-30 md:hidden backdrop-blur-sm transition-opacity"
          onClick={onToggle}
        />
      )}

      {/* Sidebar Container */}
      <aside className={`
        fixed inset-y-0 left-0 z-40 bg-slate-900 border-r border-slate-800 flex flex-col h-full shadow-2xl transition-all duration-300
        ${isOpen ? 'translate-x-0 w-64' : '-translate-x-full w-64 md:translate-x-0 md:w-0 md:border-none'}
        md:relative md:shadow-none
      `}>
        
        {/* Desktop Toggle Button (Chevron) */}
        <button
          onClick={onToggle}
          className={`
            hidden md:flex absolute -right-3 top-6 bg-slate-800 border border-slate-700 text-slate-400 p-0.5 rounded-full hover:text-white hover:bg-slate-700 cursor-pointer z-50 w-6 h-6 items-center justify-center shadow-md transition-opacity duration-300
            ${isOpen ? 'opacity-100' : 'opacity-100 translate-x-3'}
          `}
          title={isOpen ? "Collapse Sidebar" : "Expand Sidebar"}
        >
          {isOpen ? <ChevronLeft size={14} /> : <ChevronRight size={14} />}
        </button>

        {/* Content Container */}
        <div className={`flex flex-col h-full overflow-hidden whitespace-nowrap ${!isOpen ? 'md:opacity-0 md:invisible' : 'opacity-100 visible'} transition-all duration-200`}>
          <div className="p-4 border-b border-slate-800 bg-slate-900 space-y-4 min-w-[16rem]">
            <div className="flex justify-between items-start">
              <div 
                onClick={onBackToHome}
                className="cursor-pointer group"
                title="Return to Home"
              >
                <div className="flex items-center gap-3 mb-1">
                    <img 
                      src="https://huggingface.co/spaces/wuhp/testarcbuilder/resolve/main/public/logo.png" 
                      alt="wuhp" 
                      className="w-10 h-10 object-contain transition-transform group-hover:scale-110" 
                    />
                    <h1 className="text-xl font-bold bg-gradient-to-r from-blue-400 to-violet-400 bg-clip-text text-transparent">
                      wuhp
                    </h1>
                </div>
                <p className="text-xs text-slate-500 pl-8 group-hover:text-slate-400 transition-colors">Visual AI Architect</p>
              </div>
              {/* Mobile Close Button */}
              <button onClick={onToggle} className="md:hidden text-slate-500 hover:text-white">
                <X size={20} />
              </button>
            </div>
            
            <div className="grid grid-cols-2 gap-2">
              <button 
                onClick={onOpenAIBuilder}
                className="flex flex-col items-center justify-center p-2 bg-purple-500/10 hover:bg-purple-500/20 border border-purple-500/30 rounded-lg text-purple-300 transition-colors group"
              >
                <Sparkles size={18} className="mb-1 group-hover:scale-110 transition-transform" />
                <span className="text-[10px] font-bold">AI Builder</span>
              </button>
              <button 
                onClick={() => onSelectTemplate('menu')}
                className="flex flex-col items-center justify-center p-2 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded-lg text-blue-300 transition-colors group"
              >
                <LayoutTemplate size={18} className="mb-1 group-hover:scale-110 transition-transform" />
                <span className="text-[10px] font-bold">Templates</span>
              </button>
            </div>

            {/* Search Bar */}
            <div className="relative">
              <Search className="absolute left-3 top-2.5 text-slate-500" size={14} />
              <input 
                type="text" 
                placeholder="Search layers..." 
                className="w-full bg-slate-950 border border-slate-700 rounded-lg pl-9 pr-8 py-2 text-xs text-slate-200 focus:outline-none focus:border-blue-500 transition-colors placeholder-slate-600"
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
              />
              {searchQuery && (
                <button 
                  onClick={() => setSearchQuery('')}
                  className="absolute right-2 top-2 text-slate-500 hover:text-slate-300"
                >
                  <X size={14} />
                </button>
              )}
            </div>
          </div>
          
          <div className="flex-1 overflow-y-auto p-4 space-y-6 scrollbar-thin scrollbar-thumb-slate-700 min-w-[16rem]">
            
            {searchQuery ? (
              // Search Results View
              <div className="space-y-2">
                <h3 className="text-xs font-semibold text-slate-500 uppercase tracking-widest mb-3">
                  Search Results ({filteredLayers.length})
                </h3>
                {filteredLayers.length > 0 ? (
                  filteredLayers.map(layer => (
                    <div
                      key={layer.type}
                      className="bg-slate-800 hover:bg-slate-750 p-3 rounded border border-slate-700 cursor-grab active:cursor-grabbing transition-colors group relative overflow-hidden"
                      onDragStart={(event) => onDragStart(event, layer.type)}
                      draggable
                    >
                      <div className="flex items-center gap-3 relative z-10">
                        <div className={`p-1.5 rounded transition-colors group-hover:bg-slate-900 bg-slate-900/50 shrink-0`}>
                          <Box size={14} className="text-slate-400 group-hover:text-blue-400" />
                        </div>
                        <div className="flex-1 min-w-0">
                          <div className="text-sm font-medium text-slate-200 group-hover:text-white truncate">{layer.label}</div>
                          <div className="text-xs text-slate-500 leading-tight group-hover:text-slate-400 whitespace-normal mt-0.5">{layer.description}</div>
                        </div>
                      </div>
                    </div>
                  ))
                ) : (
                  <div className="text-center text-slate-500 py-8 text-xs italic">
                    No layers found matching "{searchQuery}"
                  </div>
                )}
              </div>
            ) : (
              // Categorized View
              categories.map(category => (
                <div key={category}>
                  <h3 className="text-xs font-semibold text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2">
                    {category}
                    <div className="h-px flex-1 bg-slate-800"></div>
                  </h3>
                  <div className="grid grid-cols-1 gap-2">
                    {Object.values(LAYER_DEFINITIONS)
                      .filter(l => l.category === category)
                      .map(layer => (
                        <div
                          key={layer.type}
                          className="bg-slate-800 hover:bg-slate-750 p-3 rounded border border-slate-700 cursor-grab active:cursor-grabbing transition-colors group relative overflow-hidden"
                          onDragStart={(event) => onDragStart(event, layer.type)}
                          draggable
                        >
                          <div className="flex items-center gap-3 relative z-10">
                            <div className={`p-1.5 rounded transition-colors group-hover:bg-slate-900 bg-slate-900/50 shrink-0`}>
                              <Box size={14} className="text-slate-400 group-hover:text-blue-400" />
                            </div>
                            <div className="flex-1 min-w-0">
                              <div className="text-sm font-medium text-slate-200 group-hover:text-white truncate">{layer.label}</div>
                              <div className="text-xs text-slate-500 leading-tight group-hover:text-slate-400 whitespace-normal mt-0.5">{layer.description}</div>
                            </div>
                          </div>
                        </div>
                      ))}
                  </div>
                </div>
              ))
            )}
            
            {/* Sidebar Ad Spot - Only render when open to avoid 0-width errors */}
            {isOpen && (
              <div className="pt-4 border-t border-slate-800/50">
                <div className="text-[10px] text-slate-600 mb-2 uppercase tracking-wider font-semibold text-center">Sponsored</div>
                <GoogleAd />
              </div>
            )}

          </div>
          
          <div className="p-4 border-t border-slate-800 text-[10px] text-slate-500 text-center flex items-center justify-center gap-2 min-w-[16rem]">
             <span>v1.3.0 • Powered by Gemini 2.5</span>
             <Circle size={8} className={isConnected ? "fill-emerald-500 text-emerald-500" : "fill-red-500 text-red-500"} />
          </div>
        </div>
      </aside>
    </>
  );
};

export default Sidebar;