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 {
|
import {
|
||||||
FolderInput,
|
FolderInput,
|
||||||
FolderPlus,
|
FolderPlus,
|
||||||
|
FolderSync,
|
||||||
ListFilter,
|
ListFilter,
|
||||||
Plus,
|
Plus,
|
||||||
Settings2,
|
Settings2,
|
||||||
|
|
@ -32,9 +33,12 @@ import type { FolderDisplay } from "@/components/documents/FolderNode";
|
||||||
import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog";
|
import { FolderPickerDialog } from "@/components/documents/FolderPickerDialog";
|
||||||
import { FolderTreeView } from "@/components/documents/FolderTreeView";
|
import { FolderTreeView } from "@/components/documents/FolderTreeView";
|
||||||
import { VersionHistoryDialog } from "@/components/documents/version-history";
|
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 { 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 {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
|
|
@ -187,13 +191,27 @@ export function EmbeddedDocumentsMenu({
|
||||||
* OAuth or open the existing account's config. In anonymous mode, `gate`
|
* OAuth or open the existing account's config. In anonymous mode, `gate`
|
||||||
* intercepts every item to trigger the login flow.
|
* 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 { openDialog } = useDocumentUploadDialog();
|
||||||
const setImportRequest = useSetAtom(importConnectorRequestAtom);
|
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 { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
|
||||||
const { data: connectors } = useAtomValue(connectorsAtom);
|
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.
|
// Native Google Drive connector self-hosted only; hosted deployments use Composio.
|
||||||
const driveType = selfHosted
|
const driveType = selfHosted
|
||||||
? EnumConnectorName.GOOGLE_DRIVE_CONNECTOR
|
? EnumConnectorName.GOOGLE_DRIVE_CONNECTOR
|
||||||
|
|
@ -223,6 +241,14 @@ export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void
|
||||||
<Upload className="h-4 w-4" />
|
<Upload className="h-4 w-4" />
|
||||||
Upload Files
|
Upload Files
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
{isDesktop && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={() => (gate ? gate("watch local folders") : setFolderWatchOpen(true))}
|
||||||
|
>
|
||||||
|
<FolderSync className="h-4 w-4" />
|
||||||
|
Watch Local Folder
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{cloudItems.map((item) => {
|
{cloudItems.map((item) => {
|
||||||
const enabled = gate ? true : isConnectorEnabled(item.type);
|
const enabled = gate ? true : isConnectorEnabled(item.type);
|
||||||
|
|
@ -297,6 +323,14 @@ export function EmbeddedImportMenu({ gate }: { gate?: (feature: string) => void
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
{isDesktop && !gate && (
|
||||||
|
<FolderWatchDialog
|
||||||
|
open={folderWatchOpen}
|
||||||
|
onOpenChange={setFolderWatchOpen}
|
||||||
|
workspaceId={workspaceId}
|
||||||
|
onSuccess={onFolderWatched}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1180,7 +1214,7 @@ function AuthenticatedDocumentsSidebarBase({
|
||||||
contentClassName="px-0"
|
contentClassName="px-0"
|
||||||
persistentAction={
|
persistentAction={
|
||||||
<div className="flex items-center gap-0.5">
|
<div className="flex items-center gap-0.5">
|
||||||
<EmbeddedImportMenu />
|
<EmbeddedImportMenu onFolderWatched={refreshWatchedIds} />
|
||||||
<EmbeddedDocumentsMenu
|
<EmbeddedDocumentsMenu
|
||||||
typeCounts={typeCounts}
|
typeCounts={typeCounts}
|
||||||
activeTypes={activeTypes}
|
activeTypes={activeTypes}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,13 @@ async function fetchZeroContext(isDesktop: boolean): Promise<LoadedZeroContext |
|
||||||
const response = await authenticatedFetch(buildBackendUrl("/zero/context"), {
|
const response = await authenticatedFetch(buildBackendUrl("/zero/context"), {
|
||||||
skipAuthRedirect: true,
|
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;
|
if (!response.ok) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -234,6 +241,12 @@ function ZeroClientProvider({
|
||||||
function WebZeroProvider({ children }: { children: React.ReactNode }) {
|
function WebZeroProvider({ children }: { children: React.ReactNode }) {
|
||||||
const session = useSession();
|
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") {
|
if (session.status !== "authenticated") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@ export function useRuntimeConfig() {
|
||||||
return context;
|
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() {
|
export function useIsLocalAuth() {
|
||||||
return useRuntimeConfig().authType === "LOCAL";
|
return useRuntimeConfig().authType === "LOCAL";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ class BaseApiService {
|
||||||
|
|
||||||
const refreshRetryKey = getRefreshRetryKey(mergedOptions.method, url);
|
const refreshRetryKey = getRefreshRetryKey(mergedOptions.method, url);
|
||||||
if (this.isDesktopClient && !desktopAccessToken && !isNoAuthEndpoint) {
|
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.");
|
throw new AuthenticationError("You are not authenticated. Please login again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue