Add a files browser for every folder on the data volume
A bot's deliverables and the raw material behind them only existed as paths in a message, so they couldn't be opened from the app at all. This browses them read-only at /admin/files, one volume at a time: a bot's own folder, a workspace's shared one, a computer's home (where a bot's ~/... actually is), the media library, an MCP server's install folder and the database. Access is the whole gate: resolveStorageSpec checks the viewer against the volume first (a bot through grabUserBot, a workspace through grabUserWorkspace, a computer through grabUserComputer, media only their own unless they manage the crew) and no path is built before it passes. resolveStoragePath then applies the resolveBotPath rules to any root. The picker lists folders through reachableBotsSql, so it never offers one the resolver would refuse. Reading is listStorageEntries (folders first, paginated, symlinks skipped, catalog rows joined so a bot file still opens through its media row) and grabStorageFile for bytes under a cap, both behind an ETag.
This commit is contained in:
@@ -18,6 +18,7 @@ import type {
|
||||
BunSQLiteTables,
|
||||
} from "@/db/types/db";
|
||||
import type { UserTypes } from "../dict/user-types-dict";
|
||||
import type { StorageVolume } from "../utils/storage-volumes";
|
||||
import type { WebsocketRooms } from "../dict/websocket-rooms";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ServerQueryParam } from "@moduletrace/bun-sqlite";
|
||||
@@ -712,6 +713,58 @@ export type BotFileScopesPayload = {
|
||||
quota_mb: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* One entry in a folder the files browser shows: a file or a subfolder.
|
||||
*/
|
||||
export type StorageEntry = {
|
||||
name: string;
|
||||
/**
|
||||
* Where it is inside the volume, e.g. `seo/coderank/raw`.
|
||||
*/
|
||||
path: string;
|
||||
kind: "file" | "folder";
|
||||
size: number;
|
||||
modified_at: number;
|
||||
media_type?: string;
|
||||
/**
|
||||
* The `bot_files` row that holds it, when the folder is a bot's or a
|
||||
* workspace's.
|
||||
*/
|
||||
file_id?: number;
|
||||
/**
|
||||
* Where it opens: a `bot_files` row's media URL, else the browser's own
|
||||
* streaming route.
|
||||
*/
|
||||
url?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* One folder of the files browser: what it holds and how to get back up.
|
||||
*/
|
||||
export type StorageFolderPayload = {
|
||||
spec: { volume: StorageVolume; owner_id: number };
|
||||
/**
|
||||
* What the folder is called: the bot's, workspace's or computer's name.
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The folder asked for, inside the volume (empty at the top).
|
||||
*/
|
||||
path: string;
|
||||
breadcrumbs: { name: string; href: string }[];
|
||||
entries: StorageEntry[];
|
||||
total: number;
|
||||
offset: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* A folder in the files browser's picker.
|
||||
*/
|
||||
export type StorageSpecOption = {
|
||||
spec: { volume: StorageVolume; owner_id: number };
|
||||
label: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A worker bot's model settings page data, also returned when it saves.
|
||||
*/
|
||||
@@ -907,6 +960,12 @@ export type PagePropsType = {
|
||||
mcps_overview?: McpServersOverviewPayload | null;
|
||||
tools_overview?: CustomToolsOverviewPayload | null;
|
||||
bot_extensions?: BotExtensionsPayload | null;
|
||||
/**
|
||||
* The files browser's folders (a user's own crew and media, and what a
|
||||
* super admin may open) and the folder it is showing.
|
||||
*/
|
||||
storage_folders?: StorageSpecOption[] | null;
|
||||
storage_folder?: StorageFolderPayload | null;
|
||||
/**
|
||||
* The open conversation's files (a bot's own, or a workspace's shared
|
||||
* ones), for the files modal and the chips that name one.
|
||||
|
||||
Reference in New Issue
Block a user