diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx new file mode 100644 index 00000000..9385c474 --- /dev/null +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -0,0 +1,212 @@ +"use client"; + +import { + Building2, + Cloud, + FileText, + HardDrive, + Loader2, + Search, +} from "lucide-react"; +import { Suspense, useCallback, useEffect, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { SiGoogledrive } from "react-icons/si"; +import { TbBrandOnedrive } from "react-icons/tb"; +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 ChunkResult, + type File, + useGetSearchQuery, +} from "../../api/queries/useGetSearchQuery"; + +// 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 ChunksPageContent() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { isMenuOpen } = useTask(); + const { parsedFilterData, isPanelOpen } = useKnowledgeFilter(); + + const filename = searchParams.get("filename"); + const [chunks, setChunks] = useState([]); + + // Use the same search query as the knowledge page, but we'll filter for the specific file + const { data = [], isFetching } = useGetSearchQuery("*", parsedFilterData); + + // Extract chunks for the specific file + useEffect(() => { + if (!filename || !(data as File[]).length) { + setChunks([]); + return; + } + + const fileData = (data as File[]).find( + (file: File) => file.filename === filename + ); + setChunks(fileData?.chunks || []); + }, [data, filename]); + + const handleBack = useCallback(() => { + router.back(); + }, [router]); + + if (!filename) { + return ( +
+
+ +

No file specified

+

+ Please select a file from the knowledge page +

+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+
+ +
+

Document Chunks

+

+ {decodeURIComponent(filename)} +

+
+
+
+ {!isFetching && chunks.length > 0 && ( + + {chunks.length} chunk{chunks.length !== 1 ? "s" : ""} found + + )} +
+
+ + {/* Content Area - matches knowledge page structure */} +
+ {isFetching ? ( +
+
+ +

+ Loading chunks... +

+
+
+ ) : chunks.length === 0 ? ( +
+
+ +

No chunks found

+

+ This file may not have been indexed yet +

+
+
+ ) : ( +
+ {chunks.map((chunk, index) => ( +
+
+
+ + + {chunk.filename} + + {chunk.connector_type && ( +
+ {getSourceIcon(chunk.connector_type)} +
+ )} +
+ + {chunk.score.toFixed(2)} + +
+
+ {chunk.mimetype} + Page {chunk.page} + {chunk.owner_name && Owner: {chunk.owner_name}} +
+

+ {chunk.text} +

+
+ ))} +
+ )} +
+
+
+ ); +} + +function ChunksPage() { + return ( + +
+ +

Loading...

+
+ + } + > + +
+ ); +} + +export default function ProtectedChunksPage() { + return ( + + + + ); +} diff --git a/frontend/src/app/knowledge/page.tsx b/frontend/src/app/knowledge/page.tsx index 701b62c8..16b0cf6b 100644 --- a/frontend/src/app/knowledge/page.tsx +++ b/frontend/src/app/knowledge/page.tsx @@ -3,7 +3,6 @@ import { Building2, Cloud, - FileText, HardDrive, Loader2, Search, @@ -17,6 +16,7 @@ import { useState, useRef, } from "react"; +import { useRouter } from "next/navigation"; import { SiGoogledrive } from "react-icons/si"; import { TbBrandOnedrive } from "react-icons/tb"; import { KnowledgeDropdown } from "@/components/knowledge-dropdown"; @@ -51,11 +51,11 @@ function getSourceIcon(connectorType?: string) { } function SearchPage() { + const router = useRouter(); const { isMenuOpen } = useTask(); const { parsedFilterData, isPanelOpen } = useKnowledgeFilter(); const [query, setQuery] = useState(""); const [queryInputText, setQueryInputText] = useState(""); - const [selectedFile, setSelectedFile] = useState(null); const [selectedRows, setSelectedRows] = useState([]); const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false); @@ -104,8 +104,13 @@ function SearchPage() {
{ + e.preventDefault(); e.stopPropagation(); - setSelectedFile(data?.filename ?? ""); + router.push( + `/knowledge/chunks?filename=${encodeURIComponent( + data?.filename ?? "" + )}` + ); }} > {getSourceIcon(data?.connector_type)} @@ -147,13 +152,13 @@ function SearchPage() { { field: "chunkCount", headerName: "Chunks", - flex: 1, + flex: 2, minWidth: 70, }, { field: "avgScore", headerName: "Avg score", - flex: 1, + flex: 2, minWidth: 90, cellRenderer: ({ value }: CustomCellRendererProps) => { return ( @@ -297,78 +302,31 @@ function SearchPage() {
- {selectedFile ? ( - // Show chunks for selected file - <> -
- - - Chunks from {selectedFile} - + params.data.filename} + onSelectionChanged={onSelectionChanged} + suppressHorizontalScroll={false} + noRowsOverlayComponent={() => ( +
+ +

+ No documents found +

+

+ Try adjusting your search terms +

- {fileResults - .filter(file => file.filename === selectedFile) - .flatMap(file => file.chunks) - .map((chunk, index) => ( -
-
-
- - - {chunk.filename} - -
- - {chunk.score.toFixed(2)} - -
-
- {chunk.mimetype} • Page {chunk.page} -
-

- {chunk.text} -

-
- ))} - - ) : ( - //
- params.data.filename} - onSelectionChanged={onSelectionChanged} - suppressHorizontalScroll={false} - noRowsOverlayComponent={() => ( -
- -

- No documents found -

-

- Try adjusting your search terms -

-
- )} - /> - //
- )} + )} + />
{/* Bulk Delete Confirmation Dialog */}