167 lines
5.2 KiB
TypeScript
167 lines
5.2 KiB
TypeScript
|
|
import { useCallback, useRef, useMemo } from 'react'
|
|
import type { UploadFile } from '../types'
|
|
import { request } from '@/http/request'
|
|
import { createCustomOSSUploader } from '@bty/uploader'
|
|
import { getSTSToken, getOssSignatureUrl } from '@apis/oss'
|
|
|
|
// Simple helper to mimic MIME type detection
|
|
export const ACCEPT_FILE_TYPE_LIST = [
|
|
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
'.txt', '.json', '.csv', '.md',
|
|
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg', '.ico',
|
|
'.html', '.py', '.jsonld', '.xml', '.zip',
|
|
'.mp3', '.mp4', '.mov', '.m4a',
|
|
'.pdb', '.mermaid',
|
|
]
|
|
|
|
export function getMimeByAcceptList(filename: string): string | undefined {
|
|
const ext = `.${(filename.split('.').pop() || '').toLowerCase()}`
|
|
const map: Record<string, string> = {
|
|
'.pdf': 'application/pdf',
|
|
'.doc': 'application/msword',
|
|
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'.xls': 'application/vnd.ms-excel',
|
|
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
'.ppt': 'application/vnd.ms-powerpoint',
|
|
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
'.txt': 'text/plain',
|
|
'.json': 'application/json',
|
|
'.csv': 'text/csv',
|
|
'.md': 'text/markdown',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.jpeg': 'image/jpeg',
|
|
'.gif': 'image/gif',
|
|
'.bmp': 'image/bmp',
|
|
'.webp': 'image/webp',
|
|
'.svg': 'image/svg+xml',
|
|
'.ico': 'image/x-icon',
|
|
'.html': 'text/html',
|
|
'.jsonld': 'application/ld+json',
|
|
'.pdb': 'application/vnd.microsoft.portable-executable',
|
|
'.mermaid': 'text/mermaid',
|
|
}
|
|
return map[ext] || undefined
|
|
}
|
|
|
|
export interface UseFileUploaderProps {
|
|
onUploadStart?: (file: UploadFile) => void
|
|
onUploadEnd?: (file: UploadFile) => void
|
|
onUploadError?: (error: Error, file?: UploadFile) => void
|
|
onFileUpdate?: (file: UploadFile) => void
|
|
}
|
|
|
|
export function useFileUploader({
|
|
onUploadStart,
|
|
onUploadEnd,
|
|
onUploadError,
|
|
onFileUpdate,
|
|
}: UseFileUploaderProps = {}) {
|
|
const uploadUUId = useRef<string>('')
|
|
|
|
// Initialize OSS Uploader with STS token provider from our local package
|
|
const ossUploader = useMemo(() => {
|
|
return createCustomOSSUploader(getSTSToken)
|
|
}, [])
|
|
|
|
const uploadFile = useCallback(async (file: File) => {
|
|
// 1. Validation
|
|
const isValidSize = file.size <= 100 * 1024 * 1024 // 100MB
|
|
if (!isValidSize) {
|
|
console.warn('File size exceeds 100MB limit')
|
|
return
|
|
}
|
|
|
|
// 2. Init file object and State
|
|
const uid = crypto.randomUUID()
|
|
const mimeType = getMimeByAcceptList(file.name) || file.type || 'application/octet-stream'
|
|
|
|
const tempFile: UploadFile = {
|
|
uid,
|
|
name: file.name,
|
|
type: mimeType,
|
|
byte_size: file.size,
|
|
uploadStatus: 'pending',
|
|
progress: 0,
|
|
url: URL.createObjectURL(file),
|
|
}
|
|
|
|
onUploadStart?.(tempFile)
|
|
|
|
try {
|
|
// 3. Construct File Path (Business Logic)
|
|
const timestamp = new Date().valueOf()
|
|
const uuid = crypto.randomUUID()
|
|
uploadUUId.current = uuid
|
|
const filePath = `super_agent/user_upload_file/${uuid}/_${timestamp}_${file.name}`
|
|
|
|
// 4. Upload to OSS using the shared package
|
|
await ossUploader.multipartUpload({
|
|
filePath,
|
|
file,
|
|
options: {
|
|
headers: {
|
|
'Content-Type': mimeType,
|
|
'Content-Disposition': 'inline',
|
|
},
|
|
progress: (progress: number) => {
|
|
// OSS SDK returns progress as 0-1
|
|
onFileUpdate?.({
|
|
...tempFile,
|
|
progress: Math.floor(progress * 100),
|
|
uploadStatus: 'uploading'
|
|
})
|
|
}
|
|
},
|
|
})
|
|
|
|
// 5. Get Signature URL (Optional / if private)
|
|
const signatureUrl = await getOssSignatureUrl(filePath)
|
|
|
|
// 6. Create File Upload Record (Backend Sync)
|
|
const lastDotIndex = file.name.lastIndexOf('.')
|
|
const splitName = lastDotIndex !== -1
|
|
? [file.name.substring(0, lastDotIndex), file.name.substring(lastDotIndex + 1)]
|
|
: [file.name]
|
|
|
|
const safeName = `${splitName[0]}-${Math.random().toString(36).substring(2, 5)}${splitName.length > 1 ? `.${splitName[1]}` : ''}`
|
|
|
|
const res = await request.post<{ file_upload_record_id: string }>('/file/record', {
|
|
file_url: filePath,
|
|
file_type: file.type || 'application/octet-stream',
|
|
file_name: safeName,
|
|
file_byte_size: file.size || 0,
|
|
conversation_id: uuid,
|
|
})
|
|
|
|
// 7. Finalize
|
|
const finalFile: UploadFile = {
|
|
...tempFile,
|
|
name: safeName,
|
|
url: signatureUrl,
|
|
upload_file_id: res.file_upload_record_id,
|
|
progress: 100,
|
|
uploadStatus: 'success',
|
|
}
|
|
|
|
onFileUpdate?.(finalFile)
|
|
onUploadEnd?.(finalFile)
|
|
|
|
} catch (error) {
|
|
console.error('Upload failed:', error)
|
|
const errorFile: UploadFile = {
|
|
...tempFile,
|
|
uploadStatus: 'error'
|
|
}
|
|
onFileUpdate?.(errorFile)
|
|
onUploadError?.(error as Error, errorFile)
|
|
}
|
|
}, [ossUploader, onUploadStart, onUploadEnd, onUploadError, onFileUpdate])
|
|
|
|
return {
|
|
uploadFile,
|
|
accept: ACCEPT_FILE_TYPE_LIST.join(','),
|
|
}
|
|
}
|