apiKey = (string) config('services.replicate.api_key', ''); $this->baseUrl = rtrim((string) config('services.replicate.base_url', 'https://api.replicate.com/v1'), '/'); $this->model = (string) config('services.replicate.tryon_model', 'cedoysch/flux-fill-redux-try-on'); $this->version = (string) config('services.replicate.tryon_version', 'cf5cb07a25e726fe2fac166a8c5ab52ddccd48657741670fb09d9954d4d8446f'); $this->timeout = max(60, (int) config('services.replicate.timeout', 180)); } public function isAvailable(): bool { return trim($this->apiKey) !== ''; } public function submit(string $modelImageUrl, array $garmentInputs, array $options = []): array { if (! $this->isAvailable()) { throw new RuntimeException('REPLICATE_API_KEY belum diset di server.'); } if (trim($modelImageUrl) === '') { throw new RuntimeException('Foto model kosong, jadi Try On belum bisa dijalankan.'); } if ($garmentInputs === []) { throw new RuntimeException('Reference outfit kosong, jadi Try On belum bisa dijalankan.'); } $clothImageUrl = $this->resolvePrimaryClothImage($garmentInputs); if ($clothImageUrl === '') { throw new RuntimeException('Reference outfit utama kosong, jadi Try On belum bisa dijalankan.'); } $input = [ 'person_image' => trim($modelImageUrl), 'cloth_image' => $clothImageUrl, 'output_format' => 'webp', 'output_quality' => 95, ]; $endpoint = $this->baseUrl.'/predictions'; $response = Http::withToken($this->apiKey) ->timeout(30) ->post($endpoint, [ 'version' => $this->version, 'input' => $input, ]); if ($response->status() === 429) { sleep(3); $response = Http::withToken($this->apiKey) ->timeout(30) ->post($endpoint, [ 'version' => $this->version, 'input' => $input, ]); } if ($response->failed()) { $detail = trim((string) ($response->json('detail') ?? $response->body())); throw new RuntimeException('Replicate Try On submit gagal: '.Str::limit($detail, 220)); } $predictionId = (string) $response->json('id', ''); if ($predictionId === '') { throw new RuntimeException('Replicate Try On tidak mengembalikan prediction ID.'); } Log::info('Replicate Try On submitted.', [ 'prediction_id' => $predictionId, 'model' => $this->model, 'version' => $this->version, 'garment_keys' => array_keys($garmentInputs), ]); return [ 'prediction_id' => $predictionId, 'provider' => 'replicate-tryon', 'model' => $this->model, 'version' => $this->version, 'garment_keys' => array_keys($garmentInputs), ]; } public function checkStatus(string $predictionId): array { $response = Http::withToken($this->apiKey) ->timeout(20) ->get($this->baseUrl.'/predictions/'.$predictionId); if ($response->failed()) { $detail = trim((string) ($response->json('detail') ?? $response->body())); return [ 'status' => 'failed', 'error' => 'Replicate Try On poll gagal: '.Str::limit($detail, 220), ]; } $status = (string) $response->json('status', ''); if (in_array($status, ['starting', 'processing'], true)) { return [ 'status' => 'processing', 'provider' => 'replicate-tryon', 'queue_status' => $status, 'created_at' => (string) $response->json('created_at', ''), 'started_at' => (string) $response->json('started_at', ''), ]; } if (in_array($status, ['failed', 'canceled'], true)) { return [ 'status' => 'failed', 'provider' => 'replicate-tryon', 'error' => (string) ($response->json('error') ?? 'Replicate Try On gagal.'), ]; } if ($status === 'succeeded') { $output = $response->json('output'); $url = ''; if (is_array($output) && filled($output[0] ?? null)) { $url = (string) $output[0]; } elseif (is_string($output) && trim($output) !== '') { $url = trim($output); } if ($url === '') { return [ 'status' => 'failed', 'provider' => 'replicate-tryon', 'error' => 'Replicate Try On selesai, tapi URL hasilnya kosong.', ]; } return [ 'status' => 'succeeded', 'provider' => 'replicate-tryon', 'url' => $url, 'thumbnail_url' => $url, ]; } return [ 'status' => 'failed', 'provider' => 'replicate-tryon', 'error' => 'Status Replicate Try On tidak dikenali: '.$status, ]; } private function resolvePrimaryClothImage(array $garmentInputs): string { foreach ($garmentInputs as $value) { $url = trim((string) $value); if ($url !== '') { return $url; } } return ''; } }