"use client"; import type { ColDef } from "ag-grid-community"; import { AgGridReact, type CustomCellRendererProps } from "ag-grid-react"; import { Building2, Cloud, HardDrive, Search, Trash2, X } from "lucide-react"; import { useRouter } from "next/navigation"; import { type ChangeEvent, useCallback, useRef, useState } from "react"; import { SiGoogledrive } from "react-icons/si"; import { TbBrandOnedrive } from "react-icons/tb"; import { KnowledgeDropdown } from "@/components/knowledge-dropdown"; import { ProtectedRoute } from "@/components/protected-route"; import { Button } from "@/components/ui/button"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; import { useTask } from "@/contexts/task-context"; import { type File, useGetSearchQuery } from "../api/queries/useGetSearchQuery"; import "@/components/AgGrid/registerAgGridModules"; import "@/components/AgGrid/agGridStyles.css"; import { toast } from "sonner"; import { KnowledgeActionsDropdown } from "@/components/knowledge-actions-dropdown"; import { StatusBadge } from "@/components/ui/status-badge"; import { DeleteConfirmationDialog } from "../../../components/confirmation-dialog"; import { useDeleteDocument } from "../api/mutations/useDeleteDocument"; import { filterAccentClasses } from "@/components/knowledge-filter-panel"; // Function to get the appropriate icon for a connector type function getSourceIcon(connectorType?: string) { switch (connectorType) { case "google_drive": return ( ); case "onedrive": return ( ); case "sharepoint": return ; case "s3": return ; default: return ( ); } } function SearchPage() { const router = useRouter(); const { isMenuOpen, files: taskFiles } = useTask(); const { selectedFilter, setSelectedFilter, parsedFilterData, isPanelOpen } = useKnowledgeFilter(); const [selectedRows, setSelectedRows] = useState([]); const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false); const deleteDocumentMutation = useDeleteDocument(); const { data = [], isFetching } = useGetSearchQuery( parsedFilterData?.query || "*", parsedFilterData ); const handleTableSearch = (e: ChangeEvent) => { gridRef.current?.api.setGridOption("quickFilterText", e.target.value); }; // Convert TaskFiles to File format and merge with backend results const taskFilesAsFiles: File[] = taskFiles.map((taskFile) => { return { filename: taskFile.filename, mimetype: taskFile.mimetype, source_url: taskFile.source_url, size: taskFile.size, connector_type: taskFile.connector_type, status: taskFile.status, }; }); const backendFiles = data as File[]; const filteredTaskFiles = taskFilesAsFiles.filter((taskFile) => { return ( taskFile.status !== "active" && !backendFiles.some( (backendFile) => backendFile.filename === taskFile.filename ) ); }); // Combine task files first, then backend files const fileResults = [...backendFiles, ...filteredTaskFiles]; const gridRef = useRef(null); const [columnDefs] = useState[]>([ { field: "filename", headerName: "Source", checkboxSelection: true, headerCheckboxSelection: true, initialFlex: 2, minWidth: 220, cellRenderer: ({ data, value }: CustomCellRendererProps) => { return ( ); }, }, { field: "size", headerName: "Size", valueFormatter: (params) => params.value ? `${Math.round(params.value / 1024)} KB` : "-", }, { field: "mimetype", headerName: "Type", }, { field: "owner", headerName: "Owner", valueFormatter: (params) => params.data?.owner_name || params.data?.owner_email || "—", }, { field: "chunkCount", headerName: "Chunks", valueFormatter: (params) => params.data?.chunkCount?.toString() || "-", }, { field: "avgScore", headerName: "Avg score", initialFlex: 0.5, cellRenderer: ({ value }: CustomCellRendererProps) => { return ( {value?.toFixed(2) ?? "-"} ); }, }, { field: "status", headerName: "Status", cellRenderer: ({ data }: CustomCellRendererProps) => { // Default to 'active' status if no status is provided const status = data?.status || "active"; return ; }, }, { cellRenderer: ({ data }: CustomCellRendererProps) => { return ; }, cellStyle: { alignItems: "center", display: "flex", justifyContent: "center", padding: 0, }, colId: "actions", filter: false, minWidth: 0, width: 40, resizable: false, sortable: false, initialFlex: 0, }, ]); const defaultColDef: ColDef = { resizable: false, suppressMovable: true, initialFlex: 1, minWidth: 100, }; const onSelectionChanged = useCallback(() => { if (gridRef.current) { const selectedNodes = gridRef.current.api.getSelectedRows(); setSelectedRows(selectedNodes); } }, []); const handleBulkDelete = async () => { if (selectedRows.length === 0) return; try { // Delete each file individually since the API expects one filename at a time const deletePromises = selectedRows.map((row) => deleteDocumentMutation.mutateAsync({ filename: row.filename }) ); await Promise.all(deletePromises); toast.success( `Successfully deleted ${selectedRows.length} document${ selectedRows.length > 1 ? "s" : "" }` ); setSelectedRows([]); setShowBulkDeleteDialog(false); // Clear selection in the grid if (gridRef.current) { gridRef.current.api.deselectAll(); } } catch (error) { toast.error( error instanceof Error ? error.message : "Failed to delete some documents" ); } }; return (

Project Knowledge

{/* Search Input Area */}
{selectedFilter?.name && (
{selectedFilter?.name} setSelectedFilter(null)} />
)}
{/* */} {/* //TODO: Implement sync button */} {/* */} {selectedRows.length > 0 && ( )}
params.data.filename} domLayout="autoHeight" onSelectionChanged={onSelectionChanged} noRowsOverlayComponent={() => (

No documents found

Try adjusting your search terms

)} />
{/* Bulk Delete Confirmation Dialog */} 1 ? "s" : "" }? This will remove all chunks and data associated with these documents. This action cannot be undone. Documents to be deleted: ${selectedRows.map((row) => `• ${row.filename}`).join("\n")}`} confirmText="Delete All" onConfirm={handleBulkDelete} isLoading={deleteDocumentMutation.isPending} />
); } export default function ProtectedSearchPage() { return ( ); }