| | 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 |