diff --git a/apps/sim/app/api/knowledge/route.ts b/apps/sim/app/api/knowledge/route.ts
index 3c6188963f..09b6a39570 100644
--- a/apps/sim/app/api/knowledge/route.ts
+++ b/apps/sim/app/api/knowledge/route.ts
@@ -64,19 +64,6 @@ export async function GET(req: NextRequest) {
.groupBy(knowledgeBase.id)
.orderBy(knowledgeBase.createdAt)
- // Debug logging
- logger.info(`[${requestId}] Knowledge bases with counts:`, {
- data: knowledgeBasesWithCounts.map((kb) => ({
- id: kb.id,
- name: kb.name,
- docCount: kb.docCount,
- })),
- })
-
- logger.info(
- `[${requestId}] Retrieved ${knowledgeBasesWithCounts.length} knowledge bases for user ${session.user.id}`
- )
-
return NextResponse.json({
success: true,
data: knowledgeBasesWithCounts,
diff --git a/apps/sim/app/api/workflows/sync/route.ts b/apps/sim/app/api/workflows/sync/route.ts
index 115e29791c..f17dbb967c 100644
--- a/apps/sim/app/api/workflows/sync/route.ts
+++ b/apps/sim/app/api/workflows/sync/route.ts
@@ -202,9 +202,6 @@ export async function GET(request: Request) {
}
const elapsed = Date.now() - startTime
- logger.info(
- `[${requestId}] Workflow fetch completed in ${elapsed}ms for ${workflows.length} workflows`
- )
// Return the workflows
return NextResponse.json({ data: workflows }, { status: 200 })
diff --git a/apps/sim/app/w/knowledge/[id]/base.tsx b/apps/sim/app/w/knowledge/[id]/base.tsx
index 7f4a3bbd60..f3dabcf7a4 100644
--- a/apps/sim/app/w/knowledge/[id]/base.tsx
+++ b/apps/sim/app/w/knowledge/[id]/base.tsx
@@ -62,64 +62,50 @@ function formatFileSize(bytes: number): string {
}
const getStatusDisplay = (doc: DocumentData) => {
- const processingStatus = (() => {
- switch (doc.processingStatus) {
- case 'pending':
- return {
- text: 'Pending',
- className:
- 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300',
- }
- case 'processing':
- return {
- text: (
- <>
-
- Processing
- >
- ),
- className:
- 'inline-flex items-center rounded-md bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
- }
- case 'completed':
- return {
- text: 'Completed',
- className:
- 'inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400',
- }
- case 'failed':
- return {
- text: 'Failed',
- className:
- 'inline-flex items-center rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-700 dark:bg-red-900/30 dark:text-red-300',
- }
- default:
- return {
- text: 'Unknown',
- className:
- 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300',
- }
- }
- })()
-
- const activeStatus = (() => {
- if (doc.processingStatus === 'completed') {
+ // Consolidated status: show processing status when not completed, otherwise show enabled/disabled
+ switch (doc.processingStatus) {
+ case 'pending':
+ return {
+ text: 'Pending',
+ className:
+ 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300',
+ }
+ case 'processing':
+ return {
+ text: (
+ <>
+
+ Processing
+ >
+ ),
+ className:
+ 'inline-flex items-center rounded-md bg-[#701FFC]/10 px-2 py-1 text-xs font-medium text-[#701FFC] dark:bg-[#701FFC]/20 dark:text-[#8B5FFF]',
+ }
+ case 'failed':
+ return {
+ text: 'Failed',
+ className:
+ 'inline-flex items-center rounded-md bg-red-100 px-2 py-1 text-xs font-medium text-red-700 dark:bg-red-900/30 dark:text-red-300',
+ }
+ case 'completed':
return doc.enabled
? {
text: 'Enabled',
className:
- 'inline-flex items-center rounded-md bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400',
+ 'inline-flex items-center rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400',
}
: {
text: 'Disabled',
className:
'inline-flex items-center rounded-md bg-orange-100 px-2 py-1 text-xs font-medium text-orange-700 dark:bg-orange-900/30 dark:text-orange-400',
}
- }
- return null
- })()
-
- return { processingStatus, activeStatus }
+ default:
+ return {
+ text: 'Unknown',
+ className:
+ 'inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700 dark:bg-gray-800 dark:text-gray-300',
+ }
+ }
}
const getProcessingTime = (doc: DocumentData) => {
@@ -583,9 +569,9 @@ export function KnowledgeBase({
onClick={handleAddDocuments}
disabled={isUploading}
size='sm'
- className='mt-1 mr-1 bg-[#701FFC] font-[480] text-primary-foreground shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_3px_rgba(127,47,255,0.12)]'
+ className='flex items-center gap-1 bg-[#701FFC] font-[480] text-primary-foreground shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_3px_rgba(127,47,255,0.12)]'
>
-
+
{isUploading ? 'Uploading...' : 'Add Documents'}
@@ -602,17 +588,16 @@ export function KnowledgeBase({
{/* Table header - fixed */}
-
+
-
+
-
-
-
+
+
@@ -641,11 +626,6 @@ export function KnowledgeBase({
Uploaded
- |
-
- Processing
-
- |
Status
|
@@ -661,17 +641,16 @@ export function KnowledgeBase({
{/* Table body - scrollable */}
-
+
-
+
-
-
-
+
+
{filteredDocuments.length === 0 && !isLoadingDocuments ? (
@@ -713,11 +692,6 @@ export function KnowledgeBase({
—
- {/* Processing column */}
-
- —
- |
-
{/* Status column */}
—
@@ -752,9 +726,6 @@ export function KnowledgeBase({
|
|
-
-
- |
|
@@ -764,7 +735,7 @@ export function KnowledgeBase({
filteredDocuments.map((doc, index) => {
const isSelected = selectedDocuments.has(doc.id)
const statusDisplay = getStatusDisplay(doc)
- const processingTime = getProcessingTime(doc)
+ // const processingTime = getProcessingTime(doc)
return (
- {/* Processing column */}
- |
-
- {statusDisplay.processingStatus.text}
-
- |
-
{/* Status column */}
- {statusDisplay.activeStatus ? (
-
- {statusDisplay.activeStatus.text}
-
- ) : (
- —
- )}
+ {statusDisplay.text}
|
{/* Actions column */}
diff --git a/apps/sim/app/w/knowledge/components/create-modal/components/create-form/create-form.tsx b/apps/sim/app/w/knowledge/components/create-modal/components/create-form/create-form.tsx
index cc24044c1a..001c022216 100644
--- a/apps/sim/app/w/knowledge/components/create-modal/components/create-form/create-form.tsx
+++ b/apps/sim/app/w/knowledge/components/create-modal/components/create-form/create-form.tsx
@@ -442,7 +442,7 @@ export function CreateForm({ onClose, onKnowledgeBaseCreated }: CreateFormProps)
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
- className={`relative cursor-pointer rounded-lg border-2 border-dashed p-16 text-center transition-all duration-200 ${
+ className={`relative cursor-pointer rounded-lg border-[1.5px] border-dashed p-16 text-center transition-all duration-200 ${
isDragging
? 'border-purple-300 bg-purple-50 shadow-sm'
: 'border-muted-foreground/25 hover:border-muted-foreground/40 hover:bg-muted/10'
@@ -457,13 +457,6 @@ export function CreateForm({ onClose, onKnowledgeBaseCreated }: CreateFormProps)
multiple
/>
-
- 📁
-
-
- 📁
-
- {/* Processing Status column */}
-
-
- |
-
- {/* Active Status column */}
+ {/* Status column */}
|
@@ -113,17 +108,16 @@ export function DocumentTableSkeleton({
{/* Table header - fixed */}
-
+
-
+
-
-
-
+
+
@@ -145,9 +139,6 @@ export function DocumentTableSkeleton({
|
Uploaded
|
-
- Processing
- |
Status
|
@@ -161,17 +152,16 @@ export function DocumentTableSkeleton({
{/* Table body - scrollable */}
-
+
-
+
-
-
-
+
+
{Array.from({ length: rowCount }).map((_, i) => (
diff --git a/apps/sim/hooks/use-knowledge.ts b/apps/sim/hooks/use-knowledge.ts
index 055d4aea53..dfd2d5f0a4 100644
--- a/apps/sim/hooks/use-knowledge.ts
+++ b/apps/sim/hooks/use-knowledge.ts
@@ -10,19 +10,27 @@ export function useKnowledgeBase(id: string) {
const isLoading = loadingKnowledgeBases.has(id)
useEffect(() => {
+ if (!id || knowledgeBase || isLoading) return
+
+ let isMounted = true
+
const loadData = async () => {
try {
setError(null)
await getKnowledgeBase(id)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load knowledge base')
+ if (isMounted) {
+ setError(err instanceof Error ? err.message : 'Failed to load knowledge base')
+ }
}
}
- if (id && !knowledgeBase && !isLoading) {
- loadData()
+ loadData()
+
+ return () => {
+ isMounted = false
}
- }, [id, knowledgeBase, isLoading, getKnowledgeBase])
+ }, [id, knowledgeBase, isLoading]) // Removed getKnowledgeBase from dependencies
return {
knowledgeBase,
@@ -41,19 +49,27 @@ export function useKnowledgeBaseDocuments(knowledgeBaseId: string) {
const isLoading = loadingDocuments.has(knowledgeBaseId)
useEffect(() => {
+ if (!knowledgeBaseId || documents.length > 0 || isLoading) return
+
+ let isMounted = true
+
const loadData = async () => {
try {
setError(null)
await getDocuments(knowledgeBaseId)
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load documents')
+ if (isMounted) {
+ setError(err instanceof Error ? err.message : 'Failed to load documents')
+ }
}
}
- if (knowledgeBaseId && documents.length === 0 && !isLoading) {
- loadData()
+ loadData()
+
+ return () => {
+ isMounted = false
}
- }, [knowledgeBaseId, documents.length, isLoading, getDocuments])
+ }, [knowledgeBaseId, documents.length, isLoading]) // Removed getDocuments from dependencies
const refreshDocumentsData = async () => {
try {
@@ -88,29 +104,95 @@ export function useKnowledgeBasesList() {
} = useKnowledgeStore()
const [error, setError] = useState(null)
+ const [retryCount, setRetryCount] = useState(0)
+ const maxRetries = 3
useEffect(() => {
- const loadData = async () => {
+ if (knowledgeBasesList.length > 0 || loadingKnowledgeBasesList) return
+
+ let isMounted = true
+ let retryTimeoutId: NodeJS.Timeout | null = null
+
+ const loadData = async (attempt = 0) => {
+ // Don't proceed if component is unmounted
+ if (!isMounted) return
+
try {
setError(null)
await getKnowledgeBasesList()
+
+ // Reset retry count on success
+ if (isMounted) {
+ setRetryCount(0)
+ }
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load knowledge bases')
+ if (!isMounted) return
+
+ const errorMessage = err instanceof Error ? err.message : 'Failed to load knowledge bases'
+
+ // Only set error and retry if we haven't exceeded max retries
+ if (attempt < maxRetries) {
+ console.warn(`Knowledge bases load attempt ${attempt + 1} failed, retrying...`, err)
+ setRetryCount(attempt + 1)
+
+ // Exponential backoff: 1s, 2s, 4s
+ const delay = 2 ** attempt * 1000
+ retryTimeoutId = setTimeout(() => {
+ if (isMounted) {
+ loadData(attempt + 1)
+ }
+ }, delay)
+ } else {
+ console.error('All retry attempts failed for knowledge bases list:', err)
+ setError(errorMessage)
+ setRetryCount(maxRetries)
+ }
}
}
- if (knowledgeBasesList.length === 0 && !loadingKnowledgeBasesList) {
- loadData()
+ // Always start from attempt 0
+ loadData(0)
+
+ // Cleanup function
+ return () => {
+ isMounted = false
+ if (retryTimeoutId) {
+ clearTimeout(retryTimeoutId)
+ }
}
- }, [knowledgeBasesList.length, loadingKnowledgeBasesList, getKnowledgeBasesList])
+ }, [knowledgeBasesList.length, loadingKnowledgeBasesList]) // Removed getKnowledgeBasesList from dependencies
const refreshList = async () => {
try {
setError(null)
+ setRetryCount(0)
clearKnowledgeBasesList()
await getKnowledgeBasesList()
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to refresh knowledge bases')
+ const errorMessage = err instanceof Error ? err.message : 'Failed to refresh knowledge bases'
+ setError(errorMessage)
+ console.error('Error refreshing knowledge bases list:', err)
+ }
+ }
+
+ // Force refresh function that bypasses cache and resets everything
+ const forceRefresh = async () => {
+ setError(null)
+ setRetryCount(0)
+ clearKnowledgeBasesList()
+
+ // Force reload by clearing cache and loading state
+ useKnowledgeStore.setState({
+ knowledgeBasesList: [],
+ loadingKnowledgeBasesList: false,
+ })
+
+ try {
+ await getKnowledgeBasesList()
+ } catch (err) {
+ const errorMessage = err instanceof Error ? err.message : 'Failed to refresh knowledge bases'
+ setError(errorMessage)
+ console.error('Error force refreshing knowledge bases list:', err)
}
}
@@ -119,8 +201,11 @@ export function useKnowledgeBasesList() {
isLoading: loadingKnowledgeBasesList,
error,
refreshList,
+ forceRefresh,
addKnowledgeBase,
removeKnowledgeBase,
+ retryCount,
+ maxRetries,
}
}
@@ -140,79 +225,86 @@ export function useDocumentChunks(knowledgeBaseId: string, documentId: string) {
offset: 0,
hasMore: false,
})
+ const [initialLoadDone, setInitialLoadDone] = useState(false)
const isStoreLoading = isChunksLoading(documentId)
const combinedIsLoading = isLoading || isStoreLoading
+ // Single effect to handle all data loading and syncing
useEffect(() => {
if (!knowledgeBaseId || !documentId) return
- const cached = getCachedChunks(documentId)
- if (cached) {
- setChunks(cached.chunks)
- setPagination(cached.pagination)
- setIsLoading(false)
- }
- }, [knowledgeBaseId, documentId, getCachedChunks])
+ let isMounted = true
- // Initial load
- useEffect(() => {
- if (!knowledgeBaseId || !documentId) return
-
- const loadChunks = async () => {
+ const loadAndSyncData = async () => {
try {
- setIsLoading(true)
- setError(null)
-
- // Try to get cached chunks first
+ // Check cache first
const cached = getCachedChunks(documentId)
if (cached) {
- setChunks(cached.chunks)
- setPagination(cached.pagination)
- setIsLoading(false)
+ if (isMounted) {
+ setChunks(cached.chunks)
+ setPagination(cached.pagination)
+ setIsLoading(false)
+ setInitialLoadDone(true)
+ }
return
}
- // If not cached, fetch from API
- const fetchedChunks = await getChunks(knowledgeBaseId, documentId, {
- limit: pagination.limit,
- offset: pagination.offset,
- })
+ // If not cached and we haven't done initial load, fetch from API
+ if (!initialLoadDone && !isStoreLoading) {
+ setIsLoading(true)
+ setError(null)
- setChunks(fetchedChunks)
+ const fetchedChunks = await getChunks(knowledgeBaseId, documentId, {
+ limit: 50, // Use fixed initial values to avoid dependency issues
+ offset: 0,
+ })
- // Update pagination from cache after fetch
- const updatedCache = getCachedChunks(documentId)
- if (updatedCache) {
- setPagination(updatedCache.pagination)
+ if (isMounted) {
+ setChunks(fetchedChunks)
+
+ // Update pagination from cache after fetch
+ const updatedCache = getCachedChunks(documentId)
+ if (updatedCache) {
+ setPagination(updatedCache.pagination)
+ }
+
+ setInitialLoadDone(true)
+ }
}
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load chunks')
+ if (isMounted) {
+ setError(err instanceof Error ? err.message : 'Failed to load chunks')
+ }
} finally {
- setIsLoading(false)
+ if (isMounted) {
+ setIsLoading(false)
+ }
}
}
- loadChunks()
- }, [knowledgeBaseId, documentId, getChunks, getCachedChunks])
+ loadAndSyncData()
- // Sync with store state changes
+ return () => {
+ isMounted = false
+ }
+ }, [knowledgeBaseId, documentId, isStoreLoading, initialLoadDone]) // Removed getCachedChunks and getChunks from dependencies
+
+ // Separate effect to sync with store state changes (no API calls)
useEffect(() => {
+ if (!documentId || !initialLoadDone) return
+
const cached = getCachedChunks(documentId)
if (cached) {
setChunks(cached.chunks)
setPagination(cached.pagination)
}
- }, [documentId, getCachedChunks])
- useEffect(() => {
+ // Update loading state based on store
if (!isStoreLoading && isLoading) {
- const cached = getCachedChunks(documentId)
- if (cached) {
- setIsLoading(false)
- }
+ setIsLoading(false)
}
- }, [isStoreLoading, isLoading, documentId, getCachedChunks])
+ }, [documentId, isStoreLoading, isLoading, initialLoadDone]) // Removed getCachedChunks from dependencies
const refreshChunksData = async (options?: {
search?: string
diff --git a/apps/sim/stores/knowledge/knowledge.ts b/apps/sim/stores/knowledge/knowledge.ts
index 272e8ec07b..29644c2ed6 100644
--- a/apps/sim/stores/knowledge/knowledge.ts
+++ b/apps/sim/stores/knowledge/knowledge.ts
@@ -373,13 +373,29 @@ export const useKnowledgeStore = create((set, get) => ({
return state.knowledgeBasesList
}
+ // Create an AbortController for request cancellation
+ const abortController = new AbortController()
+ const timeoutId = setTimeout(() => {
+ abortController.abort()
+ }, 10000) // 10 second timeout
+
try {
set({ loadingKnowledgeBasesList: true })
- const response = await fetch('/api/knowledge')
+ const response = await fetch('/api/knowledge', {
+ signal: abortController.signal,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+
+ // Clear the timeout since request completed
+ clearTimeout(timeoutId)
if (!response.ok) {
- throw new Error(`Failed to fetch knowledge bases: ${response.statusText}`)
+ throw new Error(
+ `Failed to fetch knowledge bases: ${response.status} ${response.statusText}`
+ )
}
const result = await response.json()
@@ -388,7 +404,7 @@ export const useKnowledgeStore = create((set, get) => ({
throw new Error(result.error || 'Failed to fetch knowledge bases')
}
- const knowledgeBasesList = result.data
+ const knowledgeBasesList = result.data || []
set({
knowledgeBasesList,
@@ -398,9 +414,20 @@ export const useKnowledgeStore = create((set, get) => ({
logger.info(`Knowledge bases list loaded: ${knowledgeBasesList.length} items`)
return knowledgeBasesList
} catch (error) {
+ // Clear the timeout in case of error
+ clearTimeout(timeoutId)
+
logger.error('Error fetching knowledge bases list:', error)
+ // Always set loading to false, even on error
set({ loadingKnowledgeBasesList: false })
+
+ // Don't throw on AbortError (timeout or cancellation)
+ if (error instanceof Error && error.name === 'AbortError') {
+ logger.warn('Knowledge bases list request was aborted (timeout or cancellation)')
+ return state.knowledgeBasesList // Return whatever we have cached
+ }
+
throw error
}
},