diff --git a/packages/app/src/components/file-explorer-pane.tsx b/packages/app/src/components/file-explorer-pane.tsx index 33d0b1afc..111c46ade 100644 --- a/packages/app/src/components/file-explorer-pane.tsx +++ b/packages/app/src/components/file-explorer-pane.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; import { ActivityIndicator, FlatList, @@ -264,25 +265,35 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { setSortOption(SORT_OPTIONS[nextIndex].value); }, [sortOption, setSortOption]); - const handleRefresh = useCallback(() => { - if (!agentId || !requestDirectoryListing) { - return; - } - // Refresh the root directory - requestDirectoryListing(agentId, ".", { recordHistory: false, setCurrentPath: false }); - - // Also refresh any expanded directories - expandedPaths.forEach((path) => { - if (path !== ".") { - requestDirectoryListing(agentId, path, { recordHistory: false, setCurrentPath: false }); + const { refetch: refetchExplorer, isFetching: isRefreshFetching } = useQuery({ + queryKey: ["fileExplorerRefresh", serverId, agentId], + queryFn: async () => { + if (!agentId) { + return null; } - }); - // Refresh the preview if a file is selected - if (selectedEntryPath && requestFilePreview) { - requestFilePreview(agentId, selectedEntryPath); - } - }, [agentId, expandedPaths, requestDirectoryListing, requestFilePreview, selectedEntryPath]); + const directoryPaths = Array.from(expandedPaths); + if (!directoryPaths.includes(".")) { + directoryPaths.unshift("."); + } + + await Promise.all([ + ...directoryPaths.map((path) => + requestDirectoryListing(agentId, path, { + recordHistory: false, + setCurrentPath: false, + }) + ), + ...(selectedEntryPath ? [requestFilePreview(agentId, selectedEntryPath)] : []), + ]); + return null; + }, + enabled: false, + }); + + const handleRefresh = useCallback(() => { + void refetchExplorer(); + }, [refetchExplorer]); const currentSortLabel = SORT_OPTIONS.find((opt) => opt.value === sortOption)?.label ?? "Name"; @@ -627,15 +638,20 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { [ styles.iconButton, (hovered || pressed) && styles.iconButtonHovered, + pressed && styles.iconButtonPressed, + isRefreshFetching && styles.iconButtonLoading, ]} accessibilityRole="button" accessibilityLabel="Refresh files" > - + + + {currentSortLabel} @@ -660,15 +676,20 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { [ styles.iconButton, (hovered || pressed) && styles.iconButtonHovered, + pressed && styles.iconButtonPressed, + isRefreshFetching && styles.iconButtonLoading, ]} accessibilityRole="button" accessibilityLabel="Refresh files" > - + + + {currentSortLabel} @@ -1221,6 +1242,16 @@ const styles = StyleSheet.create((theme) => ({ iconButtonHovered: { backgroundColor: theme.colors.surface2, }, + iconButtonPressed: { + opacity: 0.8, + transform: [{ scale: 0.96 }], + }, + iconButtonLoading: { + opacity: 0.8, + }, + refreshIconLoading: { + opacity: 0.55, + }, previewContent: { flex: 1, }, diff --git a/packages/app/src/hooks/use-file-explorer-actions.ts b/packages/app/src/hooks/use-file-explorer-actions.ts index e3b37794a..22501495a 100644 --- a/packages/app/src/hooks/use-file-explorer-actions.ts +++ b/packages/app/src/hooks/use-file-explorer-actions.ts @@ -41,7 +41,7 @@ export function useFileExplorerActions(serverId: string) { ); const requestDirectoryListing = useCallback( - ( + async ( agentId: string, path: string, options?: { recordHistory?: boolean; setCurrentPath?: boolean } @@ -77,42 +77,40 @@ export function useFileExplorerActions(serverId: string) { return; } - void client - .exploreFileSystem(agentId, normalizedPath, "list") - .then((payload) => { - updateExplorerState(agentId, (state) => { - const nextState: AgentFileExplorerState = { - ...state, - isLoading: false, - lastError: payload.error ?? null, - pendingRequest: null, - directories: state.directories, - files: state.files, - }; - - if (!payload.error && payload.directory) { - const directories = new Map(state.directories); - directories.set(payload.directory.path, payload.directory); - nextState.directories = directories; - } - - return nextState; - }); - }) - .catch((error) => { - updateExplorerState(agentId, (state) => ({ + try { + const payload = await client.exploreFileSystem(agentId, normalizedPath, "list"); + updateExplorerState(agentId, (state) => { + const nextState: AgentFileExplorerState = { ...state, isLoading: false, - lastError: error instanceof Error ? error.message : "Failed to list directory", + lastError: payload.error ?? null, pendingRequest: null, - })); + directories: state.directories, + files: state.files, + }; + + if (!payload.error && payload.directory) { + const directories = new Map(state.directories); + directories.set(payload.directory.path, payload.directory); + nextState.directories = directories; + } + + return nextState; }); + } catch (error) { + updateExplorerState(agentId, (state) => ({ + ...state, + isLoading: false, + lastError: error instanceof Error ? error.message : "Failed to list directory", + pendingRequest: null, + })); + } }, [client, updateExplorerState] ); const requestFilePreview = useCallback( - (agentId: string, path: string) => { + async (agentId: string, path: string) => { const normalizedPath = path && path.length > 0 ? path : "."; updateExplorerState(agentId, (state) => ({ ...state, @@ -131,36 +129,34 @@ export function useFileExplorerActions(serverId: string) { return; } - void client - .exploreFileSystem(agentId, normalizedPath, "file") - .then((payload) => { - updateExplorerState(agentId, (state) => { - const nextState: AgentFileExplorerState = { - ...state, - isLoading: false, - pendingRequest: null, - directories: state.directories, - files: state.files, - }; - - if (!payload.error && payload.file) { - const files = new Map(state.files); - files.set(payload.file.path, payload.file); - nextState.files = files; - } else if (payload.error) { - nextState.lastError = payload.error; - } - - return nextState; - }); - }) - .catch(() => { - updateExplorerState(agentId, (state) => ({ + try { + const payload = await client.exploreFileSystem(agentId, normalizedPath, "file"); + updateExplorerState(agentId, (state) => { + const nextState: AgentFileExplorerState = { ...state, isLoading: false, pendingRequest: null, - })); + directories: state.directories, + files: state.files, + }; + + if (!payload.error && payload.file) { + const files = new Map(state.files); + files.set(payload.file.path, payload.file); + nextState.files = files; + } else if (payload.error) { + nextState.lastError = payload.error; + } + + return nextState; }); + } catch { + updateExplorerState(agentId, (state) => ({ + ...state, + isLoading: false, + pendingRequest: null, + })); + } }, [client, updateExplorerState] );