MogensR's picture
Create web/src/lib/api/processing.ts
5d187ed
raw
history blame
6.94 kB
import { apiClient } from './client'
import { io, Socket } from 'socket.io-client'
export interface ProcessingOptions {
model?: 'rembg' | 'u2net' | 'deeplab' | 'custom'
quality?: 'low' | 'medium' | 'high' | 'ultra'
returnMask?: boolean
edgeRefinement?: number
feather?: number
tolerance?: number
preserveDetails?: boolean
}
export interface ProcessingResult {
id: string
image: string
mask?: string
metadata: {
width: number
height: number
format: string
processingTime: number
}
}
export interface BatchJob {
id: string
status: 'pending' | 'processing' | 'completed' | 'failed'
progress: number
totalFiles: number
processedFiles: number
results?: ProcessingResult[]
error?: string
createdAt: string
completedAt?: string
}
class ProcessingAPI {
private socket: Socket | null = null
private listeners: Map<string, Set<Function>> = new Map()
/**
* Process a single image
*/
async processImage(
file: File,
options: ProcessingOptions = {},
onProgress?: (progress: number) => void
): Promise<ProcessingResult> {
const formData = new FormData()
formData.append('file', file)
formData.append('options', JSON.stringify(options))
return apiClient.upload<ProcessingResult>(
'/api/v1/process/remove-background',
file,
onProgress
)
}
/**
* Process multiple images in batch
*/
async processBatch(
files: File[],
options: ProcessingOptions = {}
): Promise<BatchJob> {
const formData = new FormData()
files.forEach((file) => formData.append('files', file))
formData.append('options', JSON.stringify(options))
const response = await apiClient.post<BatchJob>('/api/v1/process/batch', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
// Connect to WebSocket for real-time updates
this.connectWebSocket(response.id)
return response
}
/**
* Get batch job status
*/
async getJobStatus(jobId: string): Promise<BatchJob> {
return apiClient.get<BatchJob>(`/api/v1/process/jobs/${jobId}`)
}
/**
* Cancel a batch job
*/
async cancelJob(jobId: string): Promise<void> {
await apiClient.post(`/api/v1/process/jobs/${jobId}/cancel`)
}
/**
* Replace background with a new one
*/
async replaceBackground(
imageId: string,
background: string | File
): Promise<ProcessingResult> {
const formData = new FormData()
formData.append('imageId', imageId)
if (background instanceof File) {
formData.append('background', background)
} else {
formData.append('backgroundUrl', background)
}
return apiClient.post<ProcessingResult>(
'/api/v1/process/replace-background',
formData,
{ headers: { 'Content-Type': 'multipart/form-data' } }
)
}
/**
* Apply AI-powered enhancements
*/
async enhanceImage(
imageId: string,
enhancements: {
sharpness?: number
contrast?: number
brightness?: number
saturation?: number
denoise?: boolean
upscale?: 2 | 4
}
): Promise<ProcessingResult> {
return apiClient.post<ProcessingResult>('/api/v1/process/enhance', {
imageId,
enhancements,
})
}
/**
* Generate AI background
*/
async generateBackground(
prompt: string,
style?: 'realistic' | 'artistic' | 'abstract' | 'gradient'
): Promise<{ id: string; url: string }> {
return apiClient.post('/api/v1/backgrounds/generate', {
prompt,
style,
})
}
/**
* Smart edge detection and refinement
*/
async refineEdges(
imageId: string,
maskId: string,
options: {
mode: 'hair' | 'fur' | 'smooth' | 'detailed'
strength: number
}
): Promise<ProcessingResult> {
return apiClient.post<ProcessingResult>('/api/v1/process/refine-edges', {
imageId,
maskId,
...options,
})
}
/**
* Connect to WebSocket for real-time updates
*/
private connectWebSocket(jobId: string) {
if (this.socket?.connected) {
this.socket.emit('subscribe', { jobId })
return
}
const wsUrl = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:8000'
this.socket = io(wsUrl, {
transports: ['websocket'],
query: { jobId },
})
this.socket.on('connect', () => {
console.log('WebSocket connected')
this.socket?.emit('subscribe', { jobId })
})
this.socket.on('job:progress', (data) => {
this.emit('progress', data)
})
this.socket.on('job:complete', (data) => {
this.emit('complete', data)
this.disconnectWebSocket()
})
this.socket.on('job:error', (data) => {
this.emit('error', data)
this.disconnectWebSocket()
})
this.socket.on('disconnect', () => {
console.log('WebSocket disconnected')
})
}
/**
* Disconnect WebSocket
*/
private disconnectWebSocket() {
if (this.socket) {
this.socket.disconnect()
this.socket = null
}
}
/**
* Subscribe to events
*/
on(event: string, callback: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set())
}
this.listeners.get(event)?.add(callback)
}
/**
* Unsubscribe from events
*/
off(event: string, callback: Function) {
this.listeners.get(event)?.delete(callback)
}
/**
* Emit events
*/
private emit(event: string, data: any) {
this.listeners.get(event)?.forEach((callback) => callback(data))
}
/**
* Estimate processing time
*/
estimateProcessingTime(
fileSize: number,
options: ProcessingOptions
): number {
const basetime = 1000 // 1 second base
const sizeMultiplier = fileSize / (1024 * 1024) // MB
const qualityMultiplier = {
low: 0.5,
medium: 1,
high: 1.5,
ultra: 2,
}[options.quality || 'medium']
return Math.round(basetime * sizeMultiplier * qualityMultiplier)
}
/**
* Validate image before processing
*/
validateImage(file: File): { valid: boolean; error?: string } {
const maxSize = 50 * 1024 * 1024 // 50MB
const allowedTypes = ['image/png', 'image/jpeg', 'image/webp', 'image/gif']
if (file.size > maxSize) {
return { valid: false, error: 'File size exceeds 50MB limit' }
}
if (!allowedTypes.includes(file.type)) {
return { valid: false, error: 'Unsupported file type' }
}
return { valid: true }
}
}
export const processingAPI = new ProcessingAPI()
// Helper functions
export async function processImage(
file: File,
options?: ProcessingOptions,
onProgress?: (progress: number) => void
): Promise<ProcessingResult> {
return processingAPI.processImage(file, options, onProgress)
}
export async function processBatch(
files: File[],
options?: ProcessingOptions
): Promise<BatchJob> {
return processingAPI.processBatch(files, options)
}
export default processingAPI