diff --git a/surfsense_web/contracts/enums/connector.ts b/surfsense_web/contracts/enums/connector.ts index ecf96d88e..501f5d9a3 100644 --- a/surfsense_web/contracts/enums/connector.ts +++ b/surfsense_web/contracts/enums/connector.ts @@ -25,7 +25,6 @@ export enum EnumConnectorName { YOUTUBE_CONNECTOR = "YOUTUBE_CONNECTOR", CIRCLEBACK_CONNECTOR = "CIRCLEBACK_CONNECTOR", OBSIDIAN_CONNECTOR = "OBSIDIAN_CONNECTOR", - LOCAL_FOLDER_CONNECTOR = "LOCAL_FOLDER_CONNECTOR", DROPBOX_CONNECTOR = "DROPBOX_CONNECTOR", MCP_CONNECTOR = "MCP_CONNECTOR", COMPOSIO_GOOGLE_DRIVE_CONNECTOR = "COMPOSIO_GOOGLE_DRIVE_CONNECTOR", diff --git a/surfsense_web/contracts/enums/connectorIcons.tsx b/surfsense_web/contracts/enums/connectorIcons.tsx index f7378b74b..2e609b060 100644 --- a/surfsense_web/contracts/enums/connectorIcons.tsx +++ b/surfsense_web/contracts/enums/connectorIcons.tsx @@ -3,7 +3,6 @@ import { BookOpen, File, FileText, - FolderSync, Globe, Microscope, Search, @@ -76,8 +75,6 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas return Circleback; case EnumConnectorName.MCP_CONNECTOR: return MCP; - case EnumConnectorName.LOCAL_FOLDER_CONNECTOR: - return ; case EnumConnectorName.OBSIDIAN_CONNECTOR: return Obsidian; case EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR: diff --git a/surfsense_web/contracts/types/connector.types.ts b/surfsense_web/contracts/types/connector.types.ts index 269941375..b83e05dcc 100644 --- a/surfsense_web/contracts/types/connector.types.ts +++ b/surfsense_web/contracts/types/connector.types.ts @@ -30,7 +30,6 @@ export const searchSourceConnectorTypeEnum = z.enum([ "DROPBOX_CONNECTOR", "MCP_CONNECTOR", "OBSIDIAN_CONNECTOR", - "LOCAL_FOLDER_CONNECTOR", "COMPOSIO_GOOGLE_DRIVE_CONNECTOR", "COMPOSIO_GMAIL_CONNECTOR", "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR", diff --git a/surfsense_web/hooks/use-folder-sync.ts b/surfsense_web/hooks/use-folder-sync.ts index a35faf98f..fcfb2814e 100644 --- a/surfsense_web/hooks/use-folder-sync.ts +++ b/surfsense_web/hooks/use-folder-sync.ts @@ -1,41 +1,73 @@ "use client"; import { useEffect, useRef } from "react"; -import { connectorsApiService } from "@/lib/apis/connectors-api.service"; +import { documentsApiService } from "@/lib/apis/documents-api.service"; + +interface FileChangedEvent { + rootFolderId: number | null; + searchSpaceId: number; + folderPath: string; + folderName: string; + relativePath: string; + fullPath: string; + action: string; + timestamp: number; +} const DEBOUNCE_MS = 2000; export function useFolderSync() { - const pendingRef = useRef>>(new Map()); + const queueRef = useRef([]); + const processingRef = useRef(false); + const debounceTimers = useRef>>(new Map()); + + async function processQueue() { + if (processingRef.current) return; + processingRef.current = true; + while (queueRef.current.length > 0) { + const event = queueRef.current.shift()!; + try { + await documentsApiService.folderIndexFile(event.searchSpaceId, { + folder_path: event.folderPath, + folder_name: event.folderName, + search_space_id: event.searchSpaceId, + target_file_path: event.fullPath, + }); + } catch (err) { + console.error("[FolderSync] Failed to trigger re-index:", err); + } + } + processingRef.current = false; + } useEffect(() => { const api = typeof window !== "undefined" ? window.electronAPI : null; if (!api?.onFileChanged) return; - const cleanup = api.onFileChanged((event) => { - const key = `${event.connectorId}:${event.fullPath}`; + // Signal to main process that the renderer is ready to receive events + api.signalRendererReady?.(); - const existing = pendingRef.current.get(key); + const cleanup = api.onFileChanged((event: FileChangedEvent) => { + const key = `${event.folderPath}:${event.fullPath}`; + + const existing = debounceTimers.current.get(key); if (existing) clearTimeout(existing); - const timeout = setTimeout(async () => { - pendingRef.current.delete(key); - try { - await connectorsApiService.indexFile(event.connectorId, event.fullPath); - } catch (err) { - console.error("[FolderSync] Failed to trigger re-index:", err); - } + const timeout = setTimeout(() => { + debounceTimers.current.delete(key); + queueRef.current.push(event); + processQueue(); }, DEBOUNCE_MS); - pendingRef.current.set(key, timeout); + debounceTimers.current.set(key, timeout); }); return () => { cleanup(); - for (const timeout of pendingRef.current.values()) { + for (const timeout of debounceTimers.current.values()) { clearTimeout(timeout); } - pendingRef.current.clear(); + debounceTimers.current.clear(); }; }, []); } diff --git a/surfsense_web/lib/apis/connectors-api.service.ts b/surfsense_web/lib/apis/connectors-api.service.ts index f2722df70..7b94b3746 100644 --- a/surfsense_web/lib/apis/connectors-api.service.ts +++ b/surfsense_web/lib/apis/connectors-api.service.ts @@ -405,17 +405,6 @@ class ConnectorsApiService { ); }; - // ============================================================================= - // Local Folder Connector Methods - // ============================================================================= - - indexFile = async (connectorId: number, filePath: string) => { - return baseApiService.post( - `/api/v1/search-source-connectors/${connectorId}/index-file`, - undefined, - { body: { file_path: filePath } } - ); - }; } export type { SlackChannel, DiscordChannel }; diff --git a/surfsense_web/lib/apis/documents-api.service.ts b/surfsense_web/lib/apis/documents-api.service.ts index d4a80f8a0..c77cd6848 100644 --- a/surfsense_web/lib/apis/documents-api.service.ts +++ b/surfsense_web/lib/apis/documents-api.service.ts @@ -395,6 +395,14 @@ class DocumentsApiService { ); }; + folderIndex = async (searchSpaceId: number, body: { folder_path: string; folder_name: string; search_space_id: number; exclude_patterns?: string[]; file_extensions?: string[]; root_folder_id?: number; enable_summary?: boolean }) => { + return baseApiService.post(`/api/v1/documents/folder-index`, undefined, { body }); + }; + + folderIndexFile = async (searchSpaceId: number, body: { folder_path: string; folder_name: string; search_space_id: number; target_file_path: string; enable_summary?: boolean }) => { + return baseApiService.post(`/api/v1/documents/folder-index-file`, undefined, { body }); + }; + /** * Delete a document */ diff --git a/surfsense_web/lib/connectors/utils.ts b/surfsense_web/lib/connectors/utils.ts index 6ce78be67..90f7f5d21 100644 --- a/surfsense_web/lib/connectors/utils.ts +++ b/surfsense_web/lib/connectors/utils.ts @@ -30,7 +30,6 @@ export const getConnectorTypeDisplay = (type: string): string => { YOUTUBE_CONNECTOR: "YouTube", CIRCLEBACK_CONNECTOR: "Circleback", OBSIDIAN_CONNECTOR: "Obsidian", - LOCAL_FOLDER_CONNECTOR: "Local Folder", DROPBOX_CONNECTOR: "Dropbox", MCP_CONNECTOR: "MCP Server", }; diff --git a/surfsense_web/types/window.d.ts b/surfsense_web/types/window.d.ts index 921449b41..b399664d6 100644 --- a/surfsense_web/types/window.d.ts +++ b/surfsense_web/types/window.d.ts @@ -5,15 +5,16 @@ interface WatchedFolderConfig { name: string; excludePatterns: string[]; fileExtensions: string[] | null; - connectorId: number; + rootFolderId: number | null; searchSpaceId: number; active: boolean; } interface FolderSyncFileChangedEvent { - connectorId: number; + rootFolderId: number | null; searchSpaceId: number; folderPath: string; + folderName: string; relativePath: string; fullPath: string; action: "add" | "change" | "unlink"; @@ -21,7 +22,7 @@ interface FolderSyncFileChangedEvent { } interface FolderSyncWatcherReadyEvent { - connectorId: number; + rootFolderId: number | null; folderPath: string; } @@ -49,6 +50,7 @@ interface ElectronAPI { onWatcherReady: (callback: (data: FolderSyncWatcherReadyEvent) => void) => () => void; pauseWatcher: () => Promise; resumeWatcher: () => Promise; + signalRendererReady: () => Promise; } declare global {