diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx index b85af13b7..fcd3a39da 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx @@ -1,6 +1,6 @@ "use client"; -import { FolderPlus, ListFilter, Search, Upload, X } from "lucide-react"; +import { Eye, FolderPlus, ListFilter, Search, Upload, X } from "lucide-react"; import { useTranslations } from "next-intl"; import React, { useCallback, useMemo, useRef, useState } from "react"; import { useDocumentUploadDialog } from "@/components/assistant-ui/document-upload-popup"; @@ -19,6 +19,7 @@ export function DocumentsFilters({ onToggleType, activeTypes, onCreateFolder, + onWatchFolder, }: { typeCounts: Partial>; onSearch: (v: string) => void; @@ -26,6 +27,7 @@ export function DocumentsFilters({ onToggleType: (type: DocumentTypeEnum, checked: boolean) => void; activeTypes: DocumentTypeEnum[]; onCreateFolder?: () => void; + onWatchFolder?: () => void; }) { const t = useTranslations("documents"); const id = React.useId(); @@ -214,17 +216,34 @@ export function DocumentsFilters({ )} - {/* Upload Button */} - + {/* Watch Folder Button (desktop only) */} + {onWatchFolder && ( + + + + + Watch folder + + )} + + {/* Upload Button */} + ); diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/local-folder-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/local-folder-connect-form.tsx deleted file mode 100644 index 2e893c1c0..000000000 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/local-folder-connect-form.tsx +++ /dev/null @@ -1,272 +0,0 @@ -"use client"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { FolderSync, Info } from "lucide-react"; -import type { FC } from "react"; -import { useRef } from "react"; -import { useForm } from "react-hook-form"; -import * as z from "zod"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { EnumConnectorName } from "@/contracts/enums/connector"; -import { getConnectorBenefits } from "../connector-benefits"; -import type { ConnectFormProps } from "../index"; - -const localFolderFormSchema = z.object({ - name: z.string().min(3, { - message: "Connector name must be at least 3 characters.", - }), - folder_path: z.string().min(1, { - message: "Folder path is required.", - }), - folder_name: z.string().min(1, { - message: "Folder name is required.", - }), - exclude_patterns: z.string().optional(), - file_extensions: z.string().optional(), -}); - -type LocalFolderFormValues = z.infer; - -export const LocalFolderConnectForm: FC = ({ onSubmit, isSubmitting }) => { - const isSubmittingRef = useRef(false); - const isElectron = typeof window !== "undefined" && !!window.electronAPI; - - const form = useForm({ - resolver: zodResolver(localFolderFormSchema), - defaultValues: { - name: "Local Folder", - folder_path: "", - folder_name: "", - exclude_patterns: "node_modules,.git,.DS_Store", - file_extensions: "", - }, - }); - - const handleBrowse = async () => { - if (!isElectron) return; - const selected = await window.electronAPI!.selectFolder(); - if (selected) { - form.setValue("folder_path", selected); - const folderName = selected.split(/[\\/]/).pop() || "folder"; - if (!form.getValues("folder_name")) { - form.setValue("folder_name", folderName); - } - if (form.getValues("name") === "Local Folder") { - form.setValue("name", folderName); - } - } - }; - - const handleSubmit = async (values: LocalFolderFormValues) => { - if (isSubmittingRef.current || isSubmitting) return; - isSubmittingRef.current = true; - - try { - const excludePatterns = values.exclude_patterns - ? values.exclude_patterns - .split(",") - .map((p) => p.trim()) - .filter(Boolean) - : []; - - const fileExtensions = values.file_extensions - ? values.file_extensions - .split(",") - .map((e) => { - const ext = e.trim(); - return ext.startsWith(".") ? ext : `.${ext}`; - }) - .filter(Boolean) - : null; - - await onSubmit({ - name: values.name, - connector_type: EnumConnectorName.LOCAL_FOLDER_CONNECTOR, - config: { - folder_path: values.folder_path, - folder_name: values.folder_name, - exclude_patterns: excludePatterns, - file_extensions: fileExtensions, - }, - is_indexable: true, - is_active: true, - last_indexed_at: null, - periodic_indexing_enabled: false, - indexing_frequency_minutes: null, - next_scheduled_at: null, - }); - } finally { - isSubmittingRef.current = false; - } - }; - - return ( -
- - - Desktop App Required - - Real-time file watching is powered by the SurfSense desktop app. Files are - automatically synced whenever changes are detected. - - - -
-
- - ( - - Connector Name - - - - - - )} - /> - - ( - - Folder Path -
- - - - {isElectron && ( - - )} -
- - The absolute path to the folder to watch and sync. - - -
- )} - /> - - ( - - Display Name - - - - - A friendly name shown in the documents sidebar. - - - - )} - /> - - ( - - Exclude Patterns - - - - - Comma-separated patterns of directories/files to exclude. - - - - )} - /> - - ( - - File Extensions (optional) - - - - - Leave empty to index all supported files, or specify comma-separated extensions. - - - - )} - /> - - - -
- - {getConnectorBenefits(EnumConnectorName.LOCAL_FOLDER_CONNECTOR) && ( -
-

- What you get with Local Folder sync: -

-
    - {getConnectorBenefits(EnumConnectorName.LOCAL_FOLDER_CONNECTOR)?.map( - (benefit) =>
  • {benefit}
  • - )} -
-
- )} -
- ); -}; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts index 40c6a7fdd..0dc093100 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts @@ -111,14 +111,6 @@ export function getConnectorBenefits(connectorType: string): string[] | null { "Incremental sync - only changed files are re-indexed", "Full support for your vault's folder structure", ], - LOCAL_FOLDER_CONNECTOR: [ - "Watch local folders for real-time changes via the desktop app", - "Automatic change detection — only modified files are re-indexed", - "Version history with up to 20 snapshots per document", - "Mirrors your folder structure in the SurfSense sidebar", - "Supports any text-based file format", - "Works as a periodic sync fallback when the desktop app is not running", - ], }; return benefits[connectorType] || null; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx index 116893399..b6d813748 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx @@ -7,7 +7,6 @@ import { GithubConnectForm } from "./components/github-connect-form"; import { LinkupApiConnectForm } from "./components/linkup-api-connect-form"; import { LumaConnectForm } from "./components/luma-connect-form"; import { MCPConnectForm } from "./components/mcp-connect-form"; -import { LocalFolderConnectForm } from "./components/local-folder-connect-form"; import { ObsidianConnectForm } from "./components/obsidian-connect-form"; import { TavilyApiConnectForm } from "./components/tavily-api-connect-form"; @@ -59,8 +58,6 @@ export function getConnectFormComponent(connectorType: string): ConnectFormCompo return MCPConnectForm; case "OBSIDIAN_CONNECTOR": return ObsidianConnectForm; - case "LOCAL_FOLDER_CONNECTOR": - return LocalFolderConnectForm; default: return null; } diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/local-folder-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/local-folder-config.tsx deleted file mode 100644 index cb4295079..000000000 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/local-folder-config.tsx +++ /dev/null @@ -1,163 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import { useState } from "react"; -import { FolderSync } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import type { ConnectorConfigProps } from "../index"; - -export const LocalFolderConfig: FC = ({ - connector, - onConfigChange, - onNameChange, -}) => { - const isElectron = typeof window !== "undefined" && !!window.electronAPI; - - const [folderPath, setFolderPath] = useState( - (connector.config?.folder_path as string) || "" - ); - const [folderName, setFolderName] = useState( - (connector.config?.folder_name as string) || "" - ); - const [excludePatterns, setExcludePatterns] = useState(() => { - const patterns = connector.config?.exclude_patterns; - if (Array.isArray(patterns)) { - return patterns.join(", "); - } - return (patterns as string) || "node_modules, .git, .DS_Store"; - }); - const [fileExtensions, setFileExtensions] = useState(() => { - const exts = connector.config?.file_extensions; - if (Array.isArray(exts)) { - return exts.join(", "); - } - return (exts as string) || ""; - }); - const [name, setName] = useState(connector.name || ""); - - const handleFolderPathChange = (value: string) => { - setFolderPath(value); - onConfigChange?.({ ...connector.config, folder_path: value }); - }; - - const handleFolderNameChange = (value: string) => { - setFolderName(value); - onConfigChange?.({ ...connector.config, folder_name: value }); - }; - - const handleExcludePatternsChange = (value: string) => { - setExcludePatterns(value); - const arr = value - .split(",") - .map((p) => p.trim()) - .filter(Boolean); - onConfigChange?.({ ...connector.config, exclude_patterns: arr }); - }; - - const handleFileExtensionsChange = (value: string) => { - setFileExtensions(value); - const arr = value - ? value - .split(",") - .map((e) => { - const ext = e.trim(); - return ext.startsWith(".") ? ext : `.${ext}`; - }) - .filter(Boolean) - : null; - onConfigChange?.({ ...connector.config, file_extensions: arr }); - }; - - const handleNameChange = (value: string) => { - setName(value); - onNameChange?.(value); - }; - - const handleBrowse = async () => { - if (!isElectron) return; - const selected = await window.electronAPI!.selectFolder(); - if (selected) { - handleFolderPathChange(selected); - const autoName = selected.split(/[\\/]/).pop() || "folder"; - if (!folderName) handleFolderNameChange(autoName); - } - }; - - return ( -
-
-
- - handleNameChange(e.target.value)} - placeholder="Local Folder" - className="border-slate-400/20 focus-visible:border-slate-400/40" - /> -
-
- -
-

Folder Configuration

- -
-
- -
- handleFolderPathChange(e.target.value)} - placeholder="/path/to/your/folder" - className="border-slate-400/20 focus-visible:border-slate-400/40 font-mono flex-1" - /> - {isElectron && ( - - )} -
-
- -
- - handleFolderNameChange(e.target.value)} - placeholder="My Notes" - className="border-slate-400/20 focus-visible:border-slate-400/40" - /> -
- -
- - handleExcludePatternsChange(e.target.value)} - placeholder="node_modules, .git, .DS_Store" - className="border-slate-400/20 focus-visible:border-slate-400/40 font-mono" - /> -

- Comma-separated patterns of directories/files to exclude. -

-
- -
- - handleFileExtensionsChange(e.target.value)} - placeholder=".md, .txt, .rst" - className="border-slate-400/20 focus-visible:border-slate-400/40 font-mono" - /> -

- Leave empty to index all supported files. -

-
-
-
-
- ); -}; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx index 3dc1891c8..a63435260 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx @@ -19,7 +19,6 @@ import { JiraConfig } from "./components/jira-config"; import { LinkupApiConfig } from "./components/linkup-api-config"; import { LumaConfig } from "./components/luma-config"; import { MCPConfig } from "./components/mcp-config"; -import { LocalFolderConfig } from "./components/local-folder-config"; import { ObsidianConfig } from "./components/obsidian-config"; import { OneDriveConfig } from "./components/onedrive-config"; import { SlackConfig } from "./components/slack-config"; @@ -83,8 +82,6 @@ export function getConnectorConfigComponent( return MCPConfig; case "OBSIDIAN_CONNECTOR": return ObsidianConfig; - case "LOCAL_FOLDER_CONNECTOR": - return LocalFolderConfig; case "COMPOSIO_GOOGLE_DRIVE_CONNECTOR": return ComposioDriveConfig; case "COMPOSIO_GMAIL_CONNECTOR": 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 0b6d0917a..596b98e93 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,7 +20,6 @@ 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 dcedb4743..05d42adcb 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 @@ -278,8 +278,7 @@ export const ConnectorEditView: FC = ({ connector.connector_type !== "DROPBOX_CONNECTOR" && connector.connector_type !== "ONEDRIVE_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" && - connector.connector_type !== "GITHUB_CONNECTOR" && - connector.connector_type !== "LOCAL_FOLDER_CONNECTOR" && ( + connector.connector_type !== "GITHUB_CONNECTOR" && ( = ({ /> )} - {/* 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"; 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 436ce7843..e583cbe17 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 @@ -164,8 +164,7 @@ export const IndexingConfigurationView: FC = ({ config.connectorType !== "DROPBOX_CONNECTOR" && config.connectorType !== "ONEDRIVE_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" && - config.connectorType !== "GITHUB_CONNECTOR" && - config.connectorType !== "LOCAL_FOLDER_CONNECTOR" && ( + config.connectorType !== "GITHUB_CONNECTOR" && ( = ({ /> )} - {/* 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 !== "DROPBOX_CONNECTOR" && - config.connectorType !== "ONEDRIVE_CONNECTOR" && - config.connectorType !== "LOCAL_FOLDER_CONNECTOR" && ( + config.connectorType !== "ONEDRIVE_CONNECTOR" && (