teknolis / app /Services /ReplicateTryOnService.php
Codex
Deploy latest assistant, DeepSeek defaults, and automation updates
74acf19
Raw
History Blame Contribute Delete
6.03 kB
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use RuntimeException;
class ReplicateTryOnService
{
private string $apiKey;
private string $baseUrl;
private string $model;
private string $version;
private int $timeout;
public function __construct()
{
$this->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 '';
}
}