mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-01 03:46:25 +02:00
feat(filesystem): enhance local file handling in editor and IPC integration
This commit is contained in:
parent
4899588cd7
commit
864f6f798a
12 changed files with 350 additions and 47 deletions
|
|
@ -34,6 +34,8 @@ export const IPC_CHANNELS = {
|
|||
FOLDER_SYNC_SEED_MTIMES: 'folder-sync:seed-mtimes',
|
||||
BROWSE_FILES: 'browse:files',
|
||||
READ_LOCAL_FILES: 'browse:read-local-files',
|
||||
READ_AGENT_LOCAL_FILE_TEXT: 'agent-filesystem:read-local-file-text',
|
||||
WRITE_AGENT_LOCAL_FILE_TEXT: 'agent-filesystem:write-local-file-text',
|
||||
// Auth token sync across windows
|
||||
GET_AUTH_TOKENS: 'auth:get-tokens',
|
||||
SET_AUTH_TOKENS: 'auth:set-tokens',
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ import {
|
|||
trackEvent,
|
||||
} from '../modules/analytics';
|
||||
import {
|
||||
readAgentLocalFileText,
|
||||
writeAgentLocalFileText,
|
||||
getAgentFilesystemSettings,
|
||||
pickAgentFilesystemRoot,
|
||||
setAgentFilesystemSettings,
|
||||
|
|
@ -123,6 +125,29 @@ export function registerIpcHandlers(): void {
|
|||
readLocalFiles(paths)
|
||||
);
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.READ_AGENT_LOCAL_FILE_TEXT, async (_event, virtualPath: string) => {
|
||||
try {
|
||||
const result = await readAgentLocalFileText(virtualPath);
|
||||
return { ok: true, path: result.path, content: result.content };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to read local file';
|
||||
return { ok: false, path: virtualPath, error: message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.WRITE_AGENT_LOCAL_FILE_TEXT,
|
||||
async (_event, virtualPath: string, content: string) => {
|
||||
try {
|
||||
const result = await writeAgentLocalFileText(virtualPath, content);
|
||||
return { ok: true, path: result.path };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to write local file';
|
||||
return { ok: false, path: virtualPath, error: message };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.SET_AUTH_TOKENS, (_event, tokens: { bearer: string; refresh: string }) => {
|
||||
authTokens = tokens;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { app, dialog } from "electron";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
||||
|
||||
export type AgentFilesystemMode = "cloud" | "desktop_local_folder";
|
||||
|
||||
|
|
@ -72,3 +72,62 @@ export async function pickAgentFilesystemRoot(): Promise<string | null> {
|
|||
}
|
||||
return result.filePaths[0] ?? null;
|
||||
}
|
||||
|
||||
function resolveVirtualPath(rootPath: string, virtualPath: string): string {
|
||||
if (!virtualPath.startsWith("/")) {
|
||||
throw new Error("Path must start with '/'");
|
||||
}
|
||||
const normalizedRoot = resolve(rootPath);
|
||||
const relativePath = virtualPath.replace(/^\/+/, "");
|
||||
if (!relativePath) {
|
||||
throw new Error("Path must refer to a file under the selected root");
|
||||
}
|
||||
const absolutePath = resolve(normalizedRoot, relativePath);
|
||||
const rel = relative(normalizedRoot, absolutePath);
|
||||
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
||||
throw new Error("Path escapes selected local root");
|
||||
}
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
function toVirtualPath(rootPath: string, absolutePath: string): string {
|
||||
const normalizedRoot = resolve(rootPath);
|
||||
const rel = relative(normalizedRoot, absolutePath);
|
||||
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
||||
return "/";
|
||||
}
|
||||
return `/${rel.replace(/\\/g, "/")}`;
|
||||
}
|
||||
|
||||
async function resolveCurrentRootPath(): Promise<string> {
|
||||
const settings = await getAgentFilesystemSettings();
|
||||
if (!settings.localRootPath) {
|
||||
throw new Error("No local filesystem root selected");
|
||||
}
|
||||
return settings.localRootPath;
|
||||
}
|
||||
|
||||
export async function readAgentLocalFileText(
|
||||
virtualPath: string
|
||||
): Promise<{ path: string; content: string }> {
|
||||
const rootPath = await resolveCurrentRootPath();
|
||||
const absolutePath = resolveVirtualPath(rootPath, virtualPath);
|
||||
const content = await readFile(absolutePath, "utf8");
|
||||
return {
|
||||
path: toVirtualPath(rootPath, absolutePath),
|
||||
content,
|
||||
};
|
||||
}
|
||||
|
||||
export async function writeAgentLocalFileText(
|
||||
virtualPath: string,
|
||||
content: string
|
||||
): Promise<{ path: string }> {
|
||||
const rootPath = await resolveCurrentRootPath();
|
||||
const absolutePath = resolveVirtualPath(rootPath, virtualPath);
|
||||
await mkdir(dirname(absolutePath), { recursive: true });
|
||||
await writeFile(absolutePath, content, "utf8");
|
||||
return {
|
||||
path: toVirtualPath(rootPath, absolutePath),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
// Browse files via native dialog
|
||||
browseFiles: () => ipcRenderer.invoke(IPC_CHANNELS.BROWSE_FILES),
|
||||
readLocalFiles: (paths: string[]) => ipcRenderer.invoke(IPC_CHANNELS.READ_LOCAL_FILES, paths),
|
||||
readAgentLocalFileText: (virtualPath: string) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.READ_AGENT_LOCAL_FILE_TEXT, virtualPath),
|
||||
writeAgentLocalFileText: (virtualPath: string, content: string) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.WRITE_AGENT_LOCAL_FILE_TEXT, virtualPath, content),
|
||||
|
||||
// Auth token sync across windows
|
||||
getAuthTokens: () => ipcRenderer.invoke(IPC_CHANNELS.GET_AUTH_TOKENS),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue