diff --git a/frontend/src/components/ConnectorTreeComponent.tsx b/frontend/src/components/ConnectorTreeComponent.tsx index a191e4ab..dc0f937b 100644 --- a/frontend/src/components/ConnectorTreeComponent.tsx +++ b/frontend/src/components/ConnectorTreeComponent.tsx @@ -16,6 +16,15 @@ import EyeView from '../assets/eye-view.svg'; import SyncIcon from '../assets/sync.svg'; import CheckmarkIcon from '../assets/checkMark2.svg'; import { useOutsideAlerter } from '../hooks'; +import { + Table, + TableContainer, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, +} from './Table'; interface FileNode { type?: string; @@ -380,39 +389,36 @@ const ConnectorTreeComponent: React.FC = ({ ); }; - const renderFileTree = (directory: DirectoryStructure) => { - if (!directory) return []; - + const renderFileTree = (directory: DirectoryStructure): React.ReactNode[] => { // Create parent directory row const parentRow = currentPath.length > 0 ? [ - - -
- {t('settings.sources.parentFolderAlt')} - - .. - -
- - - - - - - - - - - , - ] + + +
+ {t('settings.sources.parentFolderAlt')} + + .. + +
+
+ + - + + + - + + +
, + ] : []; // Sort entries: directories first, then files, both alphabetically @@ -440,36 +446,35 @@ const ConnectorTreeComponent: React.FC = ({ const dirStats = calculateDirectoryStats(node as DirectoryStructure); return ( - navigateToDirectory(name)} > - +
{t('settings.sources.folderAlt')} - + {name}
- - +
+ {dirStats.totalTokens > 0 ? dirStats.totalTokens.toLocaleString() : '-'} - - + + {dirStats.totalSize > 0 ? formatBytes(dirStats.totalSize) : '-'} - - + +
- - +
+ ); }); @@ -502,30 +507,29 @@ const ConnectorTreeComponent: React.FC = ({ const menuRef = getMenuRef(itemId); return ( - handleFileClick(name)} > - +
{t('settings.sources.fileAlt')} - + {name}
- - +
+ {node.token_count?.toLocaleString() || '-'} - - + + {node.size_bytes ? formatBytes(node.size_bytes) : '-'} - - + +
- - +
+ ); }); @@ -708,25 +712,31 @@ const ConnectorTreeComponent: React.FC = ({
{renderPathNavigation()}
-
- - - -
+ + + + + {t('settings.sources.fileName')} - - - - - - - {renderFileTree(getCurrentDirectory())} -
+ + {t('settings.sources.tokens')} - + + {t('settings.sources.size')} -
- + + + + {t('settings.sources.actions')} + + + + + + {renderFileTree(getCurrentDirectory())} + +
+
)} diff --git a/frontend/src/components/FilePicker.tsx b/frontend/src/components/FilePicker.tsx index 453b994d..fad833fb 100644 --- a/frontend/src/components/FilePicker.tsx +++ b/frontend/src/components/FilePicker.tsx @@ -8,6 +8,15 @@ import FolderIcon from '../assets/folder.svg'; import CheckIcon from '../assets/checkmark.svg'; import SearchIcon from '../assets/search.svg'; import Input from './Input'; +import { + Table, + TableContainer, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, +} from './Table'; interface CloudFile { id: string; @@ -34,6 +43,20 @@ export const FilePicker: React.FC = ({ token, initialSelectedFiles = [], }) => { + const PROVIDER_CONFIG = { + google_drive: { + displayName: 'Drive', + rootName: 'My Drive', + }, + } as const; + + const getProviderConfig = (provider: string) => { + return PROVIDER_CONFIG[provider as keyof typeof PROVIDER_CONFIG] || { + displayName: provider, + rootName: 'Root', + }; + }; + const [files, setFiles] = useState([]); const [selectedFiles, setSelectedFiles] = useState(initialSelectedFiles); const [selectedFolders, setSelectedFolders] = useState([]); @@ -41,29 +64,22 @@ export const FilePicker: React.FC = ({ const [hasMoreFiles, setHasMoreFiles] = useState(false); const [nextPageToken, setNextPageToken] = useState(null); const [currentFolderId, setCurrentFolderId] = useState(null); - const [folderPath, setFolderPath] = useState>([{ + const [folderPath, setFolderPath] = useState>([{ id: null, - name: 'Drive' + name: getProviderConfig(provider).rootName }]); const [searchQuery, setSearchQuery] = useState(''); const [authError, setAuthError] = useState(''); const [isConnected, setIsConnected] = useState(false); + const [userEmail, setUserEmail] = useState(''); const scrollContainerRef = useRef(null); const searchTimeoutRef = useRef | null>(null); const isFolder = (file: CloudFile) => { return file.isFolder || - file.type === 'application/vnd.google-apps.folder' || - file.type === 'folder'; - }; - - const providerDisplayNames = { - google_drive: 'Drive', - }; - - const getConnectorDisplayName = (provider: string) => { - return providerDisplayNames[provider as keyof typeof providerDisplayNames] || provider; + file.type === 'application/vnd.google-apps.folder' || + file.type === 'folder'; }; const loadCloudFiles = useCallback( @@ -147,7 +163,7 @@ export const FilePicker: React.FC = ({ const validateData = await validateResponse.json(); if (validateData.success) { - + setUserEmail(validateData.user_email || 'Connected User'); setIsConnected(true); setAuthError(''); @@ -155,9 +171,9 @@ export const FilePicker: React.FC = ({ setNextPageToken(null); setHasMoreFiles(false); setCurrentFolderId(null); - setFolderPath([{id: null, name: provider === 'google_drive' ? 'My Drive' : - provider === 'onedrive' ? 'My OneDrive' : - provider === 'sharepoint' ? 'SharePoint' : 'Root'}]); + setFolderPath([{ + id: null, name: getProviderConfig(provider).rootName + }]); loadCloudFiles(sessionToken, null, undefined, ''); } else { removeSessionToken(provider); @@ -273,13 +289,14 @@ export const FilePicker: React.FC = ({ // Render authentication UI if (!isConnected) { return ( -
+
{authError && (
{authError}
)} { + setUserEmail(data.user_email || 'Connected User'); setIsConnected(true); setAuthError(''); @@ -299,15 +316,15 @@ export const FilePicker: React.FC = ({ // Render file browser UI return ( -
+
{/* Connected state indicator */}
-
+
- + - Connected to {getConnectorDisplayName(provider)} + Connected as {userEmail}
-
-
+
+
{/* Breadcrumb navigation */} -
- {folderPath.map((path, index) => ( -
- {index > 0 && /} - -
- ))} -
- - {/* Search input */} -
- handleSearchChange(e.target.value)} - colorVariant="silver" - borderVariant="thin" - labelBgClassName="bg-white dark:bg-charleston-green-2" - leftIcon={Search} - /> -
- -
-

- Select Files from {getConnectorDisplayName(provider)} -

- - {selectedFiles.length + selectedFolders.length > 0 - ? `${selectedFiles.length + selectedFolders.length} item${(selectedFiles.length + selectedFolders.length) !== 1 ? 's' : ''} selected` - : '' - } - -
-
- -
- {isLoading && files.length === 0 ? ( -
-
-
- Loading files... -
-
- ) : files.length === 0 ? ( -
- No files found in your {getConnectorDisplayName(provider)} -
- ) : ( - <> -
- {files.map((file, index) => ( -
+
+ {folderPath.map((path, index) => ( +
+ {index > 0 && /} +
- ))} -
+ {path.name} + +
+ ))} +
- {isLoading && ( -
+
+ Select Files from {getProviderConfig(provider).displayName} +
+ +
+ handleSearchChange(e.target.value)} + colorVariant="silver" + borderVariant="thin" + labelBgClassName="bg-[#EEE6FF78] dark:bg-[#2A262E]" + leftIcon={Search} + /> +
+ + {/* Selected Files Message */} +
+ {selectedFiles.length} file(s) selected +
+
+ +
+ + {isLoading && files.length === 0 ? ( +
- Loading more files... + Loading files...
+ ) : files.length === 0 ? ( +
+ No files found in your {getProviderConfig(provider).displayName} +
+ ) : ( + <> + + + + + Name + Last Modified + Size + + + + {files.map((file, index) => ( + { + if (isFolder(file)) { + handleFolderClick(file.id, file.name); + } else { + handleFileSelect(file.id, false); + } + }} + > + +
{ + e.stopPropagation(); + handleFileSelect(file.id, isFolder(file)); + }} + > + {(isFolder(file) ? selectedFolders : selectedFiles).includes(file.id) && ( + Selected + )} +
+
+ +
+
+ {isFolder(file) +
+ {file.name} +
+
+ + {formatDate(file.modifiedTime)} + + + {file.size ? formatBytes(file.size) : '-'} + +
+ ))} +
+
+ + {isLoading && files.length > 0 && ( +
+
+
+ Loading more files... +
+
+ )} + )} - - )} +
+
diff --git a/frontend/src/components/FileTreeComponent.tsx b/frontend/src/components/FileTreeComponent.tsx index 3e97d4e9..7f124fa7 100644 --- a/frontend/src/components/FileTreeComponent.tsx +++ b/frontend/src/components/FileTreeComponent.tsx @@ -11,11 +11,18 @@ import FolderIcon from '../assets/folder.svg'; import ArrowLeft from '../assets/arrow-left.svg'; import ThreeDots from '../assets/three-dots.svg'; import EyeView from '../assets/eye-view.svg'; -import OutlineSource from '../assets/outline-source.svg'; import Trash from '../assets/red-trash.svg'; -import SearchIcon from '../assets/search.svg'; import { useOutsideAlerter } from '../hooks'; import ConfirmationModal from '../modals/ConfirmationModal'; +import { + Table, + TableContainer, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, +} from './Table'; interface FileNode { type?: string; @@ -538,31 +545,30 @@ const FileTreeComponent: React.FC = ({ const parentRow = currentPath.length > 0 ? [ - - +
{t('settings.sources.parentFolderAlt')} - + ..
- - +
+ - - - + + - - - - , + + + , ] : []; @@ -575,32 +581,31 @@ const FileTreeComponent: React.FC = ({ const dirStats = calculateDirectoryStats(node as DirectoryStructure); return ( - navigateToDirectory(name)} > - +
{t('settings.sources.folderAlt')} - + {name}
- - +
+ + {dirStats.totalSize > 0 ? formatBytes(dirStats.totalSize) : '-'} + + {dirStats.totalTokens > 0 ? dirStats.totalTokens.toLocaleString() : '-'} - - - {dirStats.totalSize > 0 ? formatBytes(dirStats.totalSize) : '-'} - - + +
- - +
+ ); }), ...files.map(([name, node]) => { @@ -633,30 +638,29 @@ const FileTreeComponent: React.FC = ({ const menuRef = getMenuRef(itemId); return ( - handleFileClick(name)} > - +
{t('settings.sources.fileAlt')} - + {name}
- - - {node.token_count?.toLocaleString() || '-'} - - +
+ {node.size_bytes ? formatBytes(node.size_bytes) : '-'} - - + + + {node.token_count?.toLocaleString() || '-'} + +
- - +
+ ); }), ]; @@ -834,31 +838,31 @@ const FileTreeComponent: React.FC = ({
{renderPathNavigation()}
-
- - - -
+ + + + + {t('settings.sources.fileName')} - - - - - - - + + + + {renderFileTree(currentDirectory)} - -
- {t('settings.sources.tokens')} - + + {t('settings.sources.size')} - + + + {t('settings.sources.tokens')} + + {t('settings.sources.actions')} -
- + +
+
)} diff --git a/frontend/src/components/Table.tsx b/frontend/src/components/Table.tsx new file mode 100644 index 00000000..6eb49349 --- /dev/null +++ b/frontend/src/components/Table.tsx @@ -0,0 +1,174 @@ +import React from 'react'; + +interface TableProps { + children: React.ReactNode; + className?: string; + minWidth?: string; +} + + +interface TableContainerProps { + children: React.ReactNode; + className?: string; + ref?: React.Ref; + height?: string; + bordered?: boolean; +} + +interface TableHeadProps { + children: React.ReactNode; + className?: string; +} + +interface TableRowProps { + children: React.ReactNode; + className?: string; + onClick?: () => void; +} + +interface TableCellProps { + children?: React.ReactNode; + className?: string; + minWidth?: string; + width?: string; + align?: 'left' | 'right' | 'center'; +} + +const TableContainer = React.forwardRef(({ + children, + className = '', + height = 'auto', + bordered = true +}, ref) => { + return ( +
+
+ {children} +
+
+ ); +});; +const Table: React.FC = ({ + children, + className = '', + minWidth = 'min-w-[600px]' +}) => { + return ( + + {children} +
+ ); +}; +const TableHead: React.FC = ({ children, className = '' }) => { + return ( + + {children} + + ); +}; + +const TableBody: React.FC = ({ children, className = '' }) => { + return ( + tr:last-child]:border-b-0 ${className}`}> + {children} + + ); +}; + +const TableRow: React.FC = ({ children, className = '', onClick }) => { + const baseClasses = "border-b border-[#EEE6FF78] hover:bg-[#ECEEEF] dark:border-[#6A6A6A] dark:hover:bg-[#27282D]"; + const cursorClass = onClick ? "cursor-pointer" : ""; + + return ( + + {children} + + ); +}; + +const TableHeader: React.FC = ({ + children, + className = '', + minWidth, + width, + align = 'left' +}) => { + const getAlignmentClass = () => { + switch (align) { + case 'right': + return 'text-right'; + case 'center': + return 'text-center'; + default: + return 'text-left'; + } + }; + + const baseClasses = `px-2 py-3 text-sm font-medium text-gray-700 lg:px-3 dark:text-[#59636E] border-t border-b border-[#EEE6FF78] dark:border-[#6A6A6A] relative box-border ${getAlignmentClass()}`; + const widthClasses = minWidth ? minWidth : ''; + + return ( + + {children} + + ); +}; + +const TableCell: React.FC = ({ + children, + className = '', + minWidth, + width, + align = 'left' +}) => { + const getAlignmentClass = () => { + switch (align) { + case 'right': + return 'text-right'; + case 'center': + return 'text-center'; + default: + return 'text-left'; + } + }; + + const baseClasses = `px-2 py-2 text-sm lg:px-3 dark:text-[#E0E0E0] box-border ${getAlignmentClass()}`; + const widthClasses = minWidth ? minWidth : ''; + + return ( + + {children} + + ); +}; + +export { + Table, + TableContainer, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, +}; + +export default Table; diff --git a/frontend/src/settings/Sources.tsx b/frontend/src/settings/Sources.tsx index 945bd16c..d27d0296 100644 --- a/frontend/src/settings/Sources.tsx +++ b/frontend/src/settings/Sources.tsx @@ -272,27 +272,27 @@ export default function Sources({ return documentToView ? (
- {documentToView.isNested ? ( - documentToView.type === 'connector' ? ( - setDocumentToView(undefined)} - /> + {documentToView.isNested ? ( + documentToView.type === 'connector:file' ? ( + setDocumentToView(undefined)} + /> + ) : ( + setDocumentToView(undefined)} + /> + ) ) : ( - setDocumentToView(undefined)} + setDocumentToView(undefined)} /> - ) - ) : ( - setDocumentToView(undefined)} - /> - )} + )}
) : (
@@ -352,18 +352,17 @@ export default function Sources({
) : (
- {currentDocuments.map((document, index) => { - const docId = document.id ? document.id.toString() : ''; + {currentDocuments.map((document, index) => { + const docId = document.id ? document.id.toString() : ''; - return ( -
-
+ return ( +
+