fix(webui): preserve current page when pipeline status changes

- Add intelligent refresh function to handle boundary cases
- Replace manual refresh with smart page preservation logic
- Auto-redirect to last page when current page becomes invalid
- Maintain user's browsing position during pipeline start/stop
- Fix issue where document list would reset to first page after pipeline operations
This commit is contained in:
yangdx 2025-08-21 22:53:24 +08:00
parent 8c6b5f4a3a
commit ec1bf43667

View File

@ -469,22 +469,42 @@ export default function DocumentManager() {
}; };
}, [docs]); }, [docs]);
// New paginated data fetching function // Utility function to update component state
const fetchPaginatedDocuments = useCallback(async ( const updateComponentState = useCallback((response: any) => {
page: number, setPagination(response.pagination);
pageSize: number, setCurrentPageDocs(response.documents);
statusFilter: StatusFilter setStatusCounts(response.status_counts);
// Update legacy docs state for backward compatibility
const legacyDocs: DocsStatusesResponse = {
statuses: {
processed: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processed'),
processing: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processing'),
pending: response.documents.filter((doc: DocStatusResponse) => doc.status === 'pending'),
failed: response.documents.filter((doc: DocStatusResponse) => doc.status === 'failed')
}
};
setDocs(response.pagination.total_count > 0 ? legacyDocs : null);
}, []);
// Intelligent refresh function: handles all boundary cases
const handleIntelligentRefresh = useCallback(async (
targetPage?: number, // Optional target page, defaults to current page
resetToFirst?: boolean // Whether to force reset to first page
) => { ) => {
try { try {
if (!isMountedRef.current) return; if (!isMountedRef.current) return;
setIsRefreshing(true); setIsRefreshing(true);
// Prepare request parameters // Determine target page
const pageToFetch = resetToFirst ? 1 : (targetPage || pagination.page);
const request: DocumentsRequest = { const request: DocumentsRequest = {
status_filter: statusFilter === 'all' ? null : statusFilter, status_filter: statusFilter === 'all' ? null : statusFilter,
page, page: pageToFetch,
page_size: pageSize, page_size: pagination.page_size,
sort_field: sortField, sort_field: sortField,
sort_direction: sortDirection sort_direction: sortDirection
}; };
@ -493,26 +513,34 @@ export default function DocumentManager() {
if (!isMountedRef.current) return; if (!isMountedRef.current) return;
// Update pagination state // Boundary case handling: if target page has no data but total count > 0
setPagination(response.pagination); if (response.documents.length === 0 && response.pagination.total_count > 0) {
setCurrentPageDocs(response.documents); // Calculate last page
setStatusCounts(response.status_counts); const lastPage = Math.max(1, response.pagination.total_pages);
// Update legacy docs state for backward compatibility if (pageToFetch !== lastPage) {
const legacyDocs: DocsStatusesResponse = { // Re-request last page
statuses: { const lastPageRequest: DocumentsRequest = {
processed: response.documents.filter(doc => doc.status === 'processed'), ...request,
processing: response.documents.filter(doc => doc.status === 'processing'), page: lastPage
pending: response.documents.filter(doc => doc.status === 'pending'),
failed: response.documents.filter(doc => doc.status === 'failed')
}
}; };
if (response.pagination.total_count > 0) { const lastPageResponse = await getDocumentsPaginated(lastPageRequest);
setDocs(legacyDocs);
} else { if (!isMountedRef.current) return;
setDocs(null);
// Update page state to last page
setPageByStatus(prev => ({ ...prev, [statusFilter]: lastPage }));
updateComponentState(lastPageResponse);
return;
} }
}
// Normal case: update state
if (pageToFetch !== pagination.page) {
setPageByStatus(prev => ({ ...prev, [statusFilter]: pageToFetch }));
}
updateComponentState(response);
} catch (err) { } catch (err) {
if (isMountedRef.current) { if (isMountedRef.current) {
@ -523,7 +551,20 @@ export default function DocumentManager() {
setIsRefreshing(false); setIsRefreshing(false);
} }
} }
}, [sortField, sortDirection, t]); }, [statusFilter, pagination.page, pagination.page_size, sortField, sortDirection, t, updateComponentState]);
// New paginated data fetching function
const fetchPaginatedDocuments = useCallback(async (
page: number,
pageSize: number,
_statusFilter: StatusFilter // eslint-disable-line @typescript-eslint/no-unused-vars
) => {
// Update pagination state
setPagination(prev => ({ ...prev, page, page_size: pageSize }));
// Use intelligent refresh
await handleIntelligentRefresh(page);
}, [handleIntelligentRefresh]);
// Legacy fetchDocuments function for backward compatibility // Legacy fetchDocuments function for backward compatibility
const fetchDocuments = useCallback(async () => { const fetchDocuments = useCallback(async () => {
@ -678,9 +719,10 @@ export default function DocumentManager() {
if (prevPipelineBusyRef.current !== undefined && prevPipelineBusyRef.current !== pipelineBusy) { if (prevPipelineBusyRef.current !== undefined && prevPipelineBusyRef.current !== pipelineBusy) {
// pipelineBusy state has changed, trigger immediate refresh // pipelineBusy state has changed, trigger immediate refresh
if (currentTab === 'documents' && health && isMountedRef.current) { if (currentTab === 'documents' && health && isMountedRef.current) {
handleManualRefresh(); // Use intelligent refresh to preserve current page
handleIntelligentRefresh();
// Reset polling timer after manual refresh // Reset polling timer after intelligent refresh
const hasActiveDocuments = (statusCounts.processing || 0) > 0 || (statusCounts.pending || 0) > 0; const hasActiveDocuments = (statusCounts.processing || 0) > 0 || (statusCounts.pending || 0) > 0;
const pollingInterval = hasActiveDocuments ? 5000 : 30000; const pollingInterval = hasActiveDocuments ? 5000 : 30000;
startPollingInterval(pollingInterval); startPollingInterval(pollingInterval);
@ -688,7 +730,7 @@ export default function DocumentManager() {
} }
// Update the previous state // Update the previous state
prevPipelineBusyRef.current = pipelineBusy; prevPipelineBusyRef.current = pipelineBusy;
}, [pipelineBusy, currentTab, health, handleManualRefresh, statusCounts.processing, statusCounts.pending, startPollingInterval]); }, [pipelineBusy, currentTab, health, handleIntelligentRefresh, statusCounts.processing, statusCounts.pending, startPollingInterval]);
// Set up intelligent polling with dynamic interval based on document status // Set up intelligent polling with dynamic interval based on document status
useEffect(() => { useEffect(() => {