/** * Worksheet Cleanup Service * * API client for the worksheet cleanup endpoints: * - Handwriting detection * - Handwriting removal (inpainting) * - Layout reconstruction * * All processing happens on the local Mac Mini server. */ export interface CleanupCapabilities { opencv_available: boolean lama_available: boolean paddleocr_available: boolean } export interface DetectionResult { has_handwriting: boolean confidence: number handwriting_ratio: number detection_method: string mask_base64?: string } export interface PreviewResult { has_handwriting: boolean confidence: number handwriting_ratio: number image_width: number image_height: number estimated_times_ms: { detection: number inpainting: number reconstruction: number total: number } capabilities: { lama_available: boolean } } export interface PipelineResult { success: boolean handwriting_detected: boolean handwriting_removed: boolean layout_reconstructed: boolean cleaned_image_base64?: string fabric_json?: any metadata: { detection?: { confidence: number handwriting_ratio: number method: string } inpainting?: { method_used: string processing_time_ms: number } layout?: { element_count: number table_count: number page_width: number page_height: number } } } export type InpaintingMethod = 'auto' | 'opencv_telea' | 'opencv_ns' | 'lama' export interface CleanupOptions { removeHandwriting: boolean reconstructLayout: boolean inpaintingMethod: InpaintingMethod } /** * Get the API base URL for the klausur-service */ function getApiUrl(): string { if (typeof window === 'undefined') return 'http://localhost:8086' const { hostname, protocol } = window.location return hostname === 'localhost' ? 'http://localhost:8086' : `${protocol}//${hostname}:8086` } /** * Get available cleanup capabilities on the server */ export async function getCapabilities(): Promise { const response = await fetch(`${getApiUrl()}/api/v1/worksheet/capabilities`) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } return response.json() } /** * Quick preview of cleanup without full processing */ export async function previewCleanup(file: File): Promise { const formData = new FormData() formData.append('image', file) const response = await fetch(`${getApiUrl()}/api/v1/worksheet/preview-cleanup`, { method: 'POST', body: formData }) if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(error.detail || `HTTP ${response.status}`) } return response.json() } /** * Detect handwriting in an image */ export async function detectHandwriting( file: File, options: { returnMask?: boolean; minConfidence?: number } = {} ): Promise { const formData = new FormData() formData.append('image', file) formData.append('return_mask', String(options.returnMask ?? true)) formData.append('min_confidence', String(options.minConfidence ?? 0.3)) const response = await fetch(`${getApiUrl()}/api/v1/worksheet/detect-handwriting`, { method: 'POST', body: formData }) if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(error.detail || `HTTP ${response.status}`) } return response.json() } /** * Get the handwriting detection mask as an image blob */ export async function getHandwritingMask(file: File): Promise { const formData = new FormData() formData.append('image', file) const response = await fetch(`${getApiUrl()}/api/v1/worksheet/detect-handwriting/mask`, { method: 'POST', body: formData }) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } return response.blob() } /** * Remove handwriting from an image */ export async function removeHandwriting( file: File, options: { mask?: File method?: InpaintingMethod returnBase64?: boolean } = {} ): Promise<{ imageBlob?: Blob; imageBase64?: string; metadata: any }> { const formData = new FormData() formData.append('image', file) formData.append('method', options.method ?? 'auto') formData.append('return_base64', String(options.returnBase64 ?? false)) if (options.mask) { formData.append('mask', options.mask) } const response = await fetch(`${getApiUrl()}/api/v1/worksheet/remove-handwriting`, { method: 'POST', body: formData }) if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(error.detail || `HTTP ${response.status}`) } if (options.returnBase64) { const data = await response.json() return { imageBase64: data.image_base64, metadata: data.metadata } } else { const imageBlob = await response.blob() const methodUsed = response.headers.get('X-Method-Used') || 'unknown' const processingTime = parseFloat(response.headers.get('X-Processing-Time-Ms') || '0') return { imageBlob, metadata: { method_used: methodUsed, processing_time_ms: processingTime } } } } /** * Run the full cleanup pipeline */ export async function runCleanupPipeline( file: File, options: CleanupOptions = { removeHandwriting: true, reconstructLayout: true, inpaintingMethod: 'auto' } ): Promise { const formData = new FormData() formData.append('image', file) formData.append('remove_handwriting', String(options.removeHandwriting)) formData.append('reconstruct', String(options.reconstructLayout)) formData.append('inpainting_method', options.inpaintingMethod) const response = await fetch(`${getApiUrl()}/api/v1/worksheet/cleanup-pipeline`, { method: 'POST', body: formData }) if (!response.ok) { const error = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(error.detail || `HTTP ${response.status}`) } return response.json() } /** * Helper to convert base64 to blob */ export function base64ToBlob(base64: string, mimeType: string = 'image/png'): Blob { const byteCharacters = atob(base64) const byteArrays = [] for (let offset = 0; offset < byteCharacters.length; offset += 512) { const slice = byteCharacters.slice(offset, offset + 512) const byteNumbers = new Array(slice.length) for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i) } const byteArray = new Uint8Array(byteNumbers) byteArrays.push(byteArray) } return new Blob(byteArrays, { type: mimeType }) } /** * Helper to create object URL from base64 */ export function base64ToObjectUrl(base64: string, mimeType: string = 'image/png'): string { const blob = base64ToBlob(base64, mimeType) return URL.createObjectURL(blob) }