diff --git a/code_samples/ai_actions/config/services.yaml b/code_samples/ai_actions/config/services.yaml index 786132d019..68ae24c706 100644 --- a/code_samples/ai_actions/config/services.yaml +++ b/code_samples/ai_actions/config/services.yaml @@ -25,7 +25,7 @@ services: App\Command\AddMissingAltTextCommand: arguments: - $projectDir: '%kernel.project_dir%' + $binaryDataHandler: '@Ibexa\Core\IO\IOBinarydataHandler\SiteAccessDependentBinaryDataHandler' App\AI\Handler\LLaVATextToTextActionHandler: tags: diff --git a/code_samples/ai_actions/src/Command/ActionConfigurationCreateCommand.php b/code_samples/ai_actions/src/Command/ActionConfigurationCreateCommand.php index 0715e57a2e..644dc26467 100644 --- a/code_samples/ai_actions/src/Command/ActionConfigurationCreateCommand.php +++ b/code_samples/ai_actions/src/Command/ActionConfigurationCreateCommand.php @@ -76,9 +76,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->actionConfigurationService->createActionConfiguration($actionConfigurationCreateStruct); $action = new RefineTextAction(new Text([ - <<actionConfigurationService->getActionConfiguration('rewrite_casual'); diff --git a/code_samples/ai_actions/src/Command/AddMissingAltTextCommand.php b/code_samples/ai_actions/src/Command/AddMissingAltTextCommand.php index 0dab4e6725..0d527b8c44 100644 --- a/code_samples/ai_actions/src/Command/AddMissingAltTextCommand.php +++ b/code_samples/ai_actions/src/Command/AddMissingAltTextCommand.php @@ -21,6 +21,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator; use Ibexa\Contracts\Core\Repository\Values\Filter\Filter; use Ibexa\Core\FieldType\Image\Value; +use Ibexa\Core\IO\IOBinarydataHandler; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -42,7 +43,7 @@ final class AddMissingAltTextCommand extends Command private ActionServiceInterface $actionService; - private string $projectDir; + private IOBinarydataHandler $binaryDataHandler; public function __construct( ContentService $contentService, @@ -50,7 +51,7 @@ public function __construct( UserService $userService, FieldTypeService $fieldTypeService, ActionServiceInterface $actionService, - string $projectDir + IOBinarydataHandler $binaryDataHandler ) { parent::__construct(); $this->contentService = $contentService; @@ -58,7 +59,7 @@ public function __construct( $this->userService = $userService; $this->fieldTypeService = $fieldTypeService; $this->actionService = $actionService; - $this->projectDir = $projectDir; + $this->binaryDataHandler = $binaryDataHandler; } protected function configure(): void @@ -75,11 +76,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Content $content */ foreach ($modifiedImages as $content) { - /** @var ?Value $value */ + /** @var \Ibexa\Core\FieldType\Image\Value $value */ $value = $content->getFieldValue(self::IMAGE_FIELD_IDENTIFIER); if ($value === null || !$this->shouldGenerateAltText($value)) { - $output->writeln(sprintf('Image %s has the image field empty or the alternative text is already specified. Skipping.', $content->getName())); + $output->writeln(sprintf('Image %s has the image field empty, the file cannot be accessed, or the alternative text is already specified. Skipping.', $content->getName())); continue; } @@ -124,12 +125,10 @@ private function getSuggestedAltText(string $imageEncodedInBase64, string $langu return $output->getText(); } - private function convertImageToBase64(?string $uri): string + private function convertImageToBase64(string $uri): string { - $file = file_get_contents($this->projectDir . \DIRECTORY_SEPARATOR . 'public' . \DIRECTORY_SEPARATOR . $uri); - if ($file === false) { - throw new \RuntimeException('Cannot read file'); - } + $id = $this->binaryDataHandler->getIdFromUri($uri); + $file = $this->binaryDataHandler->getContents($id); return 'data:image/jpeg;base64,' . base64_encode($file); } @@ -145,10 +144,12 @@ private function getModifiedImages(): ContentList return $this->contentService->find($filter); } + /** @phpstan-assert-if-true string $value->uri */ private function shouldGenerateAltText(Value $value): bool { return $this->fieldTypeService->getFieldType('ezimage')->isEmptyValue($value) === false && - $value->isAlternativeTextEmpty(); + $value->isAlternativeTextEmpty() && + $value->uri !== null; } private function setUser(string $userLogin): void diff --git a/docs/ai_actions/extend_ai_actions.md b/docs/ai_actions/extend_ai_actions.md index a2d29742e5..9edd155acc 100644 --- a/docs/ai_actions/extend_ai_actions.md +++ b/docs/ai_actions/extend_ai_actions.md @@ -5,7 +5,7 @@ month_change: true # Extend AI Actions -By extending AI Actions, you can make regular content management and editing tasks more appealing and less demanding. +By extending [AI Actions](ai_actions_guide.md), you can make regular content management and editing tasks more appealing and less demanding. You can start by integrating additional AI services to the existing action types or develop custom ones that impact completely new areas of application. For example, you can create a handler that connects to a translation model and use it to translate your website on-the-fly, or generate illustrations based on a body of an article. @@ -14,7 +14,7 @@ For example, you can create a handler that connects to a translation model and u You can execute AI Actions by using the [ActionServiceInterface](../api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionServiceInterface.html) service, as in the following example: ``` php -[[= include_file('code_samples/ai_actions/src/Command/AddMissingAltTextCommand.php', 101, 120) =]] +[[= include_file('code_samples/ai_actions/src/Command/AddMissingAltTextCommand.php', 102, 121) =]] ``` The `GenerateAltTextAction` is a built-in action that implements the [ActionInterface](../api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionInterface.html), takes an [Image](../api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-Action-DataType-Image.html) as an input, and generates the alternative text in the response. @@ -43,7 +43,7 @@ You can influence the execution of an Action with two events: Below you can find the full example of a Symfony Command, together with a matching service definition. The command finds the images modified in the last 24 hours, and adds the alternative text to them if it's missing. -``` php hl_lines="87 100-125" +``` php hl_lines="88 101-126" [[= include_file('code_samples/ai_actions/src/Command/AddMissingAltTextCommand.php') =]] ``` @@ -114,7 +114,7 @@ Create a class implementing the [ActionHandlerInterface](../api/php_api/php_api_ See the code sample below, together with a matching service definition: -``` php hl_lines="21 29-31 34-69 71-74" +``` php hl_lines="21 29-32 34-69 71-74" [[= include_file('code_samples/ai_actions/src/AI/Handler/LLaVaTextToTextActionHandler.php') =]] ``` @@ -146,10 +146,14 @@ The created Form Type adds the `system_prompt` field to the Form. Use the `Ibexa\Bundle\ConnectorAi\Form\FormMapper\ActionConfiguration\ActionHandlerOptionsFormMapper` class together with the `ibexa.connector_ai.action_configuration.form_mapper.options` service tag to make it part of the Action Handler options form. Pass the Action Handler identifier (`LLaVATextToText`) as the type when tagging the service. -The Action Handler and Action Type options are rendered in the back office using the built-in Twig option formatter. +The Action Handler and Action Type options are rendered in the back office using the built-in Twig options formatter. + +![Custom Action Handler options rendered using the default Twig options formatter](img/action_handler_options.png "Custom Action Handler options rendered using the default Twig options formatter") + + You can create your own formatting by creating a class implementing the [OptionsFormatterInterface](../api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-ActionConfiguration-OptionsFormatterInterface.html) interface and aliasing it to `Ibexa\Contracts\ConnectorAi\ActionConfiguration\OptionsFormatterInterface`. -The following service definition switches the options rendering to the other built-in option formatter, displaying the options as JSON. +The following service definition switches the options rendering to the other built-in options formatter, displaying the options as JSON. ``` yaml [[= include_file('code_samples/ai_actions/config/services.yaml', 64, 66) =]] @@ -192,7 +196,7 @@ See the built-in Action Types like Generate Alt Text or Refine Text for an examp ### Create custom Data classes -The `TranscribeAudio` Action Type requires adding two data classes that exists in its definition: +The `TranscribeAudio` Action Type requires adding two data classes that exist in its definition: - an `Audio` class, implementing the [DataType interface](../api/php_api/php_api_reference/classes/Ibexa-Contracts-ConnectorAi-DataType.html), to store the input data for the Action diff --git a/docs/ai_actions/img/action_handler_options.png b/docs/ai_actions/img/action_handler_options.png new file mode 100644 index 0000000000..da514f31ea Binary files /dev/null and b/docs/ai_actions/img/action_handler_options.png differ