Files
paseo/packages/app/src/hooks/use-file-explorer-actions.ts
T

195 lines
5.6 KiB
TypeScript

import { useCallback } from "react";
import { useSessionStore, type AgentFileExplorerState } from "@/stores/session-store";
function createExplorerState(): AgentFileExplorerState {
return {
directories: new Map(),
files: new Map(),
isLoading: false,
lastError: null,
pendingRequest: null,
currentPath: ".",
history: ["."],
lastVisitedPath: ".",
selectedEntryPath: null,
};
}
function pushHistory(history: string[], path: string): string[] {
const normalizedHistory = history.length === 0 ? ["."] : history;
const last = normalizedHistory[normalizedHistory.length - 1];
if (last === path) {
return normalizedHistory;
}
return [...normalizedHistory, path];
}
export function useFileExplorerActions(serverId: string) {
const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null);
const setFileExplorer = useSessionStore((state) => state.setFileExplorer);
const updateExplorerState = useCallback(
(agentId: string, updater: (prev: AgentFileExplorerState) => AgentFileExplorerState) => {
setFileExplorer(serverId, (prev) => {
const next = new Map(prev);
const current = next.get(agentId) ?? createExplorerState();
next.set(agentId, updater(current));
return next;
});
},
[serverId, setFileExplorer]
);
const requestDirectoryListing = useCallback(
async (
agentId: string,
path: string,
options?: { recordHistory?: boolean; setCurrentPath?: boolean }
) => {
const normalizedPath = path && path.length > 0 ? path : ".";
const shouldSetCurrentPath = options?.setCurrentPath ?? true;
const shouldRecordHistory =
options?.recordHistory ?? (shouldSetCurrentPath ? true : false);
updateExplorerState(agentId, (state) => ({
...state,
isLoading: true,
lastError: null,
pendingRequest: { path: normalizedPath, mode: "list" },
...(shouldSetCurrentPath
? {
currentPath: normalizedPath,
history: shouldRecordHistory
? pushHistory(state.history, normalizedPath)
: state.history,
lastVisitedPath: normalizedPath,
}
: {}),
}));
if (!client) {
updateExplorerState(agentId, (state) => ({
...state,
isLoading: false,
lastError: "Host is not connected",
pendingRequest: null,
}));
return;
}
try {
const payload = await client.exploreFileSystem(agentId, normalizedPath, "list");
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) => ({
...state,
isLoading: false,
lastError: error instanceof Error ? error.message : "Failed to list directory",
pendingRequest: null,
}));
}
},
[client, updateExplorerState]
);
const requestFilePreview = useCallback(
async (agentId: string, path: string) => {
const normalizedPath = path && path.length > 0 ? path : ".";
updateExplorerState(agentId, (state) => ({
...state,
isLoading: true,
lastError: null,
pendingRequest: { path: normalizedPath, mode: "file" },
}));
if (!client) {
updateExplorerState(agentId, (state) => ({
...state,
isLoading: false,
lastError: "Host is not connected",
pendingRequest: null,
}));
return;
}
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]
);
const requestFileDownloadToken = useCallback(
async (agentId: string, path: string) => {
if (!client) {
throw new Error("Host is not connected");
}
const payload = await client.requestDownloadToken(agentId, path);
if (payload.error) {
throw new Error(payload.error);
}
return payload;
},
[client]
);
const selectExplorerEntry = useCallback(
(agentId: string, path: string | null) => {
updateExplorerState(agentId, (state) => ({
...state,
selectedEntryPath: path,
}));
},
[updateExplorerState]
);
return {
requestDirectoryListing,
requestFilePreview,
requestFileDownloadToken,
selectExplorerEntry,
};
}