// @ts-nocheck import { usePolicyStore } from '../../stores/policy'; export const uploadMethods = { triggerFileUpload() { if (this.uploading) { return; } const input = this.getComposerElement('fileUploadInput'); if (input) { input.click(); } }, handleFileSelected(files) { const policyStore = usePolicyStore(); if (policyStore.uiBlocks?.block_upload) { this.uiPushToast({ title: '上传被禁用', message: '已被管理员禁用上传功能', type: 'warning' }); return; } this.uploadHandleSelected(files); }, normalizeLocalFiles(files) { if (!files) return []; const list = Array.isArray(files) ? files : Array.from(files); return list.filter(Boolean); }, isImageFile(file) { const name = file?.name || ''; const type = file?.type || ''; return type.startsWith('image/') || /\.(png|jpe?g|webp|gif|bmp|svg)$/i.test(name); }, isVideoFile(file) { const name = file?.name || ''; const type = file?.type || ''; return type.startsWith('video/') || /\.(mp4|mov|m4v|webm|avi|mkv|flv|mpg|mpeg)$/i.test(name); }, upsertImageEntry(path, filename) { if (!path) return; const name = filename || path.split('/').pop() || path; const list = Array.isArray(this.imageEntries) ? this.imageEntries : []; if (list.some((item) => item.path === path)) { return; } this.imageEntries = [{ name, path }, ...list]; }, upsertVideoEntry(path, filename) { if (!path) return; const name = filename || path.split('/').pop() || path; const list = Array.isArray(this.videoEntries) ? this.videoEntries : []; if (list.some((item) => item.path === path)) { return; } this.videoEntries = [{ name, path }, ...list]; }, async handleLocalImageFiles(files) { if (!this.isConnected) { return; } if (this.mediaUploading) { this.uiPushToast({ title: '上传中', message: '请等待当前图片上传完成', type: 'info' }); return; } const list = this.normalizeLocalFiles(files); if (!list.length) { return; } const existingCount = Array.isArray(this.selectedImages) ? this.selectedImages.length : 0; const remaining = Math.max(0, 9 - existingCount); if (!remaining) { this.uiPushToast({ title: '已达上限', message: '最多只能选择 9 张图片', type: 'warning' }); return; } const valid = list.filter((file) => this.isImageFile(file)); // 兼容 Android WebView 部分机型:返回的 File 可能缺失 type/扩展名,导致识别失败 // 若来自图片选择器但未识别出有效类型,回退为“按选择结果直接上传”。 const candidates = valid.length ? valid : list; if (valid.length < list.length && valid.length > 0) { this.uiPushToast({ title: '已忽略', message: '已跳过非图片文件', type: 'info' }); } const limited = candidates.slice(0, remaining); if (candidates.length > remaining) { this.uiPushToast({ title: '已超出数量', message: `最多还能添加 ${remaining} 张图片,已自动截断`, type: 'warning' }); } const uploaded = await this.uploadBatchFiles(limited, { markUploading: true, markMediaUploading: true }); if (!uploaded.length) { return; } uploaded.forEach((item) => { if (!item?.path) return; this.inputAddSelectedImage(item.path); this.upsertImageEntry(item.path, item.filename); }); // 上传完成后自动关闭选择窗口 this.closeImagePicker(); }, async handleLocalVideoFiles(files) { if (!this.isConnected) { return; } if (this.mediaUploading) { this.uiPushToast({ title: '上传中', message: '请等待当前视频上传完成', type: 'info' }); return; } const list = this.normalizeLocalFiles(files); if (!list.length) { return; } const valid = list.filter((file) => this.isVideoFile(file)); // 兼容 Android WebView:文件元数据缺失时回退按选择结果直接上传 const candidates = valid.length ? valid : list; if (valid.length < list.length && valid.length > 0) { this.uiPushToast({ title: '已忽略', message: '已跳过非视频文件', type: 'info' }); } if (candidates.length > 1) { this.uiPushToast({ title: '视频数量过多', message: '一次只能选择 1 个视频,已使用第一个', type: 'warning' }); } const [file] = candidates; if (!file) { return; } const uploaded = await this.uploadBatchFiles([file], { markUploading: true, markMediaUploading: true }); const [item] = uploaded; if (!item?.path) { return; } this.inputSetSelectedVideos([item.path]); this.inputClearSelectedImages(); this.upsertVideoEntry(item.path, item.filename); }, async openImagePicker() { if (!['qwen3-vl-plus', 'kimi-k2.5'].includes(this.currentModelKey)) { this.uiPushToast({ title: '当前模型不支持图片', message: '请选择 Qwen3.5 或 Kimi-k2.5 后再发送图片', type: 'error' }); return; } this.closeQuickMenu(); this.inputSetImagePickerOpen(true); await this.loadWorkspaceImages(); }, closeImagePicker() { this.inputSetImagePickerOpen(false); }, async openVideoPicker() { if (!['qwen3-vl-plus', 'kimi-k2.5'].includes(this.currentModelKey)) { this.uiPushToast({ title: '当前模型不支持视频', message: '请切换到 Qwen3.5 或 Kimi-k2.5 后再发送视频', type: 'error' }); return; } this.closeQuickMenu(); this.inputSetVideoPickerOpen(true); await this.loadWorkspaceVideos(); }, closeVideoPicker() { this.inputSetVideoPickerOpen(false); }, async loadWorkspaceImages() { this.imageLoading = true; try { const entries = await this.fetchAllImageEntries(''); this.imageEntries = entries; if (!entries.length) { this.uiPushToast({ title: '未找到图片', message: '工作区内没有可用的图片文件', type: 'info' }); } } catch (error) { console.error('加载图片列表失败', error); this.uiPushToast({ title: '加载图片失败', message: error?.message || '请稍后重试', type: 'error' }); } finally { this.imageLoading = false; } }, async fetchAllImageEntries(startPath = '') { const queue: string[] = [startPath || '']; const visited = new Set(); const results: Array<{ name: string; path: string }> = []; const exts = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp', '.svg']); const maxFolders = 120; while (queue.length && visited.size < maxFolders) { const path = queue.shift() || ''; if (visited.has(path)) { continue; } visited.add(path); try { const resp = await fetch(`/api/gui/files/entries?path=${encodeURIComponent(path)}`, { method: 'GET', credentials: 'include', headers: { Accept: 'application/json' } }); const data = await resp.json().catch(() => null); if (!data?.success) { continue; } const items = Array.isArray(data?.data?.items) ? data.data.items : []; for (const item of items) { const rawPath = item?.path || [path, item?.name].filter(Boolean).join('/').replace(/\\/g, '/').replace(/\/{2,}/g, '/'); const type = String(item?.type || '').toLowerCase(); if (type === 'directory' || type === 'folder') { queue.push(rawPath); continue; } const ext = String(item?.extension || '').toLowerCase() || (rawPath.includes('.') ? `.${rawPath.split('.').pop()?.toLowerCase()}` : ''); if (exts.has(ext)) { results.push({ name: item?.name || rawPath.split('/').pop() || rawPath, path: rawPath }); if (results.length >= 400) { return results; } } } } catch (error) { console.warn('遍历文件夹失败', path, error); } } return results; }, async fetchAllVideoEntries(startPath = '') { const queue: string[] = [startPath || '']; const visited = new Set(); const results: Array<{ name: string; path: string }> = []; const exts = new Set(['.mp4', '.mov', '.mkv', '.avi', '.webm']); const maxFolders = 120; while (queue.length && visited.size < maxFolders) { const path = queue.shift() || ''; if (visited.has(path)) { continue; } visited.add(path); try { const resp = await fetch(`/api/gui/files/entries?path=${encodeURIComponent(path)}`, { method: 'GET', credentials: 'include', headers: { Accept: 'application/json' } }); const data = await resp.json().catch(() => null); if (!data?.success) { continue; } const items = Array.isArray(data?.data?.items) ? data.data.items : []; for (const item of items) { const rawPath = item?.path || [path, item?.name].filter(Boolean).join('/').replace(/\\/g, '/').replace(/\/{2,}/g, '/'); const type = String(item?.type || '').toLowerCase(); if (type === 'directory' || type === 'folder') { queue.push(rawPath); continue; } const ext = String(item?.extension || '').toLowerCase() || (rawPath.includes('.') ? `.${rawPath.split('.').pop()?.toLowerCase()}` : ''); if (exts.has(ext)) { results.push({ name: item?.name || rawPath.split('/').pop() || rawPath, path: rawPath }); if (results.length >= 200) { return results; } } } } catch (error) { console.warn('遍历文件夹失败', path, error); } } return results; }, async loadWorkspaceVideos() { this.videoLoading = true; try { const entries = await this.fetchAllVideoEntries(''); this.videoEntries = entries; if (!entries.length) { this.uiPushToast({ title: '未找到视频', message: '工作区内没有可用的视频文件', type: 'info' }); } } catch (error) { console.error('加载视频列表失败', error); this.uiPushToast({ title: '加载视频失败', message: error?.message || '请稍后重试', type: 'error' }); } finally { this.videoLoading = false; } }, handleImagesConfirmed(list) { this.inputSetSelectedImages(Array.isArray(list) ? list : []); this.inputSetImagePickerOpen(false); }, handleRemoveImage(path) { this.inputRemoveSelectedImage(path); }, handleVideosConfirmed(list) { const arr = Array.isArray(list) ? list.slice(0, 1) : []; this.inputSetSelectedVideos(arr); this.inputSetVideoPickerOpen(false); if (arr.length) { this.inputClearSelectedImages(); } }, handleRemoveVideo(path) { this.inputRemoveSelectedVideo(path); }, handleQuickUpload() { if (this.uploading || !this.isConnected) { return; } if (this.isPolicyBlocked('block_upload', '上传功能已被管理员禁用')) { return; } this.triggerFileUpload(); } };