Skip to content

Commit f269fc9

Browse files
Merge pull request #647 from simstudioai/staging
v0.2.10: fix
2 parents c2f786e + c65384d commit f269fc9

File tree

6 files changed

+423
-178
lines changed

6 files changed

+423
-178
lines changed

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx

Lines changed: 89 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
'use client'
22

33
import { useCallback, useEffect, useState } from 'react'
4-
import {
5-
ChevronLeft,
6-
ChevronRight,
7-
Circle,
8-
CircleOff,
9-
FileText,
10-
Plus,
11-
Search,
12-
Trash2,
13-
X,
14-
} from 'lucide-react'
15-
import { useParams } from 'next/navigation'
4+
import { ChevronLeft, ChevronRight, Circle, CircleOff, FileText, Plus, Trash2 } from 'lucide-react'
5+
import { useParams, useRouter, useSearchParams } from 'next/navigation'
166
import { Button } from '@/components/ui/button'
177
import { Checkbox } from '@/components/ui/checkbox'
8+
import { SearchHighlight } from '@/components/ui/search-highlight'
189
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
1910
import { createLogger } from '@/lib/logs/console-logger'
2011
import { ActionBar } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/action-bar/action-bar'
12+
import { SearchInput } from '@/app/workspace/[workspaceId]/knowledge/components/search-input/search-input'
2113
import { useDocumentChunks } from '@/hooks/use-knowledge'
2214
import { type ChunkData, type DocumentData, useKnowledgeStore } from '@/stores/knowledge/store'
2315
import { useSidebarStore } from '@/stores/sidebar/store'
@@ -56,78 +48,74 @@ export function Document({
5648
const { mode, isExpanded } = useSidebarStore()
5749
const { getCachedKnowledgeBase, getCachedDocuments } = useKnowledgeStore()
5850
const { workspaceId } = useParams()
51+
const router = useRouter()
52+
const searchParams = useSearchParams()
5953

6054
const isSidebarCollapsed =
6155
mode === 'expanded' ? !isExpanded : mode === 'collapsed' || mode === 'hover'
6256

63-
const [searchQuery, setSearchQuery] = useState('')
64-
const [selectedChunks, setSelectedChunks] = useState<Set<string>>(new Set())
65-
const [selectedChunk, setSelectedChunk] = useState<ChunkData | null>(null)
66-
const [isModalOpen, setIsModalOpen] = useState(false)
67-
const [isCreateChunkModalOpen, setIsCreateChunkModalOpen] = useState(false)
68-
const [chunkToDelete, setChunkToDelete] = useState<ChunkData | null>(null)
69-
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
70-
const [isBulkOperating, setIsBulkOperating] = useState(false)
57+
const currentPageFromURL = Number.parseInt(searchParams.get('page') || '1', 10)
7158

72-
const [document, setDocument] = useState<DocumentData | null>(null)
73-
const [isLoadingDocument, setIsLoadingDocument] = useState(true)
74-
const [error, setError] = useState<string | null>(null)
75-
76-
// Use the updated chunks hook with pagination
7759
const {
78-
chunks,
79-
isLoading: isLoadingChunks,
80-
error: chunksError,
60+
chunks: paginatedChunks,
61+
allChunks,
62+
filteredChunks,
63+
searchQuery,
64+
setSearchQuery,
8165
currentPage,
8266
totalPages,
8367
hasNextPage,
8468
hasPrevPage,
8569
goToPage,
8670
nextPage,
8771
prevPage,
72+
isLoading: isLoadingAllChunks,
73+
error: chunksError,
8874
refreshChunks,
8975
updateChunk,
90-
} = useDocumentChunks(knowledgeBaseId, documentId)
76+
} = useDocumentChunks(knowledgeBaseId, documentId, currentPageFromURL, '', {
77+
enableClientSearch: true,
78+
})
9179

92-
// Combine errors
93-
const combinedError = error || chunksError
80+
const [selectedChunks, setSelectedChunks] = useState<Set<string>>(new Set())
81+
const [selectedChunk, setSelectedChunk] = useState<ChunkData | null>(null)
82+
const [isModalOpen, setIsModalOpen] = useState(false)
83+
const [isCreateChunkModalOpen, setIsCreateChunkModalOpen] = useState(false)
84+
const [chunkToDelete, setChunkToDelete] = useState<ChunkData | null>(null)
85+
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
86+
const [isBulkOperating, setIsBulkOperating] = useState(false)
9487

95-
// Handle pagination navigation
96-
const handlePrevPage = useCallback(() => {
97-
if (hasPrevPage && !isLoadingChunks) {
98-
prevPage()?.catch((err) => {
99-
logger.error('Previous page failed:', err)
100-
})
101-
}
102-
}, [hasPrevPage, isLoadingChunks, prevPage])
88+
const [document, setDocument] = useState<DocumentData | null>(null)
89+
const [isLoadingDocument, setIsLoadingDocument] = useState(true)
90+
const [error, setError] = useState<string | null>(null)
10391

104-
const handleNextPage = useCallback(() => {
105-
if (hasNextPage && !isLoadingChunks) {
106-
nextPage()?.catch((err) => {
107-
logger.error('Next page failed:', err)
108-
})
109-
}
110-
}, [hasNextPage, isLoadingChunks, nextPage])
111-
112-
const handleGoToPage = useCallback(
113-
(page: number) => {
114-
if (page !== currentPage && !isLoadingChunks) {
115-
goToPage(page)?.catch((err) => {
116-
logger.error('Go to page failed:', err)
117-
})
92+
const combinedError = error || chunksError
93+
94+
// URL synchronization for pagination
95+
const updatePageInURL = useCallback(
96+
(newPage: number) => {
97+
const params = new URLSearchParams(searchParams)
98+
if (newPage > 1) {
99+
params.set('page', newPage.toString())
100+
} else {
101+
params.delete('page')
118102
}
103+
router.replace(`?${params.toString()}`, { scroll: false })
119104
},
120-
[currentPage, isLoadingChunks, goToPage]
105+
[router, searchParams]
121106
)
122107

123-
// Try to get document from store cache first, then fetch if needed
108+
// Sync URL when page changes
109+
useEffect(() => {
110+
updatePageInURL(currentPage)
111+
}, [currentPage, updatePageInURL])
112+
124113
useEffect(() => {
125114
const fetchDocument = async () => {
126115
try {
127116
setIsLoadingDocument(true)
128117
setError(null)
129118

130-
// First try to get from cached documents in the store
131119
const cachedDocuments = getCachedDocuments(knowledgeBaseId)
132120
const cachedDoc = cachedDocuments?.find((d) => d.id === documentId)
133121

@@ -137,7 +125,6 @@ export function Document({
137125
return
138126
}
139127

140-
// If not in cache, fetch from API
141128
const response = await fetch(`/api/knowledge/${knowledgeBaseId}/documents/${documentId}`)
142129

143130
if (!response.ok) {
@@ -191,7 +178,7 @@ export function Document({
191178
}
192179

193180
const handleToggleEnabled = async (chunkId: string) => {
194-
const chunk = chunks.find((c) => c.id === chunkId)
181+
const chunk = allChunks.find((c) => c.id === chunkId)
195182
if (!chunk) return
196183

197184
try {
@@ -223,7 +210,7 @@ export function Document({
223210
}
224211

225212
const handleDeleteChunk = (chunkId: string) => {
226-
const chunk = chunks.find((c) => c.id === chunkId)
213+
const chunk = allChunks.find((c) => c.id === chunkId)
227214
if (chunk) {
228215
setChunkToDelete(chunk)
229216
setIsDeleteModalOpen(true)
@@ -260,7 +247,7 @@ export function Document({
260247

261248
const handleSelectAll = (checked: boolean) => {
262249
if (checked) {
263-
setSelectedChunks(new Set(chunks.map((chunk) => chunk.id)))
250+
setSelectedChunks(new Set(paginatedChunks.map((chunk) => chunk.id)))
264251
} else {
265252
setSelectedChunks(new Set())
266253
}
@@ -329,28 +316,32 @@ export function Document({
329316
}
330317

331318
const handleBulkEnable = async () => {
332-
const chunksToEnable = chunks.filter((chunk) => selectedChunks.has(chunk.id) && !chunk.enabled)
319+
const chunksToEnable = allChunks.filter(
320+
(chunk) => selectedChunks.has(chunk.id) && !chunk.enabled
321+
)
333322
await performBulkChunkOperation('enable', chunksToEnable)
334323
}
335324

336325
const handleBulkDisable = async () => {
337-
const chunksToDisable = chunks.filter((chunk) => selectedChunks.has(chunk.id) && chunk.enabled)
326+
const chunksToDisable = allChunks.filter(
327+
(chunk) => selectedChunks.has(chunk.id) && chunk.enabled
328+
)
338329
await performBulkChunkOperation('disable', chunksToDisable)
339330
}
340331

341332
const handleBulkDelete = async () => {
342-
const chunksToDelete = chunks.filter((chunk) => selectedChunks.has(chunk.id))
333+
const chunksToDelete = allChunks.filter((chunk) => selectedChunks.has(chunk.id))
343334
await performBulkChunkOperation('delete', chunksToDelete)
344335
}
345336

346337
// Calculate bulk operation counts
347-
const selectedChunksList = chunks.filter((chunk) => selectedChunks.has(chunk.id))
338+
const selectedChunksList = allChunks.filter((chunk) => selectedChunks.has(chunk.id))
348339
const enabledCount = selectedChunksList.filter((chunk) => chunk.enabled).length
349340
const disabledCount = selectedChunksList.filter((chunk) => !chunk.enabled).length
350341

351-
const isAllSelected = chunks.length > 0 && selectedChunks.size === chunks.length
342+
const isAllSelected = paginatedChunks.length > 0 && selectedChunks.size === paginatedChunks.length
352343

353-
if (isLoadingDocument || isLoadingChunks) {
344+
if (isLoadingDocument || isLoadingAllChunks) {
354345
return (
355346
<DocumentLoading
356347
knowledgeBaseId={knowledgeBaseId}
@@ -360,7 +351,7 @@ export function Document({
360351
)
361352
}
362353

363-
if (combinedError && !isLoadingChunks) {
354+
if (combinedError && !isLoadingAllChunks) {
364355
const errorBreadcrumbs = [
365356
{ label: 'Knowledge', href: `/workspace/${workspaceId}/knowledge` },
366357
{
@@ -404,31 +395,16 @@ export function Document({
404395
<div className='px-6 pb-6'>
405396
{/* Search Section */}
406397
<div className='mb-4 flex items-center justify-between pt-1'>
407-
<div className='relative max-w-md'>
408-
<div className='relative flex items-center'>
409-
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
410-
<input
411-
type='text'
412-
value={searchQuery}
413-
onChange={(e) => setSearchQuery(e.target.value)}
414-
placeholder={
415-
document?.processingStatus === 'completed'
416-
? 'Search chunks...'
417-
: 'Document processing...'
418-
}
419-
disabled={document?.processingStatus !== 'completed'}
420-
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'
421-
/>
422-
{searchQuery && document?.processingStatus === 'completed' && (
423-
<button
424-
onClick={() => setSearchQuery('')}
425-
className='-translate-y-1/2 absolute top-1/2 right-3 transform text-muted-foreground hover:text-foreground'
426-
>
427-
<X className='h-[18px] w-[18px]' />
428-
</button>
429-
)}
430-
</div>
431-
</div>
398+
<SearchInput
399+
value={searchQuery}
400+
onChange={setSearchQuery}
401+
placeholder={
402+
document?.processingStatus === 'completed'
403+
? 'Search chunks...'
404+
: 'Document processing...'
405+
}
406+
disabled={document?.processingStatus !== 'completed'}
407+
/>
432408

433409
<Button
434410
onClick={() => setIsCreateChunkModalOpen(true)}
@@ -442,7 +418,7 @@ export function Document({
442418
</div>
443419

444420
{/* Error State for chunks */}
445-
{combinedError && !isLoadingChunks && (
421+
{combinedError && !isLoadingAllChunks && (
446422
<div className='mb-4 rounded-md border border-red-200 bg-red-50 p-4'>
447423
<p className='text-red-800 text-sm'>Error loading chunks: {combinedError}</p>
448424
</div>
@@ -540,7 +516,7 @@ export function Document({
540516
<div className='text-muted-foreground text-xs'></div>
541517
</td>
542518
</tr>
543-
) : chunks.length === 0 && !isLoadingChunks ? (
519+
) : paginatedChunks.length === 0 && !isLoadingAllChunks ? (
544520
<tr className='border-b transition-colors hover:bg-accent/30'>
545521
<td className='px-4 py-3'>
546522
<div className='h-3.5 w-3.5' />
@@ -553,7 +529,9 @@ export function Document({
553529
<FileText className='h-5 w-5 text-muted-foreground' />
554530
<span className='text-muted-foreground text-sm italic'>
555531
{document?.processingStatus === 'completed'
556-
? 'No chunks found'
532+
? searchQuery.trim()
533+
? 'No chunks match your search'
534+
: 'No chunks found'
557535
: 'Document is still processing...'}
558536
</span>
559537
</div>
@@ -568,7 +546,7 @@ export function Document({
568546
<div className='text-muted-foreground text-xs'></div>
569547
</td>
570548
</tr>
571-
) : isLoadingChunks ? (
549+
) : isLoadingAllChunks ? (
572550
// Show loading skeleton rows when chunks are loading
573551
Array.from({ length: 5 }).map((_, index) => (
574552
<tr key={`loading-${index}`} className='border-b transition-colors'>
@@ -593,7 +571,7 @@ export function Document({
593571
</tr>
594572
))
595573
) : (
596-
chunks.map((chunk) => (
574+
paginatedChunks.map((chunk) => (
597575
<tr
598576
key={chunk.id}
599577
className='cursor-pointer border-b transition-colors hover:bg-accent/30'
@@ -620,7 +598,10 @@ export function Document({
620598
{/* Content column */}
621599
<td className='px-4 py-3'>
622600
<div className='text-sm' title={chunk.content}>
623-
{truncateContent(chunk.content)}
601+
<SearchHighlight
602+
text={truncateContent(chunk.content)}
603+
searchQuery={searchQuery}
604+
/>
624605
</div>
625606
</td>
626607

@@ -700,8 +681,8 @@ export function Document({
700681
<Button
701682
variant='ghost'
702683
size='sm'
703-
onClick={handlePrevPage}
704-
disabled={!hasPrevPage || isLoadingChunks}
684+
onClick={prevPage}
685+
disabled={!hasPrevPage || isLoadingAllChunks}
705686
className='h-8 w-8 p-0'
706687
>
707688
<ChevronLeft className='h-4 w-4' />
@@ -726,8 +707,8 @@ export function Document({
726707
return (
727708
<button
728709
key={page}
729-
onClick={() => handleGoToPage(page)}
730-
disabled={isLoadingChunks}
710+
onClick={() => goToPage(page)}
711+
disabled={isLoadingAllChunks}
731712
className={`font-medium text-sm transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50 ${
732713
page === currentPage ? 'text-foreground' : 'text-muted-foreground'
733714
}`}
@@ -741,8 +722,8 @@ export function Document({
741722
<Button
742723
variant='ghost'
743724
size='sm'
744-
onClick={handleNextPage}
745-
disabled={!hasNextPage || isLoadingChunks}
725+
onClick={nextPage}
726+
disabled={!hasNextPage || isLoadingAllChunks}
746727
className='h-8 w-8 p-0'
747728
>
748729
<ChevronRight className='h-4 w-4' />
@@ -767,7 +748,7 @@ export function Document({
767748
updateChunk(updatedChunk.id, updatedChunk)
768749
setSelectedChunk(updatedChunk)
769750
}}
770-
allChunks={chunks}
751+
allChunks={allChunks}
771752
currentPage={currentPage}
772753
totalPages={totalPages}
773754
onNavigateToChunk={(chunk: ChunkData) => {
@@ -777,11 +758,11 @@ export function Document({
777758
await goToPage(page)
778759

779760
const checkAndSelectChunk = () => {
780-
if (!isLoadingChunks && chunks.length > 0) {
761+
if (!isLoadingAllChunks && paginatedChunks.length > 0) {
781762
if (selectChunk === 'first') {
782-
setSelectedChunk(chunks[0])
763+
setSelectedChunk(paginatedChunks[0])
783764
} else {
784-
setSelectedChunk(chunks[chunks.length - 1])
765+
setSelectedChunk(paginatedChunks[paginatedChunks.length - 1])
785766
}
786767
} else {
787768
// Retry after a short delay if chunks aren't loaded yet

0 commit comments

Comments
 (0)