mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
Merge pull request #1185 from AnishSarkar22/fix/folder-watch
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
fix: harden folder watch feature with file hash dedup, mtime seeding, and stable spinner
This commit is contained in:
commit
fe6f830eab
39 changed files with 1955 additions and 497 deletions
|
|
@ -152,16 +152,10 @@ export function CreateSearchSpaceDialog({ open, onOpenChange }: CreateSearchSpac
|
|||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="h-8 sm:h-9 text-xs sm:text-sm"
|
||||
className="h-8 sm:h-9 text-xs sm:text-sm relative"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Spinner size="sm" className="mr-1.5" />
|
||||
{t("creating")}
|
||||
</>
|
||||
) : (
|
||||
<>{t("create_button")}</>
|
||||
)}
|
||||
<span className={isSubmitting ? "opacity-0" : ""}>{t("create_button")}</span>
|
||||
{isSubmitting && <Spinner size="sm" className="absolute" />}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog";
|
|||
import { FolderTreeView } from "@/components/documents/FolderTreeView";
|
||||
import { VersionHistoryDialog } from "@/components/documents/version-history";
|
||||
import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems";
|
||||
import { FolderWatchDialog, type SelectedFolder } from "@/components/sources/FolderWatchDialog";
|
||||
import {
|
||||
DEFAULT_EXCLUDE_PATTERNS,
|
||||
FolderWatchDialog,
|
||||
type SelectedFolder,
|
||||
} from "@/components/sources/FolderWatchDialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -46,6 +50,8 @@ import { useElectronAPI } from "@/hooks/use-platform";
|
|||
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
||||
import { foldersApiService } from "@/lib/apis/folders-api.service";
|
||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||
import { uploadFolderScan } from "@/lib/folder-sync-upload";
|
||||
import { getSupportedExtensionsSet } from "@/lib/supported-extensions";
|
||||
import { queries } from "@/zero/queries/index";
|
||||
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
||||
|
||||
|
|
@ -114,48 +120,48 @@ export function DocumentsSidebar({
|
|||
setFolderWatchOpen(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const refreshWatchedIds = useCallback(async () => {
|
||||
if (!electronAPI?.getWatchedFolders) return;
|
||||
const api = electronAPI;
|
||||
|
||||
async function loadWatchedIds() {
|
||||
const folders = await api.getWatchedFolders();
|
||||
const folders = await api.getWatchedFolders();
|
||||
|
||||
if (folders.length === 0) {
|
||||
try {
|
||||
const backendFolders = await documentsApiService.getWatchedFolders(searchSpaceId);
|
||||
for (const bf of backendFolders) {
|
||||
const meta = bf.metadata as Record<string, unknown> | null;
|
||||
if (!meta?.watched || !meta.folder_path) continue;
|
||||
await api.addWatchedFolder({
|
||||
path: meta.folder_path as string,
|
||||
name: bf.name,
|
||||
rootFolderId: bf.id,
|
||||
searchSpaceId: bf.search_space_id,
|
||||
excludePatterns: (meta.exclude_patterns as string[]) ?? [],
|
||||
fileExtensions: (meta.file_extensions as string[] | null) ?? null,
|
||||
active: true,
|
||||
});
|
||||
}
|
||||
const recovered = await api.getWatchedFolders();
|
||||
const ids = new Set(
|
||||
recovered.filter((f) => f.rootFolderId != null).map((f) => f.rootFolderId as number)
|
||||
);
|
||||
setWatchedFolderIds(ids);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error("[DocumentsSidebar] Recovery from backend failed:", err);
|
||||
if (folders.length === 0) {
|
||||
try {
|
||||
const backendFolders = await documentsApiService.getWatchedFolders(searchSpaceId);
|
||||
for (const bf of backendFolders) {
|
||||
const meta = bf.metadata as Record<string, unknown> | null;
|
||||
if (!meta?.watched || !meta.folder_path) continue;
|
||||
await api.addWatchedFolder({
|
||||
path: meta.folder_path as string,
|
||||
name: bf.name,
|
||||
rootFolderId: bf.id,
|
||||
searchSpaceId: bf.search_space_id,
|
||||
excludePatterns: (meta.exclude_patterns as string[]) ?? [],
|
||||
fileExtensions: (meta.file_extensions as string[] | null) ?? null,
|
||||
active: true,
|
||||
});
|
||||
}
|
||||
const recovered = await api.getWatchedFolders();
|
||||
const ids = new Set(
|
||||
recovered.filter((f) => f.rootFolderId != null).map((f) => f.rootFolderId as number)
|
||||
);
|
||||
setWatchedFolderIds(ids);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error("[DocumentsSidebar] Recovery from backend failed:", err);
|
||||
}
|
||||
|
||||
const ids = new Set(
|
||||
folders.filter((f) => f.rootFolderId != null).map((f) => f.rootFolderId as number)
|
||||
);
|
||||
setWatchedFolderIds(ids);
|
||||
}
|
||||
|
||||
loadWatchedIds();
|
||||
const ids = new Set(
|
||||
folders.filter((f) => f.rootFolderId != null).map((f) => f.rootFolderId as number)
|
||||
);
|
||||
setWatchedFolderIds(ids);
|
||||
}, [searchSpaceId, electronAPI]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshWatchedIds();
|
||||
}, [refreshWatchedIds]);
|
||||
const { mutateAsync: deleteDocumentMutation } = useAtomValue(deleteDocumentMutationAtom);
|
||||
|
||||
const [sidebarDocs, setSidebarDocs] = useAtom(sidebarSelectedDocumentsAtom);
|
||||
|
|
@ -192,6 +198,7 @@ export function DocumentsSidebar({
|
|||
position: f.position,
|
||||
parentId: f.parentId ?? null,
|
||||
searchSpaceId: f.searchSpaceId,
|
||||
metadata: f.metadata as Record<string, unknown> | null | undefined,
|
||||
})),
|
||||
[zeroFolders]
|
||||
);
|
||||
|
|
@ -304,14 +311,17 @@ export function DocumentsSidebar({
|
|||
}
|
||||
|
||||
try {
|
||||
await documentsApiService.folderIndex(searchSpaceId, {
|
||||
folder_path: matched.path,
|
||||
folder_name: matched.name,
|
||||
search_space_id: searchSpaceId,
|
||||
root_folder_id: folder.id,
|
||||
file_extensions: matched.fileExtensions ?? undefined,
|
||||
toast.info(`Re-scanning folder: ${matched.name}`);
|
||||
await uploadFolderScan({
|
||||
folderPath: matched.path,
|
||||
folderName: matched.name,
|
||||
searchSpaceId,
|
||||
excludePatterns: matched.excludePatterns ?? DEFAULT_EXCLUDE_PATTERNS,
|
||||
fileExtensions: matched.fileExtensions ?? Array.from(getSupportedExtensionsSet()),
|
||||
enableSummary: false,
|
||||
rootFolderId: folder.id,
|
||||
});
|
||||
toast.success(`Re-scanning folder: ${matched.name}`);
|
||||
toast.success(`Re-scan complete: ${matched.name}`);
|
||||
} catch (err) {
|
||||
toast.error((err as Error)?.message || "Failed to re-scan folder");
|
||||
}
|
||||
|
|
@ -337,8 +347,9 @@ export function DocumentsSidebar({
|
|||
console.error("[DocumentsSidebar] Failed to clear watched metadata:", err);
|
||||
}
|
||||
toast.success(`Stopped watching: ${matched.name}`);
|
||||
refreshWatchedIds();
|
||||
},
|
||||
[electronAPI]
|
||||
[electronAPI, refreshWatchedIds]
|
||||
);
|
||||
|
||||
const handleRenameFolder = useCallback(async (folder: FolderDisplay, newName: string) => {
|
||||
|
|
@ -867,6 +878,7 @@ export function DocumentsSidebar({
|
|||
}}
|
||||
searchSpaceId={searchSpaceId}
|
||||
initialFolder={watchInitialFolder}
|
||||
onSuccess={refreshWatchedIds}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,13 +91,12 @@ export function SidebarSlideOutPanel({
|
|||
|
||||
{/* Panel extending from sidebar's right edge, flush with the wrapper border */}
|
||||
<motion.div
|
||||
style={{ width }}
|
||||
initial={{ x: -width }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -width }}
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width }}
|
||||
exit={{ width: 0 }}
|
||||
transition={{ type: "tween", duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
|
||||
className="absolute z-20 overflow-hidden"
|
||||
style={{ width, left: "100%", top: -1, bottom: -1 }}
|
||||
style={{ left: "100%", top: -1, bottom: -1 }}
|
||||
>
|
||||
<div
|
||||
style={{ width }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue