mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-06 20:15:17 +02:00
feat(ui): integrate Google Drive folder selection into manage connectors page
- Add folder selection dialog for Google Drive indexing - Hide date picker and quick index for Google Drive - Show folder tree browser in modal - Pass selected folder to indexing API - Adjust modal size to prevent overflow
This commit is contained in:
parent
5df04c3caa
commit
c4a95ecc02
1 changed files with 190 additions and 25 deletions
|
|
@ -5,6 +5,8 @@ import {
|
||||||
Calendar as CalendarIcon,
|
Calendar as CalendarIcon,
|
||||||
Clock,
|
Clock,
|
||||||
Edit,
|
Edit,
|
||||||
|
Folder,
|
||||||
|
HardDrive,
|
||||||
Loader2,
|
Loader2,
|
||||||
Plus,
|
Plus,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
|
|
@ -61,6 +63,13 @@ import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
|
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
import { GoogleDriveFolderTree } from "@/components/connectors/google-drive-folder-tree";
|
||||||
|
|
||||||
|
interface DriveFolder {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ConnectorsPage() {
|
export default function ConnectorsPage() {
|
||||||
const t = useTranslations("connectors");
|
const t = useTranslations("connectors");
|
||||||
|
|
@ -105,6 +114,13 @@ export default function ConnectorsPage() {
|
||||||
const [customFrequency, setCustomFrequency] = useState<string>("");
|
const [customFrequency, setCustomFrequency] = useState<string>("");
|
||||||
const [isSavingPeriodic, setIsSavingPeriodic] = useState(false);
|
const [isSavingPeriodic, setIsSavingPeriodic] = useState(false);
|
||||||
|
|
||||||
|
// Google Drive folder selection state
|
||||||
|
const [driveFolderDialogOpen, setDriveFolderDialogOpen] = useState(false);
|
||||||
|
const [driveFolders, setDriveFolders] = useState<DriveFolder[]>([]);
|
||||||
|
const [selectedFolderId, setSelectedFolderId] = useState<string>("");
|
||||||
|
const [selectedFolderName, setSelectedFolderName] = useState<string>("");
|
||||||
|
const [isLoadingFolders, setIsLoadingFolders] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
toast.error(t("failed_load"));
|
toast.error(t("failed_load"));
|
||||||
|
|
@ -129,8 +145,78 @@ export default function ConnectorsPage() {
|
||||||
|
|
||||||
// Handle opening date picker for indexing
|
// Handle opening date picker for indexing
|
||||||
const handleOpenDatePicker = (connectorId: number) => {
|
const handleOpenDatePicker = (connectorId: number) => {
|
||||||
|
// Check if this is a Google Drive connector
|
||||||
|
const connector = connectors.find((c) => c.id === connectorId);
|
||||||
|
if (connector?.connector_type === EnumConnectorName.GOOGLE_DRIVE_CONNECTOR) {
|
||||||
|
// Open folder selection dialog for Google Drive
|
||||||
|
handleOpenDriveFolderDialog(connectorId);
|
||||||
|
} else {
|
||||||
|
// Open date picker for other connectors
|
||||||
|
setSelectedConnectorForIndexing(connectorId);
|
||||||
|
setDatePickerOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle opening Google Drive folder selection dialog
|
||||||
|
const handleOpenDriveFolderDialog = async (connectorId: number) => {
|
||||||
setSelectedConnectorForIndexing(connectorId);
|
setSelectedConnectorForIndexing(connectorId);
|
||||||
setDatePickerOpen(true);
|
setDriveFolderDialogOpen(true);
|
||||||
|
setIsLoadingFolders(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await authenticatedFetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/connectors/${connectorId}/google-drive/folders`,
|
||||||
|
{ method: "GET" }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to load folders");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setDriveFolders(data.folders || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading folders:", error);
|
||||||
|
toast.error("Failed to load Google Drive folders");
|
||||||
|
setDriveFolderDialogOpen(false);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingFolders(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle Google Drive folder indexing
|
||||||
|
const handleIndexDriveFolder = async () => {
|
||||||
|
if (selectedConnectorForIndexing === null || !selectedFolderId) {
|
||||||
|
toast.error("Please select a folder");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDriveFolderDialogOpen(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIndexingConnectorId(selectedConnectorForIndexing);
|
||||||
|
const selectedFolder = driveFolders.find((f) => f.id === selectedFolderId);
|
||||||
|
const folderName = selectedFolder?.name || "Selected Folder";
|
||||||
|
|
||||||
|
// Call indexConnector with folder_id and folder_name as query params
|
||||||
|
await indexConnector(
|
||||||
|
selectedConnectorForIndexing,
|
||||||
|
searchSpaceId,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
selectedFolderId,
|
||||||
|
folderName
|
||||||
|
);
|
||||||
|
toast.success(t("indexing_started"));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error indexing connector content:", error);
|
||||||
|
toast.error(error instanceof Error ? error.message : t("indexing_failed"));
|
||||||
|
} finally {
|
||||||
|
setIndexingConnectorId(null);
|
||||||
|
setSelectedConnectorForIndexing(null);
|
||||||
|
setSelectedFolderId("");
|
||||||
|
setDriveFolders([]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle connector indexing with dates
|
// Handle connector indexing with dates
|
||||||
|
|
@ -361,39 +447,52 @@ export default function ConnectorsPage() {
|
||||||
>
|
>
|
||||||
{indexingConnectorId === connector.id ? (
|
{indexingConnectorId === connector.id ? (
|
||||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||||
|
) : connector.connector_type === EnumConnectorName.GOOGLE_DRIVE_CONNECTOR ? (
|
||||||
|
<Folder className="h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
<CalendarIcon className="h-4 w-4" />
|
<CalendarIcon className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
<span className="sr-only">{t("index_date_range")}</span>
|
<span className="sr-only">
|
||||||
|
{connector.connector_type === EnumConnectorName.GOOGLE_DRIVE_CONNECTOR
|
||||||
|
? "Select folder to index"
|
||||||
|
: t("index_date_range")}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("index_date_range")}</p>
|
<p>
|
||||||
</TooltipContent>
|
{connector.connector_type === EnumConnectorName.GOOGLE_DRIVE_CONNECTOR
|
||||||
</Tooltip>
|
? "Select folder to index"
|
||||||
</TooltipProvider>
|
: t("index_date_range")}
|
||||||
<TooltipProvider>
|
</p>
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => handleQuickIndexConnector(connector.id)}
|
|
||||||
disabled={indexingConnectorId === connector.id}
|
|
||||||
>
|
|
||||||
{indexingConnectorId === connector.id ? (
|
|
||||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<RefreshCw className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
<span className="sr-only">{t("quick_index")}</span>
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("quick_index_auto")}</p>
|
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
{/* Hide quick index button for Google Drive (requires folder selection) */}
|
||||||
|
{connector.connector_type !== EnumConnectorName.GOOGLE_DRIVE_CONNECTOR && (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleQuickIndexConnector(connector.id)}
|
||||||
|
disabled={indexingConnectorId === connector.id}
|
||||||
|
>
|
||||||
|
{indexingConnectorId === connector.id ? (
|
||||||
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<RefreshCw className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
<span className="sr-only">{t("quick_index")}</span>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t("quick_index_auto")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{connector.is_indexable && (
|
{connector.is_indexable && (
|
||||||
|
|
@ -581,6 +680,72 @@ export default function ConnectorsPage() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Google Drive Folder Selection Dialog */}
|
||||||
|
<Dialog open={driveFolderDialogOpen} onOpenChange={setDriveFolderDialogOpen}>
|
||||||
|
<DialogContent className="w-auto max-w-full">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Select Google Drive Folder</DialogTitle>
|
||||||
|
<DialogDescription className="text-sm">
|
||||||
|
Browse and select a folder to index. Click folders to expand and see subfolders.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-4 py-4 overflow-hidden w-full">
|
||||||
|
<div className="space-y-3 w-full overflow-hidden">
|
||||||
|
<Label>Browse Folders</Label>
|
||||||
|
{selectedConnectorForIndexing && (
|
||||||
|
<GoogleDriveFolderTree
|
||||||
|
connectorId={selectedConnectorForIndexing}
|
||||||
|
selectedFolderId={selectedFolderId}
|
||||||
|
onSelectFolder={(folderId, folderName) => {
|
||||||
|
setSelectedFolderId(folderId);
|
||||||
|
setSelectedFolderName(folderName);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Changes to files in this folder will be automatically detected and re-indexed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{selectedFolderId && selectedFolderName && (
|
||||||
|
<div className="p-3 bg-muted rounded-lg text-sm space-y-2">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium mb-1">Selected folder:</p>
|
||||||
|
<p className="text-sm text-muted-foreground truncate" title={selectedFolderName}>
|
||||||
|
{selectedFolderName}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-medium mb-1">What will be indexed:</p>
|
||||||
|
<ul className="list-disc list-inside space-y-1 text-muted-foreground text-xs">
|
||||||
|
<li>Google Docs, Sheets, Slides (as PDFs)</li>
|
||||||
|
<li>PDFs, Word, Excel, PowerPoint files</li>
|
||||||
|
<li>Text files, markdown, code files</li>
|
||||||
|
<li>Images (with OCR if enabled)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setDriveFolderDialogOpen(false);
|
||||||
|
setSelectedConnectorForIndexing(null);
|
||||||
|
setSelectedFolderId("");
|
||||||
|
setSelectedFolderName("");
|
||||||
|
setDriveFolders([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tCommon("cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleIndexDriveFolder} disabled={!selectedFolderId}>
|
||||||
|
{t("start_indexing")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Periodic Indexing Configuration Dialog */}
|
{/* Periodic Indexing Configuration Dialog */}
|
||||||
<Dialog open={periodicDialogOpen} onOpenChange={setPeriodicDialogOpen}>
|
<Dialog open={periodicDialogOpen} onOpenChange={setPeriodicDialogOpen}>
|
||||||
<DialogContent className="sm:max-w-[500px]">
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue