diff --git a/surfsense_web/components/chat/ChatInputGroup.tsx b/surfsense_web/components/chat/ChatInputGroup.tsx
index 866ac7b0f..dfce22227 100644
--- a/surfsense_web/components/chat/ChatInputGroup.tsx
+++ b/surfsense_web/components/chat/ChatInputGroup.tsx
@@ -25,7 +25,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-import { type Document, useDocuments } from "@/hooks/use-documents";
+import type { Document } from "@/hooks/use-documents";
import { useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
@@ -40,20 +40,9 @@ const DocumentSelector = React.memo(
const { search_space_id } = useParams();
const [isOpen, setIsOpen] = useState(false);
- const { documents, loading, isLoaded, fetchDocuments } = useDocuments(Number(search_space_id), {
- lazy: true,
- pageSize: -1, // Fetch all documents with large page size
- });
-
- const handleOpenChange = useCallback(
- (open: boolean) => {
- setIsOpen(open);
- if (open && !isLoaded) {
- fetchDocuments();
- }
- },
- [fetchDocuments, isLoaded]
- );
+ const handleOpenChange = useCallback((open: boolean) => {
+ setIsOpen(open);
+ }, []);
const handleSelectionChange = useCallback(
(documents: Document[]) => {
@@ -91,21 +80,12 @@ const DocumentSelector = React.memo(
- {loading ? (
-
-
-
-
Loading documents...
-
-
- ) : isLoaded ? (
-
- ) : null}
+
diff --git a/surfsense_web/components/chat/DocumentsDataTable.tsx b/surfsense_web/components/chat/DocumentsDataTable.tsx
index 79b6216b3..ae647f341 100644
--- a/surfsense_web/components/chat/DocumentsDataTable.tsx
+++ b/surfsense_web/components/chat/DocumentsDataTable.tsx
@@ -2,21 +2,18 @@
import {
type ColumnDef,
- type ColumnFiltersState,
flexRender,
getCoreRowModel,
- getFilteredRowModel,
- getPaginationRowModel,
- getSortedRowModel,
type SortingState,
useReactTable,
- type VisibilityState,
} from "@tanstack/react-table";
-import { ArrowUpDown, Calendar, FileText, Search } from "lucide-react";
-import { useEffect, useMemo, useState } from "react";
+import { ArrowUpDown, Calendar, FileText, Filter, Search } from "lucide-react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Select,
SelectContent,
@@ -32,26 +29,24 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
-import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
-import type { Document, DocumentType } from "@/hooks/use-documents";
+import { type Document, type DocumentType, useDocuments } from "@/hooks/use-documents";
interface DocumentsDataTableProps {
- documents: Document[];
+ searchSpaceId: number;
onSelectionChange: (documents: Document[]) => void;
onDone: () => void;
initialSelectedDocuments?: Document[];
}
-// Combine EnumConnectorName with additional document types
-const DOCUMENT_TYPES: (string | "ALL")[] = [
- "ALL",
- "FILE",
- "EXTENSION",
- "CRAWLED_URL",
- "YOUTUBE_VIDEO",
- ...Object.values(EnumConnectorName),
-];
+function useDebounced(value: T, delay = 300) {
+ const [debounced, setDebounced] = useState(value);
+ useEffect(() => {
+ const t = setTimeout(() => setDebounced(value), delay);
+ return () => clearTimeout(t);
+ }, [value, delay]);
+ return debounced;
+}
const columns: ColumnDef[] = [
{
@@ -177,93 +172,193 @@ const columns: ColumnDef[] = [
];
export function DocumentsDataTable({
- documents,
+ searchSpaceId,
onSelectionChange,
onDone,
initialSelectedDocuments = [],
}: DocumentsDataTableProps) {
const [sorting, setSorting] = useState([]);
- const [columnFilters, setColumnFilters] = useState([]);
- const [columnVisibility, setColumnVisibility] = useState({});
- const [documentTypeFilter, setDocumentTypeFilter] = useState("ALL");
+ const [search, setSearch] = useState("");
+ const debouncedSearch = useDebounced(search, 300);
+ const [documentTypeFilter, setDocumentTypeFilter] = useState([]);
+ const [pageIndex, setPageIndex] = useState(0);
+ const [pageSize, setPageSize] = useState(10);
+ const [typeCounts, setTypeCounts] = useState>({});
+
+ // Use server-side pagination, search, and filtering
+ const { documents, total, loading, fetchDocuments, searchDocuments, getDocumentTypeCounts } =
+ useDocuments(searchSpaceId, {
+ page: pageIndex,
+ pageSize: pageSize,
+ });
+
+ // Fetch document type counts on mount
+ useEffect(() => {
+ if (searchSpaceId && getDocumentTypeCounts) {
+ getDocumentTypeCounts().then(setTypeCounts);
+ }
+ }, [searchSpaceId, getDocumentTypeCounts]);
+
+ // Refetch when pagination changes or when search/filters change
+ useEffect(() => {
+ if (searchSpaceId) {
+ if (debouncedSearch.trim()) {
+ searchDocuments?.(
+ debouncedSearch,
+ pageIndex,
+ pageSize,
+ documentTypeFilter.length > 0 ? documentTypeFilter : undefined
+ );
+ } else {
+ fetchDocuments?.(
+ pageIndex,
+ pageSize,
+ documentTypeFilter.length > 0 ? documentTypeFilter : undefined
+ );
+ }
+ }
+ }, [
+ pageIndex,
+ pageSize,
+ debouncedSearch,
+ documentTypeFilter,
+ searchSpaceId,
+ fetchDocuments,
+ searchDocuments,
+ ]);
// Memoize initial row selection to prevent infinite loops
const initialRowSelection = useMemo(() => {
- if (!documents.length || !initialSelectedDocuments.length) return {};
+ if (!initialSelectedDocuments.length) return {};
const selection: Record = {};
initialSelectedDocuments.forEach((selectedDoc) => {
selection[selectedDoc.id] = true;
});
return selection;
- }, [documents, initialSelectedDocuments]);
+ }, [initialSelectedDocuments]);
- const [rowSelection, setRowSelection] = useState>({});
+ const [rowSelection, setRowSelection] = useState>(
+ () => initialRowSelection
+ );
- // Only update row selection when initialRowSelection actually changes and is not empty
+ // Maintain a separate state for actually selected documents (across all pages)
+ const [selectedDocumentsMap, setSelectedDocumentsMap] = useState