initial commit

This commit is contained in:
Jurgis Sakalauskas
2025-01-08 12:45:00 +02:00
commit 6df493fab3
86 changed files with 11890 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Services\Google;
use Google\Client;
class ApiAuthService
{
/**
* @throws \Google\Exception
*/
public function getAccessToken(): string
{
$client = new Client();
$serviceAccountKeyPath = env('GOOGLE_APPLICATION_CREDENTIALS');
$client->setAuthConfig($serviceAccountKeyPath);
$client->addScope('https://www.googleapis.com/auth/cloud-platform');
$client->fetchAccessTokenWithAssertion();
// todo - cache, refresh, etc
$accessToken = $client->getAccessToken();
return $accessToken['access_token'];
}
}
@@ -0,0 +1,15 @@
<?php
namespace App\Services\Google\DTO;
class ImageGenerationPrediction
{
public readonly string $bytesBase64Encoded;
public readonly string $mimeType;
public function __construct(string $bytesBase64Encoded, string $mimeType)
{
$this->bytesBase64Encoded = $bytesBase64Encoded;
$this->mimeType = $mimeType;
}
}
@@ -0,0 +1,16 @@
<?php
namespace App\Services\Google\DTO;
class ImageGenerationRequest
{
public function __construct(
public readonly string $prompt,
public readonly ?string $negativePrompt = null,
public readonly ?int $sampleCount = null,
public readonly ?string $aspectRatio = null,
public readonly ?string $safetySetting = null,
)
{
}
}
@@ -0,0 +1,35 @@
<?php
namespace App\Services\Google\DTO;
use App\Services\Google\Error\APIError;
class ImageGenerationResponse
{
/**
* @var \App\Services\Google\DTO\ImageGenerationPrediction[]
*/
public readonly array $predictions;
public readonly ?APIError $error;
public function __construct(array $rawResponse)
{
if (isset($rawResponse['predictions']) && count($rawResponse['predictions']) > 0) {
$predictions = [];
foreach ($rawResponse['predictions'] as $prediction) {
$predictions[] = new ImageGenerationPrediction($prediction['bytesBase64Encoded'], $prediction['mimeType']);
}
$this->predictions = $predictions;
$this->error = null;
}
if (isset($rawResponse['error'])) {
$error = $rawResponse['error'];
$this->error = new APIError($error['message'], $error['code']);
}
if (!isset($this->predictions) && !isset($this->error)) {
$this->error = new APIError('An unknown error occurred.', 0,);
}
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Services\Google;
class EndpointBuilderService
{
public function buildPredictionEndpoint(string $model): string
{
return $this->buildEndpoint($model, 'predict');
}
public function buildGenerationEndpoint(string $model): string
{
return $this->buildEndpoint($model, 'generateContent');
}
private function buildEndpoint(string $model, string $action): string
{
$projectId = env('GOOGLE_CLOUD_PROJECT');
$location = env('GOOGLE_LOCATION');
$modelPath = 'projects/' . $projectId .
'/locations/' . $location .
'/publishers/google/models/' . $model .
':' . $action;
return 'https://' . $location . '-aiplatform.googleapis.com/v1/' . $modelPath;
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Services\Google\Error;
use Exception;
class APIError extends Exception
{
public $code;
public $message;
public function __construct(string $message, int $code)
{
parent::__construct($message);
$this->code = $code;
$this->message = $message;
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Services\Google;
/**
* "aspectRatio": "1:1, 9:16, 16:9, 3:4, or 4:3. default 1:1",
*/
enum ImageAspectRatios: string
{
case _1_1 = '1:1';
case _9_16 = '9:16';
case _16_9 = '16:9';
case _3_4 = '3:4';
case _4_3 = '4:3';
}
@@ -0,0 +1,14 @@
<?php
namespace App\Services\Google;
/**
* "safetySetting": "block_none, block_only_high, block_medium_and_above, block_low_and_above. default block_medium_and_above"
*/
enum ImageGenerationSafetySettings: string
{
case BLOCK_NONE = 'block_none';
case BLOCK_ONLY_HIGH = 'block_only_high';
case BLOCK_MEDIUM_AND_ABOVE = 'block_medium_and_above';
case BLOCK_LOW_AND_ABOVE = 'block_low_and_above';
}
@@ -0,0 +1,91 @@
<?php
namespace App\Services\Google;
use App\Services\Google\DTO\ImageGenerationRequest;
use App\Services\Google\DTO\ImageGenerationResponse;
/**
* ref: https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/imagen-api
*/
class ImageGenerationService
{
private readonly string $imagenModel;
public function __construct(
private readonly ApiAuthService $apiAuthService,
private readonly EndpointBuilderService $endpointBuilderService,
)
{
$this->imagenModel = env('GOOGLE_IMAGEN_MODEL');
}
/**
* @throws \Exception
*/
public function generateImage(ImageGenerationRequest $request): ImageGenerationResponse
{
$requestBody = $this->buildRequestBody($request);
return $this->sendGenerationRequest($requestBody);
}
private function buildRequestBody(ImageGenerationRequest $request): string {
$instance = [
'prompt' => $request->prompt,
];
if ($request->negativePrompt) {
$instance['negativePrompt'] = $request->negativePrompt;
}
$parameters = [
'sampleCount' => 1,
];
if ($request->sampleCount) {
$parameters['sampleCount'] = $request->sampleCount;
}
if ($request->aspectRatio) {
$parameters['aspectRatio'] = $request->aspectRatio;
}
if ($request->safetySetting) {
$parameters['safetySetting'] = $request->safetySetting;
}
$requestBody = [
'instances' => [$instance],
'parameters' => $parameters
];
return json_encode($requestBody);
}
/**
* @throws \Google\Exception
* @throws \Exception
*/
private function sendGenerationRequest(string $requestBody): ImageGenerationResponse
{
$endpoint = $this->endpointBuilderService->buildPredictionEndpoint($this->imagenModel);
$accessToken = $this->apiAuthService->getAccessToken();
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken,
'Content-Type: application/json',
]);
$response = curl_exec($ch);
if (curl_errno($ch) || $response === false) {
throw new \Exception(curl_error($ch));
}
curl_close($ch);
$responseArray = json_decode($response, true);
return new ImageGenerationResponse($responseArray);
}
}