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:
DESKTOP-RTLN3BA\$punk 2026-07-06 21:31:35 -07:00
parent 1fd58752a3
commit 8df8565e0a
4 changed files with 60 additions and 5 deletions

View file

@ -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}

View file

@ -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;
}

View file

@ -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";
}

View file

@ -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.");
}