summaryrefslogtreecommitdiff
path: root/frontend/src/components/LeftSidebar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/LeftSidebar.tsx')
-rw-r--r--frontend/src/components/LeftSidebar.tsx58
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>