File size: 7,745 Bytes
8b8b9f7
14ee8ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6630c3d
14ee8ac
6630c3d
 
 
 
 
 
 
c37eb46
6630c3d
 
 
 
00d41a8
 
6630c3d
 
4049016
c37eb46
4049016
 
 
 
 
 
 
50eeaa1
 
c37eb46
14ee8ac
 
4049016
c37eb46
c63b8e0
14ee8ac
 
6630c3d
14ee8ac
6630c3d
 
4a24f4a
60e228b
 
 
 
14ee8ac
c63b8e0
1914082
 
c63b8e0
1914082
 
 
 
 
 
c63b8e0
1914082
 
 
 
c63b8e0
1914082
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c63b8e0
1914082
 
c63b8e0
 
 
 
 
 
 
 
 
 
9735639
 
 
 
c63b8e0
9735639
cfea625
 
4049016
c37eb46
4049016
cfea625
4049016
cfea625
4049016
cfea625
4049016
cfea625
 
 
 
 
14ee8ac
 
c63b8e0
cfea625
 
00d41a8
 
 
cfea625
 
00d41a8
 
 
 
c63b8e0
 
 
 
00d41a8
 
 
 
 
 
 
 
 
 
 
 
c63b8e0
00d41a8
 
 
 
 
cfea625
4a24f4a
 
00d41a8
4049016
23fe54d
 
 
 
60e228b
14ee8ac
 
3de211c
6630c3d
 
00d41a8
23fe54d
00d41a8
14ee8ac
6630c3d
 
 
00d41a8
14ee8ac
50eeaa1
14ee8ac
 
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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');
        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}` } : {})
        };

        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,
            ssGuidanceNum,
            0.7,
            ssStepsNum,
            5.0,
            slatGuidanceNum,
            0.5,
            slatStepsNum,
            3.0,
            1.0,
            0.0,
            12,
            3.0
        ];

        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
        });
    }
}