From e2f946b7c03c71496515ec722254d53de2155146 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:38:40 +0530 Subject: [PATCH] feat: add support for Local Folder connector in UI components and configuration --- .../[search_space_id]/client-layout.tsx | 4 ++ .../views/connector-connect-view.tsx | 1 + .../views/connector-edit-view.tsx | 69 ++++++++++--------- .../views/indexing-configuration-view.tsx | 12 ++-- .../constants/connector-constants.ts | 8 +++ .../tabs/all-connectors-tab.tsx | 25 +++---- surfsense_web/contracts/enums/connector.ts | 1 + .../contracts/enums/connectorIcons.tsx | 3 + .../contracts/types/connector.types.ts | 1 + 9 files changed, 72 insertions(+), 52 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx b/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx index 1715e525f..60b8aef12 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/client-layout.tsx @@ -17,6 +17,7 @@ import { DocumentUploadDialogProvider } from "@/components/assistant-ui/document import { LayoutDataProvider } from "@/components/layout"; import { OnboardingTour } from "@/components/onboarding-tour"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { useFolderSync } from "@/hooks/use-folder-sync"; import { useGlobalLoadingEffect } from "@/hooks/use-global-loading"; export function DashboardClientLayout({ @@ -159,6 +160,9 @@ export function DashboardClientLayout({ // Use global loading screen - spinner animation won't reset useGlobalLoadingEffect(shouldShowLoading); + // Wire desktop app file watcher -> single-file re-index API + useFolderSync(); + if (shouldShowLoading) { return null; } diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx index 596b98e93..0b6d0917a 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx @@ -20,6 +20,7 @@ const FORM_ID_MAP: Record = { CIRCLEBACK_CONNECTOR: "circleback-connect-form", MCP_CONNECTOR: "mcp-connect-form", OBSIDIAN_CONNECTOR: "obsidian-connect-form", + LOCAL_FOLDER_CONNECTOR: "local-folder-connect-form", }; interface ConnectorConnectViewProps { diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index 20d4a8e53..dcedb4743 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -272,13 +272,14 @@ export const ConnectorEditView: FC = ({ {/* AI Summary toggle */} - {/* Date range selector - not shown for file-based connectors (Drive, Dropbox, OneDrive), Webcrawler, or GitHub (indexes full repo snapshots) */} + {/* Date range selector - not shown for file-based connectors (Drive, Dropbox, OneDrive), Webcrawler, GitHub, or Local Folder */} {connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "DROPBOX_CONNECTOR" && connector.connector_type !== "ONEDRIVE_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" && - connector.connector_type !== "GITHUB_CONNECTOR" && ( + connector.connector_type !== "GITHUB_CONNECTOR" && + connector.connector_type !== "LOCAL_FOLDER_CONNECTOR" && ( = ({ /> )} - {/* Periodic sync - shown for all indexable connectors */} - {(() => { - // Check if Google Drive (regular or Composio) has folders/files selected - const isGoogleDrive = connector.connector_type === "GOOGLE_DRIVE_CONNECTOR"; - const isComposioGoogleDrive = - connector.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"; - const requiresFolderSelection = isGoogleDrive || isComposioGoogleDrive; - const selectedFolders = - (connector.config?.selected_folders as - | Array<{ id: string; name: string }> - | undefined) || []; - const selectedFiles = - (connector.config?.selected_files as - | Array<{ id: string; name: string }> - | undefined) || []; - const hasItemsSelected = selectedFolders.length > 0 || selectedFiles.length > 0; - const isDisabled = requiresFolderSelection && !hasItemsSelected; + {/* Periodic sync - shown for all indexable connectors except Local Folder */} + {connector.connector_type !== "LOCAL_FOLDER_CONNECTOR" && + (() => { + const isGoogleDrive = connector.connector_type === "GOOGLE_DRIVE_CONNECTOR"; + const isComposioGoogleDrive = + connector.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"; + const requiresFolderSelection = isGoogleDrive || isComposioGoogleDrive; + const selectedFolders = + (connector.config?.selected_folders as + | Array<{ id: string; name: string }> + | undefined) || []; + const selectedFiles = + (connector.config?.selected_files as + | Array<{ id: string; name: string }> + | undefined) || []; + const hasItemsSelected = selectedFolders.length > 0 || selectedFiles.length > 0; + const isDisabled = requiresFolderSelection && !hasItemsSelected; - return ( - - ); - })()} + return ( + + ); + })()} )} diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx index 564cb87ee..436ce7843 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx @@ -158,13 +158,14 @@ export const IndexingConfigurationView: FC = ({ {/* AI Summary toggle */} - {/* Date range selector - not shown for file-based connectors (Drive, Dropbox, OneDrive), Webcrawler, or GitHub (indexes full repo snapshots) */} + {/* Date range selector - not shown for file-based connectors (Drive, Dropbox, OneDrive), Webcrawler, GitHub, or Local Folder */} {config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "DROPBOX_CONNECTOR" && config.connectorType !== "ONEDRIVE_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" && - config.connectorType !== "GITHUB_CONNECTOR" && ( + config.connectorType !== "GITHUB_CONNECTOR" && + config.connectorType !== "LOCAL_FOLDER_CONNECTOR" && ( = ({ /> )} - {/* Periodic sync - not shown for Google Drive (regular and Composio) */} + {/* Periodic sync - not shown for file-based connectors (Drive, Dropbox, OneDrive) or Local Folder in initial setup; configured in edit view instead */} {config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && - config.connectorType !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" && ( + config.connectorType !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" && + config.connectorType !== "DROPBOX_CONNECTOR" && + config.connectorType !== "ONEDRIVE_CONNECTOR" && + config.connectorType !== "LOCAL_FOLDER_CONNECTOR" && ( = ({ }) => { // Check if self-hosted mode (for showing self-hosted only connectors) const selfHosted = isSelfHosted(); + const isDesktop = typeof window !== "undefined" && !!window.electronAPI; + + const matchesSearch = (title: string, description: string) => + title.toLowerCase().includes(searchQuery.toLowerCase()) || + description.toLowerCase().includes(searchQuery.toLowerCase()); + + const passesDeploymentFilter = (c: { selfHostedOnly?: boolean; desktopOnly?: boolean }) => + (!c.selfHostedOnly || selfHosted) && (!c.desktopOnly || isDesktop); // Filter connectors based on search and deployment mode const filteredOAuth = OAUTH_CONNECTORS.filter( - (c) => - // Filter by search query - (c.title.toLowerCase().includes(searchQuery.toLowerCase()) || - c.description.toLowerCase().includes(searchQuery.toLowerCase())) && - // Filter self-hosted only connectors in cloud mode - (!("selfHostedOnly" in c) || !c.selfHostedOnly || selfHosted) + (c) => matchesSearch(c.title, c.description) && passesDeploymentFilter(c) ); const filteredCrawlers = CRAWLERS.filter( - (c) => - (c.title.toLowerCase().includes(searchQuery.toLowerCase()) || - c.description.toLowerCase().includes(searchQuery.toLowerCase())) && - (!("selfHostedOnly" in c) || !c.selfHostedOnly || selfHosted) + (c) => matchesSearch(c.title, c.description) && passesDeploymentFilter(c) ); const filteredOther = OTHER_CONNECTORS.filter( - (c) => - (c.title.toLowerCase().includes(searchQuery.toLowerCase()) || - c.description.toLowerCase().includes(searchQuery.toLowerCase())) && - (!("selfHostedOnly" in c) || !c.selfHostedOnly || selfHosted) + (c) => matchesSearch(c.title, c.description) && passesDeploymentFilter(c) ); // Filter Composio connectors diff --git a/surfsense_web/contracts/enums/connector.ts b/surfsense_web/contracts/enums/connector.ts index 501f5d9a3..ecf96d88e 100644 --- a/surfsense_web/contracts/enums/connector.ts +++ b/surfsense_web/contracts/enums/connector.ts @@ -25,6 +25,7 @@ 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 2e609b060..f7378b74b 100644 --- a/surfsense_web/contracts/enums/connectorIcons.tsx +++ b/surfsense_web/contracts/enums/connectorIcons.tsx @@ -3,6 +3,7 @@ import { BookOpen, File, FileText, + FolderSync, Globe, Microscope, Search, @@ -75,6 +76,8 @@ 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 b83e05dcc..269941375 100644 --- a/surfsense_web/contracts/types/connector.types.ts +++ b/surfsense_web/contracts/types/connector.types.ts @@ -30,6 +30,7 @@ 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",