diff --git a/surfsense_backend/app/routes/documents_routes.py b/surfsense_backend/app/routes/documents_routes.py index e71cef7e4..53312c647 100644 --- a/surfsense_backend/app/routes/documents_routes.py +++ b/surfsense_backend/app/routes/documents_routes.py @@ -1459,7 +1459,7 @@ async def folder_mtime_check( existing_by_hash = {doc.unique_identifier_hash: doc for doc in existing_docs} - MTIME_TOLERANCE = 1.0 + mtime_tolerance = 1.0 files_to_upload: list[str] = [] for uid_hash, file_info in uid_hashes.items(): @@ -1473,7 +1473,7 @@ async def folder_mtime_check( files_to_upload.append(file_info.relative_path) continue - if abs(file_info.mtime - stored_mtime) >= MTIME_TOLERANCE: + if abs(file_info.mtime - stored_mtime) >= mtime_tolerance: files_to_upload.append(file_info.relative_path) return {"files_to_upload": files_to_upload} @@ -1641,9 +1641,7 @@ async def folder_unlink( existing = ( await session.execute( - select(Document).where( - Document.unique_identifier_hash == uid_hash - ) + select(Document).where(Document.unique_identifier_hash == uid_hash) ) ).scalar_one_or_none() 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 8805558bd..f503ff864 100644 --- a/surfsense_backend/app/tasks/connector_indexers/local_folder_indexer.py +++ b/surfsense_backend/app/tasks/connector_indexers/local_folder_indexer.py @@ -14,13 +14,14 @@ no connector row is read. """ import asyncio +import contextlib import os from collections.abc import Awaitable, Callable from datetime import UTC, datetime from pathlib import Path from sqlalchemy import select -from sqlalchemy.exc import IntegrityError, SQLAlchemyError +from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.ext.asyncio import AsyncSession from app.db import ( @@ -676,9 +677,9 @@ async def index_local_folder( _compute_raw_file_hash, file_path_abs ) - stored_raw_hash = ( - existing_document.document_metadata or {} - ).get("raw_file_hash") + stored_raw_hash = (existing_document.document_metadata or {}).get( + "raw_file_hash" + ) if stored_raw_hash and stored_raw_hash == raw_hash: meta = dict(existing_document.document_metadata or {}) meta["mtime"] = current_mtime @@ -824,9 +825,7 @@ async def index_local_folder( parent_dir = str(Path(rel_path).parent) if rel_path else "" if parent_dir == ".": parent_dir = "" - cd.folder_id = folder_mapping.get( - parent_dir, folder_mapping.get("") - ) + cd.folder_id = folder_mapping.get(parent_dir, folder_mapping.get("")) pipeline = IndexingPipelineService(session) doc_map = {compute_unique_identifier_hash(cd): cd for cd in connector_docs} @@ -883,7 +882,11 @@ async def index_local_folder( subtree_ids = await get_folder_subtree_ids(session, root_fid) await _cleanup_empty_folders( - session, root_fid, search_space_id, existing_dirs, folder_mapping, + session, + root_fid, + search_space_id, + existing_dirs, + folder_mapping, subtree_ids=subtree_ids, ) @@ -1058,17 +1061,13 @@ async def _index_single_file( existing = await check_document_by_unique_identifier(session, uid_hash) if existing: - stored_raw_hash = (existing.document_metadata or {}).get( - "raw_file_hash" - ) + stored_raw_hash = (existing.document_metadata or {}).get("raw_file_hash") if stored_raw_hash and stored_raw_hash == raw_hash: mtime = full_path.stat().st_mtime meta = dict(existing.document_metadata or {}) meta["mtime"] = mtime existing.document_metadata = meta - if not DocumentStatus.is_state( - existing.status, DocumentStatus.READY - ): + if not DocumentStatus.is_state(existing.status, DocumentStatus.READY): existing.status = DocumentStatus.ready() await session.commit() return 0, 0, None @@ -1285,7 +1284,7 @@ async def index_uploaded_files( try: all_relative_paths = [m["relative_path"] for m in file_mappings] - folder_mapping, root_folder_id = await _mirror_folder_structure_from_paths( + _folder_mapping, root_folder_id = await _mirror_folder_structure_from_paths( session=session, relative_paths=all_relative_paths, folder_name=folder_name, @@ -1318,13 +1317,9 @@ async def index_uploaded_files( search_space_id, ) - raw_hash = await asyncio.to_thread( - _compute_raw_file_hash, temp_path - ) + raw_hash = await asyncio.to_thread(_compute_raw_file_hash, temp_path) - existing = await check_document_by_unique_identifier( - session, uid_hash - ) + existing = await check_document_by_unique_identifier(session, uid_hash) if existing: stored_raw_hash = (existing.document_metadata or {}).get( @@ -1428,23 +1423,17 @@ async def index_uploaded_files( await on_heartbeat_callback(i + 1) except Exception as e: - logger.exception( - f"Error indexing uploaded file {relative_path}: {e}" - ) + logger.exception(f"Error indexing uploaded file {relative_path}: {e}") await session.rollback() failed_count += 1 errors.append(f"{filename}: {e}") finally: - try: + with contextlib.suppress(OSError): os.unlink(temp_path) - except OSError: - pass error_summary = None if errors: - error_summary = ( - f"{failed_count} file(s) failed: " + "; ".join(errors[:5]) - ) + error_summary = f"{failed_count} file(s) failed: " + "; ".join(errors[:5]) if len(errors) > 5: error_summary += f" ... and {len(errors) - 5} more" diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py b/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py index 1508fb26f..4dc5742f7 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py @@ -1265,7 +1265,9 @@ class TestIndexingProgressFlag: (tmp_path / "note.md").write_text("# Check flag\n\nDuring indexing.") - from app.indexing_pipeline.indexing_pipeline_service import IndexingPipelineService + from app.indexing_pipeline.indexing_pipeline_service import ( + IndexingPipelineService, + ) original_index = IndexingPipelineService.index flag_observed = [] diff --git a/surfsense_web/app/(home)/login/page.tsx b/surfsense_web/app/(home)/login/page.tsx index 161951d97..3dbbf21a9 100644 --- a/surfsense_web/app/(home)/login/page.tsx +++ b/surfsense_web/app/(home)/login/page.tsx @@ -1,8 +1,7 @@ "use client"; import { AnimatePresence, motion } from "motion/react"; -import { useRouter } from "next/navigation"; -import { useSearchParams } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { Suspense, useEffect, useState } from "react"; import { toast } from "sonner"; diff --git a/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx index 443c964cf..c5be2b590 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/logs/(manage)/page.tsx @@ -46,7 +46,6 @@ import { useParams } from "next/navigation"; import { useTranslations } from "next-intl"; import React, { useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; -import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { createLogMutationAtom, deleteLogMutationAtom, @@ -96,6 +95,7 @@ import { TableRow, } from "@/components/ui/table"; import type { CreateLogRequest, Log, UpdateLogRequest } from "@/contracts/types/log.types"; +import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { type LogLevel, type LogStatus, useLogs, useLogsSummary } from "@/hooks/use-logs"; import { cn } from "@/lib/utils"; @@ -728,10 +728,7 @@ function LogsFilters({ setFilterInput(e.target.value)} placeholder={t("filter_by_message")} diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 080a36167..b545a6be2 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -43,15 +43,24 @@ import { useMessagesSync } from "@/hooks/use-messages-sync"; import Loading from "../loading"; const MobileEditorPanel = dynamic( - () => import("@/components/editor-panel/editor-panel").then((m) => ({ default: m.MobileEditorPanel })), + () => + import("@/components/editor-panel/editor-panel").then((m) => ({ + default: m.MobileEditorPanel, + })), { ssr: false } ); const MobileHitlEditPanel = dynamic( - () => import("@/components/hitl-edit-panel/hitl-edit-panel").then((m) => ({ default: m.MobileHitlEditPanel })), + () => + import("@/components/hitl-edit-panel/hitl-edit-panel").then((m) => ({ + default: m.MobileHitlEditPanel, + })), { ssr: false } ); const MobileReportPanel = dynamic( - () => import("@/components/report-panel/report-panel").then((m) => ({ default: m.MobileReportPanel })), + () => + import("@/components/report-panel/report-panel").then((m) => ({ + default: m.MobileReportPanel, + })), { ssr: false } ); diff --git a/surfsense_web/components/assistant-ui/assistant-message.tsx b/surfsense_web/components/assistant-ui/assistant-message.tsx index 626e6237f..097197107 100644 --- a/surfsense_web/components/assistant-ui/assistant-message.tsx +++ b/surfsense_web/components/assistant-ui/assistant-message.tsx @@ -51,131 +51,172 @@ const IS_QUICK_ASSIST_WINDOW = // Dynamically import tool UI components to avoid loading them in main bundle const GenerateReportToolUI = dynamic( - () => import("@/components/tool-ui/generate-report").then(m => ({ default: m.GenerateReportToolUI })), + () => + import("@/components/tool-ui/generate-report").then((m) => ({ + default: m.GenerateReportToolUI, + })), { ssr: false } ); const GeneratePodcastToolUI = dynamic( - () => import("@/components/tool-ui/generate-podcast").then(m => ({ default: m.GeneratePodcastToolUI })), + () => + import("@/components/tool-ui/generate-podcast").then((m) => ({ + default: m.GeneratePodcastToolUI, + })), { ssr: false } ); const GenerateVideoPresentationToolUI = dynamic( - () => import("@/components/tool-ui/video-presentation").then(m => ({ default: m.GenerateVideoPresentationToolUI })), + () => + import("@/components/tool-ui/video-presentation").then((m) => ({ + default: m.GenerateVideoPresentationToolUI, + })), { ssr: false } ); const GenerateImageToolUI = dynamic( - () => import("@/components/tool-ui/generate-image").then(m => ({ default: m.GenerateImageToolUI })), + () => + import("@/components/tool-ui/generate-image").then((m) => ({ default: m.GenerateImageToolUI })), { ssr: false } ); const SaveMemoryToolUI = dynamic( - () => import("@/components/tool-ui/user-memory").then(m => ({ default: m.SaveMemoryToolUI })), + () => import("@/components/tool-ui/user-memory").then((m) => ({ default: m.SaveMemoryToolUI })), { ssr: false } ); const RecallMemoryToolUI = dynamic( - () => import("@/components/tool-ui/user-memory").then(m => ({ default: m.RecallMemoryToolUI })), + () => import("@/components/tool-ui/user-memory").then((m) => ({ default: m.RecallMemoryToolUI })), { ssr: false } ); const SandboxExecuteToolUI = dynamic( - () => import("@/components/tool-ui/sandbox-execute").then(m => ({ default: m.SandboxExecuteToolUI })), + () => + import("@/components/tool-ui/sandbox-execute").then((m) => ({ + default: m.SandboxExecuteToolUI, + })), { ssr: false } ); const CreateNotionPageToolUI = dynamic( - () => import("@/components/tool-ui/notion").then(m => ({ default: m.CreateNotionPageToolUI })), + () => import("@/components/tool-ui/notion").then((m) => ({ default: m.CreateNotionPageToolUI })), { ssr: false } ); const UpdateNotionPageToolUI = dynamic( - () => import("@/components/tool-ui/notion").then(m => ({ default: m.UpdateNotionPageToolUI })), + () => import("@/components/tool-ui/notion").then((m) => ({ default: m.UpdateNotionPageToolUI })), { ssr: false } ); const DeleteNotionPageToolUI = dynamic( - () => import("@/components/tool-ui/notion").then(m => ({ default: m.DeleteNotionPageToolUI })), + () => import("@/components/tool-ui/notion").then((m) => ({ default: m.DeleteNotionPageToolUI })), { ssr: false } ); const CreateLinearIssueToolUI = dynamic( - () => import("@/components/tool-ui/linear").then(m => ({ default: m.CreateLinearIssueToolUI })), + () => import("@/components/tool-ui/linear").then((m) => ({ default: m.CreateLinearIssueToolUI })), { ssr: false } ); const UpdateLinearIssueToolUI = dynamic( - () => import("@/components/tool-ui/linear").then(m => ({ default: m.UpdateLinearIssueToolUI })), + () => import("@/components/tool-ui/linear").then((m) => ({ default: m.UpdateLinearIssueToolUI })), { ssr: false } ); const DeleteLinearIssueToolUI = dynamic( - () => import("@/components/tool-ui/linear").then(m => ({ default: m.DeleteLinearIssueToolUI })), + () => import("@/components/tool-ui/linear").then((m) => ({ default: m.DeleteLinearIssueToolUI })), { ssr: false } ); const CreateGoogleDriveFileToolUI = dynamic( - () => import("@/components/tool-ui/google-drive").then(m => ({ default: m.CreateGoogleDriveFileToolUI })), + () => + import("@/components/tool-ui/google-drive").then((m) => ({ + default: m.CreateGoogleDriveFileToolUI, + })), { ssr: false } ); const DeleteGoogleDriveFileToolUI = dynamic( - () => import("@/components/tool-ui/google-drive").then(m => ({ default: m.DeleteGoogleDriveFileToolUI })), + () => + import("@/components/tool-ui/google-drive").then((m) => ({ + default: m.DeleteGoogleDriveFileToolUI, + })), { ssr: false } ); const CreateOneDriveFileToolUI = dynamic( - () => import("@/components/tool-ui/onedrive").then(m => ({ default: m.CreateOneDriveFileToolUI })), + () => + import("@/components/tool-ui/onedrive").then((m) => ({ default: m.CreateOneDriveFileToolUI })), { ssr: false } ); const DeleteOneDriveFileToolUI = dynamic( - () => import("@/components/tool-ui/onedrive").then(m => ({ default: m.DeleteOneDriveFileToolUI })), + () => + import("@/components/tool-ui/onedrive").then((m) => ({ default: m.DeleteOneDriveFileToolUI })), { ssr: false } ); const CreateDropboxFileToolUI = dynamic( - () => import("@/components/tool-ui/dropbox").then(m => ({ default: m.CreateDropboxFileToolUI })), + () => + import("@/components/tool-ui/dropbox").then((m) => ({ default: m.CreateDropboxFileToolUI })), { ssr: false } ); const DeleteDropboxFileToolUI = dynamic( - () => import("@/components/tool-ui/dropbox").then(m => ({ default: m.DeleteDropboxFileToolUI })), + () => + import("@/components/tool-ui/dropbox").then((m) => ({ default: m.DeleteDropboxFileToolUI })), { ssr: false } ); const CreateCalendarEventToolUI = dynamic( - () => import("@/components/tool-ui/google-calendar").then(m => ({ default: m.CreateCalendarEventToolUI })), + () => + import("@/components/tool-ui/google-calendar").then((m) => ({ + default: m.CreateCalendarEventToolUI, + })), { ssr: false } ); const UpdateCalendarEventToolUI = dynamic( - () => import("@/components/tool-ui/google-calendar").then(m => ({ default: m.UpdateCalendarEventToolUI })), + () => + import("@/components/tool-ui/google-calendar").then((m) => ({ + default: m.UpdateCalendarEventToolUI, + })), { ssr: false } ); const DeleteCalendarEventToolUI = dynamic( - () => import("@/components/tool-ui/google-calendar").then(m => ({ default: m.DeleteCalendarEventToolUI })), + () => + import("@/components/tool-ui/google-calendar").then((m) => ({ + default: m.DeleteCalendarEventToolUI, + })), { ssr: false } ); const CreateGmailDraftToolUI = dynamic( - () => import("@/components/tool-ui/gmail").then(m => ({ default: m.CreateGmailDraftToolUI })), + () => import("@/components/tool-ui/gmail").then((m) => ({ default: m.CreateGmailDraftToolUI })), { ssr: false } ); const UpdateGmailDraftToolUI = dynamic( - () => import("@/components/tool-ui/gmail").then(m => ({ default: m.UpdateGmailDraftToolUI })), + () => import("@/components/tool-ui/gmail").then((m) => ({ default: m.UpdateGmailDraftToolUI })), { ssr: false } ); const SendGmailEmailToolUI = dynamic( - () => import("@/components/tool-ui/gmail").then(m => ({ default: m.SendGmailEmailToolUI })), + () => import("@/components/tool-ui/gmail").then((m) => ({ default: m.SendGmailEmailToolUI })), { ssr: false } ); const TrashGmailEmailToolUI = dynamic( - () => import("@/components/tool-ui/gmail").then(m => ({ default: m.TrashGmailEmailToolUI })), + () => import("@/components/tool-ui/gmail").then((m) => ({ default: m.TrashGmailEmailToolUI })), { ssr: false } ); const CreateJiraIssueToolUI = dynamic( - () => import("@/components/tool-ui/jira").then(m => ({ default: m.CreateJiraIssueToolUI })), + () => import("@/components/tool-ui/jira").then((m) => ({ default: m.CreateJiraIssueToolUI })), { ssr: false } ); const UpdateJiraIssueToolUI = dynamic( - () => import("@/components/tool-ui/jira").then(m => ({ default: m.UpdateJiraIssueToolUI })), + () => import("@/components/tool-ui/jira").then((m) => ({ default: m.UpdateJiraIssueToolUI })), { ssr: false } ); const DeleteJiraIssueToolUI = dynamic( - () => import("@/components/tool-ui/jira").then(m => ({ default: m.DeleteJiraIssueToolUI })), + () => import("@/components/tool-ui/jira").then((m) => ({ default: m.DeleteJiraIssueToolUI })), { ssr: false } ); const CreateConfluencePageToolUI = dynamic( - () => import("@/components/tool-ui/confluence").then(m => ({ default: m.CreateConfluencePageToolUI })), + () => + import("@/components/tool-ui/confluence").then((m) => ({ + default: m.CreateConfluencePageToolUI, + })), { ssr: false } ); const UpdateConfluencePageToolUI = dynamic( - () => import("@/components/tool-ui/confluence").then(m => ({ default: m.UpdateConfluencePageToolUI })), + () => + import("@/components/tool-ui/confluence").then((m) => ({ + default: m.UpdateConfluencePageToolUI, + })), { ssr: false } ); const DeleteConfluencePageToolUI = dynamic( - () => import("@/components/tool-ui/confluence").then(m => ({ default: m.DeleteConfluencePageToolUI })), + () => + import("@/components/tool-ui/confluence").then((m) => ({ + default: m.DeleteConfluencePageToolUI, + })), { ssr: false } ); diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx index 20762c1b3..24ead1552 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx @@ -25,16 +25,38 @@ export interface ConnectFormProps { export type ConnectFormComponent = FC; const formMap: Record Promise<{ default: FC }>> = { - TAVILY_API: () => import("./components/tavily-api-connect-form").then(m => ({ default: m.TavilyApiConnectForm })), - LINKUP_API: () => import("./components/linkup-api-connect-form").then(m => ({ default: m.LinkupApiConnectForm })), - BAIDU_SEARCH_API: () => import("./components/baidu-search-api-connect-form").then(m => ({ default: m.BaiduSearchApiConnectForm })), - ELASTICSEARCH_CONNECTOR: () => import("./components/elasticsearch-connect-form").then(m => ({ default: m.ElasticsearchConnectForm })), - BOOKSTACK_CONNECTOR: () => import("./components/bookstack-connect-form").then(m => ({ default: m.BookStackConnectForm })), - GITHUB_CONNECTOR: () => import("./components/github-connect-form").then(m => ({ default: m.GithubConnectForm })), - LUMA_CONNECTOR: () => import("./components/luma-connect-form").then(m => ({ default: m.LumaConnectForm })), - CIRCLEBACK_CONNECTOR: () => import("./components/circleback-connect-form").then(m => ({ default: m.CirclebackConnectForm })), - MCP_CONNECTOR: () => import("./components/mcp-connect-form").then(m => ({ default: m.MCPConnectForm })), - OBSIDIAN_CONNECTOR: () => import("./components/obsidian-connect-form").then(m => ({ default: m.ObsidianConnectForm })), + TAVILY_API: () => + import("./components/tavily-api-connect-form").then((m) => ({ + default: m.TavilyApiConnectForm, + })), + LINKUP_API: () => + import("./components/linkup-api-connect-form").then((m) => ({ + default: m.LinkupApiConnectForm, + })), + BAIDU_SEARCH_API: () => + import("./components/baidu-search-api-connect-form").then((m) => ({ + default: m.BaiduSearchApiConnectForm, + })), + ELASTICSEARCH_CONNECTOR: () => + import("./components/elasticsearch-connect-form").then((m) => ({ + default: m.ElasticsearchConnectForm, + })), + BOOKSTACK_CONNECTOR: () => + import("./components/bookstack-connect-form").then((m) => ({ + default: m.BookStackConnectForm, + })), + GITHUB_CONNECTOR: () => + import("./components/github-connect-form").then((m) => ({ default: m.GithubConnectForm })), + LUMA_CONNECTOR: () => + import("./components/luma-connect-form").then((m) => ({ default: m.LumaConnectForm })), + CIRCLEBACK_CONNECTOR: () => + import("./components/circleback-connect-form").then((m) => ({ + default: m.CirclebackConnectForm, + })), + MCP_CONNECTOR: () => + import("./components/mcp-connect-form").then((m) => ({ default: m.MCPConnectForm })), + OBSIDIAN_CONNECTOR: () => + import("./components/obsidian-connect-form").then((m) => ({ default: m.ObsidianConnectForm })), }; const componentCache = new Map(); diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx index fc378612f..e3d6641fc 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx @@ -14,29 +14,53 @@ export interface ConnectorConfigProps { export type ConnectorConfigComponent = FC; const configMap: Record Promise<{ default: FC }>> = { - GOOGLE_DRIVE_CONNECTOR: () => import("./components/google-drive-config").then(m => ({ default: m.GoogleDriveConfig })), - TAVILY_API: () => import("./components/tavily-api-config").then(m => ({ default: m.TavilyApiConfig })), - LINKUP_API: () => import("./components/linkup-api-config").then(m => ({ default: m.LinkupApiConfig })), - BAIDU_SEARCH_API: () => import("./components/baidu-search-api-config").then(m => ({ default: m.BaiduSearchApiConfig })), - WEBCRAWLER_CONNECTOR: () => import("./components/webcrawler-config").then(m => ({ default: m.WebcrawlerConfig })), - ELASTICSEARCH_CONNECTOR: () => import("./components/elasticsearch-config").then(m => ({ default: m.ElasticsearchConfig })), - SLACK_CONNECTOR: () => import("./components/slack-config").then(m => ({ default: m.SlackConfig })), - DISCORD_CONNECTOR: () => import("./components/discord-config").then(m => ({ default: m.DiscordConfig })), - TEAMS_CONNECTOR: () => import("./components/teams-config").then(m => ({ default: m.TeamsConfig })), - DROPBOX_CONNECTOR: () => import("./components/dropbox-config").then(m => ({ default: m.DropboxConfig })), - ONEDRIVE_CONNECTOR: () => import("./components/onedrive-config").then(m => ({ default: m.OneDriveConfig })), - CONFLUENCE_CONNECTOR: () => import("./components/confluence-config").then(m => ({ default: m.ConfluenceConfig })), - BOOKSTACK_CONNECTOR: () => import("./components/bookstack-config").then(m => ({ default: m.BookStackConfig })), - GITHUB_CONNECTOR: () => import("./components/github-config").then(m => ({ default: m.GithubConfig })), - JIRA_CONNECTOR: () => import("./components/jira-config").then(m => ({ default: m.JiraConfig })), - CLICKUP_CONNECTOR: () => import("./components/clickup-config").then(m => ({ default: m.ClickUpConfig })), - LUMA_CONNECTOR: () => import("./components/luma-config").then(m => ({ default: m.LumaConfig })), - CIRCLEBACK_CONNECTOR: () => import("./components/circleback-config").then(m => ({ default: m.CirclebackConfig })), - MCP_CONNECTOR: () => import("./components/mcp-config").then(m => ({ default: m.MCPConfig })), - OBSIDIAN_CONNECTOR: () => import("./components/obsidian-config").then(m => ({ default: m.ObsidianConfig })), - COMPOSIO_GOOGLE_DRIVE_CONNECTOR: () => import("./components/composio-drive-config").then(m => ({ default: m.ComposioDriveConfig })), - COMPOSIO_GMAIL_CONNECTOR: () => import("./components/composio-gmail-config").then(m => ({ default: m.ComposioGmailConfig })), - COMPOSIO_GOOGLE_CALENDAR_CONNECTOR: () => import("./components/composio-calendar-config").then(m => ({ default: m.ComposioCalendarConfig })), + GOOGLE_DRIVE_CONNECTOR: () => + import("./components/google-drive-config").then((m) => ({ default: m.GoogleDriveConfig })), + TAVILY_API: () => + import("./components/tavily-api-config").then((m) => ({ default: m.TavilyApiConfig })), + LINKUP_API: () => + import("./components/linkup-api-config").then((m) => ({ default: m.LinkupApiConfig })), + BAIDU_SEARCH_API: () => + import("./components/baidu-search-api-config").then((m) => ({ + default: m.BaiduSearchApiConfig, + })), + WEBCRAWLER_CONNECTOR: () => + import("./components/webcrawler-config").then((m) => ({ default: m.WebcrawlerConfig })), + ELASTICSEARCH_CONNECTOR: () => + import("./components/elasticsearch-config").then((m) => ({ default: m.ElasticsearchConfig })), + SLACK_CONNECTOR: () => + import("./components/slack-config").then((m) => ({ default: m.SlackConfig })), + DISCORD_CONNECTOR: () => + import("./components/discord-config").then((m) => ({ default: m.DiscordConfig })), + TEAMS_CONNECTOR: () => + import("./components/teams-config").then((m) => ({ default: m.TeamsConfig })), + DROPBOX_CONNECTOR: () => + import("./components/dropbox-config").then((m) => ({ default: m.DropboxConfig })), + ONEDRIVE_CONNECTOR: () => + import("./components/onedrive-config").then((m) => ({ default: m.OneDriveConfig })), + CONFLUENCE_CONNECTOR: () => + import("./components/confluence-config").then((m) => ({ default: m.ConfluenceConfig })), + BOOKSTACK_CONNECTOR: () => + import("./components/bookstack-config").then((m) => ({ default: m.BookStackConfig })), + GITHUB_CONNECTOR: () => + import("./components/github-config").then((m) => ({ default: m.GithubConfig })), + JIRA_CONNECTOR: () => import("./components/jira-config").then((m) => ({ default: m.JiraConfig })), + CLICKUP_CONNECTOR: () => + import("./components/clickup-config").then((m) => ({ default: m.ClickUpConfig })), + LUMA_CONNECTOR: () => import("./components/luma-config").then((m) => ({ default: m.LumaConfig })), + CIRCLEBACK_CONNECTOR: () => + import("./components/circleback-config").then((m) => ({ default: m.CirclebackConfig })), + MCP_CONNECTOR: () => import("./components/mcp-config").then((m) => ({ default: m.MCPConfig })), + OBSIDIAN_CONNECTOR: () => + import("./components/obsidian-config").then((m) => ({ default: m.ObsidianConfig })), + COMPOSIO_GOOGLE_DRIVE_CONNECTOR: () => + import("./components/composio-drive-config").then((m) => ({ default: m.ComposioDriveConfig })), + COMPOSIO_GMAIL_CONNECTOR: () => + import("./components/composio-gmail-config").then((m) => ({ default: m.ComposioGmailConfig })), + COMPOSIO_GOOGLE_CALENDAR_CONNECTOR: () => + import("./components/composio-calendar-config").then((m) => ({ + default: m.ComposioCalendarConfig, + })), }; const componentCache = new Map(); diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx index 0e01f3006..814959ec4 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx @@ -307,7 +307,7 @@ export const AllConnectorsTab: FC = ({

- File Storage Integrations + File Storage Integrations

diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx index 46b9f3af9..3c1d64a6a 100644 --- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx +++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx @@ -20,7 +20,13 @@ import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog. import { DocumentUploadTab } from "@/components/sources/DocumentUploadTab"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; // Context for opening the dialog from anywhere interface DocumentUploadDialogContextType { @@ -129,7 +135,9 @@ const DocumentUploadPopupContent: FC<{ >
- Upload Documents + + Upload Documents + Upload and sync your documents to your search space diff --git a/surfsense_web/components/assistant-ui/tooltip-icon-button.tsx b/surfsense_web/components/assistant-ui/tooltip-icon-button.tsx index f003c55c0..3dfeb9196 100644 --- a/surfsense_web/components/assistant-ui/tooltip-icon-button.tsx +++ b/surfsense_web/components/assistant-ui/tooltip-icon-button.tsx @@ -20,7 +20,10 @@ export const TooltipIconButton = forwardRef +
@@ -68,10 +68,7 @@ function UnlimitedSkeleton({ className }: { className?: string }) { ]; return ( -
+
{items.map((item, index) => ( {item.icon} - - {item.label} - + {item.label}
{item.notebookLm} @@ -125,10 +120,7 @@ function LLMFlexibilitySkeleton({ className }: { className?: string }) { return (
-

- {model.name} -

-

- {model.provider} -

+

{model.name}

+

{model.provider}

{selected === index && ( -
+
))} @@ -295,9 +275,7 @@ function MultiplayerSkeleton({ className }: { className?: string }) {
{collaborator.name[0]}
- - {collaborator.name} - + {collaborator.name} {collaborator.role} @@ -321,9 +299,7 @@ function FeatureCard({
{skeleton}
-

- {title} -

+

{title}

{description}

@@ -408,9 +384,7 @@ function ComparisonStrip() { transition={{ duration: 0.3, delay: 0.15 + index * 0.06 }} >
- - {row.feature} - + {row.feature} {typeof row.notebookLm === "boolean" ? ( row.notebookLm ? ( @@ -419,9 +393,7 @@ function ComparisonStrip() { ) ) : ( - - {row.notebookLm} - + {row.notebookLm} )} @@ -436,9 +408,7 @@ function ComparisonStrip() { )}
- {index !== comparisonRows.length - 1 && ( - - )} + {index !== comparisonRows.length - 1 && } ))} diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index 2fca68c1e..346e3fa9e 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -410,7 +410,7 @@ export function LayoutShell({ pageUsage={pageUsage} theme={theme} setTheme={setTheme} - className={cn( + className={cn( "flex shrink-0 transition-[border-radius] duration-200", anySlideOutOpen ? "rounded-l-xl delay-0" : "rounded-xl delay-150" )} diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 7679faae5..8b3a119ae 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -23,9 +23,11 @@ import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog"; import { FolderTreeView } from "@/components/documents/FolderTreeView"; import { VersionHistoryDialog } from "@/components/documents/version-history"; import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems"; -import { DEFAULT_EXCLUDE_PATTERNS, FolderWatchDialog, type SelectedFolder } from "@/components/sources/FolderWatchDialog"; -import { uploadFolderScan } from "@/lib/folder-sync-upload"; -import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; +import { + DEFAULT_EXCLUDE_PATTERNS, + FolderWatchDialog, + type SelectedFolder, +} from "@/components/sources/FolderWatchDialog"; import { AlertDialog, AlertDialogAction, @@ -48,6 +50,8 @@ import { useElectronAPI } from "@/hooks/use-platform"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { foldersApiService } from "@/lib/apis/folders-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { uploadFolderScan } from "@/lib/folder-sync-upload"; +import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; import { queries } from "@/zero/queries/index"; import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel"; diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx index 79d27a1ac..5195082cd 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx @@ -90,14 +90,14 @@ export function SidebarSlideOutPanel({ /> {/* Panel extending from sidebar's right edge, flush with the wrapper border */} - +
import("@/components/tool-ui/video-presentation").then((m) => ({ default: m.GenerateVideoPresentationToolUI })), + () => + import("@/components/tool-ui/video-presentation").then((m) => ({ + default: m.GenerateVideoPresentationToolUI, + })), { ssr: false } ); diff --git a/surfsense_web/components/settings/search-space-settings-dialog.tsx b/surfsense_web/components/settings/search-space-settings-dialog.tsx index dc0627305..34d28eb2a 100644 --- a/surfsense_web/components/settings/search-space-settings-dialog.tsx +++ b/surfsense_web/components/settings/search-space-settings-dialog.tsx @@ -1,43 +1,62 @@ "use client"; -import dynamic from "next/dynamic"; import { useAtom } from "jotai"; import { Bot, Brain, Eye, FileText, Globe, ImageIcon, MessageSquare, Shield } from "lucide-react"; +import dynamic from "next/dynamic"; import { useTranslations } from "next-intl"; import type React from "react"; import { searchSpaceSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; import { SettingsDialog } from "@/components/settings/settings-dialog"; const GeneralSettingsManager = dynamic( - () => import("@/components/settings/general-settings-manager").then(m => ({ default: m.GeneralSettingsManager })), + () => + import("@/components/settings/general-settings-manager").then((m) => ({ + default: m.GeneralSettingsManager, + })), { ssr: false } ); const ModelConfigManager = dynamic( - () => import("@/components/settings/model-config-manager").then(m => ({ default: m.ModelConfigManager })), + () => + import("@/components/settings/model-config-manager").then((m) => ({ + default: m.ModelConfigManager, + })), { ssr: false } ); const LLMRoleManager = dynamic( - () => import("@/components/settings/llm-role-manager").then(m => ({ default: m.LLMRoleManager })), + () => + import("@/components/settings/llm-role-manager").then((m) => ({ default: m.LLMRoleManager })), { ssr: false } ); const ImageModelManager = dynamic( - () => import("@/components/settings/image-model-manager").then(m => ({ default: m.ImageModelManager })), + () => + import("@/components/settings/image-model-manager").then((m) => ({ + default: m.ImageModelManager, + })), { ssr: false } ); const VisionModelManager = dynamic( - () => import("@/components/settings/vision-model-manager").then(m => ({ default: m.VisionModelManager })), + () => + import("@/components/settings/vision-model-manager").then((m) => ({ + default: m.VisionModelManager, + })), { ssr: false } ); const RolesManager = dynamic( - () => import("@/components/settings/roles-manager").then(m => ({ default: m.RolesManager })), + () => import("@/components/settings/roles-manager").then((m) => ({ default: m.RolesManager })), { ssr: false } ); const PromptConfigManager = dynamic( - () => import("@/components/settings/prompt-config-manager").then(m => ({ default: m.PromptConfigManager })), + () => + import("@/components/settings/prompt-config-manager").then((m) => ({ + default: m.PromptConfigManager, + })), { ssr: false } ); const PublicChatSnapshotsManager = dynamic( - () => import("@/components/public-chat-snapshots/public-chat-snapshots-manager").then(m => ({ default: m.PublicChatSnapshotsManager })), + () => + import("@/components/public-chat-snapshots/public-chat-snapshots-manager").then((m) => ({ + default: m.PublicChatSnapshotsManager, + })), { ssr: false } ); diff --git a/surfsense_web/components/settings/user-settings-dialog.tsx b/surfsense_web/components/settings/user-settings-dialog.tsx index ee0f7e62d..e755da197 100644 --- a/surfsense_web/components/settings/user-settings-dialog.tsx +++ b/surfsense_web/components/settings/user-settings-dialog.tsx @@ -1,8 +1,8 @@ "use client"; -import dynamic from "next/dynamic"; import { useAtom } from "jotai"; import { Globe, KeyRound, Monitor, Receipt, Sparkles, User } from "lucide-react"; +import dynamic from "next/dynamic"; import { useTranslations } from "next-intl"; import { useMemo } from "react"; import { userSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; @@ -10,27 +10,45 @@ import { SettingsDialog } from "@/components/settings/settings-dialog"; import { usePlatform } from "@/hooks/use-platform"; const ProfileContent = dynamic( - () => import("@/app/dashboard/[search_space_id]/user-settings/components/ProfileContent").then(m => ({ default: m.ProfileContent })), + () => + import("@/app/dashboard/[search_space_id]/user-settings/components/ProfileContent").then( + (m) => ({ default: m.ProfileContent }) + ), { ssr: false } ); const ApiKeyContent = dynamic( - () => import("@/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent").then(m => ({ default: m.ApiKeyContent })), + () => + import("@/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent").then( + (m) => ({ default: m.ApiKeyContent }) + ), { ssr: false } ); const PromptsContent = dynamic( - () => import("@/app/dashboard/[search_space_id]/user-settings/components/PromptsContent").then(m => ({ default: m.PromptsContent })), + () => + import("@/app/dashboard/[search_space_id]/user-settings/components/PromptsContent").then( + (m) => ({ default: m.PromptsContent }) + ), { ssr: false } ); const CommunityPromptsContent = dynamic( - () => import("@/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent").then(m => ({ default: m.CommunityPromptsContent })), + () => + import( + "@/app/dashboard/[search_space_id]/user-settings/components/CommunityPromptsContent" + ).then((m) => ({ default: m.CommunityPromptsContent })), { ssr: false } ); const PurchaseHistoryContent = dynamic( - () => import("@/app/dashboard/[search_space_id]/user-settings/components/PurchaseHistoryContent").then(m => ({ default: m.PurchaseHistoryContent })), + () => + import( + "@/app/dashboard/[search_space_id]/user-settings/components/PurchaseHistoryContent" + ).then((m) => ({ default: m.PurchaseHistoryContent })), { ssr: false } ); const DesktopContent = dynamic( - () => import("@/app/dashboard/[search_space_id]/user-settings/components/DesktopContent").then(m => ({ default: m.DesktopContent })), + () => + import("@/app/dashboard/[search_space_id]/user-settings/components/DesktopContent").then( + (m) => ({ default: m.DesktopContent }) + ), { ssr: false } ); diff --git a/surfsense_web/components/sources/DocumentUploadTab.tsx b/surfsense_web/components/sources/DocumentUploadTab.tsx index 636b2bb35..2802dbe93 100644 --- a/surfsense_web/components/sources/DocumentUploadTab.tsx +++ b/surfsense_web/components/sources/DocumentUploadTab.tsx @@ -341,36 +341,36 @@ export function DocumentUploadTab({ ) ) : ( -
{ - if (!isElectron) fileInputRef.current?.click(); - }} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - if (!isElectron) fileInputRef.current?.click(); - } - }} - > - -
-

- {isElectron ? "Select files or folder" : "Tap to select files or folder"} -

-

{t("file_size_limit")}

-
e.stopPropagation()} - onKeyDown={(e) => e.stopPropagation()} - role="group" + role="button" + tabIndex={0} + className="flex flex-col items-center gap-4 py-12 px-4 cursor-pointer w-full bg-transparent border-none" + onClick={() => { + if (!isElectron) fileInputRef.current?.click(); + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + if (!isElectron) fileInputRef.current?.click(); + } + }} > - {renderBrowseButton({ fullWidth: true })} + +
+

+ {isElectron ? "Select files or folder" : "Tap to select files or folder"} +

+

{t("file_size_limit")}

+
+
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + role="group" + > + {renderBrowseButton({ fullWidth: true })} +
-
)}
diff --git a/surfsense_web/components/sources/FolderWatchDialog.tsx b/surfsense_web/components/sources/FolderWatchDialog.tsx index 4cb311a46..3590279ce 100644 --- a/surfsense_web/components/sources/FolderWatchDialog.tsx +++ b/surfsense_web/components/sources/FolderWatchDialog.tsx @@ -13,8 +13,8 @@ import { } from "@/components/ui/dialog"; import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; -import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; import { type FolderSyncProgress, uploadFolderScan } from "@/lib/folder-sync-upload"; +import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; export interface SelectedFolder { path: string; @@ -166,8 +166,12 @@ export function FolderWatchDialog({ - Watch Local Folder - Select a folder to sync and watch for changes + + Watch Local Folder + + + Select a folder to sync and watch for changes +
@@ -218,7 +222,9 @@ export function FolderWatchDialog({
)} diff --git a/surfsense_web/hooks/use-folder-sync.ts b/surfsense_web/hooks/use-folder-sync.ts index 7a85c31fe..8e0d0ebdc 100644 --- a/surfsense_web/hooks/use-folder-sync.ts +++ b/surfsense_web/hooks/use-folder-sync.ts @@ -50,7 +50,9 @@ export function useFolderSync() { while (queueRef.current.length > 0) { const batch = queueRef.current.shift()!; try { - const addChangeFiles = batch.files.filter((f) => f.action === "add" || f.action === "change"); + const addChangeFiles = batch.files.filter( + (f) => f.action === "add" || f.action === "change" + ); const unlinkFiles = batch.files.filter((f) => f.action === "unlink"); if (addChangeFiles.length > 0 && electronAPI?.readLocalFiles) { @@ -128,11 +130,13 @@ export function useFolderSync() { folderName: event.folderName, searchSpaceId: event.searchSpaceId, rootFolderId: event.rootFolderId, - files: [{ - fullPath: event.fullPath, - relativePath: event.relativePath, - action: event.action, - }], + files: [ + { + fullPath: event.fullPath, + relativePath: event.relativePath, + action: event.action, + }, + ], ackIds: [event.id], }); firstEventTime.current.set(folderKey, Date.now()); diff --git a/surfsense_web/lib/apis/documents-api.service.ts b/surfsense_web/lib/apis/documents-api.service.ts index 34a0b6dce..584f2e212 100644 --- a/surfsense_web/lib/apis/documents-api.service.ts +++ b/surfsense_web/lib/apis/documents-api.service.ts @@ -429,7 +429,9 @@ class DocumentsApiService { search_space_id: number; files: { relative_path: string; mtime: number }[]; }): Promise<{ files_to_upload: string[] }> => { - return baseApiService.post(`/api/v1/documents/folder-mtime-check`, undefined, { body }) as unknown as { files_to_upload: string[] }; + return baseApiService.post(`/api/v1/documents/folder-mtime-check`, undefined, { + body, + }) as unknown as { files_to_upload: string[] }; }; folderUploadFiles = async ( @@ -441,7 +443,7 @@ class DocumentsApiService { root_folder_id?: number | null; enable_summary?: boolean; }, - signal?: AbortSignal, + signal?: AbortSignal ): Promise<{ message: string; status: string; root_folder_id: number; file_count: number }> => { const formData = new FormData(); for (const file of files) { @@ -466,11 +468,10 @@ class DocumentsApiService { } try { - return await baseApiService.postFormData( - `/api/v1/documents/folder-upload`, - undefined, - { body: formData, signal: controller.signal }, - ) as { message: string; status: string; root_folder_id: number; file_count: number }; + return (await baseApiService.postFormData(`/api/v1/documents/folder-upload`, undefined, { + body: formData, + signal: controller.signal, + })) as { message: string; status: string; root_folder_id: number; file_count: number }; } finally { clearTimeout(timeoutId); } @@ -482,7 +483,9 @@ class DocumentsApiService { root_folder_id: number | null; relative_paths: string[]; }): Promise<{ deleted_count: number }> => { - return baseApiService.post(`/api/v1/documents/folder-unlink`, undefined, { body }) as unknown as { deleted_count: number }; + return baseApiService.post(`/api/v1/documents/folder-unlink`, undefined, { + body, + }) as unknown as { deleted_count: number }; }; folderSyncFinalize = async (body: { @@ -491,7 +494,9 @@ class DocumentsApiService { root_folder_id: number | null; all_relative_paths: string[]; }): Promise<{ deleted_count: number }> => { - return baseApiService.post(`/api/v1/documents/folder-sync-finalize`, undefined, { body }) as unknown as { deleted_count: number }; + return baseApiService.post(`/api/v1/documents/folder-sync-finalize`, undefined, { + body, + }) as unknown as { deleted_count: number }; }; getWatchedFolders = async (searchSpaceId: number) => { diff --git a/surfsense_web/lib/folder-sync-upload.ts b/surfsense_web/lib/folder-sync-upload.ts index ef01d52bd..7a9810d76 100644 --- a/surfsense_web/lib/folder-sync-upload.ts +++ b/surfsense_web/lib/folder-sync-upload.ts @@ -22,9 +22,7 @@ export interface FolderSyncParams { signal?: AbortSignal; } -function buildBatches( - entries: FolderFileEntry[], -): FolderFileEntry[][] { +function buildBatches(entries: FolderFileEntry[]): FolderFileEntry[][] { const batches: FolderFileEntry[][] = []; let currentBatch: FolderFileEntry[] = []; let currentSize = 0; @@ -40,10 +38,7 @@ function buildBatches( continue; } - if ( - currentBatch.length >= MAX_BATCH_FILES || - currentSize + entry.size > MAX_BATCH_SIZE_BYTES - ) { + if (currentBatch.length >= MAX_BATCH_FILES || currentSize + entry.size > MAX_BATCH_SIZE_BYTES) { batches.push(currentBatch); currentBatch = []; currentSize = 0; @@ -69,7 +64,7 @@ async function uploadBatchesWithConcurrency( enableSummary: boolean; signal?: AbortSignal; onBatchComplete?: (filesInBatch: number) => void; - }, + } ): Promise { const api = window.electronAPI; if (!api) throw new Error("Electron API not available"); @@ -105,7 +100,7 @@ async function uploadBatchesWithConcurrency( root_folder_id: resolvedRootFolderId, enable_summary: params.enableSummary, }, - params.signal, + params.signal ); if (result.root_folder_id && !resolvedRootFolderId) { @@ -121,7 +116,9 @@ async function uploadBatchesWithConcurrency( } } - const workers = Array.from({ length: Math.min(UPLOAD_CONCURRENCY, batches.length) }, () => processNext()); + const workers = Array.from({ length: Math.min(UPLOAD_CONCURRENCY, batches.length) }, () => + processNext() + ); await Promise.all(workers); if (errors.length > 0 && !params.signal?.aborted) { @@ -141,7 +138,15 @@ export async function uploadFolderScan(params: FolderSyncParams): Promise f.relativePath), }); - params.onProgress?.({ phase: "done", uploaded: entriesToUpload.length, total: entriesToUpload.length }); + params.onProgress?.({ + phase: "done", + uploaded: entriesToUpload.length, + total: entriesToUpload.length, + }); // Seed the Electron mtime store so the reconciliation scan in // startWatcher won't re-emit events for files we just indexed.