diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 9df41ee55..6fde33061 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -110,11 +110,15 @@ const COMPOSER_PLACEHOLDER = "Ask anything, type / for prompts, type @ to mentio type ComposerFilesystemSettings = { mode: "cloud" | "desktop_local_folder"; - localRootPath: string | null; + localRootPaths: string[]; updatedAt: string; }; const LOCAL_FILESYSTEM_TRUST_KEY = "surfsense.local-filesystem-trust.v1"; +const MAX_LOCAL_FILESYSTEM_ROOTS = 5; + +const getFolderDisplayName = (rootPath: string): string => + rootPath.split(/[\\/]/).at(-1) || rootPath; export const Thread: FC = () => { return ; @@ -388,6 +392,7 @@ const Composer: FC = () => { null ); const [localTrustDialogOpen, setLocalTrustDialogOpen] = useState(false); + const [localFoldersOpen, setLocalFoldersOpen] = useState(false); const [pendingLocalPath, setPendingLocalPath] = useState(null); const [clipboardInitialText, setClipboardInitialText] = useState(); const clipboardLoadedRef = useRef(false); @@ -414,7 +419,7 @@ const Composer: FC = () => { if (!mounted) return; setFilesystemSettings({ mode: "cloud", - localRootPath: null, + localRootPaths: [], updatedAt: new Date().toISOString(), }); }); @@ -431,16 +436,27 @@ const Composer: FC = () => { } }, []); + const localRootPaths = filesystemSettings?.localRootPaths ?? []; + const primaryLocalRootPath = localRootPaths[0] ?? null; + const extraLocalRootCount = Math.max(0, localRootPaths.length - 1); + const canAddMoreLocalRoots = localRootPaths.length < MAX_LOCAL_FILESYSTEM_ROOTS; + const applyLocalRootPath = useCallback( async (path: string) => { if (!electronAPI?.setAgentFilesystemSettings) return; + const nextLocalRootPaths = [...localRootPaths, path] + .filter((rootPath, index, allPaths) => allPaths.indexOf(rootPath) === index) + .slice(0, MAX_LOCAL_FILESYSTEM_ROOTS); + if (nextLocalRootPaths.length === localRootPaths.length) { + return; + } const updated = await electronAPI.setAgentFilesystemSettings({ mode: "desktop_local_folder", - localRootPath: path, + localRootPaths: nextLocalRootPaths, }); setFilesystemSettings(updated); }, - [electronAPI] + [electronAPI, localRootPaths] ); const runSwitchToLocalMode = useCallback(async () => { @@ -467,6 +483,7 @@ const Composer: FC = () => { ); const handlePickFilesystemRoot = useCallback(async () => { + if (!canAddMoreLocalRoots) return; if (hasLocalFilesystemTrust()) { await runPickLocalRoot(); return; @@ -476,13 +493,25 @@ const Composer: FC = () => { if (!picked) return; setPendingLocalPath(picked); setLocalTrustDialogOpen(true); - }, [electronAPI, hasLocalFilesystemTrust, runPickLocalRoot]); + }, [canAddMoreLocalRoots, electronAPI, hasLocalFilesystemTrust, runPickLocalRoot]); - const handleClearFilesystemRoot = useCallback(async () => { + const handleRemoveFilesystemRoot = useCallback( + async (rootPathToRemove: string) => { + if (!electronAPI?.setAgentFilesystemSettings) return; + const updated = await electronAPI.setAgentFilesystemSettings({ + mode: "desktop_local_folder", + localRootPaths: localRootPaths.filter((rootPath) => rootPath !== rootPathToRemove), + }); + setFilesystemSettings(updated); + }, + [electronAPI, localRootPaths] + ); + + const handleClearFilesystemRoots = useCallback(async () => { if (!electronAPI?.setAgentFilesystemSettings) return; const updated = await electronAPI.setAgentFilesystemSettings({ mode: "desktop_local_folder", - localRootPath: null, + localRootPaths: [], }); setFilesystemSettings(updated); }, [electronAPI]); @@ -833,31 +862,89 @@ const Composer: FC = () => { {filesystemSettings.mode === "desktop_local_folder" && ( <>
-
- {filesystemSettings.localRootPath ? ( +
+ {primaryLocalRootPath ? ( <>
- {filesystemSettings.localRootPath.split("/").at(-1) || - filesystemSettings.localRootPath} + {getFolderDisplayName(primaryLocalRootPath)}
+ {extraLocalRootCount > 0 && ( + + + + + +
+ {localRootPaths.map((rootPath) => ( +
+ + + {getFolderDisplayName(rootPath)} + + +
+ ))} +
+ +
+
+
+
+ )} @@ -909,9 +1001,9 @@ const Composer: FC = () => { Local mode can read and edit files inside the folders you select. Continue only if you trust this workspace and its contents. - {(pendingLocalPath || filesystemSettings?.localRootPath) && ( + {(pendingLocalPath || primaryLocalRootPath) && ( - Folder path: {pendingLocalPath || filesystemSettings?.localRootPath} + Folder path: {pendingLocalPath || primaryLocalRootPath} )}