From 2a453fbe3700915c8cfd8018aef3918f0f738225 Mon Sep 17 00:00:00 2001 From: hzywhite <1569582518@qq.com> Date: Thu, 4 Sep 2025 11:24:06 +0800 Subject: [PATCH] webui --- lightrag_webui/src/App.tsx | 1 - lightrag_webui/src/api/lightrag.ts | 3 +- .../documents/UploadDocumentsDialog.tsx | 2 +- .../src/components/graph/GraphControl.tsx | 2 +- .../src/components/graph/GraphLabels.tsx | 4 +- .../src/components/status/StatusCard.tsx | 2 +- .../src/components/ui/AsyncSelect.tsx | 44 ++++--- .../src/features/DocumentManager.tsx | 111 +++++++++++------- 8 files changed, 104 insertions(+), 65 deletions(-) diff --git a/lightrag_webui/src/App.tsx b/lightrag_webui/src/App.tsx index 6bd181fa..1f594e6e 100644 --- a/lightrag_webui/src/App.tsx +++ b/lightrag_webui/src/App.tsx @@ -17,7 +17,6 @@ import RetrievalTesting from '@/features/RetrievalTesting' import ApiSite from '@/features/ApiSite' import { SchemeProvider } from '@/contexts/SchemeContext'; - import { Tabs, TabsContent } from '@/components/ui/Tabs' function App() { diff --git a/lightrag_webui/src/api/lightrag.ts b/lightrag_webui/src/api/lightrag.ts index 293d1fb1..4eb383d0 100644 --- a/lightrag_webui/src/api/lightrag.ts +++ b/lightrag_webui/src/api/lightrag.ts @@ -35,7 +35,6 @@ export type LightragStatus = { embedding_binding: string embedding_binding_host: string embedding_model: string - max_tokens: number kv_storage: string doc_status_storage: string graph_storage: string @@ -43,6 +42,7 @@ export type LightragStatus = { workspace?: string max_graph_nodes?: string enable_rerank?: boolean + rerank_binding?: string | null rerank_model?: string | null rerank_binding_host?: string | null summary_language: string @@ -599,6 +599,7 @@ export const uploadDocument = async ( const formData = new FormData() formData.append('file', file) formData.append('schemeId', schemeId.toString()) + const response = await axiosInstance.post('/documents/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' diff --git a/lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx b/lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx index 1906cde1..a9176481 100644 --- a/lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx +++ b/lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx @@ -64,7 +64,7 @@ export default function UploadDocumentsDialog({ onDocumentsUploaded }: UploadDoc toast.error(t('schemeManager.upload.noSchemeSelected')); return; } - + setIsUploading(true) let hasSuccessfulUpload = false diff --git a/lightrag_webui/src/components/graph/GraphControl.tsx b/lightrag_webui/src/components/graph/GraphControl.tsx index 8211178a..af7cf250 100644 --- a/lightrag_webui/src/components/graph/GraphControl.tsx +++ b/lightrag_webui/src/components/graph/GraphControl.tsx @@ -142,7 +142,7 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean }) // Register the events registerEvents(events) - }, [registerEvents, enableEdgeEvents]) + }, [registerEvents, enableEdgeEvents, sigma]) /** * When edge size settings change, recalculate edge sizes and refresh the sigma instance diff --git a/lightrag_webui/src/components/graph/GraphLabels.tsx b/lightrag_webui/src/components/graph/GraphLabels.tsx index 9321a14e..4938ee05 100644 --- a/lightrag_webui/src/components/graph/GraphLabels.tsx +++ b/lightrag_webui/src/components/graph/GraphLabels.tsx @@ -140,9 +140,9 @@ const GraphLabels = () => { searchInputClassName="max-h-8" triggerTooltip={t('graphPanel.graphLabels.selectTooltip')} fetcher={fetchData} - renderOption={(item) =>
{item}
} + renderOption={(item) =>
{item}
} getOptionValue={(item) => item} - getDisplayValue={(item) =>
{item}
} + getDisplayValue={(item) =>
{item}
} notFound={
No labels found
} label={t('graphPanel.graphLabels.label')} placeholder={t('graphPanel.graphLabels.placeholder')} diff --git a/lightrag_webui/src/components/status/StatusCard.tsx b/lightrag_webui/src/components/status/StatusCard.tsx index a084ea13..8eeaaf70 100644 --- a/lightrag_webui/src/components/status/StatusCard.tsx +++ b/lightrag_webui/src/components/status/StatusCard.tsx @@ -52,7 +52,7 @@ const StatusCard = ({ status }: { status: LightragStatus | null }) => { {t('graphPanel.statusCard.rerankerBindingHost')}: {status.configuration.rerank_binding_host || '-'} {t('graphPanel.statusCard.rerankerModel')}: - {status.configuration.rerank_model || '-'} + {(status.configuration.rerank_binding || '-')} : {(status.configuration.rerank_model || '-')} )} diff --git a/lightrag_webui/src/components/ui/AsyncSelect.tsx b/lightrag_webui/src/components/ui/AsyncSelect.tsx index c42d60b7..9a5a17f8 100644 --- a/lightrag_webui/src/components/ui/AsyncSelect.tsx +++ b/lightrag_webui/src/components/ui/AsyncSelect.tsx @@ -245,22 +245,34 @@ export function AsyncSelect({ ))} - {options.map((option) => ( - - {renderOption(option)} - - - ))} + {options.map((option, index) => { + const optionValue = getOptionValue(option); + // Use index as a safe value that won't be trimmed by cmdk + const safeValue = `option-${index}-${optionValue.length}`; + + return ( + { + // Extract the original value from the safe value + const selectedIndex = parseInt(selectedSafeValue.split('-')[1]); + const originalValue = getOptionValue(options[selectedIndex]); + console.log(`CommandItem onSelect: safeValue='${selectedSafeValue}', originalValue='${originalValue}' (length: ${originalValue.length})`); + handleSelect(originalValue); + }} + className="truncate" + > + {renderOption(option)} + + + ); + })} diff --git a/lightrag_webui/src/features/DocumentManager.tsx b/lightrag_webui/src/features/DocumentManager.tsx index b1c27100..464ac744 100644 --- a/lightrag_webui/src/features/DocumentManager.tsx +++ b/lightrag_webui/src/features/DocumentManager.tsx @@ -153,7 +153,7 @@ type SortDirection = 'asc' | 'desc'; export default function DocumentManager() { const { selectedScheme } = useScheme(); - console.log('selectedScheme in DocumentManager:', selectedScheme); + // Track component mount status const isMountedRef = useRef(true); @@ -189,7 +189,7 @@ export default function DocumentManager() { const setDocumentsPageSize = useSettingsStore.use.setDocumentsPageSize() // New pagination state - const [, setCurrentPageDocs] = useState([]) + const [currentPageDocs, setCurrentPageDocs] = useState([]) const [pagination, setPagination] = useState({ page: 1, page_size: documentsPageSize, @@ -302,6 +302,16 @@ export default function DocumentManager() { type DocStatusWithStatus = DocStatusResponse & { status: DocStatus }; const filteredAndSortedDocs = useMemo(() => { + // Use currentPageDocs directly if available (from paginated API) + // This preserves the backend's sort order and prevents status grouping + if (currentPageDocs && currentPageDocs.length > 0) { + return currentPageDocs.map(doc => ({ + ...doc, + status: doc.status as DocStatus + })) as DocStatusWithStatus[]; + } + + // Fallback to legacy docs structure for backward compatibility if (!docs) return null; // Create a flat array of documents with status information @@ -334,7 +344,7 @@ export default function DocumentManager() { } return allDocuments; - }, [docs, sortField, sortDirection, statusFilter, sortDocuments]); + }, [currentPageDocs, docs, sortField, sortDirection, statusFilter, sortDocuments]); // Calculate current page selection state (after filteredAndSortedDocs is defined) const currentPageDocIds = useMemo(() => { @@ -402,7 +412,7 @@ export default function DocumentManager() { processing: 0, handling: 0, pending: 0, - ready: 0, + ready: 0, failed: 0 }) @@ -481,13 +491,6 @@ export default function DocumentManager() { }; }, [docs]); -<<<<<<< Updated upstream - // New paginated data fetching function - const fetchPaginatedDocuments = useCallback(async ( - page: number, - pageSize: number, - statusFilter: StatusFilter -======= // Utility function to update component state const updateComponentState = useCallback((response: any) => { setPagination(response.pagination); @@ -500,9 +503,9 @@ export default function DocumentManager() { 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'), ready: response.documents.filter((doc: DocStatusResponse) => doc.status === 'ready'), - handling: response.documents.filter((doc: DocStatusResponse) => doc.status === 'handling') + handling: response.documents.filter((doc: DocStatusResponse) => doc.status === 'handling'), + failed: response.documents.filter((doc: DocStatusResponse) => doc.status === 'failed') } }; @@ -513,18 +516,19 @@ export default function DocumentManager() { const handleIntelligentRefresh = useCallback(async ( targetPage?: number, // Optional target page, defaults to current page resetToFirst?: boolean // Whether to force reset to first page ->>>>>>> Stashed changes ) => { try { if (!isMountedRef.current) return; setIsRefreshing(true); - // Prepare request parameters + // Determine target page + const pageToFetch = resetToFirst ? 1 : (targetPage || pagination.page); + const request: DocumentsRequest = { status_filter: statusFilter === 'all' ? null : statusFilter, - page, - page_size: pageSize, + page: pageToFetch, + page_size: pagination.page_size, sort_field: sortField, sort_direction: sortDirection }; @@ -533,27 +537,35 @@ export default function DocumentManager() { if (!isMountedRef.current) return; - // Update pagination state - setPagination(response.pagination); - setCurrentPageDocs(response.documents); - setStatusCounts(response.status_counts); + // Boundary case handling: if target page has no data but total count > 0 + if (response.documents.length === 0 && response.pagination.total_count > 0) { + // Calculate last page + const lastPage = Math.max(1, response.pagination.total_pages); - // Update legacy docs state for backward compatibility - const legacyDocs: DocsStatusesResponse = { - statuses: { - processed: response.documents.filter(doc => doc.status === 'processed'), - processing: response.documents.filter(doc => doc.status === 'processing'), - pending: response.documents.filter(doc => doc.status === 'pending'), - failed: response.documents.filter(doc => doc.status === 'failed') + if (pageToFetch !== lastPage) { + // Re-request last page + const lastPageRequest: DocumentsRequest = { + ...request, + page: lastPage + }; + + const lastPageResponse = await getDocumentsPaginated(lastPageRequest); + + if (!isMountedRef.current) return; + + // Update page state to last page + setPageByStatus(prev => ({ ...prev, [statusFilter]: lastPage })); + updateComponentState(lastPageResponse); + return; } - }; - - if (response.pagination.total_count > 0) { - setDocs(legacyDocs); - } else { - setDocs(null); } + // Normal case: update state + if (pageToFetch !== pagination.page) { + setPageByStatus(prev => ({ ...prev, [statusFilter]: pageToFetch })); + } + updateComponentState(response); + } catch (err) { if (isMountedRef.current) { toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) })); @@ -563,7 +575,20 @@ export default function DocumentManager() { 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 const fetchDocuments = useCallback(async () => { @@ -608,7 +633,7 @@ export default function DocumentManager() { if (!selectedScheme) { toast.error(t('documentPanel.documentManager.errors.missingSchemeId')); - return; // 直接返回,不继续执行 + return; } const framework = selectedScheme.config?.framework; @@ -699,9 +724,10 @@ export default function DocumentManager() { processed: response.documents.filter(doc => doc.status === 'processed'), processing: response.documents.filter(doc => doc.status === 'processing'), pending: response.documents.filter(doc => doc.status === 'pending'), - failed: response.documents.filter(doc => doc.status === 'failed'), - ready: response.documents.filter(doc => doc.status === 'ready'), - handling: response.documents.filter(doc => doc.status === 'handling'), } + ready: response.documents.filter((doc: DocStatusResponse) => doc.status === 'ready'), + handling: response.documents.filter((doc: DocStatusResponse) => doc.status === 'handling'), + failed: response.documents.filter(doc => doc.status === 'failed') + } }; if (response.pagination.total_count > 0) { @@ -728,9 +754,10 @@ export default function DocumentManager() { if (prevPipelineBusyRef.current !== undefined && prevPipelineBusyRef.current !== pipelineBusy) { // pipelineBusy state has changed, trigger immediate refresh 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 pollingInterval = hasActiveDocuments ? 5000 : 30000; startPollingInterval(pollingInterval); @@ -738,7 +765,7 @@ export default function DocumentManager() { } // Update the previous state 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 useEffect(() => {