agent-Specialization/static/src/stores/upload.ts

248 lines
7.5 KiB
TypeScript

import { defineStore } from 'pinia';
import { useUiStore } from './ui';
import { useFileStore } from './file';
import { usePolicyStore } from './policy';
function deriveFilenameFromType(mimeType: string | undefined): string | null {
if (!mimeType) return null;
const map: Record<string, string> = {
'image/jpeg': 'image.jpg',
'image/jpg': 'image.jpg',
'image/png': 'image.png',
'image/gif': 'image.gif',
'image/webp': 'image.webp',
'image/bmp': 'image.bmp',
'image/svg+xml': 'image.svg',
'video/mp4': 'video.mp4',
'video/quicktime': 'video.mov',
'video/x-matroska': 'video.mkv',
'video/avi': 'video.avi',
'video/webm': 'video.webm',
'application/pdf': 'document.pdf',
'text/plain': 'document.txt',
'text/markdown': 'document.md',
};
// 兼容类似 image/jpeg; charset=utf-8 的情况
const normalized = mimeType.split(';')[0].trim().toLowerCase();
return map[normalized] || null;
}
interface UploadResult {
path: string;
filename: string;
}
interface UploadState {
uploading: boolean;
mediaUploading: boolean;
}
export const useUploadStore = defineStore('upload', {
state: (): UploadState => ({
uploading: false,
mediaUploading: false
}),
actions: {
setUploading(value: boolean) {
this.uploading = value;
},
setMediaUploading(value: boolean) {
this.mediaUploading = value;
},
async handleSelectedFiles(files: FileList | File[] | null) {
if (!files) {
return;
}
const list = Array.isArray(files) ? files : Array.from(files);
const [file] = list;
if (!file) {
return;
}
await this.uploadFile(file);
},
async uploadFiles(
files: FileList | File[] | null,
options: { markUploading?: boolean; markMediaUploading?: boolean } = {}
): Promise<UploadResult[]> {
if (!files) {
return [];
}
const list = Array.isArray(files) ? files : Array.from(files);
if (!list.length) {
return [];
}
const { markUploading = true, markMediaUploading = false } = options;
const policyStore = usePolicyStore();
if (policyStore.uiBlocks?.block_upload) {
const uiStore = useUiStore();
uiStore.pushToast({
title: '上传被禁用',
message: '已被管理员禁用上传功能',
type: 'warning'
});
return [];
}
const fileStore = useFileStore();
const results: UploadResult[] = [];
if (markUploading) {
this.setUploading(true);
}
if (markMediaUploading) {
this.setMediaUploading(true);
}
try {
for (const file of list) {
const uploaded = await this.uploadFile(file, {
manageUploading: false,
refreshFileTree: false,
skipPolicyCheck: true
});
if (uploaded?.path) {
results.push(uploaded);
}
}
if (results.length) {
await fileStore.fetchFileTree();
}
} finally {
if (markMediaUploading) {
this.setMediaUploading(false);
}
if (markUploading) {
this.setUploading(false);
}
}
return results;
},
async uploadFile(
file: File,
options: {
manageUploading?: boolean;
refreshFileTree?: boolean;
skipPolicyCheck?: boolean;
} = {}
): Promise<UploadResult | null> {
const { manageUploading = true, refreshFileTree = true, skipPolicyCheck = false } = options;
if (!file || (manageUploading && this.uploading)) {
return null;
}
const policyStore = usePolicyStore();
if (!skipPolicyCheck && policyStore.uiBlocks?.block_upload) {
const uiStore = useUiStore();
uiStore.pushToast({
title: '上传被禁用',
message: '已被管理员禁用上传功能',
type: 'warning'
});
return null;
}
const uiStore = useUiStore();
const fileStore = useFileStore();
const isVideoFile =
(file.type && file.type.startsWith('video/')) ||
/\.(mp4|mov|m4v|webm|avi|mkv|flv|mpg|mpeg)$/i.test(file.name || '');
if (isVideoFile) {
uiStore.pushToast({
title: '视频处理中',
message: '读取视频需要较长时间,请耐心等待',
type: 'info',
duration: 5000
});
}
if (manageUploading) {
this.setUploading(true);
}
const toastId = uiStore.pushToast({
title: '上传文件',
message: `正在上传 ${file.name}...`,
type: 'info',
duration: null
});
try {
const safeName = file.name || deriveFilenameFromType(file.type) || `upload_${Date.now()}`;
const formData = new FormData();
formData.append('file', file);
formData.append('filename', safeName);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (toastId) {
uiStore.updateToast(toastId, {
message: '文件已上传,正在执行安全扫描...',
type: 'info'
});
}
let result: any = {};
try {
result = await response.json();
} catch (error) {
throw new Error('服务器响应无法解析');
}
if (!response.ok || !result.success) {
const message = result.error || result.message || '上传失败';
throw new Error(message);
}
if (refreshFileTree) {
await fileStore.fetchFileTree();
}
const successMessage = `上传成功:${result.path || file.name}`;
if (toastId) {
uiStore.updateToast(toastId, {
title: '上传完成',
message: successMessage,
type: 'success',
duration: 4500
});
} else {
uiStore.pushToast({
title: '上传完成',
message: successMessage,
type: 'success'
});
}
if (result?.path) {
return {
path: result.path,
filename: result.filename || file.name || result.path
};
}
return null;
} catch (error: any) {
const originalMessage = error && error.message ? String(error.message) : '';
const fileLabel = file && file.name ? file.name : '文件';
let displayMessage = originalMessage || '请稍后重试';
if (/安全审核未通过/.test(originalMessage) || /Eicar/i.test(originalMessage)) {
displayMessage = `${fileLabel} 安全审核未通过`;
} else if (/文件类型不在允许列表中/.test(originalMessage)) {
displayMessage = `${fileLabel} 文件类型不在允许列表中`;
} else if (displayMessage) {
displayMessage = `${fileLabel} 上传失败:${displayMessage}`;
} else {
displayMessage = `${fileLabel} 上传失败,请稍后重试`;
}
const uiStore = useUiStore();
if (toastId) {
uiStore.updateToast(toastId, {
title: '上传失败',
message: displayMessage,
type: 'error',
duration: 5000
});
} else {
uiStore.pushToast({
title: '上传失败',
message: displayMessage,
type: 'error'
});
}
return null;
} finally {
if (manageUploading) {
this.setUploading(false);
}
}
}
}
});