feat: add support for Local Folder connector in UI components and configuration

This commit is contained in:
Anish Sarkar 2026-04-02 11:38:40 +05:30
parent 28f556224a
commit e2f946b7c0
9 changed files with 72 additions and 52 deletions

View file

@ -17,6 +17,7 @@ import { DocumentUploadDialogProvider } from "@/components/assistant-ui/document
import { LayoutDataProvider } from "@/components/layout"; import { LayoutDataProvider } from "@/components/layout";
import { OnboardingTour } from "@/components/onboarding-tour"; import { OnboardingTour } from "@/components/onboarding-tour";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { useFolderSync } from "@/hooks/use-folder-sync";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading"; import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
export function DashboardClientLayout({ export function DashboardClientLayout({
@ -159,6 +160,9 @@ export function DashboardClientLayout({
// Use global loading screen - spinner animation won't reset // Use global loading screen - spinner animation won't reset
useGlobalLoadingEffect(shouldShowLoading); useGlobalLoadingEffect(shouldShowLoading);
// Wire desktop app file watcher -> single-file re-index API
useFolderSync();
if (shouldShowLoading) { if (shouldShowLoading) {
return null; return null;
} }

View file

@ -20,6 +20,7 @@ const FORM_ID_MAP: Record<string, string> = {
CIRCLEBACK_CONNECTOR: "circleback-connect-form", CIRCLEBACK_CONNECTOR: "circleback-connect-form",
MCP_CONNECTOR: "mcp-connect-form", MCP_CONNECTOR: "mcp-connect-form",
OBSIDIAN_CONNECTOR: "obsidian-connect-form", OBSIDIAN_CONNECTOR: "obsidian-connect-form",
LOCAL_FOLDER_CONNECTOR: "local-folder-connect-form",
}; };
interface ConnectorConnectViewProps { interface ConnectorConnectViewProps {

View file

@ -272,13 +272,14 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
{/* AI Summary toggle */} {/* AI Summary toggle */}
<SummaryConfig enabled={enableSummary} onEnabledChange={onEnableSummaryChange} /> <SummaryConfig enabled={enableSummary} onEnabledChange={onEnableSummaryChange} />
{/* 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 !== "GOOGLE_DRIVE_CONNECTOR" &&
connector.connector_type !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" &&
connector.connector_type !== "DROPBOX_CONNECTOR" && connector.connector_type !== "DROPBOX_CONNECTOR" &&
connector.connector_type !== "ONEDRIVE_CONNECTOR" && connector.connector_type !== "ONEDRIVE_CONNECTOR" &&
connector.connector_type !== "WEBCRAWLER_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" &&
connector.connector_type !== "GITHUB_CONNECTOR" && ( connector.connector_type !== "GITHUB_CONNECTOR" &&
connector.connector_type !== "LOCAL_FOLDER_CONNECTOR" && (
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
@ -293,39 +294,39 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
/> />
)} )}
{/* Periodic sync - shown for all indexable connectors */} {/* Periodic sync - shown for all indexable connectors except Local Folder */}
{(() => { {connector.connector_type !== "LOCAL_FOLDER_CONNECTOR" &&
// Check if Google Drive (regular or Composio) has folders/files selected (() => {
const isGoogleDrive = connector.connector_type === "GOOGLE_DRIVE_CONNECTOR"; const isGoogleDrive = connector.connector_type === "GOOGLE_DRIVE_CONNECTOR";
const isComposioGoogleDrive = const isComposioGoogleDrive =
connector.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"; connector.connector_type === "COMPOSIO_GOOGLE_DRIVE_CONNECTOR";
const requiresFolderSelection = isGoogleDrive || isComposioGoogleDrive; const requiresFolderSelection = isGoogleDrive || isComposioGoogleDrive;
const selectedFolders = const selectedFolders =
(connector.config?.selected_folders as (connector.config?.selected_folders as
| Array<{ id: string; name: string }> | Array<{ id: string; name: string }>
| undefined) || []; | undefined) || [];
const selectedFiles = const selectedFiles =
(connector.config?.selected_files as (connector.config?.selected_files as
| Array<{ id: string; name: string }> | Array<{ id: string; name: string }>
| undefined) || []; | undefined) || [];
const hasItemsSelected = selectedFolders.length > 0 || selectedFiles.length > 0; const hasItemsSelected = selectedFolders.length > 0 || selectedFiles.length > 0;
const isDisabled = requiresFolderSelection && !hasItemsSelected; const isDisabled = requiresFolderSelection && !hasItemsSelected;
return ( return (
<PeriodicSyncConfig <PeriodicSyncConfig
enabled={periodicEnabled} enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes} frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange} onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange} onFrequencyChange={onFrequencyChange}
disabled={isDisabled} disabled={isDisabled}
disabledMessage={ disabledMessage={
isDisabled isDisabled
? "Select at least one folder or file above to enable periodic sync" ? "Select at least one folder or file above to enable periodic sync"
: undefined : undefined
} }
/> />
); );
})()} })()}
</> </>
)} )}

View file

@ -158,13 +158,14 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
{/* AI Summary toggle */} {/* AI Summary toggle */}
<SummaryConfig enabled={enableSummary} onEnabledChange={onEnableSummaryChange} /> <SummaryConfig enabled={enableSummary} onEnabledChange={onEnableSummaryChange} />
{/* 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 !== "GOOGLE_DRIVE_CONNECTOR" &&
config.connectorType !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" &&
config.connectorType !== "DROPBOX_CONNECTOR" && config.connectorType !== "DROPBOX_CONNECTOR" &&
config.connectorType !== "ONEDRIVE_CONNECTOR" && config.connectorType !== "ONEDRIVE_CONNECTOR" &&
config.connectorType !== "WEBCRAWLER_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" &&
config.connectorType !== "GITHUB_CONNECTOR" && ( config.connectorType !== "GITHUB_CONNECTOR" &&
config.connectorType !== "LOCAL_FOLDER_CONNECTOR" && (
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
@ -179,9 +180,12 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
/> />
)} )}
{/* 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 !== "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" && (
<PeriodicSyncConfig <PeriodicSyncConfig
enabled={periodicEnabled} enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes} frequencyMinutes={frequencyMinutes}

View file

@ -184,6 +184,14 @@ export const OTHER_CONNECTORS = [
connectorType: EnumConnectorName.OBSIDIAN_CONNECTOR, connectorType: EnumConnectorName.OBSIDIAN_CONNECTOR,
selfHostedOnly: true, selfHostedOnly: true,
}, },
{
id: "local-folder-connector",
title: "Local Folder",
description: "Watch and sync local folders (desktop only)",
connectorType: EnumConnectorName.LOCAL_FOLDER_CONNECTOR,
selfHostedOnly: true,
desktopOnly: true,
},
] as const; ] as const;
// Composio Connectors - Individual entries for each supported toolkit // Composio Connectors - Individual entries for each supported toolkit

View file

@ -76,29 +76,26 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
}) => { }) => {
// Check if self-hosted mode (for showing self-hosted only connectors) // Check if self-hosted mode (for showing self-hosted only connectors)
const selfHosted = isSelfHosted(); 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 // Filter connectors based on search and deployment mode
const filteredOAuth = OAUTH_CONNECTORS.filter( const filteredOAuth = OAUTH_CONNECTORS.filter(
(c) => (c) => matchesSearch(c.title, c.description) && passesDeploymentFilter(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)
); );
const filteredCrawlers = CRAWLERS.filter( const filteredCrawlers = CRAWLERS.filter(
(c) => (c) => matchesSearch(c.title, c.description) && passesDeploymentFilter(c)
(c.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
c.description.toLowerCase().includes(searchQuery.toLowerCase())) &&
(!("selfHostedOnly" in c) || !c.selfHostedOnly || selfHosted)
); );
const filteredOther = OTHER_CONNECTORS.filter( const filteredOther = OTHER_CONNECTORS.filter(
(c) => (c) => matchesSearch(c.title, c.description) && passesDeploymentFilter(c)
(c.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
c.description.toLowerCase().includes(searchQuery.toLowerCase())) &&
(!("selfHostedOnly" in c) || !c.selfHostedOnly || selfHosted)
); );
// Filter Composio connectors // Filter Composio connectors

View file

@ -25,6 +25,7 @@ export enum EnumConnectorName {
YOUTUBE_CONNECTOR = "YOUTUBE_CONNECTOR", YOUTUBE_CONNECTOR = "YOUTUBE_CONNECTOR",
CIRCLEBACK_CONNECTOR = "CIRCLEBACK_CONNECTOR", CIRCLEBACK_CONNECTOR = "CIRCLEBACK_CONNECTOR",
OBSIDIAN_CONNECTOR = "OBSIDIAN_CONNECTOR", OBSIDIAN_CONNECTOR = "OBSIDIAN_CONNECTOR",
LOCAL_FOLDER_CONNECTOR = "LOCAL_FOLDER_CONNECTOR",
DROPBOX_CONNECTOR = "DROPBOX_CONNECTOR", DROPBOX_CONNECTOR = "DROPBOX_CONNECTOR",
MCP_CONNECTOR = "MCP_CONNECTOR", MCP_CONNECTOR = "MCP_CONNECTOR",
COMPOSIO_GOOGLE_DRIVE_CONNECTOR = "COMPOSIO_GOOGLE_DRIVE_CONNECTOR", COMPOSIO_GOOGLE_DRIVE_CONNECTOR = "COMPOSIO_GOOGLE_DRIVE_CONNECTOR",

View file

@ -3,6 +3,7 @@ import {
BookOpen, BookOpen,
File, File,
FileText, FileText,
FolderSync,
Globe, Globe,
Microscope, Microscope,
Search, Search,
@ -75,6 +76,8 @@ export const getConnectorIcon = (connectorType: EnumConnectorName | string, clas
return <Image src="/connectors/circleback.svg" alt="Circleback" {...imgProps} />; return <Image src="/connectors/circleback.svg" alt="Circleback" {...imgProps} />;
case EnumConnectorName.MCP_CONNECTOR: case EnumConnectorName.MCP_CONNECTOR:
return <Image src="/connectors/modelcontextprotocol.svg" alt="MCP" {...imgProps} />; return <Image src="/connectors/modelcontextprotocol.svg" alt="MCP" {...imgProps} />;
case EnumConnectorName.LOCAL_FOLDER_CONNECTOR:
return <FolderSync {...iconProps} />;
case EnumConnectorName.OBSIDIAN_CONNECTOR: case EnumConnectorName.OBSIDIAN_CONNECTOR:
return <Image src="/connectors/obsidian.svg" alt="Obsidian" {...imgProps} />; return <Image src="/connectors/obsidian.svg" alt="Obsidian" {...imgProps} />;
case EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR: case EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR:

View file

@ -30,6 +30,7 @@ export const searchSourceConnectorTypeEnum = z.enum([
"DROPBOX_CONNECTOR", "DROPBOX_CONNECTOR",
"MCP_CONNECTOR", "MCP_CONNECTOR",
"OBSIDIAN_CONNECTOR", "OBSIDIAN_CONNECTOR",
"LOCAL_FOLDER_CONNECTOR",
"COMPOSIO_GOOGLE_DRIVE_CONNECTOR", "COMPOSIO_GOOGLE_DRIVE_CONNECTOR",
"COMPOSIO_GMAIL_CONNECTOR", "COMPOSIO_GMAIL_CONNECTOR",
"COMPOSIO_GOOGLE_CALENDAR_CONNECTOR", "COMPOSIO_GOOGLE_CALENDAR_CONNECTOR",