diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx index a9b917256..011e83049 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx @@ -1,23 +1,15 @@ 'use client' import { useCallback, useEffect, useState } from 'react' -import { - ChevronLeft, - ChevronRight, - Circle, - CircleOff, - FileText, - Plus, - Search, - Trash2, - X, -} from 'lucide-react' -import { useParams } from 'next/navigation' +import { ChevronLeft, ChevronRight, Circle, CircleOff, FileText, Plus, Trash2 } from 'lucide-react' +import { useParams, useRouter, useSearchParams } from 'next/navigation' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' +import { SearchHighlight } from '@/components/ui/search-highlight' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { createLogger } from '@/lib/logs/console-logger' import { ActionBar } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar' +import { SearchInput } from '@/app/workspace/[workspaceId]/knowledge/components/search-input/search-input' import { useDocumentChunks } from '@/hooks/use-knowledge' import { type ChunkData, type DocumentData, useKnowledgeStore } from '@/stores/knowledge/store' import { useSidebarStore } from '@/stores/sidebar/store' @@ -56,11 +48,35 @@ export function Document({ const { mode, isExpanded } = useSidebarStore() const { getCachedKnowledgeBase, getCachedDocuments } = useKnowledgeStore() const { workspaceId } = useParams() + const router = useRouter() + const searchParams = useSearchParams() const isSidebarCollapsed = mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover' - const [searchQuery, setSearchQuery] = useState('') + const currentPageFromURL = Number.parseInt(searchParams.get('page') || '1', 10) + + const { + chunks: paginatedChunks, + allChunks, + filteredChunks, + searchQuery, + setSearchQuery, + currentPage, + totalPages, + hasNextPage, + hasPrevPage, + goToPage, + nextPage, + prevPage, + isLoading: isLoadingAllChunks, + error: chunksError, + refreshChunks, + updateChunk, + } = useDocumentChunks(knowledgeBaseId, documentId, currentPageFromURL, '', { + enableClientSearch: true, + }) + const [selectedChunks, setSelectedChunks] = useState>(new Set()) const [selectedChunk, setSelectedChunk] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false) @@ -73,61 +89,33 @@ export function Document({ const [isLoadingDocument, setIsLoadingDocument] = useState(true) const [error, setError] = useState(null) - // Use the updated chunks hook with pagination - const { - chunks, - isLoading: isLoadingChunks, - error: chunksError, - currentPage, - totalPages, - hasNextPage, - hasPrevPage, - goToPage, - nextPage, - prevPage, - refreshChunks, - updateChunk, - } = useDocumentChunks(knowledgeBaseId, documentId) - - // Combine errors const combinedError = error || chunksError - // Handle pagination navigation - const handlePrevPage = useCallback(() => { - if (hasPrevPage && !isLoadingChunks) { - prevPage()?.catch((err) => { - logger.error('Previous page failed:', err) - }) - } - }, [hasPrevPage, isLoadingChunks, prevPage]) - - const handleNextPage = useCallback(() => { - if (hasNextPage && !isLoadingChunks) { - nextPage()?.catch((err) => { - logger.error('Next page failed:', err) - }) - } - }, [hasNextPage, isLoadingChunks, nextPage]) - - const handleGoToPage = useCallback( - (page: number) => { - if (page !== currentPage && !isLoadingChunks) { - goToPage(page)?.catch((err) => { - logger.error('Go to page failed:', err) - }) + // URL synchronization for pagination + const updatePageInURL = useCallback( + (newPage: number) => { + const params = new URLSearchParams(searchParams) + if (newPage > 1) { + params.set('page', newPage.toString()) + } else { + params.delete('page') } + router.replace(`?${params.toString()}`, { scroll: false }) }, - [currentPage, isLoadingChunks, goToPage] + [router, searchParams] ) - // Try to get document from store cache first, then fetch if needed + // Sync URL when page changes + useEffect(() => { + updatePageInURL(currentPage) + }, [currentPage, updatePageInURL]) + useEffect(() => { const fetchDocument = async () => { try { setIsLoadingDocument(true) setError(null) - // First try to get from cached documents in the store const cachedDocuments = getCachedDocuments(knowledgeBaseId) const cachedDoc = cachedDocuments?.find((d) => d.id === documentId) @@ -137,7 +125,6 @@ export function Document({ return } - // If not in cache, fetch from API const response = await fetch(`/api/knowledge/${knowledgeBaseId}/documents/${documentId}`) if (!response.ok) { @@ -191,7 +178,7 @@ export function Document({ } const handleToggleEnabled = async (chunkId: string) => { - const chunk = chunks.find((c) => c.id === chunkId) + const chunk = allChunks.find((c) => c.id === chunkId) if (!chunk) return try { @@ -223,7 +210,7 @@ export function Document({ } const handleDeleteChunk = (chunkId: string) => { - const chunk = chunks.find((c) => c.id === chunkId) + const chunk = allChunks.find((c) => c.id === chunkId) if (chunk) { setChunkToDelete(chunk) setIsDeleteModalOpen(true) @@ -260,7 +247,7 @@ export function Document({ const handleSelectAll = (checked: boolean) => { if (checked) { - setSelectedChunks(new Set(chunks.map((chunk) => chunk.id))) + setSelectedChunks(new Set(paginatedChunks.map((chunk) => chunk.id))) } else { setSelectedChunks(new Set()) } @@ -329,28 +316,32 @@ export function Document({ } const handleBulkEnable = async () => { - const chunksToEnable = chunks.filter((chunk) => selectedChunks.has(chunk.id) && !chunk.enabled) + const chunksToEnable = allChunks.filter( + (chunk) => selectedChunks.has(chunk.id) && !chunk.enabled + ) await performBulkChunkOperation('enable', chunksToEnable) } const handleBulkDisable = async () => { - const chunksToDisable = chunks.filter((chunk) => selectedChunks.has(chunk.id) && chunk.enabled) + const chunksToDisable = allChunks.filter( + (chunk) => selectedChunks.has(chunk.id) && chunk.enabled + ) await performBulkChunkOperation('disable', chunksToDisable) } const handleBulkDelete = async () => { - const chunksToDelete = chunks.filter((chunk) => selectedChunks.has(chunk.id)) + const chunksToDelete = allChunks.filter((chunk) => selectedChunks.has(chunk.id)) await performBulkChunkOperation('delete', chunksToDelete) } // Calculate bulk operation counts - const selectedChunksList = chunks.filter((chunk) => selectedChunks.has(chunk.id)) + const selectedChunksList = allChunks.filter((chunk) => selectedChunks.has(chunk.id)) const enabledCount = selectedChunksList.filter((chunk) => chunk.enabled).length const disabledCount = selectedChunksList.filter((chunk) => !chunk.enabled).length - const isAllSelected = chunks.length > 0 && selectedChunks.size === chunks.length + const isAllSelected = paginatedChunks.length > 0 && selectedChunks.size === paginatedChunks.length - if (isLoadingDocument || isLoadingChunks) { + if (isLoadingDocument || isLoadingAllChunks) { return ( {/* Search Section */}
-
-
- - setSearchQuery(e.target.value)} - placeholder={ - document?.processingStatus === 'completed' - ? 'Search chunks...' - : 'Document processing...' - } - disabled={document?.processingStatus !== 'completed'} - className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50' - /> - {searchQuery && document?.processingStatus === 'completed' && ( - - )} -
-
+
{/* Error State for chunks */} - {combinedError && !isLoadingChunks && ( + {combinedError && !isLoadingAllChunks && (

Error loading chunks: {combinedError}

@@ -540,7 +516,7 @@ export function Document({
- ) : chunks.length === 0 && !isLoadingChunks ? ( + ) : paginatedChunks.length === 0 && !isLoadingAllChunks ? (
@@ -553,7 +529,9 @@ export function Document({ {document?.processingStatus === 'completed' - ? 'No chunks found' + ? searchQuery.trim() + ? 'No chunks match your search' + : 'No chunks found' : 'Document is still processing...'}
@@ -568,7 +546,7 @@ export function Document({
- ) : isLoadingChunks ? ( + ) : isLoadingAllChunks ? ( // Show loading skeleton rows when chunks are loading Array.from({ length: 5 }).map((_, index) => ( @@ -593,7 +571,7 @@ export function Document({ )) ) : ( - chunks.map((chunk) => ( + paginatedChunks.map((chunk) => (
- {truncateContent(chunk.content)} +
@@ -700,8 +681,8 @@ export function Document({