feat: Add an Ollama-based PDF OCR script and refactor frontend folder tree management with a new FoldersManager and useSyncExternalStore.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { DEFAULT_FOLDER_NAME } from '../../../constants/workspace';
|
||||
import { getFolderTree } from '../../../lib/api/apiClient';
|
||||
import React, { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
|
||||
import { DEFAULT_FOLDER_NAME } from '../../../app/workspaceUtils';
|
||||
import { useAppShell } from '../../../lib/context/AppShellContext';
|
||||
import FoldersManager from '../../FoldersManager';
|
||||
|
||||
import {
|
||||
TrashIcon,
|
||||
AnalyzeIcon,
|
||||
@@ -12,10 +14,10 @@ import {
|
||||
import SelectionAssignmentMenu, { SelectionAssignmentMenuItem } from './SelectionAssignmentMenu';
|
||||
import SelectionFolderMenu from './SelectionFolderMenu';
|
||||
import SelectionSummary from './SelectionSummary';
|
||||
import { useAppState } from '../../../lib/store/appState';
|
||||
|
||||
import { useWorkspaceSelectionContext } from '../../../app/WorkspaceSelectionContext';
|
||||
import type { DocumentId } from '../../../types/identifiers';
|
||||
import type { FolderTreeNode, FolderTreeResponseItem } from '../../../lib/api/apiTypes';
|
||||
import type { FolderTreeNode } from '../../../lib/api/apiTypes';
|
||||
|
||||
type NullableDocumentId = DocumentId | null;
|
||||
|
||||
@@ -217,62 +219,33 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
||||
onClearSelection = null,
|
||||
onMoveDocumentsToFolder,
|
||||
}) => {
|
||||
const { token, tenant } = useAppState() as { token?: string; tenant?: { id?: DocumentId; name?: string } | null };
|
||||
const tenantId = tenant?.id ?? null;
|
||||
|
||||
|
||||
const documentLookupMap = useMemo(() => (
|
||||
documentLookup instanceof Map ? documentLookup : new Map<DocumentId, Document>()
|
||||
), [documentLookup]);
|
||||
const tagLookupMap = tagLookupById instanceof Map ? tagLookupById : null;
|
||||
|
||||
const [remoteFolderTree, setRemoteFolderTree] = useState<FolderTreeNode[] | null>(null);
|
||||
const folderTreeFetchRef = useRef<Promise<FolderTreeNode[]> | null>(null);
|
||||
const shell = useAppShell();
|
||||
const foldersManager = shell.foldersManager as FoldersManager;
|
||||
|
||||
const [remoteFolderTree, setRemoteFolderTree] = useState<FolderTreeNode[]>([]);
|
||||
|
||||
// Sync with manager
|
||||
const treeSnapshot = useSyncExternalStore(
|
||||
useCallback(cb => foldersManager.subscribe(cb), [foldersManager]),
|
||||
() => foldersManager.getTreeSnapshot(),
|
||||
() => foldersManager.getTreeSnapshot(),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setRemoteFolderTree(null);
|
||||
folderTreeFetchRef.current = null;
|
||||
}, [tenantId, token]);
|
||||
setRemoteFolderTree(treeSnapshot);
|
||||
}, [treeSnapshot]);
|
||||
|
||||
const requestFolderTree = useCallback(async (): Promise<FolderTreeNode[]> => {
|
||||
if (!token) {
|
||||
setRemoteFolderTree([]);
|
||||
return [];
|
||||
}
|
||||
const requestFolderTree = useCallback(() => {
|
||||
foldersManager.ensureTree();
|
||||
}, [foldersManager]);
|
||||
|
||||
if (Array.isArray(remoteFolderTree)) {
|
||||
return remoteFolderTree;
|
||||
}
|
||||
|
||||
if (folderTreeFetchRef.current) {
|
||||
return folderTreeFetchRef.current;
|
||||
}
|
||||
|
||||
const fetchPromise = (async () => {
|
||||
try {
|
||||
const data = await getFolderTree();
|
||||
|
||||
const traverse = (items: FolderTreeResponseItem[]): FolderTreeNode[] => {
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
children: item.children ? traverse(item.children) : [],
|
||||
}));
|
||||
};
|
||||
|
||||
const roots = traverse(data);
|
||||
setRemoteFolderTree(roots);
|
||||
return roots;
|
||||
} catch (error) {
|
||||
console.warn('[selection] Failed to load folder tree', error);
|
||||
setRemoteFolderTree([]);
|
||||
return [];
|
||||
} finally {
|
||||
folderTreeFetchRef.current = null;
|
||||
}
|
||||
})();
|
||||
|
||||
folderTreeFetchRef.current = fetchPromise;
|
||||
return fetchPromise;
|
||||
}, [remoteFolderTree, token]);
|
||||
|
||||
const handleMoveMenuOpen = useCallback(() => {
|
||||
requestFolderTree();
|
||||
|
||||
Reference in New Issue
Block a user