feat: integrate MinIO-backed user filesystem
This commit is contained in:
@ -1363,3 +1363,112 @@ export async function createWorkspaceDir(path: string): Promise<WorkspaceItem> {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// User File System
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface UserFileItem {
|
||||
name: string;
|
||||
path: string;
|
||||
type: 'file' | 'directory';
|
||||
size: number | null;
|
||||
content_type?: string | null;
|
||||
modified?: string | null;
|
||||
}
|
||||
|
||||
export interface UserFileBrowseResult {
|
||||
path: string;
|
||||
items: UserFileItem[];
|
||||
}
|
||||
|
||||
export interface UserFileContent {
|
||||
name: string;
|
||||
path: string;
|
||||
size: number;
|
||||
content_type: string;
|
||||
modified: string | null;
|
||||
is_binary: boolean;
|
||||
is_truncated: boolean;
|
||||
content: string | null;
|
||||
}
|
||||
|
||||
export interface UserFilesStatus {
|
||||
configured: boolean;
|
||||
storage_mode: string;
|
||||
roots: string[];
|
||||
workspace_visible: boolean;
|
||||
}
|
||||
|
||||
export async function getUserFilesStatus(): Promise<UserFilesStatus> {
|
||||
return fetchJSON('/api/user-files/status');
|
||||
}
|
||||
|
||||
export async function browseUserFiles(path: string = ''): Promise<UserFileBrowseResult> {
|
||||
const params = path ? `?path=${encodeURIComponent(path)}` : '';
|
||||
return fetchJSON(`/api/user-files/browse${params}`);
|
||||
}
|
||||
|
||||
export async function getUserFile(path: string): Promise<UserFileContent> {
|
||||
return fetchJSON(`/api/user-files/preview?path=${encodeURIComponent(path)}`);
|
||||
}
|
||||
|
||||
export function getUserFileDownloadUrl(path: string): string {
|
||||
return buildApiUrl(`/api/user-files/download?path=${encodeURIComponent(path)}`);
|
||||
}
|
||||
|
||||
export async function uploadUserFile(
|
||||
file: File,
|
||||
dirPath: string = 'uploads',
|
||||
onProgress?: (percent: number) => void
|
||||
): Promise<UserFileItem> {
|
||||
const locale = getCurrentAppLocale();
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('path', dirPath);
|
||||
|
||||
return new Promise<UserFileItem>((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', buildApiUrl('/api/user-files/upload'));
|
||||
const token = getAccessToken();
|
||||
if (token) {
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable && onProgress) {
|
||||
onProgress(Math.round((e.loaded / e.total) * 100));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(JSON.parse(xhr.responseText));
|
||||
} else {
|
||||
let detail = '';
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
detail = typeof data?.detail === 'string' ? data.detail : '';
|
||||
} catch {
|
||||
detail = '';
|
||||
}
|
||||
reject(new Error(detail || `${pickAppText(locale, '上传失败', 'Upload failed')}: ${xhr.status}`));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => reject(new Error(pickAppText(locale, '上传失败', 'Upload failed')));
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteUserFile(path: string): Promise<void> {
|
||||
await fetchJSON(`/api/user-files/delete?path=${encodeURIComponent(path)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
export async function createUserFileDir(path: string): Promise<UserFileItem> {
|
||||
return fetchJSON(`/api/user-files/mkdir?path=${encodeURIComponent(path)}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user