$prompt, 'num_images' => 1, 'aspect_ratio' => $this->normalizeAspect($aspect), ]; $start = microtime(true); $submit = Http::timeout($timeoutSec) ->withHeaders([ 'Authorization' => 'Key '.$apiKey, 'Content-Type' => 'application/json', ]) ->post('https://queue.fal.run/'.$model, $payload); if (! $submit->successful()) { $error = trim((string) data_get($submit->json(), 'detail', data_get($submit->json(), 'error', $submit->body()))); throw new RuntimeException('FLUX request gagal: '.$submit->status().' - '.$error); } $submitJson = $submit->json(); $directUrl = $this->extractImageUrl($submitJson); if ($directUrl) { return $directUrl; } $statusUrl = data_get($submitJson, 'status_url'); if (! is_string($statusUrl) || trim($statusUrl) === '') { throw new RuntimeException('FLUX tidak mengembalikan status_url.'); } while ((microtime(true) - $start) < $timeoutSec) { usleep($pollIntervalMs * 1000); $status = Http::timeout(30) ->withHeaders([ 'Authorization' => 'Key '.$apiKey, 'Content-Type' => 'application/json', ]) ->get($statusUrl); if (! $status->successful()) { continue; } $statusJson = $status->json(); $imageUrl = $this->extractImageUrl($statusJson); if ($imageUrl) { return $imageUrl; } $state = strtoupper((string) data_get($statusJson, 'status')); if (in_array($state, ['FAILED', 'ERROR', 'CANCELLED'], true)) { $error = (string) data_get($statusJson, 'error', 'unknown error'); throw new RuntimeException('FLUX gagal: '.$error); } } throw new RuntimeException('FLUX timeout, coba prompt lebih singkat atau ulangi lagi.'); } private function normalizeAspect(string $aspect): string { return match ($aspect) { 'portrait' => '9:16', 'square' => '1:1', default => '16:9', }; } private function extractImageUrl(array $data): ?string { $candidates = [ data_get($data, 'images.0.url'), data_get($data, 'data.images.0.url'), data_get($data, 'response.images.0.url'), data_get($data, 'output.images.0.url'), ]; foreach ($candidates as $candidate) { if (is_string($candidate) && trim($candidate) !== '') { return trim($candidate); } } return null; } }