Skip to content

Commit e0bff8d

Browse files
Merge pull request #61 from WordPress/feature/image-generation
2 parents c92bc63 + 11accbb commit e0bff8d

File tree

14 files changed

+1455
-17
lines changed

14 files changed

+1455
-17
lines changed

cli.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function logError(string $message, int $exit_code = 1): void
9090

9191
// Prompt input. Allow complex input as a JSON string.
9292
$promptInput = $positional_args[0];
93-
if (strpos($promptInput, '{') === 0 || strpos($promptInput, '[') === 0) {
93+
if (str_starts_with($promptInput, '{') || str_starts_with($promptInput, '[')) {
9494
$decodedInput = json_decode($promptInput, true);
9595
if ($decodedInput) {
9696
$promptInput = $decodedInput;
@@ -156,7 +156,11 @@ function logError(string $message, int $exit_code = 1): void
156156
}
157157

158158
try {
159-
$result = $promptBuilder->generateTextResult();
159+
if ($outputFormat === 'image-json' || $outputFormat === 'image-base64') {
160+
$result = $promptBuilder->generateImageResult();
161+
} else {
162+
$result = $promptBuilder->generateTextResult();
163+
}
160164
} catch (InvalidArgumentException $e) {
161165
logError('Invalid arguments while trying to generate text result: ' . $e->getMessage());
162166
} catch (ResponseException $e) {
@@ -173,6 +177,12 @@ function logError(string $message, int $exit_code = 1): void
173177
case 'candidates-json':
174178
$output = json_encode($result->getCandidates(), JSON_PRETTY_PRINT);
175179
break;
180+
case 'image-json':
181+
$output = json_encode($result->toFile(), JSON_PRETTY_PRINT);
182+
break;
183+
case 'image-base64':
184+
$output = $result->toFile()->getBase64Data();
185+
break;
176186
case 'message-text':
177187
default:
178188
$output = $result->toText();

src/Common/AbstractEnum.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ protected static function determineClassEnumerations(string $className): array
328328
final public function __call(string $name, array $arguments): bool
329329
{
330330
// Handle is* methods
331-
if (strpos($name, 'is') === 0) {
331+
if (str_starts_with($name, 'is')) {
332332
$constantName = self::camelCaseToConstant(substr($name, 2));
333333
$constants = static::getConstants();
334334

src/Files/ValueObjects/MimeType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public static function isValid(string $mimeType): bool
204204
*/
205205
public function isType(string $mimeType): bool
206206
{
207-
return strpos($this->value, strtolower($mimeType) . '/') === 0;
207+
return str_starts_with($this->value, strtolower($mimeType) . '/');
208208
}
209209

210210
/**
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WordPress\AiClient\ProviderImplementations\Google;
6+
7+
use WordPress\AiClient\Providers\Http\DTO\Request;
8+
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
9+
use WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleImageGenerationModel;
10+
11+
/**
12+
* Class for a Google image generation model.
13+
*
14+
* @since n.e.x.t
15+
*/
16+
class GoogleImageGenerationModel extends AbstractOpenAiCompatibleImageGenerationModel
17+
{
18+
/**
19+
* @inheritDoc
20+
*/
21+
protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request
22+
{
23+
return new Request(
24+
$method,
25+
GoogleProvider::BASE_URI . '/openai/' . ltrim($path, '/'),
26+
$headers,
27+
$data
28+
);
29+
}
30+
}

src/ProviderImplementations/Google/GoogleModelMetadataDirectory.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,13 @@ protected function parseResponseToModelMetadataList(Response $response): array
158158
new SupportedOption(OptionEnum::outputFileType(), [FileTypeEnum::inline()]),
159159
new SupportedOption(OptionEnum::outputMediaOrientation(), [
160160
MediaOrientationEnum::square(),
161-
MediaOrientationEnum::landscape(),
162-
MediaOrientationEnum::portrait(),
161+
// The following orientations are normally supported, but not when using the OpenAI compatible endpoint.
162+
// MediaOrientationEnum::landscape(),
163+
// MediaOrientationEnum::portrait(),
163164
]),
164-
new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '16:9', '4:3', '9:16', '3:4']),
165+
// Aspect ratio is normally supported, but not when using the OpenAI compatible endpoint.
166+
// new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '16:9', '4:3', '9:16', '3:4']),
167+
new SupportedOption(OptionEnum::customOptions()),
165168
];
166169

167170
$modelsData = (array) $responseData['models'];

src/ProviderImplementations/Google/GoogleProvider.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ protected static function createModel(
3838
return new GoogleTextGenerationModel($modelMetadata, $providerMetadata);
3939
}
4040
if ($capability->isImageGeneration()) {
41-
// TODO: Implement GoogleImageGenerationModel.
42-
throw new RuntimeException(
43-
'Google image generation model class is not yet implemented.'
44-
);
41+
return new GoogleImageGenerationModel($modelMetadata, $providerMetadata);
4542
}
4643
}
4744

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WordPress\AiClient\ProviderImplementations\OpenAi;
6+
7+
use WordPress\AiClient\Providers\Http\DTO\Request;
8+
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
9+
use WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleImageGenerationModel;
10+
11+
/**
12+
* Class for an OpenAI image generation model.
13+
*
14+
* @since n.e.x.t
15+
*/
16+
class OpenAiImageGenerationModel extends AbstractOpenAiCompatibleImageGenerationModel
17+
{
18+
/**
19+
* @inheritDoc
20+
*/
21+
protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request
22+
{
23+
return new Request(
24+
$method,
25+
OpenAiProvider::BASE_URI . '/' . ltrim($path, '/'),
26+
$headers,
27+
$data
28+
);
29+
}
30+
31+
/**
32+
* @inheritDoc
33+
*/
34+
protected function prepareGenerateImageParams(array $prompt): array
35+
{
36+
$params = parent::prepareGenerateImageParams($prompt);
37+
38+
/*
39+
* Only the newer 'gpt-image-' models support passing a MIME type ('output_format').
40+
* Conversely, they do not support 'response_format', but always return a base64 encoded image.
41+
*/
42+
if (isset($params['model']) && is_string($params['model']) && str_starts_with($params['model'], 'gpt-image-')) {
43+
unset($params['response_format']);
44+
} else {
45+
unset($params['output_format']);
46+
}
47+
48+
return $params;
49+
}
50+
}

src/ProviderImplementations/OpenAi/OpenAiModelMetadataDirectory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
126126
MediaOrientationEnum::portrait(),
127127
]),
128128
new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '7:4', '4:7']),
129+
new SupportedOption(OptionEnum::customOptions()),
129130
];
130131
$gptImageOptions = [
131132
new SupportedOption(OptionEnum::inputModalities(), [[ModalityEnum::text()]]),
@@ -139,6 +140,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
139140
MediaOrientationEnum::portrait(),
140141
]),
141142
new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '3:2', '2:3']),
143+
new SupportedOption(OptionEnum::customOptions()),
142144
];
143145
$ttsCapabilities = [
144146
CapabilityEnum::textToSpeechConversion(),
@@ -154,6 +156,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
154156
'audio/aac',
155157
]),
156158
new SupportedOption(OptionEnum::outputSpeechVoice()),
159+
new SupportedOption(OptionEnum::customOptions()),
157160
];
158161

159162
$modelsData = (array) $responseData['data'];

src/ProviderImplementations/OpenAi/OpenAiProvider.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ protected static function createModel(
3838
return new OpenAiTextGenerationModel($modelMetadata, $providerMetadata);
3939
}
4040
if ($capability->isImageGeneration()) {
41-
// TODO: Implement OpenAiImageGenerationModel.
42-
throw new RuntimeException(
43-
'OpenAI image generation model class is not yet implemented.'
44-
);
41+
return new OpenAiImageGenerationModel($modelMetadata, $providerMetadata);
4542
}
4643
if ($capability->isTextToSpeechConversion()) {
4744
// TODO: Implement OpenAiTextToSpeechConversionModel.

src/Providers/Models/Enums/OptionEnum.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ protected static function determineClassEnumerations(string $className): array
9797

9898
// Add ModelConfig constants that start with KEY_
9999
foreach ($modelConfigConstants as $constantName => $constantValue) {
100-
if (strpos($constantName, 'KEY_') === 0) {
100+
if (str_starts_with($constantName, 'KEY_')) {
101101
// Remove KEY_ prefix to get the enum constant name
102102
$enumConstantName = substr($constantName, 4);
103103

0 commit comments

Comments
 (0)