Spaces:
Build error
Build error
LogicalTrue
fix: agregar cabecera obligatoria Accept: text/event-stream para la lectura de eventos Gradio 4
7b8522b | export default async function handler(req, res) { | |
| res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); | |
| res.setHeader('Pragma', 'no-cache'); | |
| res.setHeader('Expires', '0'); | |
| if (req.method !== 'GET') { | |
| return res.status(405).json({ error: 'Método no permitido' }); | |
| } | |
| try { | |
| let jobId = req.query?.job_id; | |
| if (!jobId && req.url) { | |
| try { | |
| const parsedUrl = new URL(req.url, 'https://localhost'); | |
| jobId = parsedUrl.searchParams.get('job_id'); | |
| } catch (e) {} | |
| } | |
| if (!jobId) { | |
| return res.status(400).json({ error: 'El parámetro job_id es requerido.' }); | |
| } | |
| const hfToken = process.env.HF_TOKEN; | |
| if (jobId.startsWith('hf:')) { | |
| const parts = jobId.split(':'); | |
| const spaceHost = parts[1] || 'logicaltrue-trellis-2'; | |
| const sessionHash = parts[2]; | |
| const eventId = parts[3]; | |
| const decimationTarget = parseInt(parts[4]) || 300000; | |
| const textureSize = parseInt(parts[5]) || 1024; | |
| const spaceUrl = `https://${spaceHost}.hf.space`; | |
| // Cabecera obligatoria Accept: text/event-stream para endpoints SSE de Gradio v4 | |
| const sseHeaders = { | |
| 'Accept': 'text/event-stream', | |
| 'Cache-Control': 'no-cache', | |
| ...(hfToken ? { 'Authorization': `Bearer ${hfToken}` } : {}) | |
| }; | |
| const jsonHeaders = { | |
| 'Content-Type': 'application/json', | |
| ...(hfToken ? { 'Authorization': `Bearer ${hfToken}` } : {}) | |
| }; | |
| // ─── 1. Verificar estado del Paso 1 (/image_to_3d) ──────────────────── | |
| const step1Urls = [ | |
| `${spaceUrl}/gradio_api/call/image_to_3d/${eventId}`, | |
| `${spaceUrl}/call/image_to_3d/${eventId}`, | |
| `${spaceUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, | |
| `${spaceUrl}/queue/data?session_hash=${sessionHash}` | |
| ]; | |
| let isStep1Complete = false; | |
| for (const statusUrl of step1Urls) { | |
| try { | |
| const qRes = await fetch(statusUrl, { | |
| headers: sseHeaders, | |
| signal: AbortSignal.timeout(9000) | |
| }).catch(() => null); | |
| if (qRes && qRes.ok) { | |
| const txt = await qRes.text().catch(() => ''); | |
| console.log(`[Paso 1 Stream SSE ${eventId}]`, txt.slice(0, 300)); | |
| // Validar si el evento complete o process_completed fue emitido con éxito | |
| if (txt.includes('event: complete') || txt.includes('process_completed') || txt.includes('event: generating')) { | |
| if (txt.includes('"success":false')) { | |
| continue; | |
| } | |
| isStep1Complete = true; | |
| break; | |
| } | |
| if (txt.includes('process_starts') || txt.includes('estimation') || txt.includes('event: heartbeat')) { | |
| return res.status(200).json({ | |
| status: 'processing', | |
| progress: 65, | |
| message: 'Inferencia 3D en progreso en la GPU A100 (Sparse & SLaT)...' | |
| }); | |
| } | |
| } | |
| } catch (e) {} | |
| } | |
| // Si la GPU aún está calculando el volumen 3D | |
| if (!isStep1Complete) { | |
| return res.status(200).json({ | |
| status: 'processing', | |
| progress: 60, | |
| message: 'Calculando volumen 3D en la GPU A100...' | |
| }); | |
| } | |
| // ─── 2. Paso 1 completado! Ejecutar Extracción GLB (/extract_glb) ───── | |
| console.log(`[Paso 1 completado] Iniciando Paso 2 (/extract_glb) decimation=${decimationTarget}...`); | |
| let extractEventId = null; | |
| try { | |
| const extractRes = await fetch(`${spaceUrl}/gradio_api/call/extract_glb`, { | |
| method: 'POST', | |
| headers: jsonHeaders, | |
| body: JSON.stringify({ | |
| data: [decimationTarget, textureSize], | |
| session_hash: sessionHash | |
| }), | |
| signal: AbortSignal.timeout(10000) | |
| }).catch(() => null); | |
| if (extractRes && extractRes.ok) { | |
| const extractJson = await extractRes.json().catch(() => ({})); | |
| extractEventId = extractJson.event_id; | |
| console.log(`[Paso 2 /extract_glb Iniciado] event_id: ${extractEventId}`); | |
| } | |
| } catch (e) {} | |
| // Consultar resultado de /extract_glb | |
| const extractCheckUrls = [ | |
| extractEventId ? `${spaceUrl}/gradio_api/call/extract_glb/${extractEventId}` : null, | |
| extractEventId ? `${spaceUrl}/call/extract_glb/${extractEventId}` : null, | |
| `${spaceUrl}/gradio_api/queue/data?session_hash=${sessionHash}`, | |
| `${spaceUrl}/queue/data?session_hash=${sessionHash}` | |
| ].filter(Boolean); | |
| for (const extUrl of extractCheckUrls) { | |
| try { | |
| const extRes = await fetch(extUrl, { | |
| headers: sseHeaders, | |
| signal: AbortSignal.timeout(9000) | |
| }).catch(() => null); | |
| if (extRes && extRes.ok) { | |
| const extTxt = await extRes.text().catch(() => ''); | |
| const glbMatch = extTxt.match(/"([^"]+\.glb)"/i); | |
| if (glbMatch && glbMatch[1]) { | |
| let rawGlb = glbMatch[1]; | |
| let fullGlbUrl = rawGlb; | |
| if (!rawGlb.startsWith('http')) { | |
| if (rawGlb.startsWith('/tmp/') || rawGlb.startsWith('tmp/')) { | |
| fullGlbUrl = `${spaceUrl}/file=${rawGlb.startsWith('/') ? '' : '/'}${rawGlb}`; | |
| } else if (rawGlb.startsWith('/file=') || rawGlb.startsWith('file=')) { | |
| fullGlbUrl = `${spaceUrl}${rawGlb.startsWith('/') ? '' : '/'}${rawGlb}`; | |
| } else { | |
| fullGlbUrl = `${spaceUrl}/file=${rawGlb}`; | |
| } | |
| } | |
| console.log(`[GLB EXTRACCION EXITOSA 100%] ${fullGlbUrl}`); | |
| return res.status(200).json({ | |
| status: 'completed', | |
| progress: 100, | |
| result: { | |
| gltfUrl: fullGlbUrl, | |
| glbUrl: fullGlbUrl, | |
| fbxUrl: fullGlbUrl, | |
| detectedCategory: 'objeto' | |
| } | |
| }); | |
| } | |
| } | |
| } catch (e) {} | |
| } | |
| return res.status(200).json({ | |
| status: 'processing', | |
| progress: 85, | |
| message: 'Extrayendo texturas PBR y generando archivo GLB...' | |
| }); | |
| } | |
| return res.status(200).json({ | |
| status: 'processing', | |
| progress: 50, | |
| message: 'Procesando modelo 3D en la nube...' | |
| }); | |
| } catch (err) { | |
| console.error('[Vercel Job-Status Error]', err); | |
| return res.status(500).json({ error: err.message || 'Error verificando el estado de la tarea.' }); | |
| } | |
| } | |