UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

122 lines (104 loc) 3.66 kB
import { fileEnv } from '@/config/file'; import { edgeClient } from '@/libs/trpc/client'; import { API_ENDPOINTS } from '@/services/_url'; import { clientS3Storage } from '@/services/file/ClientS3'; import { FileMetadata } from '@/types/files'; import { FileUploadState, FileUploadStatus } from '@/types/files/upload'; import { uuid } from '@/utils/uuid'; export const UPLOAD_NETWORK_ERROR = 'NetWorkError'; class UploadService { uploadWithProgress = async ( file: File, { onProgress, directory, }: { directory?: string; onProgress?: (status: FileUploadStatus, state: FileUploadState) => void; }, ): Promise<FileMetadata> => { const xhr = new XMLHttpRequest(); const { preSignUrl, ...result } = await this.getSignedUploadUrl(file, directory); let startTime = Date.now(); xhr.upload.addEventListener('progress', (event) => { if (event.lengthComputable) { const progress = Number(((event.loaded / event.total) * 100).toFixed(1)); const speedInByte = event.loaded / ((Date.now() - startTime) / 1000); onProgress?.('uploading', { // if the progress is 100, it means the file is uploaded // but the server is still processing it // so make it as 99.9 and let users think it's still uploading progress: progress === 100 ? 99.9 : progress, restTime: (event.total - event.loaded) / speedInByte, speed: speedInByte, }); } }); xhr.open('PUT', preSignUrl); xhr.setRequestHeader('Content-Type', file.type); const data = await file.arrayBuffer(); await new Promise((resolve, reject) => { xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { onProgress?.('success', { progress: 100, restTime: 0, speed: file.size / ((Date.now() - startTime) / 1000), }); resolve(xhr.response); } else { reject(xhr.statusText); } }); xhr.addEventListener('error', () => { if (xhr.status === 0) reject(UPLOAD_NETWORK_ERROR); else reject(xhr.statusText); }); xhr.send(data); }); return result; }; uploadToClientS3 = async (hash: string, file: File): Promise<FileMetadata> => { await clientS3Storage.putObject(hash, file); return { date: (Date.now() / 1000 / 60 / 60).toFixed(0), dirname: '', filename: file.name, path: `client-s3://${hash}`, }; }; /** * get image File item with cors image URL * @param url * @param filename * @param fileType */ getImageFileByUrlWithCORS = async (url: string, filename: string, fileType = 'image/png') => { const res = await fetch(API_ENDPOINTS.proxy, { body: url, method: 'POST' }); const data = await res.arrayBuffer(); return new File([data], filename, { lastModified: Date.now(), type: fileType }); }; private getSignedUploadUrl = async ( file: File, directory?: string, ): Promise< FileMetadata & { preSignUrl: string; } > => { const filename = `${uuid()}.${file.name.split('.').at(-1)}`; // 精确到以 h 为单位的 path const date = (Date.now() / 1000 / 60 / 60).toFixed(0); const dirname = `${directory || fileEnv.NEXT_PUBLIC_S3_FILE_PATH}/${date}`; const pathname = `${dirname}/${filename}`; const preSignUrl = await edgeClient.upload.createS3PreSignedUrl.mutate({ pathname }); return { date, dirname, filename, path: pathname, preSignUrl, }; }; } export const uploadService = new UploadService();