mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
feat: enhance document import functionality and runtime configuration
- Added support for watching local folders in the EmbeddedImportMenu, including a new FolderSync icon. - Introduced onFolderWatched callback to handle folder watch success. - Updated runtime configuration hooks to allow optional access outside the RuntimeConfigProvider. - Improved error handling in ZeroProvider for unauthorized access scenarios. - Refactored base API service to manage desktop authentication more effectively.
This commit is contained in:
parent
1fd58752a3
commit
8df8565e0a
4 changed files with 60 additions and 5 deletions
|
|
@ -5,6 +5,7 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
|||
import {
|
||||
FolderInput,
|
||||
FolderPlus,
|
||||
FolderSync,
|
||||
ListFilter,
|
||||
Plus,
|
||||
Settings2,
|
||||
|
|
@ -32,9 +33,12 @@ import type { FolderDisplay } from "@/components/documents/FolderNode";
|
|||
import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog";
|
||||
import { FolderTreeView } from "@/components/documents/FolderTreeView";
|
||||
import { VersionHistoryDialog } from "@/components/documents/version-history";
|
||||
import { useIsSelfHosted, useRuntimeConfig } from "@/components/providers/runtime-config";
|
||||
import { useOptionalRuntimeConfig, useRuntimeConfig } from "@/components/providers/runtime-config";
|
||||
import { EXPORT_FILE_EXTENSIONS } from "@/components/shared/ExportMenuItems";
|
||||
import { DEFAULT_EXCLUDE_PATTERNS } from "@/components/sources/FolderWatchDialog";
|
||||
import {
|
||||
DEFAULT_EXCLUDE_PATTERNS,
|
||||
FolderWatchDialog,
|
||||
} from "@/components/sources/FolderWatchDialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -187,13 +191,27 @@ export function EmbeddedDocumentsMenu({
|
|||
* OAuth or open the existing account's config. In anonymous mode, `gate`
|
||||
* intercepts every item to trigger the login flow.
|
||||
*/
|
||||
export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void }) {
|
||||
export function EmbeddedImportMenu({
|
||||
gate,
|
||||
onFolderWatched,
|
||||
}: {
|
||||
gate?: (feature: string) => void;
|
||||
onFolderWatched?: () => void;
|
||||
}) {
|
||||
const { openDialog } = useDocumentUploadDialog();
|
||||
const setImportRequest = useSetAtom(importConnectorRequestAtom);
|
||||
const selfHosted = useIsSelfHosted();
|
||||
// Provider is absent on anonymous /free pages, where every item is login-gated
|
||||
// anyway — defaulting to hosted (Composio) there is cosmetic.
|
||||
const selfHosted = useOptionalRuntimeConfig()?.deploymentMode === "self-hosted";
|
||||
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
|
||||
const { data: connectors } = useAtomValue(connectorsAtom);
|
||||
|
||||
// Watch Local Folder is a desktop-app feature (needs the Electron folder watcher).
|
||||
const { isDesktop } = usePlatform();
|
||||
const params = useParams();
|
||||
const workspaceId = getWorkspaceIdNumber(params) ?? 0;
|
||||
const [folderWatchOpen, setFolderWatchOpen] = useState(false);
|
||||
|
||||
// Native Google Drive connector self-hosted only; hosted deployments use Composio.
|
||||
const driveType = selfHosted
|
||||
? EnumConnectorName.GOOGLE_DRIVE_CONNECTOR
|
||||
|
|
@ -223,6 +241,14 @@ export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void
|
|||
<Upload className="h-4 w-4" />
|
||||
Upload Files
|
||||
</DropdownMenuItem>
|
||||
{isDesktop && (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => (gate ? gate("watch local folders") : setFolderWatchOpen(true))}
|
||||
>
|
||||
<FolderSync className="h-4 w-4" />
|
||||
Watch Local Folder
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
{cloudItems.map((item) => {
|
||||
const enabled = gate ? true : isConnectorEnabled(item.type);
|
||||
|
|
@ -297,6 +323,14 @@ export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void
|
|||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
{isDesktop && !gate && (
|
||||
<FolderWatchDialog
|
||||
open={folderWatchOpen}
|
||||
onOpenChange={setFolderWatchOpen}
|
||||
workspaceId={workspaceId}
|
||||
onSuccess={onFolderWatched}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
|
@ -1180,7 +1214,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
contentClassName="px-0"
|
||||
persistentAction={
|
||||
<div className="flex items-center gap-0.5">
|
||||
<EmbeddedImportMenu />
|
||||
<EmbeddedImportMenu onFolderWatched={refreshWatchedIds} />
|
||||
<EmbeddedDocumentsMenu
|
||||
typeCounts={typeCounts}
|
||||
activeTypes={activeTypes}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ async function fetchZeroContext(isDesktop: boolean): Promise<LoadedZeroContext |
|
|||
const response = await authenticatedFetch(buildBackendUrl("/zero/context"), {
|
||||
skipAuthRedirect: true,
|
||||
});
|
||||
if (response.status === 401) {
|
||||
// Auth is dead (refresh already failed inside authenticatedFetch). This
|
||||
// provider gates the whole app tree, so nothing below it (e.g.
|
||||
// DashboardShell) can run its own redirect — do it here.
|
||||
handleUnauthorized();
|
||||
return null;
|
||||
}
|
||||
if (!response.ok) return null;
|
||||
|
||||
return {
|
||||
|
|
@ -234,6 +241,12 @@ function ZeroClientProvider({
|
|||
function WebZeroProvider({ children }: { children: React.ReactNode }) {
|
||||
const session = useSession();
|
||||
|
||||
// Same reasoning as fetchZeroContext: this provider blocks the whole tree,
|
||||
// so the login redirect must happen here, not in a child that never mounts.
|
||||
useEffect(() => {
|
||||
if (session.status === "unauthenticated") handleUnauthorized();
|
||||
}, [session.status]);
|
||||
|
||||
if (session.status !== "authenticated") {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ export function useRuntimeConfig() {
|
|||
return context;
|
||||
}
|
||||
|
||||
/** For components that may render outside RuntimeConfigProvider (e.g. anonymous /free pages). */
|
||||
export function useOptionalRuntimeConfig(): RuntimeConfigValue | null {
|
||||
return useContext(RuntimeConfigContext);
|
||||
}
|
||||
|
||||
export function useIsLocalAuth() {
|
||||
return useRuntimeConfig().authType === "LOCAL";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ class BaseApiService {
|
|||
|
||||
const refreshRetryKey = getRefreshRetryKey(mergedOptions.method, url);
|
||||
if (this.isDesktopClient && !desktopAccessToken && !isNoAuthEndpoint) {
|
||||
// Desktop refresh token is gone/revoked — send the user to /desktop/login
|
||||
// (same treatment as a server 401 below) instead of erroring in place.
|
||||
handleUnauthorized();
|
||||
throw new AuthenticationError("You are not authenticated. Please login again.");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue