File size: 8,077 Bytes
eeb9657
359c092
 
 
 
6630c3d
 
 
 
 
574df02
eeb9657
574df02
eeb9657
574df02
eeb9657
574df02
6630c3d
 
 
 
 
 
 
99dcc77
6630c3d
99dcc77
50eeaa1
 
00d41a8
 
99dcc77
6630c3d
7b8522b
 
 
 
 
 
 
 
00d41a8
 
 
f3d6819
7b8522b
 
365ad0f
7b8522b
 
 
50eeaa1
f3d6819
00d41a8
 
7b8522b
50eeaa1
 
7b8522b
50eeaa1
 
5d8dd38
50eeaa1
365ad0f
7b8522b
365ad0f
7b8522b
 
 
365ad0f
 
 
 
 
b9be0c8
7b8522b
9735639
365ad0f
 
7b8522b
9735639
 
00d41a8
 
 
 
7b8522b
00d41a8
 
 
 
7b8522b
00d41a8
 
 
 
7b8522b
00d41a8
 
 
 
 
7b8522b
00d41a8
 
 
 
 
 
 
 
 
 
365ad0f
00d41a8
 
 
7b8522b
00d41a8
 
 
365ad0f
 
00d41a8
 
 
 
 
7b8522b
00d41a8
 
 
 
 
 
 
50eeaa1
b9be0c8
 
 
 
 
 
 
 
 
 
 
f3d6819
f4ab502
7b8522b
50eeaa1
 
 
 
b9be0c8
 
 
50eeaa1
23fe54d
50eeaa1
 
23fe54d
50eeaa1
f3d6819
6630c3d
 
 
00d41a8
7b8522b
6630c3d
 
 
99dcc77
 
 
 
 
6630c3d
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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.' });
    }
}