Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cac9ad2
fix(sharing): fixed folders not appearing when sharing workflows (#616)
waleedlatif1 Jul 4, 2025
231bfb9
fix(deletions): folder deletions were hanging + use cascade deletions…
icecrasher321 Jul 6, 2025
e22f012
fix(envvars): t3-env standardization (#606)
adiologydev Jul 6, 2025
0bf9ce0
feat(enhanced logs): integration + log visualizer canvas (#618)
icecrasher321 Jul 6, 2025
c635b19
fix(frozen canvas): don't error if workflow state not available for m…
icecrasher321 Jul 7, 2025
60e2e6c
fix(reddit): update to oauth endpoints (#627)
icecrasher321 Jul 7, 2025
b4eda8f
feat(tools): added reordering of tool calls in agent tool input (#629)
waleedlatif1 Jul 8, 2025
5cf7d02
fix(oauth): fix oauth to use correct subblock value setter + remove u…
icecrasher321 Jul 8, 2025
ede224a
fix(mem-deletion): hard deletion of memory (#622)
aadamgough Jul 8, 2025
7e46691
feat(build): added turbopack builds to prod (#630)
waleedlatif1 Jul 8, 2025
02b7899
fix(docs): fixed broken docs links (#632)
waleedlatif1 Jul 8, 2025
5167deb
fix(resp format): non-json input was crashing (#631)
icecrasher321 Jul 8, 2025
3e45d79
fix(revert-deployed): correctly revert to deployed state as unit op u…
icecrasher321 Jul 8, 2025
0f21fbf
fix(dropdown): simplify & fix tag dropdown for parallel & loop blocks…
waleedlatif1 Jul 8, 2025
9702155
fix(response-format): add response format to tag dropdown, chat panel…
waleedlatif1 Jul 8, 2025
6dc8b17
fix(sockets-server-disconnection): on reconnect force sync store to d…
icecrasher321 Jul 8, 2025
2c9a4f4
fix(build): fixed build
waleedlatif1 Jul 8, 2025
d904604
Revert "fix(sockets-server-disconnection): on reconnect force sync st…
waleedlatif1 Jul 9, 2025
2ce68ae
fix(sockets): force user to refresh on disconnect in order to mkae ch…
waleedlatif1 Jul 9, 2025
9097c52
fix(sockets): added debouncing for sub-block values to prevent overlo…
waleedlatif1 Jul 9, 2025
3421eae
Merge branch 'main' into staging
waleedlatif1 Jul 9, 2025
4a26b06
fix(search-chunk): searchbar in knowledge base chunk (#557)
aadamgough Jul 9, 2025
24e19a8
add 6s timeout (#645)
icecrasher321 Jul 9, 2025
c65384d
Merge branch 'main' into staging
icecrasher321 Jul 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -56,78 +48,74 @@ 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 [selectedChunks, setSelectedChunks] = useState<Set<string>>(new Set())
const [selectedChunk, setSelectedChunk] = useState<ChunkData | null>(null)
const [isModalOpen, setIsModalOpen] = useState(false)
const [isCreateChunkModalOpen, setIsCreateChunkModalOpen] = useState(false)
const [chunkToDelete, setChunkToDelete] = useState<ChunkData | null>(null)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
const [isBulkOperating, setIsBulkOperating] = useState(false)
const currentPageFromURL = Number.parseInt(searchParams.get('page') || '1', 10)

const [document, setDocument] = useState<DocumentData | null>(null)
const [isLoadingDocument, setIsLoadingDocument] = useState(true)
const [error, setError] = useState<string | null>(null)

// Use the updated chunks hook with pagination
const {
chunks,
isLoading: isLoadingChunks,
error: chunksError,
chunks: paginatedChunks,
allChunks,
filteredChunks,
searchQuery,
setSearchQuery,
currentPage,
totalPages,
hasNextPage,
hasPrevPage,
goToPage,
nextPage,
prevPage,
isLoading: isLoadingAllChunks,
error: chunksError,
refreshChunks,
updateChunk,
} = useDocumentChunks(knowledgeBaseId, documentId)
} = useDocumentChunks(knowledgeBaseId, documentId, currentPageFromURL, '', {
enableClientSearch: true,
})
Comment on lines +76 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: consider defining default client search options in a config file


// Combine errors
const combinedError = error || chunksError
const [selectedChunks, setSelectedChunks] = useState<Set<string>>(new Set())
const [selectedChunk, setSelectedChunk] = useState<ChunkData | null>(null)
const [isModalOpen, setIsModalOpen] = useState(false)
const [isCreateChunkModalOpen, setIsCreateChunkModalOpen] = useState(false)
const [chunkToDelete, setChunkToDelete] = useState<ChunkData | null>(null)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
const [isBulkOperating, setIsBulkOperating] = useState(false)

// Handle pagination navigation
const handlePrevPage = useCallback(() => {
if (hasPrevPage && !isLoadingChunks) {
prevPage()?.catch((err) => {
logger.error('Previous page failed:', err)
})
}
}, [hasPrevPage, isLoadingChunks, prevPage])
const [document, setDocument] = useState<DocumentData | null>(null)
const [isLoadingDocument, setIsLoadingDocument] = useState(true)
const [error, setError] = useState<string | null>(null)

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)
})
const combinedError = error || chunksError

// 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)

Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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 (
<DocumentLoading
knowledgeBaseId={knowledgeBaseId}
Expand All @@ -360,7 +351,7 @@ export function Document({
)
}

if (combinedError && !isLoadingChunks) {
if (combinedError && !isLoadingAllChunks) {
const errorBreadcrumbs = [
{ label: 'Knowledge', href: `/workspace/${workspaceId}/knowledge` },
{
Expand Down Expand Up @@ -404,31 +395,16 @@ export function Document({
<div className='px-6 pb-6'>
{/* Search Section */}
<div className='mb-4 flex items-center justify-between pt-1'>
<div className='relative max-w-md'>
<div className='relative flex items-center'>
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
<input
type='text'
value={searchQuery}
onChange={(e) => 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' && (
<button
onClick={() => setSearchQuery('')}
className='-translate-y-1/2 absolute top-1/2 right-3 transform text-muted-foreground hover:text-foreground'
>
<X className='h-[18px] w-[18px]' />
</button>
)}
</div>
</div>
<SearchInput
value={searchQuery}
onChange={setSearchQuery}
placeholder={
document?.processingStatus === 'completed'
? 'Search chunks...'
: 'Document processing...'
}
disabled={document?.processingStatus !== 'completed'}
/>

<Button
onClick={() => setIsCreateChunkModalOpen(true)}
Expand All @@ -442,7 +418,7 @@ export function Document({
</div>

{/* Error State for chunks */}
{combinedError && !isLoadingChunks && (
{combinedError && !isLoadingAllChunks && (
<div className='mb-4 rounded-md border border-red-200 bg-red-50 p-4'>
<p className='text-red-800 text-sm'>Error loading chunks: {combinedError}</p>
</div>
Expand Down Expand Up @@ -540,7 +516,7 @@ export function Document({
<div className='text-muted-foreground text-xs'>—</div>
</td>
</tr>
) : chunks.length === 0 && !isLoadingChunks ? (
) : paginatedChunks.length === 0 && !isLoadingAllChunks ? (
<tr className='border-b transition-colors hover:bg-accent/30'>
<td className='px-4 py-3'>
<div className='h-3.5 w-3.5' />
Expand All @@ -553,7 +529,9 @@ export function Document({
<FileText className='h-5 w-5 text-muted-foreground' />
<span className='text-muted-foreground text-sm italic'>
{document?.processingStatus === 'completed'
? 'No chunks found'
? searchQuery.trim()
? 'No chunks match your search'
: 'No chunks found'
: 'Document is still processing...'}
</span>
</div>
Expand All @@ -568,7 +546,7 @@ export function Document({
<div className='text-muted-foreground text-xs'>—</div>
</td>
</tr>
) : isLoadingChunks ? (
) : isLoadingAllChunks ? (
// Show loading skeleton rows when chunks are loading
Array.from({ length: 5 }).map((_, index) => (
<tr key={`loading-${index}`} className='border-b transition-colors'>
Expand All @@ -593,7 +571,7 @@ export function Document({
</tr>
))
) : (
chunks.map((chunk) => (
paginatedChunks.map((chunk) => (
<tr
key={chunk.id}
className='cursor-pointer border-b transition-colors hover:bg-accent/30'
Expand All @@ -620,7 +598,10 @@ export function Document({
{/* Content column */}
<td className='px-4 py-3'>
<div className='text-sm' title={chunk.content}>
{truncateContent(chunk.content)}
<SearchHighlight
text={truncateContent(chunk.content)}
searchQuery={searchQuery}
/>
</div>
</td>

Expand Down Expand Up @@ -700,8 +681,8 @@ export function Document({
<Button
variant='ghost'
size='sm'
onClick={handlePrevPage}
disabled={!hasPrevPage || isLoadingChunks}
onClick={prevPage}
disabled={!hasPrevPage || isLoadingAllChunks}
className='h-8 w-8 p-0'
>
<ChevronLeft className='h-4 w-4' />
Expand All @@ -726,8 +707,8 @@ export function Document({
return (
<button
key={page}
onClick={() => handleGoToPage(page)}
disabled={isLoadingChunks}
onClick={() => goToPage(page)}
disabled={isLoadingAllChunks}
className={`font-medium text-sm transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50 ${
page === currentPage ? 'text-foreground' : 'text-muted-foreground'
}`}
Expand All @@ -741,8 +722,8 @@ export function Document({
<Button
variant='ghost'
size='sm'
onClick={handleNextPage}
disabled={!hasNextPage || isLoadingChunks}
onClick={nextPage}
disabled={!hasNextPage || isLoadingAllChunks}
className='h-8 w-8 p-0'
>
<ChevronRight className='h-4 w-4' />
Expand All @@ -767,7 +748,7 @@ export function Document({
updateChunk(updatedChunk.id, updatedChunk)
setSelectedChunk(updatedChunk)
}}
allChunks={chunks}
allChunks={allChunks}
currentPage={currentPage}
totalPages={totalPages}
onNavigateToChunk={(chunk: ChunkData) => {
Expand All @@ -777,11 +758,11 @@ export function Document({
await goToPage(page)

const checkAndSelectChunk = () => {
if (!isLoadingChunks && chunks.length > 0) {
if (!isLoadingAllChunks && paginatedChunks.length > 0) {
if (selectChunk === 'first') {
setSelectedChunk(chunks[0])
setSelectedChunk(paginatedChunks[0])
} else {
setSelectedChunk(chunks[chunks.length - 1])
setSelectedChunk(paginatedChunks[paginatedChunks.length - 1])
}
} else {
// Retry after a short delay if chunks aren't loaded yet
Expand Down
Loading