Skip to content

Commit 83492c9

Browse files
committed
feat(gemini): add support for image generation in gemini models
1 parent 709bd5d commit 83492c9

File tree

8 files changed

+89
-19
lines changed

8 files changed

+89
-19
lines changed

assets/components/modai/js/mgr/widgets/image_prompt.window.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,22 @@ Ext.extend(modAI.window.ImagePrompt,MODx.Window, {
4040
info.update({currentPage: this._cache.visible + 1, total: this._cache.history.length})
4141
info.show();
4242

43-
this.hidenUrl.setValue(this._cache.history[this._cache.visible].url);
43+
const currentItem = this._cache.history[this._cache.visible];
44+
45+
if (currentItem.url) {
46+
this.hidenUrl.setValue(currentItem.url);
47+
} else {
48+
this.hidenUrl.setValue('');
49+
}
50+
51+
if (currentItem.base64) {
52+
this.hidenBase64.setValue(currentItem.base64);
53+
} else {
54+
this.hidenBase64.setValue('');
55+
}
56+
4457
this.prompt.setValue(this._cache.history[this._cache.visible].prompt)
45-
this.imagePreview.update({url: this._cache.history[this._cache.visible].url});
58+
this.imagePreview.update({url: currentItem?.url || currentItem.base64});
4659

4760

4861
if (this._cache.visible <= 0) {
@@ -114,6 +127,10 @@ Ext.extend(modAI.window.ImagePrompt,MODx.Window, {
114127
name: 'url'
115128
});
116129

130+
this.hidenBase64 = new Ext.form.Hidden({
131+
name: 'image'
132+
});
133+
117134
this.downloadButton = new Ext.Button({
118135
text: _('save'),
119136
cls: 'primary-button',
@@ -150,6 +167,7 @@ Ext.extend(modAI.window.ImagePrompt,MODx.Window, {
150167
name: 'fieldName'
151168
},
152169
this.hidenUrl,
170+
this.hidenBase64,
153171
this.prompt,
154172
{
155173
style: 'margin-top: 10px;',
@@ -176,7 +194,7 @@ Ext.extend(modAI.window.ImagePrompt,MODx.Window, {
176194
listeners: {
177195
success: {
178196
fn: (r) => {
179-
this.pagination.addItem({prompt: this.prompt.getValue(), url: r.object.url});
197+
this.pagination.addItem({prompt: this.prompt.getValue(), ...r.object});
180198
Ext.Msg.hide();
181199
}
182200
},

core/components/modai/src/Processors/Download/Image.php

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function process() {
1414
$resource = $this->getProperty('resource');
1515
$field = $this->getProperty('fieldName', '');
1616
$url = $this->getProperty('url');
17+
$image = $this->getProperty('image');
1718
$mediaSource = (int)$this->getProperty('mediaSource', 0);
1819

1920
if (empty($mediaSource)) {
@@ -24,21 +25,25 @@ public function process() {
2425
return $this->failure("Resource is required");
2526
}
2627

27-
if (empty($url)) {
28-
return $this->failure('URL is required');
28+
if (empty($url) && empty($image)) {
29+
return $this->failure('URL or Image is required');
2930
}
3031

31-
$additionalDomains = Settings::getSetting($this->modx, 'image.download_domains');
32+
$additionalDomains = Settings::getSetting($this->modx, 'image.download_domains', '');
3233
$additionalDomains = Utils::explodeAndClean($additionalDomains);
3334

3435
$allowedDomains = array_merge($additionalDomains, $this->allowedDomains);
3536

36-
$domainAllowed = false;
37-
foreach ($allowedDomains as $domain) {
38-
if (strncmp($url, $domain, strlen($domain)) === 0) {
39-
$domainAllowed = true;
40-
break;
37+
if (!empty($url)) {
38+
$domainAllowed = false;
39+
foreach ($allowedDomains as $domain) {
40+
if (strncmp($url, $domain, strlen($domain)) === 0) {
41+
$domainAllowed = true;
42+
break;
43+
}
4144
}
45+
} else {
46+
$domainAllowed = true;
4247
}
4348

4449
if (!$domainAllowed) {
@@ -59,7 +64,9 @@ public function process() {
5964

6065
$filePath = $this->createFilePath($path, $resource);
6166

62-
$source->createObject($filePath[0], $filePath[1], file_get_contents($url));
67+
$image = file_get_contents(empty($url) ? $image : $url);
68+
69+
$source->createObject($filePath[0], $filePath[1], $image);
6370

6471
return $this->success('', ['url' => $filePath[0].$filePath[1]]);
6572
}

core/components/modai/src/Processors/Prompt/Image.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function process()
3737
$aiService = AIServiceFactory::new($model, $this->modx);
3838
$result = $aiService->generateImage($prompt, ImageConfig::new($model)->size($size)->quality($quality));
3939

40-
return $this->success('', ['url' => $result]);
40+
return $this->success('', $result);
4141
} catch (\Exception $e) {
4242
return $this->failure($e->getMessage());
4343
}

core/components/modai/src/Services/AIService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ interface AIService {
1010

1111
public function getCompletions(array $data, CompletionsConfig $config): string;
1212
public function getVision(string $prompt, string $image, VisionConfig $config): string;
13-
public function generateImage(string $prompt, ImageConfig $config): string;
13+
public function generateImage(string $prompt, ImageConfig $config): array;
1414
}

core/components/modai/src/Services/AIServiceFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ public static function new($model, modX &$modx): AIService {
99
return new Gemini($modx);
1010
}
1111

12+
if (strncmp($model, 'imagen-', strlen('imagen-')) === 0) {
13+
return new Gemini($modx);
14+
}
15+
1216
if (strncmp($model, 'claude-', strlen('claude-')) === 0) {
1317
return new Claude($modx);
1418
}

core/components/modai/src/Services/ChatGPT.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function __construct(modX &$modx)
1818
$this->modx =& $modx;
1919
}
2020

21-
public function generateImage(string $prompt, ImageConfig $config): string {
21+
public function generateImage(string $prompt, ImageConfig $config): array {
2222
$apiKey = $this->modx->getOption('modai.api.chatgpt.key');
2323
if (empty($apiKey)) {
2424
throw new \Exception('Missing modai.api.chatgpt.key');
@@ -63,7 +63,7 @@ public function generateImage(string $prompt, ImageConfig $config): string {
6363
throw new \Exception("There was an error generating a response.");
6464
}
6565

66-
return $result['data'][0]['url'];
66+
return [ 'url' => $result['data'][0]['url'] ];
6767
}
6868

6969
/**

core/components/modai/src/Services/Claude.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function getVision(string $prompt, string $image, VisionConfig $config):
8989
throw new \Exception("not implemented");
9090
}
9191

92-
public function generateImage(string $prompt, ImageConfig $config): string
92+
public function generateImage(string $prompt, ImageConfig $config): array
9393
{
9494
throw new \Exception("not implemented");
9595
}

core/components/modai/src/Services/Gemini.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Gemini implements AIService {
1010
private modX $modx;
1111

1212
const COMPLETIONS_API = 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={apiKey}';
13+
const IMAGES_API = 'https://generativelanguage.googleapis.com/v1beta/models/{model}:predict?key={apiKey}';
1314

1415
public function __construct(modX &$modx)
1516
{
@@ -99,8 +100,48 @@ public function getVision(string $prompt, string $image, VisionConfig $config):
99100
throw new \Exception("not implemented");
100101
}
101102

102-
public function generateImage(string $prompt, ImageConfig $config): string
103+
public function generateImage(string $prompt, ImageConfig $config): array
103104
{
104-
throw new \Exception("not implemented");
105+
$apiKey = $this->modx->getOption('modai.api.gemini.key');
106+
if (empty($apiKey)) {
107+
throw new \Exception('Missing modai.api.gemini.key');
108+
}
109+
110+
$url = self::IMAGES_API;
111+
$url = str_replace("{model}", $config->getModel(), $url);
112+
$url = str_replace("{apiKey}", $apiKey, $url);
113+
114+
$input = [
115+
"instances" => [
116+
"prompt" => $prompt,
117+
],
118+
"parameters" => [
119+
"sampleCount" => $config->getN()
120+
]
121+
];
122+
123+
$ch = curl_init($url);
124+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
125+
curl_setopt($ch, CURLOPT_POST, true);
126+
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($input));
127+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
128+
'Content-Type: application/json',
129+
]);
130+
131+
$response = curl_exec($ch);
132+
if (curl_errno($ch)) {
133+
$error_msg = curl_error($ch);
134+
curl_close($ch);
135+
throw new \Exception($error_msg);
136+
}
137+
138+
curl_close($ch);
139+
140+
$result = json_decode($response, true);
141+
if (!is_array($result)) {
142+
throw new \Exception('Invalid response');
143+
}
144+
145+
return ['base64' => 'data:image/png;base64, ' . $result['predictions'][0]['bytesBase64Encoded']];
105146
}
106147
}

0 commit comments

Comments
 (0)