"use client"; import { X } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; import { type FolderSyncProgress, uploadFolderScan } from "@/lib/folder-sync-upload"; import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; export interface SelectedFolder { path: string; name: string; } interface FolderWatchDialogProps { open: boolean; onOpenChange: (open: boolean) => void; searchSpaceId: number; onSuccess?: () => void; initialFolder?: SelectedFolder | null; } export const DEFAULT_EXCLUDE_PATTERNS = [ ".git", "node_modules", "__pycache__", ".DS_Store", ".obsidian", ".trash", ]; export function FolderWatchDialog({ open, onOpenChange, searchSpaceId, onSuccess, initialFolder, }: FolderWatchDialogProps) { const [selectedFolder, setSelectedFolder] = useState(null); const [shouldSummarize, setShouldSummarize] = useState(false); const [submitting, setSubmitting] = useState(false); const [progress, setProgress] = useState(null); const abortRef = useRef(null); useEffect(() => { if (open && initialFolder) { setSelectedFolder(initialFolder); } }, [open, initialFolder]); const supportedExtensions = useMemo(() => Array.from(getSupportedExtensionsSet()), []); const handleSelectFolder = useCallback(async () => { const api = window.electronAPI; if (!api?.selectFolder) return; const folderPath = await api.selectFolder(); if (!folderPath) return; const folderName = folderPath.split(/[/\\]/).pop() || folderPath; setSelectedFolder({ path: folderPath, name: folderName }); }, []); const handleCancel = useCallback(() => { abortRef.current?.abort(); }, []); const handleSubmit = useCallback(async () => { if (!selectedFolder) return; const api = window.electronAPI; if (!api) return; const controller = new AbortController(); abortRef.current = controller; setSubmitting(true); setProgress(null); try { const rootFolderId = await uploadFolderScan({ folderPath: selectedFolder.path, folderName: selectedFolder.name, searchSpaceId, excludePatterns: DEFAULT_EXCLUDE_PATTERNS, fileExtensions: supportedExtensions, enableSummary: shouldSummarize, onProgress: setProgress, signal: controller.signal, }); await api.addWatchedFolder({ path: selectedFolder.path, name: selectedFolder.name, excludePatterns: DEFAULT_EXCLUDE_PATTERNS, fileExtensions: supportedExtensions, rootFolderId: rootFolderId ?? null, searchSpaceId, active: true, }); toast.success(`Watching folder: ${selectedFolder.name}`); setSelectedFolder(null); setShouldSummarize(false); setProgress(null); onOpenChange(false); onSuccess?.(); } catch (err) { if ((err as Error)?.name === "AbortError") { toast.info("Folder sync cancelled. Partial progress was saved."); } else { toast.error((err as Error)?.message || "Failed to watch folder"); } } finally { abortRef.current = null; setSubmitting(false); setProgress(null); } }, [ selectedFolder, searchSpaceId, shouldSummarize, supportedExtensions, onOpenChange, onSuccess, ]); const handleOpenChange = useCallback( (nextOpen: boolean) => { if (!nextOpen && !submitting) { setSelectedFolder(null); setShouldSummarize(false); setProgress(null); } onOpenChange(nextOpen); }, [onOpenChange, submitting] ); const progressLabel = useMemo(() => { if (!progress) return null; switch (progress.phase) { case "listing": return "Scanning folder..."; case "checking": return `Checking ${progress.total} file(s)...`; case "uploading": return `Uploading ${progress.uploaded}/${progress.total} file(s)...`; case "finalizing": return "Finalizing..."; case "done": return "Done!"; default: return null; } }, [progress]); return ( Watch Local Folder Select a folder to sync and watch for changes
{selectedFolder ? (

{selectedFolder.name}

{selectedFolder.path}

) : ( )} {selectedFolder && ( <>

Enable AI Summary

Improves search quality but adds latency

{progressLabel && (

{progressLabel}

{progress && progress.phase === "uploading" && progress.total > 0 && (
)}
)}
{submitting ? ( <> ) : ( )}
)}
); }