webui
This commit is contained in:
parent
7c8db78057
commit
2a453fbe37
8 changed files with 104 additions and 65 deletions
|
|
@ -17,7 +17,6 @@ import RetrievalTesting from '@/features/RetrievalTesting'
|
||||||
import ApiSite from '@/features/ApiSite'
|
import ApiSite from '@/features/ApiSite'
|
||||||
import { SchemeProvider } from '@/contexts/SchemeContext';
|
import { SchemeProvider } from '@/contexts/SchemeContext';
|
||||||
|
|
||||||
|
|
||||||
import { Tabs, TabsContent } from '@/components/ui/Tabs'
|
import { Tabs, TabsContent } from '@/components/ui/Tabs'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ export type LightragStatus = {
|
||||||
embedding_binding: string
|
embedding_binding: string
|
||||||
embedding_binding_host: string
|
embedding_binding_host: string
|
||||||
embedding_model: string
|
embedding_model: string
|
||||||
max_tokens: number
|
|
||||||
kv_storage: string
|
kv_storage: string
|
||||||
doc_status_storage: string
|
doc_status_storage: string
|
||||||
graph_storage: string
|
graph_storage: string
|
||||||
|
|
@ -43,6 +42,7 @@ export type LightragStatus = {
|
||||||
workspace?: string
|
workspace?: string
|
||||||
max_graph_nodes?: string
|
max_graph_nodes?: string
|
||||||
enable_rerank?: boolean
|
enable_rerank?: boolean
|
||||||
|
rerank_binding?: string | null
|
||||||
rerank_model?: string | null
|
rerank_model?: string | null
|
||||||
rerank_binding_host?: string | null
|
rerank_binding_host?: string | null
|
||||||
summary_language: string
|
summary_language: string
|
||||||
|
|
@ -599,6 +599,7 @@ export const uploadDocument = async (
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
formData.append('schemeId', schemeId.toString())
|
formData.append('schemeId', schemeId.toString())
|
||||||
|
|
||||||
const response = await axiosInstance.post('/documents/upload', formData, {
|
const response = await axiosInstance.post('/documents/upload', formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
|
||||||
|
|
||||||
// Register the events
|
// Register the events
|
||||||
registerEvents(events)
|
registerEvents(events)
|
||||||
}, [registerEvents, enableEdgeEvents])
|
}, [registerEvents, enableEdgeEvents, sigma])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When edge size settings change, recalculate edge sizes and refresh the sigma instance
|
* When edge size settings change, recalculate edge sizes and refresh the sigma instance
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,9 @@ const GraphLabels = () => {
|
||||||
searchInputClassName="max-h-8"
|
searchInputClassName="max-h-8"
|
||||||
triggerTooltip={t('graphPanel.graphLabels.selectTooltip')}
|
triggerTooltip={t('graphPanel.graphLabels.selectTooltip')}
|
||||||
fetcher={fetchData}
|
fetcher={fetchData}
|
||||||
renderOption={(item) => <div>{item}</div>}
|
renderOption={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</div>}
|
||||||
getOptionValue={(item) => item}
|
getOptionValue={(item) => item}
|
||||||
getDisplayValue={(item) => <div>{item}</div>}
|
getDisplayValue={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</div>}
|
||||||
notFound={<div className="py-6 text-center text-sm">No labels found</div>}
|
notFound={<div className="py-6 text-center text-sm">No labels found</div>}
|
||||||
label={t('graphPanel.graphLabels.label')}
|
label={t('graphPanel.graphLabels.label')}
|
||||||
placeholder={t('graphPanel.graphLabels.placeholder')}
|
placeholder={t('graphPanel.graphLabels.placeholder')}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ const StatusCard = ({ status }: { status: LightragStatus | null }) => {
|
||||||
<span>{t('graphPanel.statusCard.rerankerBindingHost')}:</span>
|
<span>{t('graphPanel.statusCard.rerankerBindingHost')}:</span>
|
||||||
<span>{status.configuration.rerank_binding_host || '-'}</span>
|
<span>{status.configuration.rerank_binding_host || '-'}</span>
|
||||||
<span>{t('graphPanel.statusCard.rerankerModel')}:</span>
|
<span>{t('graphPanel.statusCard.rerankerModel')}:</span>
|
||||||
<span>{status.configuration.rerank_model || '-'}</span>
|
<span>{(status.configuration.rerank_binding || '-')} : {(status.configuration.rerank_model || '-')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -245,22 +245,34 @@ export function AsyncSelect<T>({
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
))}
|
))}
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{options.map((option) => (
|
{options.map((option, index) => {
|
||||||
<CommandItem
|
const optionValue = getOptionValue(option);
|
||||||
key={getOptionValue(option)}
|
// Use index as a safe value that won't be trimmed by cmdk
|
||||||
value={getOptionValue(option)}
|
const safeValue = `option-${index}-${optionValue.length}`;
|
||||||
onSelect={handleSelect}
|
|
||||||
className="truncate"
|
return (
|
||||||
>
|
<CommandItem
|
||||||
{renderOption(option)}
|
key={optionValue}
|
||||||
<Check
|
value={safeValue}
|
||||||
className={cn(
|
onSelect={(selectedSafeValue) => {
|
||||||
'ml-auto h-3 w-3',
|
// Extract the original value from the safe value
|
||||||
selectedValue === getOptionValue(option) ? 'opacity-100' : 'opacity-0'
|
const selectedIndex = parseInt(selectedSafeValue.split('-')[1]);
|
||||||
)}
|
const originalValue = getOptionValue(options[selectedIndex]);
|
||||||
/>
|
console.log(`CommandItem onSelect: safeValue='${selectedSafeValue}', originalValue='${originalValue}' (length: ${originalValue.length})`);
|
||||||
</CommandItem>
|
handleSelect(originalValue);
|
||||||
))}
|
}}
|
||||||
|
className="truncate"
|
||||||
|
>
|
||||||
|
{renderOption(option)}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
'ml-auto h-3 w-3',
|
||||||
|
selectedValue === optionValue ? 'opacity-100' : 'opacity-0'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ type SortDirection = 'asc' | 'desc';
|
||||||
|
|
||||||
export default function DocumentManager() {
|
export default function DocumentManager() {
|
||||||
const { selectedScheme } = useScheme();
|
const { selectedScheme } = useScheme();
|
||||||
console.log('selectedScheme in DocumentManager:', selectedScheme);
|
|
||||||
// Track component mount status
|
// Track component mount status
|
||||||
const isMountedRef = useRef(true);
|
const isMountedRef = useRef(true);
|
||||||
|
|
||||||
|
|
@ -189,7 +189,7 @@ export default function DocumentManager() {
|
||||||
const setDocumentsPageSize = useSettingsStore.use.setDocumentsPageSize()
|
const setDocumentsPageSize = useSettingsStore.use.setDocumentsPageSize()
|
||||||
|
|
||||||
// New pagination state
|
// New pagination state
|
||||||
const [, setCurrentPageDocs] = useState<DocStatusResponse[]>([])
|
const [currentPageDocs, setCurrentPageDocs] = useState<DocStatusResponse[]>([])
|
||||||
const [pagination, setPagination] = useState<PaginationInfo>({
|
const [pagination, setPagination] = useState<PaginationInfo>({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: documentsPageSize,
|
page_size: documentsPageSize,
|
||||||
|
|
@ -302,6 +302,16 @@ export default function DocumentManager() {
|
||||||
type DocStatusWithStatus = DocStatusResponse & { status: DocStatus };
|
type DocStatusWithStatus = DocStatusResponse & { status: DocStatus };
|
||||||
|
|
||||||
const filteredAndSortedDocs = useMemo(() => {
|
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;
|
if (!docs) return null;
|
||||||
|
|
||||||
// Create a flat array of documents with status information
|
// Create a flat array of documents with status information
|
||||||
|
|
@ -334,7 +344,7 @@ export default function DocumentManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return allDocuments;
|
return allDocuments;
|
||||||
}, [docs, sortField, sortDirection, statusFilter, sortDocuments]);
|
}, [currentPageDocs, docs, sortField, sortDirection, statusFilter, sortDocuments]);
|
||||||
|
|
||||||
// Calculate current page selection state (after filteredAndSortedDocs is defined)
|
// Calculate current page selection state (after filteredAndSortedDocs is defined)
|
||||||
const currentPageDocIds = useMemo(() => {
|
const currentPageDocIds = useMemo(() => {
|
||||||
|
|
@ -481,13 +491,6 @@ export default function DocumentManager() {
|
||||||
};
|
};
|
||||||
}, [docs]);
|
}, [docs]);
|
||||||
|
|
||||||
<<<<<<< Updated upstream
|
|
||||||
// New paginated data fetching function
|
|
||||||
const fetchPaginatedDocuments = useCallback(async (
|
|
||||||
page: number,
|
|
||||||
pageSize: number,
|
|
||||||
statusFilter: StatusFilter
|
|
||||||
=======
|
|
||||||
// Utility function to update component state
|
// Utility function to update component state
|
||||||
const updateComponentState = useCallback((response: any) => {
|
const updateComponentState = useCallback((response: any) => {
|
||||||
setPagination(response.pagination);
|
setPagination(response.pagination);
|
||||||
|
|
@ -500,9 +503,9 @@ export default function DocumentManager() {
|
||||||
processed: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processed'),
|
processed: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processed'),
|
||||||
processing: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processing'),
|
processing: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processing'),
|
||||||
pending: response.documents.filter((doc: DocStatusResponse) => doc.status === 'pending'),
|
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'),
|
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 (
|
const handleIntelligentRefresh = useCallback(async (
|
||||||
targetPage?: number, // Optional target page, defaults to current page
|
targetPage?: number, // Optional target page, defaults to current page
|
||||||
resetToFirst?: boolean // Whether to force reset to first page
|
resetToFirst?: boolean // Whether to force reset to first page
|
||||||
>>>>>>> Stashed changes
|
|
||||||
) => {
|
) => {
|
||||||
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
|
||||||
};
|
};
|
||||||
|
|
@ -533,27 +537,35 @@ 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')
|
|
||||||
|
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) {
|
} catch (err) {
|
||||||
if (isMountedRef.current) {
|
if (isMountedRef.current) {
|
||||||
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }));
|
toast.error(t('documentPanel.documentManager.errors.loadFailed', { error: errorMessage(err) }));
|
||||||
|
|
@ -563,7 +575,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 () => {
|
||||||
|
|
@ -608,7 +633,7 @@ export default function DocumentManager() {
|
||||||
|
|
||||||
if (!selectedScheme) {
|
if (!selectedScheme) {
|
||||||
toast.error(t('documentPanel.documentManager.errors.missingSchemeId'));
|
toast.error(t('documentPanel.documentManager.errors.missingSchemeId'));
|
||||||
return; // 直接返回,不继续执行
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const framework = selectedScheme.config?.framework;
|
const framework = selectedScheme.config?.framework;
|
||||||
|
|
@ -699,9 +724,10 @@ export default function DocumentManager() {
|
||||||
processed: response.documents.filter(doc => doc.status === 'processed'),
|
processed: response.documents.filter(doc => doc.status === 'processed'),
|
||||||
processing: response.documents.filter(doc => doc.status === 'processing'),
|
processing: response.documents.filter(doc => doc.status === 'processing'),
|
||||||
pending: response.documents.filter(doc => doc.status === 'pending'),
|
pending: response.documents.filter(doc => doc.status === 'pending'),
|
||||||
failed: response.documents.filter(doc => doc.status === 'failed'),
|
ready: response.documents.filter((doc: DocStatusResponse) => doc.status === 'ready'),
|
||||||
ready: response.documents.filter(doc => doc.status === 'ready'),
|
handling: response.documents.filter((doc: DocStatusResponse) => doc.status === 'handling'),
|
||||||
handling: response.documents.filter(doc => doc.status === 'handling'), }
|
failed: response.documents.filter(doc => doc.status === 'failed')
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (response.pagination.total_count > 0) {
|
if (response.pagination.total_count > 0) {
|
||||||
|
|
@ -728,9 +754,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);
|
||||||
|
|
@ -738,7 +765,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(() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue