Skip to content

Commit

Permalink
Merge branch '11.5-alttext' into '11.5'
Browse files Browse the repository at this point in the history
Alt text functionality - TYPO3 10/11

See merge request typo3-commons/mkcontentai!19
  • Loading branch information
hannesbochmann committed Feb 1, 2024
2 parents ec29bbe + 76a318b commit eddb235
Show file tree
Hide file tree
Showing 36 changed files with 952 additions and 128 deletions.
56 changes: 56 additions & 0 deletions Classes/Backend/EventListener/CustomFileControlsEventListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

/*
* Copyright notice
*
* (c) DMK E-BUSINESS GmbH <[email protected]>
* All rights reserved
*
* This file is part of TYPO3 CMS-based extension "mkcontentai" by DMK E-BUSINESS GmbH.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

namespace DMK\MkContentAi\Backend\EventListener;

use TYPO3\CMS\Backend\Form\Event\CustomFileControlsEvent;
use TYPO3\CMS\Backend\Form\NodeFactory;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;

final class CustomFileControlsEventListener
{
/**
* @var NodeFactory
*/
public $nodeFactory;

/**
* @var IconFactory
*/
public $iconFactory;

public function __construct()
{
$this->nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
$this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
}

public function handleEvent(CustomFileControlsEvent $event): void
{
$item = ' <div class="form-control-wrap"><button type="button" class="btn btn-default t3js-prompt" id="prompt">';
$item .= $this->iconFactory->getIcon('actions-image', Icon::SIZE_SMALL)->render().' ';
$item .= htmlspecialchars('AI generation of image by text prompt');
$item .= '</button></div>';

$event->addControl($item);

$pageRenderer = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Page\PageRenderer::class);
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Mkcontentai/BackendPrompt');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

/*
* Copyright notice
*
* (c) DMK E-BUSINESS GmbH <[email protected]>
* All rights reserved
*
* This file is part of TYPO3 CMS-based extension "mkcontentai" by DMK E-BUSINESS GmbH.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

namespace DMK\MkContentAi\Backend\Form\Element;

use TYPO3\CMS\Backend\Form\Element\InputTextElement;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class InputTextWithAiAltTextSupportElement extends InputTextElement
{
/**
* @return array<string, mixed>
*/
public function render(): array
{
$resultArray = parent::render();

if ('sys_file_reference' !== $this->data['tableName'] || 'alternative' !== $this->data['fieldName']) {
return $resultArray;
}

$html = explode(LF, $resultArray['html']);
$fileUid = $this->data['databaseRow']['uid_local'][0]['uid'];
$typoThreeVersion = GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion();
$pageLanguageUid = $this->data['databaseRow']['sys_language_uid'][0];

if (11 === $typoThreeVersion) {
$pageLanguageUid = $this->data['databaseRow']['sys_language_uid'];
}

$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$item[] = ' <div data-uid-local="'.$fileUid.'" data-sys-language-uid="'.$pageLanguageUid.'"class="formengine-field-item t3js-formengine-field-item form-description">
<button type="button" class="btn btn-default t3js-prompt alt-refresh">
<span class="spinner-border spinner-border-sm" style="display: none"></span>';
$item[] = $iconFactory->getIcon('actions-image', Icon::SIZE_SMALL)->render().' ';
$item[] = htmlspecialchars('Generate alt text by AI');
$item[] = '</button></div>';

array_splice($html, 3, 0, $item);
$resultArray['html'] = implode(LF, $html);
$resultArray['requireJsModules'][] = 'TYPO3/CMS/Mkcontentai/AltText';

return $resultArray;
}
}
11 changes: 11 additions & 0 deletions Classes/ContextMenu/ContentAiItemProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ class ContentAiItemProvider extends AbstractProvider
'iconIdentifier' => 'actions-rocket',
'callbackAction' => 'extend',
],
'alt' => [
'type' => 'item',
'label' => 'Alt text generate',
'iconIdentifier' => 'actions-rocket',
'callbackAction' => 'alt',
],
];

public function canHandle(): bool
Expand Down Expand Up @@ -99,6 +105,10 @@ private function generateUrl(string $itemName): Uri
if ('extend' === $itemName) {
$parameters['tx_mkcontentai_system_mkcontentaicontentai']['action'] = 'cropAndExtend';
}
if ('alt' === $itemName) {
$parameters['tx_mkcontentai_system_mkcontentaicontentai']['controller'] = 'AiText';
$parameters['tx_mkcontentai_system_mkcontentaicontentai']['action'] = 'altText';
}

/**
* @var UriBuilder $uriBuilder
Expand All @@ -124,6 +134,7 @@ protected function canRender(string $itemName, string $type): bool
switch ($itemName) {
case 'upscale':
case 'extend':
case 'alt':
$canRender = $this->isImage();
break;
}
Expand Down
35 changes: 28 additions & 7 deletions Classes/Controller/AiImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
namespace DMK\MkContentAi\Controller;

use DMK\MkContentAi\Domain\Model\Image;
use DMK\MkContentAi\Http\Client\ClientInterface;
use DMK\MkContentAi\Http\Client\ImageApiInterface;
use DMK\MkContentAi\Http\Client\OpenAiClient;
use DMK\MkContentAi\Http\Client\StabilityAiClient;
use DMK\MkContentAi\Http\Client\StableDiffusionClient;
Expand Down Expand Up @@ -54,7 +54,7 @@ class AiImageController extends BaseController
3 => StabilityAiClient::class,
];

public ClientInterface $client;
public ImageApiInterface $client;

public function initializeAction(): void
{
Expand Down Expand Up @@ -88,21 +88,22 @@ public function initializeAction(): void
}
$actionMethodName = $this->request->getControllerActionName();
if (!in_array($actionMethodName, $this->client->getAllowedOperations())) {
$this->controllerContext = $this->buildControllerContext();
$this->addFlashMessage($actionMethodName.' is not allowed for current API '.get_class($this->client), '', AbstractMessage::ERROR);
$this->redirect('filelist');
}
parent::initializeAction();
}

/**
* @return array{client?:ClientInterface, clientClass?:string, error?:string}
* @return array{client?:ImageApiInterface, clientClass?:string, error?:string}
*/
private function initializeClient(): array
{
try {
$imageEngineKey = SettingsController::getImageAiEngine();
$client = GeneralUtility::makeInstance($this::GENERATOR_ENGINE[$imageEngineKey]);
if (is_a($client, ClientInterface::class)) {
if (is_a($client, ImageApiInterface::class)) {
return [
'client' => $client,
'clientClass' => get_class($client),
Expand All @@ -124,11 +125,26 @@ private function initializeClient(): array
*/
public function filelistAction(): void
{
$fileService = GeneralUtility::makeInstance(FileService::class, $this->client->getFolderName());
$clientResponse = $this->initializeClient();

if (!isset($clientResponse['client'])) {
$this->addFlashMessage('Please set AI client in settings first', '', AbstractMessage::WARNING);
$this->view->assignMultiple(
[
'files' => [],
'client' => null,
]
);

return;
}

$client = $clientResponse['client'];
$fileService = GeneralUtility::makeInstance(FileService::class, $client->getFolderName());
$this->view->assignMultiple(
[
'files' => $fileService->getFiles(),
'client' => $this->client,
'client' => $client,
]
);
}
Expand Down Expand Up @@ -211,7 +227,12 @@ public function promptAction(): void
public function promptResultAction(string $text): void
{
try {
$images = $this->client->image($text);
$clientResponse = $this->initializeClient();
$client = $clientResponse['client'] ?? null;
if (null === $client) {
throw new \Exception(isset($clientResponse['error']) ? $clientResponse['error'] : 'Something went wrong');
}
$images = $client->image($text);
} catch (\Exception $e) {
$this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR);
$this->redirect('prompt');
Expand Down
89 changes: 89 additions & 0 deletions Classes/Controller/AiTextController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

/*
* Copyright notice
*
* (c) DMK E-BUSINESS GmbH <[email protected]>
* All rights reserved
*
* This file is part of TYPO3 CMS-based extension "mkcontentai" by DMK E-BUSINESS GmbH.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/

namespace DMK\MkContentAi\Controller;

use DMK\MkContentAi\Service\AiAltTextService;
use DMK\MkContentAi\Service\SiteLanguageService;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Domain\Model\File;

/**
* This file is part of the "DMK Content AI" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2023
*/

/**
* ImageController.
*/
class AiTextController extends BaseController
{
public AiAltTextService $aiAltTextService;
public SiteLanguageService $siteLanguageService;

public function __construct(AiAltTextService $aiAltTextService, SiteLanguageService $siteLanguageService)
{
$this->aiAltTextService = $aiAltTextService;
$this->siteLanguageService = $siteLanguageService;
}

public function altTextAction(File $file): void
{
$this->view->assignMultiple(
[
'file' => $file,
'altText' => $this->getAltTextForFile($file),
'languageName' => $this->siteLanguageService->getFullLanguageName(),
]
);
}

public function altTextSaveAction(File $file): void
{
$altText = $this->getAltTextForFile($file);

$metadata = $file->getOriginalResource()->getMetaData();
$metadata->offsetSet('alternative', $altText);
$metadata->save();

$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$metaDataUid = $file->getOriginalResource()->getMetaData()->get()['uid'];
$editUrl = $uriBuilder->buildUriFromRoute('record_edit', [
'edit[sys_file_metadata]['.$metaDataUid.']' => 'edit',
]);
$this->redirectToUri($editUrl);
}

private function getAltTextForFile(File $file): string
{
$altTextFromFile = '';

try {
$altTextFromFile = $this->aiAltTextService->getAltText($file);
} catch (\Exception $e) {
$this->addFlashMessage($e->getMessage(), '', AbstractMessage::ERROR);
}

return $altTextFromFile;
}
}
Loading

0 comments on commit eddb235

Please sign in to comment.