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 { 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;
}

View file

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

View file

@ -272,13 +272,14 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
{/* AI Summary toggle */}
<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 !== "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" && (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
@ -293,39 +294,39 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
/>
)}
{/* 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 (
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
disabled={isDisabled}
disabledMessage={
isDisabled
? "Select at least one folder or file above to enable periodic sync"
: undefined
}
/>
);
})()}
return (
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
disabled={isDisabled}
disabledMessage={
isDisabled
? "Select at least one folder or file above to enable periodic sync"
: undefined
}
/>
);
})()}
</>
)}

View file

@ -158,13 +158,14 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
{/* AI Summary toggle */}
<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 !== "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" && (
<DateRangeSelector
startDate={startDate}
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 !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" && (
config.connectorType !== "COMPOSIO_GOOGLE_DRIVE_CONNECTOR" &&
config.connectorType !== "DROPBOX_CONNECTOR" &&
config.connectorType !== "ONEDRIVE_CONNECTOR" &&
config.connectorType !== "LOCAL_FOLDER_CONNECTOR" && (
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}

View file

@ -184,6 +184,14 @@ export const OTHER_CONNECTORS = [
connectorType: EnumConnectorName.OBSIDIAN_CONNECTOR,
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;
// 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)
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

View file

@ -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",

View file

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

View file

@ -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",