diff --git a/src/interface/web/app/search/page.tsx b/src/interface/web/app/search/page.tsx index 03a59b5f..c7ac9b3c 100644 --- a/src/interface/web/app/search/page.tsx +++ b/src/interface/web/app/search/page.tsx @@ -55,6 +55,16 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; + import { uploadDataForIndexing } from "../common/chatFunctions"; import { Progress } from "@/components/ui/progress"; interface AdditionalData { @@ -88,6 +98,132 @@ function getNoteTypeIcon(source: string) { return ; } +interface FileCardProps { + file: FileObject; + index: number; + setSelectedFile: (file: string) => void; + setSelectedFileFullText: (file: string) => void; + handleDownload: (fileName: string, content: string) => void; + handleDelete: (fileName: string) => void; + isDeleting: boolean; + selectedFileFullText: string | null; +} + +function FileCard({ file, setSelectedFile, setSelectedFileFullText, handleDownload, handleDelete, isDeleting, selectedFileFullText }: FileCardProps) { + return ( + + + + + +
{ + setSelectedFileFullText( + '', + ); + setSelectedFile( + file.file_name, + ); + }} + > + {file.file_name + .split("/") + .pop()} +
+
+ + + +
+ {file.file_name + .split("/") + .pop()} + +
+
+
+ +

+ {!selectedFileFullText && ( + + )} + { + selectedFileFullText + } +

+
+
+
+ + + + + + + + + + +
+
+ + +

+ {file.raw_text.slice(0, 100)}... +

+
+
+ +
+ {formatDateTime(file.updated_at)} +
+
+
+ ) +} + interface NoteResultProps { note: SearchResult; setFocusSearchResult: (note: SearchResult) => void; @@ -338,6 +474,8 @@ export default function Search() { const [selectedFileFullText, setSelectedFileFullText] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); + const [pageNumber, setPageNumber] = useState(0); + const [numPages, setNumPages] = useState(1); const { toast } = useToast(); @@ -365,15 +503,22 @@ export default function Search() { }); } - const fetchFiles = async () => { + const fetchFiles = async (currentPageNumber: number) => { try { - const response = await fetch("/api/content/all"); + const url = `api/content/all?page=${currentPageNumber}`; + const response = await fetch(url); if (!response.ok) throw new Error("Failed to fetch files"); - const filesList = await response.json(); + const filesData = await response.json(); + const filesList = filesData.files; + const totalPages = filesData.num_pages; + + setNumPages(totalPages); + if (Array.isArray(filesList)) { setFiles(filesList.toSorted()); } + } catch (error) { setError("Failed to load files"); console.error("Error fetching files:", error); @@ -382,6 +527,10 @@ export default function Search() { } }; + useEffect(() => { + fetchFiles(pageNumber); + }, [pageNumber]); + const fetchSpecificFile = async (fileName: string) => { try { const response = await fetch(`/api/content/file?file_name=${fileName}`); @@ -439,12 +588,13 @@ export default function Search() { }, [selectedFile]); useEffect(() => { - fetchFiles(); + fetchFiles(pageNumber); }, []); useEffect(() => { if (uploadedFiles.length > 0) { - fetchFiles(); + setPageNumber(0); + fetchFiles(0); } }, [uploadedFiles]); @@ -462,7 +612,8 @@ export default function Search() { }); // Refresh files list - fetchFiles(); + setPageNumber(0); + fetchFiles(0); } catch (error) { toast({ title: "Error deleting file", @@ -582,119 +733,85 @@ export default function Search() {
{files.map((file, index) => ( - - - - - -
{ - setSelectedFileFullText( - null, - ); - setSelectedFile( - file.file_name, - ); - }} - > - {file.file_name - .split("/") - .pop()} -
-
- - - -
- {file.file_name - .split("/") - .pop()} - -
-
-
- -

- {!selectedFileFullText && ( - - )} - { - selectedFileFullText - } -

-
-
-
- - - - - - - - - - -
-
- - -

- {file.raw_text.slice(0, 100)}... -

-
-
- -
- {formatDateTime(file.updated_at)} -
-
-
+ file={file} + index={index} + setSelectedFile={setSelectedFile} + setSelectedFileFullText={setSelectedFileFullText} + handleDownload={handleDownload} + handleDelete={handleDelete} + isDeleting={isDeleting} + selectedFileFullText={selectedFileFullText} + /> ))}
+ + + + {/* Show prev button if not on first page */} + {pageNumber > 0 && ( + + setPageNumber(pageNumber - 1)} /> + + )} + + {/* Show first page if not on first two pages */} + {pageNumber > 1 && ( + + setPageNumber(0)}>1 + + )} + + {/* Show ellipsis if there's a gap */} + {pageNumber > 2 && ( + + + + )} + + {/* Show previous page if not on first page */} + {pageNumber > 0 && ( + + setPageNumber(pageNumber - 1)}>{pageNumber} + + )} + + {/* Current page */} + + {pageNumber + 1} + + + {/* Show next page if not on last page */} + {pageNumber < numPages - 1 && ( + + setPageNumber(pageNumber + 1)}>{pageNumber + 2} + + )} + + {/* Show ellipsis if there's a gap before last page */} + {pageNumber < numPages - 3 && ( + + + + )} + + {/* Show last page if not on last two pages */} + {pageNumber < numPages - 2 && ( + + setPageNumber(numPages - 1)}>{numPages} + + )} + + {/* Show next button if not on last page */} + {pageNumber < numPages - 1 && ( + + setPageNumber(pageNumber + 1)} /> + + )} + + + )} {searchResults && searchResults.length === 0 && ( diff --git a/src/interface/web/components/ui/pagination.tsx b/src/interface/web/components/ui/pagination.tsx new file mode 100644 index 00000000..c5c99bea --- /dev/null +++ b/src/interface/web/components/ui/pagination.tsx @@ -0,0 +1,118 @@ +import * as React from "react" +import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" +import { ButtonProps, buttonVariants } from "@/components/ui/button" + +const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( +