maestro-3.0.1-rc2/modules/maestro_ai_task/src/Plugin/MaestroAiTaskCapabilities/MaestroAiTaskTextToImageUrl.php
modules/maestro_ai_task/src/Plugin/MaestroAiTaskCapabilities/MaestroAiTaskTextToImageUrl.php
<?php
namespace Drupal\maestro_ai_task\Plugin\MaestroAiTaskCapabilities;
use Drupal\ai\OperationType\TextToImage\TextToImageInput;
use Drupal\Core\Form\FormStateInterface;
use Drupal\maestro_ai_task\MaestroAiTaskCapabilitiesInterface;
use Drupal\maestro_ai_task\MaestroAiTaskCapabilitiesPluginBase;
use Drupal\core\File\FilesystemInterface;
/**
* Provides a 'MaestroAiTaskTextToImageUrl' Maestro AI Task Capability.
* This capability uses the AI module's text to image provider.
* TODO:
* Basic Sanitization of the return image done via PHP's getimagesizefromstring.
* GD/ImageMagick could be used to further sanitize the return.
*
*
* @MaestroAiTaskCapabilities(
* id = "MaestroAiTaskTextToImageUrl",
* ai_provider = "text_to_image",
* capability_description = @Translation("Text to Image operation type, saving the image as a physical file and returning the URL."),
* )
*/
class MaestroAiTaskTextToImageUrl extends MaestroAiTaskCapabilitiesPluginBase implements MaestroAiTaskCapabilitiesInterface {
/**
* task
* The task array. This will be the running instance of the MaestroAITask object.
* Contains the configuration of the task itself.
*
* @var array
*/
protected $task = NULL;
/**
* templateMachineName
* The string machine name of the Maestro template.
*
* @var string
*/
protected $templateMachineName = '';
/**
* prompt
* The string prompt from the task.
*
* @var string
*/
protected $prompt = '';
/**
* {@inheritDoc}
*/
public function getMaestroAiTaskConfigFormElements() : array {
// We can save this image in either public or private file storage.
$form = [];
// Provide a select box to choose public:: or private::
$default_value = $this->task['data']['ai']['ai_text_to_image_file_storage'] ?? '';
$form['ai_text_to_image_file_storage'] = [
'#type' => 'select',
'#title' => $this->t('File Storage'),
'#options' => [
'public://' => $this->t('Public'),
'private://' => $this->t('Private'),
],
'#description' => $this->t('The public or private filesystem to save the image to.'),
'#description_display' => 'after',
'#default_value' => 'private://',
'#required' => FALSE,
];
// Now a simple textfield so they can enter a filename.
$default_value = $this->task['data']['ai']['ai_text_to_image_filename'] ?? '';
$form['ai_text_to_image_filename'] = [
'#type' => 'textfield',
'#title' => $this->t('Filename without extension'),
'#description' =>
$this->t('The filename to save the image as without an extension.<br>') .
$this->t('<strong>Do not include a path as you will specify a path below.</strong><br>') .
$this->t('The save routine will attempt to create the appropriate image extension.<br>') .
$this->t('<strong>Please determine if your AI model can only support specific output image types (example: only PNG)</strong>.<br>') .
$this->t('Tokens are allowed.'),
'#description_display' => 'after',
'#default_value' => $default_value,
'#required' => FALSE,
];
// Now a simple textfield so they can enter a path
$default_value = $this->task['data']['ai']['ai_text_to_image_path'] ?? '';
$form['ai_text_to_image_path'] = [
'#type' => 'textfield',
'#title' => $this->t('Path'),
'#description' => $this->t('The path to save the image to (It will be created if it does not exist). Tokens are allowed.'),
'#description_display' => 'after',
'#default_value' => $default_value,
'#required' => FALSE,
];
// We have a few tokens we can use here
if (\Drupal::moduleHandler()->moduleExists('token')) {
$form['token_tree'] = [
'#theme' => 'token_tree_link',
'#token_types' => ['maestro'],
];
}
else {
$form['token_tree'] = [
'#plain_text' => $this->t('Enabling the Token module will reveal the replacable tokens.'),
];
}
$default_value = $this->task['data']['ai']['ai_text_to_image_url_or_path'] ?? 0;
$form['ai_text_to_image_url_or_path'] = [
'#type' => 'checkbox',
'#title' => $this->t('Return as browser accessible image?'),
'#description' =>
$this->t('When checked, return the image URL as a browser accessible link if possible. Otherwise returns an internal image path.<br>') .
$this->t('<strong>You will ALWAYS get an internal image path (not a browser accessble URL) when your File Storage option is set to Private.</strong>'),
'#description_display' => 'after',
'#default_value' => $default_value,
'#required' => FALSE,
];
return $form;
}
/**
* {@inheritDoc}
*/
public function validateMaestroAiTaskEditForm(array &$form, FormStateInterface $form_state) : void {
// We need to validate that our fields for filename, path and file storage are plausible.
$file_storage = $form_state->getValue('ai_text_to_image_file_storage');
$file_name = $form_state->getValue('ai_text_to_image_filename');
$file_path = $form_state->getValue('ai_text_to_image_path');
if(!$file_storage) {
$form_state->setErrorByName('ai_text_to_image_file_storage', $this->t('File storage is required.'));
}
if(!$file_name) {
$form_state->setErrorByName('ai_text_to_image_filename', $this->t('Filename is required.'));
}
if(!$file_path) {
$form_state->setErrorByName('ai_text_to_image_path', $this->t('Path is required.'));
}
// In our execution routine, the folder path will be created for the user.
}
/**
* {@inheritDoc}
*/
public function prepareTaskForSave(array &$form, FormStateInterface $form_state, array &$task) : void {
$task['data']['ai']['ai_text_to_image_file_storage'] = $form_state->getValue('ai_text_to_image_file_storage');
$task['data']['ai']['ai_text_to_image_filename'] = $form_state->getValue('ai_text_to_image_filename');
$task['data']['ai']['ai_text_to_image_path'] = $form_state->getValue('ai_text_to_image_path');
$task['data']['ai']['ai_text_to_image_url_or_path'] = $form_state->getValue('ai_text_to_image_url_or_path');
$task['data']['ai']['ai_return_format'] = ''; // Clear out the ai return format
}
/**
* {@inheritDoc}
*/
public function execute() : ?string {
/** @var \Drupal\ai\AiProviderPluginManager $service */
$service = \Drupal::service('ai.provider');
$sets = $service->getDefaultProviderForOperationType('text_to_image');
$provider = $service->createInstance($sets['provider_id']);
$input = new TextToImageInput($this->prompt);
// How are we returning a successful image?
$return_as_public_url = $this->task['data']['ai']['ai_text_to_image_url_or_path'] ?? 0; // 1 for browser url, 0 for path
// Use the task configuration to set the file URL.
$file_storage = $this->task['data']['ai']['ai_text_to_image_file_storage']; // private:// or public://
// Make sure $file_path ends in a slash and doesn't begin with one.
$file_path = trim(trim($this->task['data']['ai']['ai_text_to_image_path'], '/'), '\\') . '/';
$filename = $this->task['data']['ai']['ai_text_to_image_filename'];
/** @var \Drupal\Core\Utility\Token $tokenService */
$tokenService = \Drupal::token();
$file_path = $tokenService->replacePlain(
$file_path,
[
'maestro' => [
'task' => $this->task,
'queueID' => $this->queueID,
'processID' => $this->processID,
]
]);
$filename = $tokenService->replacePlain(
$filename,
[
'maestro' => [
'task' => $this->task,
'queueID' => $this->queueID,
'processID' => $this->processID,
]
]);
$file_url = $file_storage . $file_path . $filename;
$file_system = \Drupal::service('file_system');
$file_path_exists = $file_system->realpath($file_url);
if(!$file_path_exists) {
// Create the folder
$folder_path = $file_storage . $file_path;
$file_system->prepareDirectory($folder_path, FileSystemInterface::CREATE_DIRECTORY);
}
/** @var \Drupal\ai\OperationType\TextToImage\TextToImageOutput $return_images */
$return_images = $provider->textToImage($input, $sets['model_id'], ['maestro-ai-task-text-to-image']);
/** @var \Drupal\ai\OperationType\GenericType\ImageFile */
$image = $return_images->getNormalized()[0] ?? NULL;
if($image) {
$image_binary = $image->getAsBinary();
// Some rudimentary sanitization of the return image
if(getimagesizefromstring($image_binary) === FALSE) {
return 'invalid';
}
$extension = $image->getFileType();
$file_url = $file_url . '.' . $extension;
$save_operation = @file_put_contents($file_url, $image_binary);
if($save_operation === FALSE) {
$file_url = NULL;
}
elseif($file_storage == 'public://' && $return_as_public_url) {
// We can create an actual visible URL but only if PUBLIC and only if we've said so in the task config.
$file_url = \Drupal::service('file_url_generator')->generateAbsoluteString($file_url);
}
}
else {
$file_url = NULL;
}
return $file_url;
}
/**
* {@inheritDoc}
*/
public function performMaestroAiTaskValidityCheck(array &$validation_failure_tasks, array &$validation_information_tasks, array $task) : void {
// Nothing to implement.
}
/**
* {@inheritDoc}
*/
public function allowConfigurableReturnFormat() : bool {
return FALSE;
}
}
