feat: enhance Google Drive connector functionality and UI

- Added support for selecting both folders and files in the Google Drive connector configuration.
- Updated the UI to reflect the selection of files alongside folders, improving user clarity.
- Introduced a quick indexing feature for connectors, allowing users to start indexing without date selection.
- Adjusted periodic sync settings to be disabled for Google Drive connectors, ensuring proper functionality.
- Improved styling and accessibility across various components in the connector popup.
This commit is contained in:
Anish Sarkar 2026-01-01 21:22:34 +05:30
parent 543daa0434
commit b909032e32
7 changed files with 160 additions and 68 deletions

View file

@ -91,6 +91,7 @@ export const ConnectorIndicator: FC = () => {
handleBackFromEdit,
handleBackFromConnect,
handleBackFromYouTube,
handleQuickIndexConnector,
connectorConfig,
setConnectorConfig,
setIndexingConnectorConfig,
@ -225,6 +226,7 @@ export const ConnectorIndicator: FC = () => {
frequencyMinutes={frequencyMinutes}
isSaving={isSaving}
isDisconnecting={isDisconnecting}
isIndexing={indexingConnectorIds.has(editingConnector.id)}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
onPeriodicEnabledChange={setPeriodicEnabled}
@ -232,6 +234,7 @@ export const ConnectorIndicator: FC = () => {
onSave={() => handleSaveConnector(() => refreshConnectors())}
onDisconnect={() => handleDisconnectConnector(() => refreshConnectors())}
onBack={handleBackFromEdit}
onQuickIndex={editingConnector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" ? () => handleQuickIndexConnector(editingConnector.id) : undefined}
onConfigChange={setConnectorConfig}
onNameChange={setConnectorName}
/>

View file

@ -37,7 +37,7 @@ export const PeriodicSyncConfig: FC<PeriodicSyncConfigProps> = ({
</div>
{enabled && (
<div className="mt-4 pt-4 border-t border-border/100 space-y-3">
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={onFrequencyChange}>

View file

@ -17,15 +17,19 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
connector,
onConfigChange,
}) => {
// Initialize with existing selected folders from connector config
// Initialize with existing selected folders and files from connector config
const existingFolders = (connector.config?.selected_folders as SelectedFolder[] | undefined) || [];
const existingFiles = (connector.config?.selected_files as SelectedFolder[] | undefined) || [];
const [selectedFolders, setSelectedFolders] = useState<SelectedFolder[]>(existingFolders);
const [selectedFiles, setSelectedFiles] = useState<SelectedFolder[]>(existingFiles);
const [showFolderSelector, setShowFolderSelector] = useState(false);
// Update selected folders when connector config changes
// Update selected folders and files when connector config changes
useEffect(() => {
const folders = (connector.config?.selected_folders as SelectedFolder[] | undefined) || [];
const files = (connector.config?.selected_files as SelectedFolder[] | undefined) || [];
setSelectedFolders(folders);
setSelectedFiles(files);
}, [connector.config]);
const handleSelectFolders = (folders: SelectedFolder[]) => {
@ -35,28 +39,50 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
onConfigChange({
...connector.config,
selected_folders: folders,
selected_files: selectedFiles, // Preserve existing files
});
}
};
const handleSelectFiles = (files: SelectedFolder[]) => {
setSelectedFiles(files);
if (onConfigChange) {
// Store file IDs and names in config for indexing
onConfigChange({
...connector.config,
selected_folders: selectedFolders, // Preserve existing folders
selected_files: files,
});
}
};
const totalSelected = selectedFolders.length + selectedFiles.length;
return (
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<div className="space-y-1 sm:space-y-2">
<h3 className="font-medium text-sm sm:text-base">Folder Selection</h3>
<h3 className="font-medium text-sm sm:text-base">Folder & File Selection</h3>
<p className="text-xs sm:text-sm text-muted-foreground">
Select specific folders to index. Only files directly in each folder will be processedsubfolders must be selected separately.
Select specific folders and/or individual files to index. Only files directly in each folder will be processedsubfolders must be selected separately.
</p>
</div>
{selectedFolders.length > 0 && (
{totalSelected > 0 && (
<div className="p-2 sm:p-3 bg-muted rounded-lg text-xs sm:text-sm space-y-1 sm:space-y-2">
<p className="font-medium">
Selected {selectedFolders.length} folder{selectedFolders.length > 1 ? "s" : ""}:
Selected {totalSelected} item{totalSelected > 1 ? "s" : ""}:
{selectedFolders.length > 0 && ` ${selectedFolders.length} folder${selectedFolders.length > 1 ? "s" : ""}`}
{selectedFiles.length > 0 && ` ${selectedFiles.length} file${selectedFiles.length > 1 ? "s" : ""}`}
</p>
<div className="max-h-20 sm:max-h-24 overflow-y-auto">
<div className="max-h-20 sm:max-h-24 overflow-y-auto space-y-1">
{selectedFolders.map((folder) => (
<p key={folder.id} className="text-xs sm:text-sm text-muted-foreground truncate" title={folder.name}>
{folder.name}
📁 {folder.name}
</p>
))}
{selectedFiles.map((file) => (
<p key={file.id} className="text-xs sm:text-sm text-muted-foreground truncate" title={file.name}>
📄 {file.name}
</p>
))}
</div>
@ -69,6 +95,8 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
connectorId={connector.id}
selectedFolders={selectedFolders}
onSelectFolders={handleSelectFolders}
selectedFiles={selectedFiles}
onSelectFiles={handleSelectFiles}
/>
<Button
type="button"
@ -87,14 +115,14 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
onClick={() => setShowFolderSelector(true)}
className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-white/10 text-xs sm:text-sm h-8 sm:h-9"
>
{selectedFolders.length > 0 ? "Change Folder Selection" : "Select Folders"}
{totalSelected > 0 ? "Change Selection" : "Select Folders & Files"}
</Button>
)}
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 flex items-center gap-2 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg+div]:translate-y-0">
<Info className="h-3 w-3 sm:h-4 sm:w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
Folder selection is used when indexing. You can change this selection when you start indexing.
Folder and file selection is used when indexing. You can change this selection when you start indexing.
</AlertDescription>
</Alert>
</div>

View file

@ -1,6 +1,6 @@
"use client";
import { ArrowLeft, Info, Loader2, Trash2 } from "lucide-react";
import { ArrowLeft, Info, Loader2, RefreshCw, Trash2 } from "lucide-react";
import { type FC, useState, useCallback, useRef, useEffect, useMemo } from "react";
import { Button } from "@/components/ui/button";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
@ -18,6 +18,7 @@ interface ConnectorEditViewProps {
frequencyMinutes: string;
isSaving: boolean;
isDisconnecting: boolean;
isIndexing?: boolean;
onStartDateChange: (date: Date | undefined) => void;
onEndDateChange: (date: Date | undefined) => void;
onPeriodicEnabledChange: (enabled: boolean) => void;
@ -25,6 +26,7 @@ interface ConnectorEditViewProps {
onSave: () => void;
onDisconnect: () => void;
onBack: () => void;
onQuickIndex?: () => void;
onConfigChange?: (config: Record<string, unknown>) => void;
onNameChange?: (name: string) => void;
}
@ -37,6 +39,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
frequencyMinutes,
isSaving,
isDisconnecting,
isIndexing = false,
onStartDateChange,
onEndDateChange,
onPeriodicEnabledChange,
@ -44,6 +47,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
onSave,
onDisconnect,
onBack,
onQuickIndex,
onConfigChange,
onNameChange,
}) => {
@ -120,18 +124,42 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
</button>
{/* Connector header */}
<div className="flex items-center gap-4 mb-6">
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-primary/10 border border-primary/20">
{getConnectorIcon(connector.connector_type, "size-7")}
</div>
<div className="flex-1">
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
{connector.name}
</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
Manage your connector settings and sync configuration
</p>
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 mb-6">
<div className="flex items-center gap-4 flex-1 w-full sm:w-auto">
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-primary/10 border border-primary/20 flex-shrink-0">
{getConnectorIcon(connector.connector_type, "size-7")}
</div>
<div className="flex-1 min-w-0">
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
{connector.name}
</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
Manage your connector settings and sync configuration
</p>
</div>
</div>
{/* Quick Index Button - only show for indexable connectors, but not for Google Drive (requires folder selection) */}
{connector.is_indexable && onQuickIndex && connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && (
<Button
variant="secondary"
size="sm"
onClick={onQuickIndex}
disabled={isIndexing || isSaving || isDisconnecting}
className="text-xs sm:text-sm bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20 w-full sm:w-auto"
>
{isIndexing ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Indexing...
</>
) : (
<>
<RefreshCw className="mr-2 h-4 w-4" />
Quick Index
</>
)}
</Button>
)}
</div>
</div>
@ -165,12 +193,15 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
/>
)}
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
/>
{/* Periodic sync - not shown for Google Drive */}
{connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && (
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
/>
)}
</>
)}

View file

@ -146,12 +146,15 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
/>
)}
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
/>
{/* Periodic sync - not shown for Google Drive */}
{config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && (
<PeriodicSyncConfig
enabled={periodicEnabled}
frequencyMinutes={frequencyMinutes}
onEnabledChange={onPeriodicEnabledChange}
onFrequencyChange={onFrequencyChange}
/>
)}
</>
)}

View file

@ -141,8 +141,8 @@ export const useConnectorDialog = () => {
if (connectorValidation.success) {
setEditingConnector(connector);
setConnectorConfig(connector.config);
// Load existing periodic sync settings
setPeriodicEnabled(connector.periodic_indexing_enabled);
// Load existing periodic sync settings (disabled for Google Drive)
setPeriodicEnabled(connector.connector_type === "GOOGLE_DRIVE_CONNECTOR" ? false : connector.periodic_indexing_enabled);
setFrequencyMinutes(
connector.indexing_frequency_minutes?.toString() || "1440"
);
@ -293,7 +293,7 @@ export const useConnectorDialog = () => {
setConnectingId("webcrawler-connector");
try {
const newConnector = await createConnector({
await createConnector({
data: {
name: "Web Pages",
connector_type: EnumConnectorName.WEBCRAWLER_CONNECTOR,
@ -530,7 +530,7 @@ export const useConnectorDialog = () => {
setIsCreatingConnector(false);
// Don't clear connectingConnectorType here - it's cleared above when transitioning to config view
}
}, [connectingConnectorType, searchSpaceId, createConnector, refetchAllConnectors, updateConnector, indexConnector, router, getFrequencyLabel, queryClient]);
}, [connectingConnectorType, searchSpaceId, createConnector, refetchAllConnectors, updateConnector, indexConnector, router, getFrequencyLabel]);
// Handle going back from connect view
const handleBackFromConnect = useCallback(() => {
@ -583,15 +583,20 @@ export const useConnectorDialog = () => {
const endDateStr = endDate ? format(endDate, "yyyy-MM-dd") : undefined;
// Update connector with periodic sync settings and config changes
// Note: Periodic sync is disabled for Google Drive connectors
if (periodicEnabled || indexingConnectorConfig) {
const frequency = periodicEnabled ? parseInt(frequencyMinutes, 10) : undefined;
await updateConnector({
id: indexingConfig.connectorId,
data: {
...(periodicEnabled && {
...(periodicEnabled && indexingConfig.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && {
periodic_indexing_enabled: true,
indexing_frequency_minutes: frequency,
}),
...(indexingConfig.connectorType === "GOOGLE_DRIVE_CONNECTOR" && {
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
}),
...(indexingConnectorConfig && {
config: indexingConnectorConfig,
}),
@ -602,16 +607,17 @@ export const useConnectorDialog = () => {
// Handle Google Drive folder selection
if (indexingConfig.connectorType === "GOOGLE_DRIVE_CONNECTOR" && indexingConnectorConfig) {
const selectedFolders = indexingConnectorConfig.selected_folders as Array<{ id: string; name: string }> | undefined;
if (selectedFolders && selectedFolders.length > 0) {
// Index with folder selection
const folderIds = selectedFolders.map((f) => f.id).join(",");
const folderNames = selectedFolders.map((f) => f.name).join(", ");
const selectedFiles = indexingConnectorConfig.selected_files as Array<{ id: string; name: string }> | undefined;
if ((selectedFolders && selectedFolders.length > 0) || (selectedFiles && selectedFiles.length > 0)) {
// Index with folder/file selection
await indexConnector({
connector_id: indexingConfig.connectorId,
queryParams: {
search_space_id: searchSpaceId,
folder_ids: folderIds,
folder_names: folderNames,
},
body: {
folders: selectedFolders || [],
files: selectedFiles || [],
},
});
} else {
@ -714,8 +720,8 @@ export const useConnectorDialog = () => {
setEditingConnector(connector);
setConnectorName(connector.name);
// Load existing periodic sync settings
setPeriodicEnabled(connector.periodic_indexing_enabled);
// Load existing periodic sync settings (disabled for Google Drive)
setPeriodicEnabled(connector.connector_type === "GOOGLE_DRIVE_CONNECTOR" ? false : connector.periodic_indexing_enabled);
setFrequencyMinutes(connector.indexing_frequency_minutes?.toString() || "1440");
// Reset dates - user can set new ones for re-indexing
setStartDate(undefined);
@ -763,13 +769,14 @@ export const useConnectorDialog = () => {
const endDateStr = endDate ? format(endDate, "yyyy-MM-dd") : undefined;
// Update connector with periodic sync settings, config changes, and name
// Note: Periodic sync is disabled for Google Drive connectors
const frequency = periodicEnabled ? parseInt(frequencyMinutes, 10) : null;
await updateConnector({
id: editingConnector.id,
data: {
name: connectorName || editingConnector.name,
periodic_indexing_enabled: periodicEnabled,
indexing_frequency_minutes: frequency,
periodic_indexing_enabled: editingConnector.connector_type === "GOOGLE_DRIVE_CONNECTOR" ? false : periodicEnabled,
indexing_frequency_minutes: editingConnector.connector_type === "GOOGLE_DRIVE_CONNECTOR" ? null : frequency,
config: connectorConfig || editingConnector.config,
},
});
@ -782,18 +789,20 @@ export const useConnectorDialog = () => {
} else if (editingConnector.connector_type === "GOOGLE_DRIVE_CONNECTOR") {
// Google Drive uses folder selection from config, not date ranges
const selectedFolders = (connectorConfig || editingConnector.config)?.selected_folders as Array<{ id: string; name: string }> | undefined;
if (selectedFolders && selectedFolders.length > 0) {
const folderIds = selectedFolders.map((f) => f.id).join(",");
const folderNames = selectedFolders.map((f) => f.name).join(", ");
const selectedFiles = (connectorConfig || editingConnector.config)?.selected_files as Array<{ id: string; name: string }> | undefined;
if ((selectedFolders && selectedFolders.length > 0) || (selectedFiles && selectedFiles.length > 0)) {
await indexConnector({
connector_id: editingConnector.id,
queryParams: {
search_space_id: searchSpaceId,
folder_ids: folderIds,
folder_names: folderNames,
},
body: {
folders: selectedFolders || [],
files: selectedFiles || [],
},
});
indexingDescription = `Re-indexing started for ${selectedFolders.length} folder(s).`;
const totalItems = (selectedFolders?.length || 0) + (selectedFiles?.length || 0);
indexingDescription = `Re-indexing started for ${totalItems} item(s).`;
}
} else if (editingConnector.connector_type === "WEBCRAWLER_CONNECTOR") {
// Webcrawler uses config (API key and URLs), not date ranges
@ -875,6 +884,31 @@ export const useConnectorDialog = () => {
}
}, [editingConnector, searchSpaceId, deleteConnector, router]);
// Handle quick index (index without date picker, uses backend defaults)
const handleQuickIndexConnector = useCallback(async (connectorId: number) => {
if (!searchSpaceId) return;
try {
await indexConnector({
connector_id: connectorId,
queryParams: {
search_space_id: searchSpaceId,
},
});
toast.success("Indexing started", {
description: "You can continue working while we sync your data.",
});
// Invalidate queries to refresh data
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(Number(searchSpaceId)),
});
} catch (error) {
console.error("Error indexing connector content:", error);
toast.error(error instanceof Error ? error.message : "Failed to start indexing");
}
}, [searchSpaceId, indexConnector]);
// Handle going back from edit view
const handleBackFromEdit = useCallback(() => {
const url = new URL(window.location.href);
@ -987,6 +1021,7 @@ export const useConnectorDialog = () => {
handleBackFromEdit,
handleBackFromConnect,
handleBackFromYouTube,
handleQuickIndexConnector,
connectorConfig,
setConnectorConfig,
setIndexingConnectorConfig,