初始化模版工程
This commit is contained in:
166
components/nova-sdk/hooks/useFileUploader.ts
Normal file
166
components/nova-sdk/hooks/useFileUploader.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
|
||||
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(','),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user