diff options
Diffstat (limited to 'frontend/src/components/LeftSidebar.tsx')
| -rw-r--r-- | frontend/src/components/LeftSidebar.tsx | 58 |
1 files changed, 54 insertions, 4 deletions
diff --git a/frontend/src/components/LeftSidebar.tsx b/frontend/src/components/LeftSidebar.tsx index a75df39..aff2df8 100644 --- a/frontend/src/components/LeftSidebar.tsx +++ b/frontend/src/components/LeftSidebar.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react' import { useReactFlow } from 'reactflow'; import { Folder, FileText, Archive, ChevronLeft, ChevronRight, Trash2, MessageSquare, - MoreVertical, Download, Upload, Plus, RefreshCw, Edit3 + MoreVertical, Download, Upload, Plus, RefreshCw, Edit3, Loader2 } from 'lucide-react'; import useFlowStore, { type FSItem, type BlueprintDocument, type FileMeta } from '../store/flowStore'; @@ -19,6 +19,7 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { createNodeFromArchive, theme, files, + uploadingFileIds, projectTree, currentBlueprintPath, saveStatus, @@ -47,6 +48,9 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { const [dragItem, setDragItem] = useState<FSItem | null>(null); const [showSaveStatus, setShowSaveStatus] = useState(false); const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['.'])); + const [fileProvider, setFileProvider] = useState<'local' | 'openai' | 'google'>('local'); + const [openaiPurpose, setOpenaiPurpose] = useState<string>('user_data'); + const [fileSearch, setFileSearch] = useState(''); const handleDragStart = (e: React.DragEvent, archiveId: string) => { e.dataTransfer.setData('archiveId', archiveId); @@ -235,7 +239,10 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { let failed: string[] = []; for (const f of Array.from(list)) { try { - await uploadFile(f); + await uploadFile(f, { + provider: fileProvider, + purpose: fileProvider === 'openai' ? openaiPurpose : undefined, + }); ok += 1; } catch (e) { console.error(e); @@ -251,6 +258,13 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { } }; + const filteredFiles = useMemo(() => { + const q = fileSearch.trim().toLowerCase(); + if (!q) return files; + // Only search local files; keep provider files out of filtered results + return files.filter(f => !f.provider && f.name.toLowerCase().includes(q)); + }, [files, fileSearch]); + const handleFilesInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const files = e.target.files; if (files && files.length > 0) { @@ -610,14 +624,37 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { <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 items-center gap-2"> + <input + value={fileSearch} + onChange={(e) => setFileSearch(e.target.value)} + className={`flex-1 text-sm border rounded px-2 py-1 ${isDark ? 'bg-gray-800 border-gray-700 text-gray-100 placeholder-gray-500' : 'bg-white border-gray-200 text-gray-800 placeholder-gray-400'}`} + placeholder="Search files by name..." + /> + {fileSearch && ( + <button + onClick={() => setFileSearch('')} + className={`text-xs px-2 py-1 rounded ${isDark ? 'bg-gray-800 border border-gray-700 text-gray-200' : 'bg-gray-100 border border-gray-200 text-gray-700'}`} + title="Clear search" + > + Clear + </button> + )} + </div> + + {files.length === 0 && (uploadingFileIds?.length || 0) === 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> + ) : filteredFiles.length === 0 && (uploadingFileIds?.length || 0) === 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 match your search.</p> + </div> ) : ( <div className="flex-1 overflow-y-auto space-y-1"> - {files.map(f => ( + {filteredFiles.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'}`} @@ -627,6 +664,11 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { <span className={`text-[11px] ${isDark ? 'text-gray-500' : 'text-gray-500'}`}> {formatSize(f.size)} • {new Date(f.created_at * 1000).toLocaleString()} </span> + {f.provider && ( + <span className={`text-[11px] inline-flex items-center gap-1 mt-0.5 px-2 py-0.5 rounded ${isDark ? 'bg-gray-800 text-gray-300 border border-gray-700' : 'bg-gray-100 text-gray-700 border border-gray-200'}`}> + Provider: {f.provider === 'openai' ? 'OpenAI' : f.provider === 'google' ? 'Gemini' : f.provider} + </span> + )} </div> <div className="flex items-center gap-2"> <button @@ -646,6 +688,14 @@ const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { </div> </div> ))} + {uploadingFileIds && uploadingFileIds.length > 0 && ( + <div className={`flex items-center justify-between px-2 py-2 rounded border border-dashed ${isDark ? 'border-gray-700 text-gray-400' : 'border-gray-300 text-gray-500'}`}> + <div className="flex items-center gap-2"> + <Loader2 className="animate-spin" size={14} /> + <span className="text-sm">Uploading {uploadingFileIds.length} file{uploadingFileIds.length > 1 ? 's' : ''}…</span> + </div> + </div> + )} </div> )} </div> |
