diff --git a/surfsense_backend/app/tasks/connector_indexers/local_folder_indexer.py b/surfsense_backend/app/tasks/connector_indexers/local_folder_indexer.py index 041df71fc..7f6a35d7f 100644 --- a/surfsense_backend/app/tasks/connector_indexers/local_folder_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/local_folder_indexer.py @@ -19,7 +19,7 @@ from datetime import UTC, datetime from pathlib import Path from sqlalchemy import select -from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.exc import IntegrityError, SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession from app.config import config @@ -732,7 +732,12 @@ async def index_local_folder( document.folder_id = folder_mapping.get( parent_dir, folder_mapping.get("") ) - await session.commit() + try: + await session.commit() + except IntegrityError: + await session.rollback() + for document in documents: + await session.refresh(document) llm = await get_user_long_context_llm(session, user_id, search_space_id) @@ -905,10 +910,14 @@ async def _index_single_file( # Assign folder_id before indexing so the doc appears in the # correct folder while still pending/processing. if root_folder_id: - db_doc.folder_id = await _resolve_folder_for_file( - session, rel_path, root_folder_id, search_space_id, user_id - ) - await session.commit() + try: + db_doc.folder_id = await _resolve_folder_for_file( + session, rel_path, root_folder_id, search_space_id, user_id + ) + await session.commit() + except IntegrityError: + await session.rollback() + await session.refresh(db_doc) await pipeline.index(db_doc, connector_doc, llm) diff --git a/surfsense_web/components/documents/DocumentNode.tsx b/surfsense_web/components/documents/DocumentNode.tsx index 7a3b3e0ca..dc92109b1 100644 --- a/surfsense_web/components/documents/DocumentNode.tsx +++ b/surfsense_web/components/documents/DocumentNode.tsx @@ -237,7 +237,7 @@ export const DocumentNode = React.memo(function DocumentNode({ {onExport && ( - + Export @@ -277,7 +277,7 @@ export const DocumentNode = React.memo(function DocumentNode({ {onExport && ( - + Export diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 8dce68eeb..7aa518361 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -358,6 +358,14 @@ export function DocumentsSidebar({ const handleDeleteFolder = useCallback(async (folder: FolderDisplay) => { if (!confirm(`Delete folder "${folder.name}" and all its contents?`)) return; try { + const api = window.electronAPI; + if (api) { + const watchedFolders = await api.getWatchedFolders(); + const matched = watchedFolders.find((wf) => wf.rootFolderId === folder.id); + if (matched) { + await api.removeWatchedFolder(matched.path); + } + } await foldersApiService.deleteFolder(folder.id); toast.success("Folder deleted"); } catch (e: unknown) { diff --git a/surfsense_web/components/ui/context-menu.tsx b/surfsense_web/components/ui/context-menu.tsx index 0d3c27a6b..f3d4063e2 100644 --- a/surfsense_web/components/ui/context-menu.tsx +++ b/surfsense_web/components/ui/context-menu.tsx @@ -47,7 +47,7 @@ function ContextMenuSubTrigger({ data-slot="context-menu-sub-trigger" data-inset={inset} className={cn( - "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8", + "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8", className )} {...props} diff --git a/surfsense_web/components/ui/dropdown-menu.tsx b/surfsense_web/components/ui/dropdown-menu.tsx index 24b99467e..1584e0ea4 100644 --- a/surfsense_web/components/ui/dropdown-menu.tsx +++ b/surfsense_web/components/ui/dropdown-menu.tsx @@ -182,7 +182,7 @@ function DropdownMenuSubTrigger({ data-slot="dropdown-menu-sub-trigger" data-inset={inset} className={cn( - "focus:bg-neutral-200 focus:text-accent-foreground dark:focus:bg-neutral-700 data-[state=open]:bg-neutral-200 data-[state=open]:text-accent-foreground dark:data-[state=open]:bg-neutral-700 [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + "focus:bg-neutral-200 focus:text-accent-foreground dark:focus:bg-neutral-700 data-[state=open]:bg-neutral-200 data-[state=open]:text-accent-foreground dark:data-[state=open]:bg-neutral-700 [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className )} {...props}