File size: 3,499 Bytes
1501522 | 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 | <?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class FluxImageService
{
public function generate(string $prompt, string $aspect = 'landscape'): string
{
$apiKey = trim((string) config('services.fal.api_key'));
$model = trim((string) config('services.fal.model', 'fal-ai/flux/schnell'));
$pollIntervalMs = max(500, (int) config('services.fal.poll_interval_ms', 1500));
$timeoutSec = max(15, (int) config('services.fal.timeout', 90));
if ($apiKey === '') {
throw new RuntimeException('FAL_KEY belum diisi di .env');
}
$payload = [
'prompt' => $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;
}
}
|