Skip to content

Commit 83c14b6

Browse files
committed
feat: add system setting to configure download path for generated images
1 parent 56b33d8 commit 83c14b6

File tree

11 files changed

+121
-17
lines changed

11 files changed

+121
-17
lines changed

_build/gpm.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ systemSettings:
8585
- key: image.quality
8686
area: image
8787
value: standard
88+
- key: image.path
89+
area: image
90+
value: "assets/ai/{resourceId}/{hash}.png"
91+
- key: image.download_domains
92+
area: image
8893

8994
- key: tvs
9095
area: tvs

assets/components/modai/js/mgr/autosummary.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ Ext.onReady(function() {
218218
return aiWrapper;
219219
}
220220

221-
const createImagePrompt = (defaultPrompt, onSuccess) => {
221+
const createImagePrompt = (defaultPrompt, mediaSource, fieldName, onSuccess) => {
222222
const imageWand = createWandEl();
223223
imageWand.addEventListener('click', () => {
224224
const createColumn = MODx.load({
@@ -227,6 +227,8 @@ Ext.onReady(function() {
227227
record: {
228228
resource: MODx.request.id,
229229
prompt: defaultPrompt,
230+
mediaSource,
231+
fieldName,
230232
},
231233
listeners: {
232234
success: {
@@ -289,12 +291,13 @@ Ext.onReady(function() {
289291
field.label.appendChild(wrapper);
290292
}
291293

292-
const attachImagePlus = (imgPlusPanel) => {
294+
const attachImagePlus = (imgPlusPanel, fieldName) => {
293295
const imagePlus = Ext.getCmp(imgPlusPanel.firstChild.id);
294296

295297
const imageWand = createImagePrompt(
296-
// Ext.getCmp('modx-resource-introtext').getValue(),
297298
'',
299+
imagePlus.imageBrowser.source,
300+
fieldName,
298301
function(res) {
299302
imagePlus.imageBrowser.setValue(res.a.result.object.url);
300303
imagePlus.onImageChange(res.a.result.object.url)
@@ -368,10 +371,12 @@ Ext.onReady(function() {
368371
}
369372

370373
const field = form.findField(`tv${tvId}`);
374+
const fieldName = `tv.${tvName}`;
375+
371376
if (!field) {
372377
const imgPlusPanel = wrapper.dom.querySelector('.imageplus-panel-input');
373378
if (imgPlusPanel) {
374-
attachImagePlus(imgPlusPanel);
379+
attachImagePlus(imgPlusPanel, fieldName);
375380
}
376381
continue;
377382
}
@@ -380,7 +385,6 @@ Ext.onReady(function() {
380385
const prompt = MODx.config[`modai.tv.${tvName}.prompt`];
381386

382387
const label = wrapper.dom.querySelector('label');
383-
const fieldName = `tv.${tvName}`;
384388

385389
if (prompt) {
386390
label.appendChild(createForcedTextPrompt(field, fieldName));
@@ -392,6 +396,8 @@ Ext.onReady(function() {
392396
if (field.xtype === 'modx-panel-tv-image') {
393397
const imageWand = createImagePrompt(
394398
'',
399+
field.source,
400+
fieldName,
395401
function(res) {
396402
const eventData = {
397403
relativeUrl: res.a.result.object.url,

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Ext.extend(modAI.window.ImagePrompt,MODx.Window, {
119119
cls: 'primary-button',
120120
scope: this,
121121
handler: this.submit,
122-
disabled: true
122+
disabled: false
123123
});
124124

125125
this.pagination = {
@@ -141,6 +141,14 @@ Ext.extend(modAI.window.ImagePrompt,MODx.Window, {
141141
xtype: 'hidden',
142142
name: 'resource'
143143
},
144+
{
145+
xtype: 'hidden',
146+
name: 'mediaSource'
147+
},
148+
{
149+
xtype: 'hidden',
150+
name: 'fieldName'
151+
},
144152
this.hidenUrl,
145153
this.prompt,
146154
{

core/components/modai/lexicon/en/default.inc.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@
4444
$_lang['setting_modai.image.quality_desc'] = 'Valid options are `standard` (default) and `hd`.';
4545
$_lang['setting_modai.tvs'] = 'Use with Template Variables';
4646
$_lang['setting_modai.tvs_desc'] = 'The Template Variable fields to attached modAI generative AI buttons to. Must be a text, textarea, image or Image+ Input Type.';
47+
$_lang['setting_modai.image.path'] = 'Image Path';
48+
$_lang['setting_modai.image.path_desc'] = 'Path including file name where the AI generated image will be stored. Available placeholders: {hash}, {shortHash}, {resourceId}.';
49+
$_lang['setting_modai.image.download_domains'] = 'Allowed Download Domains';
50+
$_lang['setting_modai.image.download_domains_desc'] = 'Additional domains to allow downloading generated images from.';

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

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@
22

33
namespace modAI\Processors\Download;
44

5+
use modAI\Settings;
6+
use modAI\Utils;
57
use MODX\Revolution\Processors\Processor;
68
use MODX\Revolution\Sources\modMediaSource;
79

810
class Image extends Processor {
11+
private $allowedDomains = ['https://oaidalleapiprodscus.blob.core.windows.net'];
12+
913
public function process() {
1014
$resource = $this->getProperty('resource');
15+
$field = $this->getProperty('fieldName', '');
1116
$url = $this->getProperty('url');
17+
$mediaSource = (int)$this->getProperty('mediaSource', 0);
18+
19+
if (empty($mediaSource)) {
20+
return $this->failure("Media Source is required");
21+
}
1222

1323
if (empty($resource)) {
1424
return $this->failure("Resource is required");
@@ -18,17 +28,55 @@ public function process() {
1828
return $this->failure('URL is required');
1929
}
2030

21-
$source = modMediaSource::getDefaultSource($this->modx, 1);
31+
$additionalDomains = Settings::getSetting($this->modx, 'image.download_domains');
32+
$additionalDomains = Utils::explodeAndClean($additionalDomains);
33+
34+
$allowedDomains = array_merge($additionalDomains, $this->allowedDomains);
35+
36+
$domainAllowed = false;
37+
foreach ($allowedDomains as $domain) {
38+
if (strncmp($url, $domain, strlen($domain)) === 0) {
39+
$domainAllowed = true;
40+
break;
41+
}
42+
}
43+
44+
if (!$domainAllowed) {
45+
return $this->failure('Domain not allowed for image downloads.');
46+
}
47+
48+
$source = modMediaSource::getDefaultSource($this->modx, $mediaSource);
2249

2350
if (!$source->initialize()) {
2451
return $this->failure('fail');
2552
}
2653

27-
$dir = "assets/ai/$resource/";
28-
$name = md5(microtime()) . '.png';
54+
try {
55+
$path = Settings::getImageFieldSetting($this->modx, $field, 'path');
56+
} catch (\Exception $e) {
57+
return $this->failure($e->getMessage());
58+
}
59+
60+
$filePath = $this->createFilePath($path, $resource);
61+
62+
$source->createObject($filePath[0], $filePath[1], file_get_contents($url));
63+
64+
return $this->success('', ['url' => $filePath[0].$filePath[1]]);
65+
}
66+
67+
private function createFilePath($path, $resource): array
68+
{
69+
$hash = md5(microtime());
70+
71+
$path = str_replace('{hash}', $hash, $path);
72+
$path = str_replace('{shortHash}', substr($hash, 0, 4), $path);
73+
$path = str_replace('{resourceId}', $resource, $path);
74+
75+
$path = trim($path, DIRECTORY_SEPARATOR);
76+
$path = explode(DIRECTORY_SEPARATOR, $path);
2977

30-
$source->createObject($dir, $name, file_get_contents($url));
78+
$fileName = array_pop($path);
3179

32-
return $this->success('', ['url' => $dir.$name]);
80+
return [implode(DIRECTORY_SEPARATOR, $path) . DIRECTORY_SEPARATOR, $fileName];
3381
}
3482
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public function process()
1414
set_time_limit(0);
1515

1616
$prompt = $this->getProperty('prompt');
17-
$field = $this->getProperty('field');
17+
$field = $this->getProperty('field', '');
1818

1919
if (empty($prompt)) {
2020
return $this->failure('Prompt is required.');

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function process()
1616
set_time_limit(0);
1717

1818
$fields = array_flip(self::$validFields);
19-
$field = $this->getProperty('field');
19+
$field = $this->getProperty('field', '');
2020

2121
if (substr($field, 0, 3) === 'tv.') {
2222
$modAi = $this->modx->services->get('modai');

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
class AIServiceFactory {
77
public static function new($model, modX &$modx): AIService {
8-
if (substr($model, 0, 7) === 'gemini-') {
8+
if (strncmp($model, 'gemini-', strlen('gemini-')) === 0) {
99
return new Gemini($modx);
1010
}
1111

12-
if (substr($model, 0, 7) === 'claude-') {
12+
if (strncmp($model, 'claude-', strlen('claude-')) === 0) {
1313
return new Claude($modx);
1414
}
1515

16-
if (substr($model, 0,7) === 'custom_') {
16+
if (strncmp($model, 'custom_', strlen('custom_')) === 0) {
1717
$compatibility = $modx->getOption('modai.api.custom.compatibility');
1818
if ($compatibility === 'openai') {
1919
return new CustomChatGPT($modx);

core/components/modai/src/Services/Config/Model.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public static function new(string $model): self {
1414
}
1515

1616
public function getModel(): string {
17-
if (substr($this->model, 0,7) === 'custom_') {
17+
if (strncmp($this->model, 'custom_', strlen('custom_')) === 0) {
1818
return substr($this->model, 7);
1919
}
2020

core/components/modai/src/Settings.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,21 @@ public static function getPrompt(modX $modx, string $field, string $model = null
3535
public static function getSetting(modX $modx, string $key, string $default = null) {
3636
return $modx->getOption("modai.$key", null, $default);
3737
}
38+
39+
/**
40+
* @throws RequiredSettingException
41+
*/
42+
public static function getImageFieldSetting(modX $modx, string $field, string $setting, bool $required = true): string {
43+
if (!empty($field)) {
44+
$value = $modx->getOption("modai.image.$field.$setting", null, $modx->getOption("modai.image.$setting"), true);
45+
} else {
46+
$value = $modx->getOption("modai.image.$setting");
47+
}
48+
49+
if ($required && empty($value)) {
50+
throw new RequiredSettingException("modai.image.$setting");
51+
}
52+
53+
return $value;
54+
}
3855
}

core/components/modai/src/Utils.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
namespace modAI;
3+
4+
class Utils {
5+
public static function explodeAndClean(string $stringArray, string $delimiter = ',', bool $keepDuplicates = false): array
6+
{
7+
$array = explode($delimiter, $stringArray);
8+
$array = array_map('trim', $array);
9+
10+
if ($keepDuplicates == 0) {
11+
$array = array_keys(array_flip($array));
12+
}
13+
14+
return array_filter($array);
15+
}
16+
}

0 commit comments

Comments
 (0)