| 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() |
|
|
| |
| |
| |
| 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 |
| ) |
| } |
|
|
| |
| |
| |
| 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' }, |
| }) |
|
|
| |
| this.connectWebSocket(response.id) |
|
|
| return response |
| } |
|
|
| |
| |
| |
| async getJobStatus(jobId: string): Promise<BatchJob> { |
| return apiClient.get<BatchJob>(`/api/v1/process/jobs/${jobId}`) |
| } |
|
|
| |
| |
| |
| async cancelJob(jobId: string): Promise<void> { |
| await apiClient.post(`/api/v1/process/jobs/${jobId}/cancel`) |
| } |
|
|
| |
| |
| |
| 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' } } |
| ) |
| } |
|
|
| |
| |
| |
| 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, |
| }) |
| } |
|
|
| |
| |
| |
| async generateBackground( |
| prompt: string, |
| style?: 'realistic' | 'artistic' | 'abstract' | 'gradient' |
| ): Promise<{ id: string; url: string }> { |
| return apiClient.post('/api/v1/backgrounds/generate', { |
| prompt, |
| style, |
| }) |
| } |
|
|
| |
| |
| |
| 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, |
| }) |
| } |
|
|
| |
| |
| |
| 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') |
| }) |
| } |
|
|
| |
| |
| |
| private disconnectWebSocket() { |
| if (this.socket) { |
| this.socket.disconnect() |
| this.socket = null |
| } |
| } |
|
|
| |
| |
| |
| on(event: string, callback: Function) { |
| if (!this.listeners.has(event)) { |
| this.listeners.set(event, new Set()) |
| } |
| this.listeners.get(event)?.add(callback) |
| } |
|
|
| |
| |
| |
| off(event: string, callback: Function) { |
| this.listeners.get(event)?.delete(callback) |
| } |
|
|
| |
| |
| |
| private emit(event: string, data: any) { |
| this.listeners.get(event)?.forEach((callback) => callback(data)) |
| } |
|
|
| |
| |
| |
| estimateProcessingTime( |
| fileSize: number, |
| options: ProcessingOptions |
| ): number { |
| const basetime = 1000 |
| const sizeMultiplier = fileSize / (1024 * 1024) |
| const qualityMultiplier = { |
| low: 0.5, |
| medium: 1, |
| high: 1.5, |
| ultra: 2, |
| }[options.quality || 'medium'] |
|
|
| return Math.round(basetime * sizeMultiplier * qualityMultiplier) |
| } |
|
|
| |
| |
| |
| validateImage(file: File): { valid: boolean; error?: string } { |
| const maxSize = 50 * 1024 * 1024 |
| 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() |
|
|
| |
| 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 |