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(() => {