Files
test1/components/nova-sdk/hooks/useFileUploader.ts
2026-03-20 07:33:46 +00:00

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(','),
}
}