mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(knowledge): ui and infinite load for knowledge
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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: (
|
||||
<>
|
||||
<Loader2 className='mr-1.5 h-3 w-3 animate-spin' />
|
||||
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: (
|
||||
<>
|
||||
<Loader2 className='mr-1.5 h-3 w-3 animate-spin' />
|
||||
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)]'
|
||||
>
|
||||
<Plus className='mr-1.5 h-3.5 w-3.5' />
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
{isUploading ? 'Uploading...' : 'Add Documents'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -602,17 +588,16 @@ export function KnowledgeBase({
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Table header - fixed */}
|
||||
<div className='sticky top-0 z-10 overflow-x-auto border-b bg-background'>
|
||||
<table className='w-full min-w-[800px] table-fixed'>
|
||||
<table className='w-full min-w-[700px] table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[4%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[20%]' : 'w-[22%]'}`} />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[24%]'}`} />
|
||||
<col className='w-[8%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[16%]' : 'w-[14%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[16%]'}`} />
|
||||
<col className='w-[12%]' />
|
||||
<col className='w-[14%]' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -641,11 +626,6 @@ export function KnowledgeBase({
|
||||
Uploaded
|
||||
</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>
|
||||
Processing
|
||||
</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Status</span>
|
||||
</th>
|
||||
@@ -661,17 +641,16 @@ export function KnowledgeBase({
|
||||
|
||||
{/* Table body - scrollable */}
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<table className='w-full min-w-[800px] table-fixed'>
|
||||
<table className='w-full min-w-[700px] table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[4%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[20%]' : 'w-[22%]'}`} />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[24%]'}`} />
|
||||
<col className='w-[8%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[16%]' : 'w-[14%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[16%]'}`} />
|
||||
<col className='w-[12%]' />
|
||||
<col className='w-[14%]' />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{filteredDocuments.length === 0 && !isLoadingDocuments ? (
|
||||
@@ -713,11 +692,6 @@ export function KnowledgeBase({
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
|
||||
{/* Processing column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
</td>
|
||||
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
@@ -752,9 +726,6 @@ export function KnowledgeBase({
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-4 w-16 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-4 w-12 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-4 w-20 animate-pulse rounded bg-muted' />
|
||||
</td>
|
||||
@@ -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 (
|
||||
<tr
|
||||
@@ -859,22 +830,9 @@ export function KnowledgeBase({
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Processing column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className={statusDisplay.processingStatus.className}>
|
||||
{statusDisplay.processingStatus.text}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
{statusDisplay.activeStatus ? (
|
||||
<div className={statusDisplay.activeStatus.className}>
|
||||
{statusDisplay.activeStatus.text}
|
||||
</div>
|
||||
) : (
|
||||
<div className='text-muted-foreground text-xs'>—</div>
|
||||
)}
|
||||
<div className={statusDisplay.className}>{statusDisplay.text}</div>
|
||||
</td>
|
||||
|
||||
{/* Actions column */}
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
<div className='flex flex-col items-center gap-3'>
|
||||
<div
|
||||
className={`text-4xl transition-all duration-200 ${
|
||||
isDragging ? 'text-purple-500' : 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
📁
|
||||
</div>
|
||||
<div className='space-y-1'>
|
||||
<p
|
||||
className={`font-medium text-sm transition-colors duration-200 ${
|
||||
@@ -504,13 +497,6 @@ export function CreateForm({ onClose, onKnowledgeBaseCreated }: CreateFormProps)
|
||||
multiple
|
||||
/>
|
||||
<div className='flex items-center justify-center gap-2'>
|
||||
<div
|
||||
className={`text-base transition-colors duration-200 ${
|
||||
isDragging ? 'text-purple-500' : 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
📁
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
className={`font-medium text-sm transition-colors duration-200 ${
|
||||
|
||||
@@ -37,12 +37,7 @@ export function DocumentTableRowSkeleton({ isSidebarCollapsed }: { isSidebarColl
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Processing Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-6 w-16 animate-pulse rounded-md bg-muted' />
|
||||
</td>
|
||||
|
||||
{/* Active Status column */}
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
<div className='h-6 w-16 animate-pulse rounded-md bg-muted' />
|
||||
</td>
|
||||
@@ -113,17 +108,16 @@ export function DocumentTableSkeleton({
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Table header - fixed */}
|
||||
<div className='sticky top-0 z-10 overflow-x-auto border-b bg-background'>
|
||||
<table className='w-full min-w-[800px] table-fixed'>
|
||||
<table className='w-full min-w-[700px] table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[4%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[20%]' : 'w-[22%]'}`} />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[24%]'}`} />
|
||||
<col className='w-[8%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[16%]' : 'w-[14%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[16%]'}`} />
|
||||
<col className='w-[12%]' />
|
||||
<col className='w-[14%]' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -145,9 +139,6 @@ export function DocumentTableSkeleton({
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Uploaded</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Processing</span>
|
||||
</th>
|
||||
<th className='px-4 pt-2 pb-3 text-left font-medium'>
|
||||
<span className='text-muted-foreground text-xs leading-none'>Status</span>
|
||||
</th>
|
||||
@@ -161,17 +152,16 @@ export function DocumentTableSkeleton({
|
||||
|
||||
{/* Table body - scrollable */}
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<table className='w-full min-w-[800px] table-fixed'>
|
||||
<table className='w-full min-w-[700px] table-fixed'>
|
||||
<colgroup>
|
||||
<col className='w-[4%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[20%]' : 'w-[22%]'}`} />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[22%]' : 'w-[24%]'}`} />
|
||||
<col className='w-[8%]' />
|
||||
<col className='w-[8%]' />
|
||||
<col className='hidden w-[8%] lg:table-column' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[16%]' : 'w-[14%]'}`} />
|
||||
<col className='w-[10%]' />
|
||||
<col className='w-[10%]' />
|
||||
<col className={`${isSidebarCollapsed ? 'w-[18%]' : 'w-[16%]'}`} />
|
||||
<col className='w-[12%]' />
|
||||
<col className='w-[14%]' />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{Array.from({ length: rowCount }).map((_, i) => (
|
||||
|
||||
@@ -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<string | null>(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
|
||||
|
||||
@@ -373,13 +373,29 @@ export const useKnowledgeStore = create<KnowledgeStore>((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<KnowledgeStore>((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<KnowledgeStore>((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
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user