Spaces:
Build error
Build error
LogicalTrue
fix: castear la resolucion a String ('1024' o '512') para validar con el selector de Gradio
c37eb46 | export default async function handler(req, res) { | |
| const startTime = Date.now(); | |
| const logTrace = []; | |
| function addLog(stage, message, data = null) { | |
| const entry = { | |
| timestamp: new Date().toISOString(), | |
| elapsedMs: Date.now() - startTime, | |
| stage: stage, | |
| message: message, | |
| data: data | |
| }; | |
| logTrace.push(entry); | |
| console.log(`[3D-DIAGNOSTIC] [${stage}] ${message}`, data ? JSON.stringify(data).slice(0, 300) : ''); | |
| } | |
| if (req.method !== 'POST') { | |
| return res.status(405).json({ error: 'Método no permitido', logs: logTrace }); | |
| } | |
| try { | |
| const body = req.body || {}; | |
| const { | |
| image, | |
| seed = Math.floor(Math.random() * 100000), | |
| resolution = '1024', | |
| ss_guidance = 7.5, | |
| ss_steps = 12, | |
| slat_guidance = 3.0, | |
| slat_steps = 12, | |
| texture_size = 1024, | |
| decimation_target = 300000 | |
| } = body; | |
| const seedNum = parseInt(seed) || Math.floor(Math.random() * 100000); | |
| const resolutionStr = String(resolution || '1024'); // Gradio Dropdown exige formato String | |
| const ssGuidanceNum = parseFloat(ss_guidance) || 7.5; | |
| const ssStepsNum = parseInt(ss_steps) || 12; | |
| const slatGuidanceNum = parseFloat(slat_guidance) || 3.0; | |
| const slatStepsNum = parseInt(slat_steps) || 12; | |
| const textureSizeNum = parseInt(texture_size) || 1024; | |
| const decimationNum = parseInt(decimation_target) || 300000; | |
| const sessionHash = Math.random().toString(36).substring(2, 13) + Math.random().toString(36).substring(2, 13); | |
| addLog('INIT', 'Iniciando Carga de Imagen y Generación Gradio 4 v3', { | |
| hasImage: Boolean(image), | |
| imageLength: image ? image.length : 0, | |
| seed: seedNum, | |
| resolution: resolutionStr, | |
| sessionHash | |
| }); | |
| if (!image) { | |
| return res.status(400).json({ error: 'La imagen 2D es requerida para la generación 3D.', logs: logTrace }); | |
| } | |
| const hfToken = process.env.HF_TOKEN; | |
| const spaceUrl = 'https://logicaltrue-trellis-2.hf.space'; | |
| const headers = { | |
| ...(hfToken ? { 'Authorization': `Bearer ${hfToken}` } : {}) | |
| }; | |
| // ─── PASO 0: Subir imagen a /gradio_api/upload ───────────────────────── | |
| let remoteFilePath = null; | |
| const base64Data = image.replace(/^data:image\/\w+;base64,/, ''); | |
| const imgBuffer = Buffer.from(base64Data, 'base64'); | |
| const boundary = '----WebKitFormBoundary' + Math.random().toString(36).substring(2); | |
| let formDataParts = []; | |
| formDataParts.push(`--${boundary}\r\nContent-Disposition: form-data; name="files"; filename="input.png"\r\nContent-Type: image/png\r\n\r\n`); | |
| const headerBuf = Buffer.from(formDataParts.join('')); | |
| const footerBuf = Buffer.from(`\r\n--${boundary}--\r\n`); | |
| const fullBody = Buffer.concat([headerBuf, imgBuffer, footerBuf]); | |
| const uploadEndpoints = [ | |
| `${spaceUrl}/gradio_api/upload`, | |
| `${spaceUrl}/upload` | |
| ]; | |
| for (const uUrl of uploadEndpoints) { | |
| addLog('UPLOAD_TRY', `Subiendo imagen 2D a ${uUrl}...`); | |
| try { | |
| const uploadRes = await fetch(uUrl, { | |
| method: 'POST', | |
| headers: { | |
| ...headers, | |
| 'Content-Type': `multipart/form-data; boundary=${boundary}` | |
| }, | |
| body: fullBody, | |
| signal: AbortSignal.timeout(12000) | |
| }).catch(e => ({ ok: false, statusText: e.message })); | |
| if (uploadRes && uploadRes.ok) { | |
| const uploadJson = await uploadRes.json().catch(() => []); | |
| if (Array.isArray(uploadJson) && uploadJson.length > 0) { | |
| remoteFilePath = uploadJson[0]; | |
| addLog('UPLOAD_SUCCESS', `Imagen cargada con éxito en ${uUrl}`, { remotePath: remoteFilePath }); | |
| break; | |
| } | |
| } else if (uploadRes) { | |
| const errTxt = await uploadRes.text().catch(() => ''); | |
| addLog('UPLOAD_WARN', `Status ${uploadRes.status} en ${uUrl}`, { error: errTxt.slice(0, 200) }); | |
| } | |
| } catch (e) { | |
| addLog('UPLOAD_EXCEPT', `Excepción en ${uUrl}`, { error: e.message }); | |
| } | |
| } | |
| const gradioImageObj = remoteFilePath ? { | |
| path: remoteFilePath, | |
| url: `${spaceUrl}/file=${remoteFilePath}`, | |
| orig_name: 'input.png', | |
| mime_type: 'image/png', | |
| meta: { _type: 'gradio.FileData' } | |
| } : (typeof image === 'string' && (image.startsWith('data:') || image.startsWith('http')) ? { | |
| path: image, | |
| url: image, | |
| orig_name: 'input.png', | |
| meta: { _type: 'gradio.FileData' } | |
| } : image); | |
| const gradioData3D = [ | |
| gradioImageObj, | |
| seedNum, | |
| resolutionStr, // String para validar con las opciones Dropdown de Gradio | |
| ssGuidanceNum, | |
| 0.7, | |
| ssStepsNum, | |
| 5.0, | |
| slatGuidanceNum, | |
| 0.5, | |
| slatStepsNum, | |
| 3.0, | |
| 1.0, | |
| 0.0, | |
| 12, | |
| 3.0 | |
| ]; | |
| // ─── PASO 1: Iniciar Inferencia 3D (/image_to_3d) ───────────────────── | |
| addLog('PASO1_SUBMIT', 'Enviando FileData verificado a /image_to_3d...', { remotePath: remoteFilePath }); | |
| let step1EventId = null; | |
| const step1Targets = [ | |
| `${spaceUrl}/gradio_api/call/image_to_3d`, | |
| `${spaceUrl}/call/image_to_3d` | |
| ]; | |
| for (const targetUrl of step1Targets) { | |
| try { | |
| const sRes = await fetch(targetUrl, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| ...headers | |
| }, | |
| body: JSON.stringify({ | |
| data: gradioData3D, | |
| fn_index: 0, | |
| session_hash: sessionHash | |
| }), | |
| signal: AbortSignal.timeout(12000) | |
| }).catch(() => null); | |
| if (sRes && sRes.ok) { | |
| const sJson = await sRes.json().catch(() => ({})); | |
| if (sJson.event_id) { | |
| step1EventId = sJson.event_id; | |
| addLog('PASO1_SUCCESS', 'Paso 1 (/image_to_3d) ejecutándose en la GPU A100', { event_id: step1EventId }); | |
| break; | |
| } | |
| } | |
| } catch (e) { | |
| addLog('PASO1_ERROR', `Error llamando ${targetUrl}`, { error: e.message }); | |
| } | |
| } | |
| if (step1EventId) { | |
| const jobId = `hf:logicaltrue-trellis-2:${sessionHash}:${step1EventId}:${decimationNum}:${textureSizeNum}`; | |
| return res.status(202).json({ | |
| success: true, | |
| job_id: jobId, | |
| status: 'pending', | |
| provider: 'Hugging Face Space (LogicalTrue/TRELLIS.2)', | |
| credits: 999, | |
| diagnostics: logTrace | |
| }); | |
| } | |
| addLog('FATAL_END', 'No se pudo iniciar /image_to_3d en Trellis 2.'); | |
| return res.status(500).json({ | |
| error: 'No se pudo iniciar el proceso 3D en Hugging Face Space.', | |
| diagnostics: logTrace | |
| }); | |
| } catch (err) { | |
| addLog('FATAL_EXCEPT', 'Excepción general', { error: err.message, stack: err.stack }); | |
| return res.status(500).json({ | |
| error: err.message || 'Error en la generación 3D.', | |
| diagnostics: logTrace | |
| }); | |
| } | |
| } | |