mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 08:00:05 +00:00
- Import ImageRef from maskEditorDataStore instead of duplicating - Replace 'any' with proper UploadApiResponse type - Add validation for dataURL strings - Fix test mocking: use vi.spyOn for global.fetch - Fix uploadMediaBatch test to use distinct response mocks - Add test for invalid dataURL rejection - Fix uploadMultipleFiles to return array of successful paths - Optimize file size test to avoid timeout
156 lines
3.5 KiB
TypeScript
156 lines
3.5 KiB
TypeScript
import type { ResultItemType } from '@/schemas/apiSchema'
|
|
import { api } from '@/scripts/api'
|
|
import type { ImageRef } from '@/stores/maskEditorDataStore'
|
|
|
|
interface UploadInput {
|
|
source: File | Blob | string
|
|
filename?: string
|
|
}
|
|
|
|
interface UploadConfig {
|
|
subfolder?: string
|
|
type?: ResultItemType
|
|
endpoint?: '/upload/image' | '/upload/mask'
|
|
originalRef?: ImageRef
|
|
maxSizeMB?: number
|
|
}
|
|
|
|
interface UploadApiResponse {
|
|
name: string
|
|
subfolder?: string
|
|
type?: string
|
|
}
|
|
|
|
interface UploadResult {
|
|
success: boolean
|
|
path: string
|
|
name: string
|
|
subfolder: string
|
|
error?: string
|
|
response: UploadApiResponse | null
|
|
}
|
|
|
|
function isDataURL(str: string): boolean {
|
|
return typeof str === 'string' && str.startsWith('data:')
|
|
}
|
|
|
|
async function convertToFile(
|
|
input: UploadInput,
|
|
mimeType: string = 'image/png'
|
|
): Promise<File> {
|
|
const { source, filename } = input
|
|
|
|
if (source instanceof File) {
|
|
return source
|
|
}
|
|
|
|
if (source instanceof Blob) {
|
|
const name = filename || `upload-${Date.now()}.png`
|
|
return new File([source], name, { type: mimeType })
|
|
}
|
|
|
|
// dataURL string
|
|
if (!isDataURL(source)) {
|
|
throw new Error(`Invalid data URL: ${source.substring(0, 50)}...`)
|
|
}
|
|
|
|
try {
|
|
const blob = await fetch(source).then((r) => r.blob())
|
|
const name = filename || `upload-${Date.now()}.png`
|
|
return new File([blob], name, { type: mimeType })
|
|
} catch (error) {
|
|
throw new Error(
|
|
`Failed to convert data URL to file: ${error instanceof Error ? error.message : String(error)}`
|
|
)
|
|
}
|
|
}
|
|
|
|
function validateFileSize(file: File, maxSizeMB?: number): string | null {
|
|
if (!maxSizeMB) return null
|
|
|
|
const fileSizeMB = file.size / 1024 / 1024
|
|
if (fileSizeMB > maxSizeMB) {
|
|
return `File size ${fileSizeMB.toFixed(1)}MB exceeds maximum ${maxSizeMB}MB`
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export async function uploadMedia(
|
|
input: UploadInput,
|
|
config: UploadConfig = {}
|
|
): Promise<UploadResult> {
|
|
const {
|
|
subfolder,
|
|
type,
|
|
endpoint = '/upload/image',
|
|
originalRef,
|
|
maxSizeMB
|
|
} = config
|
|
|
|
try {
|
|
const file = await convertToFile(input)
|
|
|
|
const sizeError = validateFileSize(file, maxSizeMB)
|
|
if (sizeError) {
|
|
return {
|
|
success: false,
|
|
path: '',
|
|
name: '',
|
|
subfolder: '',
|
|
error: sizeError,
|
|
response: null
|
|
}
|
|
}
|
|
|
|
const body = new FormData()
|
|
body.append('image', file)
|
|
if (subfolder) body.append('subfolder', subfolder)
|
|
if (type) body.append('type', type)
|
|
if (originalRef) body.append('original_ref', JSON.stringify(originalRef))
|
|
|
|
const resp = await api.fetchApi(endpoint, {
|
|
method: 'POST',
|
|
body
|
|
})
|
|
|
|
if (resp.status !== 200) {
|
|
return {
|
|
success: false,
|
|
path: '',
|
|
name: '',
|
|
subfolder: '',
|
|
error: `${resp.status} - ${resp.statusText}`,
|
|
response: null
|
|
}
|
|
}
|
|
|
|
const data: UploadApiResponse = await resp.json()
|
|
const path = data.subfolder ? `${data.subfolder}/${data.name}` : data.name
|
|
|
|
return {
|
|
success: true,
|
|
path,
|
|
name: data.name,
|
|
subfolder: data.subfolder || '',
|
|
response: data
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
path: '',
|
|
name: '',
|
|
subfolder: '',
|
|
error: error instanceof Error ? error.message : String(error),
|
|
response: null
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function uploadMediaBatch(
|
|
inputs: UploadInput[],
|
|
config: UploadConfig = {}
|
|
): Promise<UploadResult[]> {
|
|
return Promise.all(inputs.map((input) => uploadMedia(input, config)))
|
|
}
|