'Servicio no configurado.']); exit; } $body = json_decode(file_get_contents('php://input'), true); $SPACE = 'https://blackmistcode-morphos-medgemma.hf.space/gradio_api'; $auth = ['Content-Type: application/json', "Authorization: Bearer $hfKey"]; set_time_limit(120); function hf_get(string $url, array $headers, ?string $post = null): array { $ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 120]); if ($post !== null) { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); } return [curl_exec($ch), curl_getinfo($ch, CURLINFO_HTTP_CODE)]; } function uploadImagen(string $space, string $hfKey, string $dataUrl): ?array { if (!preg_match('/^data:(image\/[\w+]+);base64,(.+)$/s', $dataUrl, $m)) return null; $mimeType = $m[1]; $ext = explode('/', $mimeType)[1] ?? 'jpg'; $binary = base64_decode($m[2]); if ($binary === false) return null; // Construye manualmente el cuerpo multipart para enviar la imagen al upload endpoint de Gradio $boundary = bin2hex(random_bytes(16)); $body = "--$boundary\r\n" . "Content-Disposition: form-data; name=\"files\"; filename=\"image.$ext\"\r\n" . "Content-Type: $mimeType\r\n\r\n" . $binary . "\r\n--$boundary--\r\n"; $ch = curl_init("$space/upload"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => [ "Authorization: Bearer $hfKey", "Content-Type: multipart/form-data; boundary=$boundary", ], CURLOPT_TIMEOUT => 60, ]); $result = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Si el upload falla, devuelve la imagen inline para que el modelo la procese igualmente if ($code >= 400 || !$result) { return ['url' => $dataUrl, 'orig_name' => "image.$ext", 'mime_type' => $mimeType]; } $files = json_decode($result, true); $path = is_array($files) && isset($files[0]) ? $files[0] : null; if (!$path) { return ['url' => $dataUrl, 'orig_name' => "image.$ext", 'mime_type' => $mimeType]; } return [ 'path' => $path, 'url' => "$space/file=" . $path, 'orig_name' => "image.$ext", 'mime_type' => $mimeType, ]; } $rawImages = array_slice(array_values($body['images'] ?? []), 0, 4); $data = []; foreach ($rawImages as $img) { $data[] = $img ? uploadImagen($SPACE, $hfKey, $img) : null; } while (count($data) < 4) $data[] = null; $data[] = $body['prompt'] ?? ''; [$submitBody, $code] = hf_get("$SPACE/call/analyze", $auth, json_encode(['data' => $data])); if ($code >= 400) { http_response_code(502); echo json_encode(['error' => "Error Space: HTTP $code"]); exit; } $eventId = json_decode($submitBody, true)['event_id'] ?? null; if (!$eventId) { http_response_code(502); echo json_encode(['error' => 'No se obtuvo event_id.']); exit; } [$stream] = hf_get("$SPACE/call/analyze/$eventId", ["Authorization: Bearer $hfKey"]); $result = $error = null; $lastEvent = ''; // Parsea el stream SSE de Gradio buscando el evento 'complete' o 'process_completed' foreach (explode("\n", $stream) as $raw) { $line = rtrim($raw, "\r"); if (str_starts_with($line, 'event:')) $lastEvent = trim(substr($line, 6)); elseif (str_starts_with($line, 'data:')) { $parsed = json_decode(trim(substr($line, 5)), true); if (in_array($lastEvent, ['complete', 'process_completed'])) $result = is_array($parsed) ? $parsed[0] : ($parsed['output'] ?? $parsed); elseif ($lastEvent === 'error') $error = $parsed['error'] ?? 'Error del modelo.'; } } if ($error) { http_response_code(503); echo json_encode(['error' => $error]); } elseif ($result !== null) { echo json_encode(['text' => $result]); } else { http_response_code(502); echo json_encode(['error' => 'Sin respuesta del modelo.']); }