diff --git a/Classes/DataProcessing/ChatbotConfigProcessor.php b/Classes/DataProcessing/ChatbotConfigProcessor.php index b92fc55..116aa64 100644 --- a/Classes/DataProcessing/ChatbotConfigProcessor.php +++ b/Classes/DataProcessing/ChatbotConfigProcessor.php @@ -7,6 +7,8 @@ use Moinferdi\Chatbot\Service\ConfigurationResolver; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Core\Resource\FileRepository; +use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface; @@ -14,10 +16,12 @@ final class ChatbotConfigProcessor implements DataProcessorInterface { private const DEFAULT_START_MESSAGE = 'LLL:EXT:chatbot/Resources/Private/Language/locallang.xlf:widget.startMessage'; + private const DEFAULT_TITLE = 'LLL:EXT:chatbot/Resources/Private/Language/locallang.xlf:widget.title'; public function __construct( private readonly ConfigurationResolver $configResolver, private readonly LanguageServiceFactory $languageServiceFactory, + private readonly FileRepository $fileRepository, ) {} /** @@ -56,22 +60,74 @@ public function process( $processedData['startMessage'] = $override !== '' ? $override : $this->localizedDefaultStartMessage($request); + // Per-language title: DB override wins, otherwise fall back to the + // localized default title. + $title = (string)($config->title ?? ''); + $processedData['chatTitle'] = $title !== '' + ? $title + : $this->localizedDefaultTitle($request); $processedData['colorPrimary'] = $config->colorPrimary; $processedData['colorBackground'] = $config->colorBackground; $processedData['colorText'] = $config->colorText; + $processedData['colorTitle'] = $config->colorTitle; $processedData['position'] = $config->position; + $processedData['avatarUrl'] = $this->resolveAvatarUrl($request); return $processedData; } private function localizedDefaultStartMessage(ServerRequestInterface $request): string + { + return $this->translate(self::DEFAULT_START_MESSAGE, $request); + } + + private function localizedDefaultTitle(ServerRequestInterface $request): string + { + return $this->translate(self::DEFAULT_TITLE, $request); + } + + private function translate(string $lllKey, ServerRequestInterface $request): string { $language = $request->getAttribute('language'); $languageService = $language instanceof SiteLanguage ? $this->languageServiceFactory->createFromSiteLanguage($language) : $this->languageServiceFactory->create('default'); - return $languageService->sL(self::DEFAULT_START_MESSAGE); + return $languageService->sL($lllKey); + } + + /** + * Resolve the FAL avatar image URL from the site root page. + * Returns empty string if no avatar is configured. + */ + private function resolveAvatarUrl(ServerRequestInterface $request): string + { + /** @var Site|null $site */ + $site = $request->getAttribute('site'); + if (!$site) { + return ''; + } + + $rootPageId = $site->getRootPageId(); + + try { + $fileObjects = $this->fileRepository->findByRelation( + 'pages', + 'tx_chatbot_avatar', + $rootPageId + ); + if (!empty($fileObjects)) { + $file = $fileObjects[0]; + $publicUrl = $file->getPublicUrl(); + if ($publicUrl !== null) { + return $publicUrl; + } + } + } catch (\Throwable) { + // FAL lookup may fail in some contexts; degrade gracefully. + } + + return ''; } private function getRequest(ContentObjectRenderer $cObj): \Psr\Http\Message\ServerRequestInterface diff --git a/Classes/Service/ChatbotConfig.php b/Classes/Service/ChatbotConfig.php index b6db679..b9e332f 100644 --- a/Classes/Service/ChatbotConfig.php +++ b/Classes/Service/ChatbotConfig.php @@ -15,27 +15,31 @@ private function __construct( public readonly string $colorPrimary, public readonly string $colorBackground, public readonly string $colorText, + public readonly string $colorTitle, public readonly string $position, public readonly ?string $startMessage, + public readonly ?string $title, ) {} public static function disabled(): self { return new self( enabled: false, baseUrl: '', apiKey: '', model: '', everywhere: false, - colorPrimary: '', colorBackground: '', colorText: '', position: '', startMessage: null, + colorPrimary: '', colorBackground: '', colorText: '', colorTitle: '', position: '', startMessage: null, title: null, ); } public static function enabled( string $baseUrl, string $apiKey, string $model, bool $everywhere, string $colorPrimary, string $colorBackground, string $colorText, - string $position, ?string $startMessage, + string $colorTitle, + string $position, ?string $startMessage, ?string $title = null, ): self { return new self( enabled: true, baseUrl: $baseUrl, apiKey: $apiKey, model: $model, everywhere: $everywhere, colorPrimary: $colorPrimary, colorBackground: $colorBackground, colorText: $colorText, - position: $position, startMessage: $startMessage, + colorTitle: $colorTitle, + position: $position, startMessage: $startMessage, title: $title, ); } diff --git a/Classes/Service/ConfigurationResolver.php b/Classes/Service/ConfigurationResolver.php index d004b19..6e570ad 100644 --- a/Classes/Service/ConfigurationResolver.php +++ b/Classes/Service/ConfigurationResolver.php @@ -64,13 +64,16 @@ public function resolve(ServerRequestInterface $request): ChatbotConfig $colorPrimary = ($rootPage['tx_chatbot_color_primary'] ?? '') ?: ($settings['color']['primary'] ?? '#4F9EF7'); $colorBackground = ($rootPage['tx_chatbot_color_background'] ?? '') ?: ($settings['color']['background'] ?? '#ffffff'); $colorText = ($rootPage['tx_chatbot_color_text'] ?? '') ?: ($settings['color']['text'] ?? '#1a1a1a'); + $colorTitle = ($rootPage['tx_chatbot_color_title'] ?? '') ?: ($settings['color']['title'] ?? '#ffffff'); $position = ($rootPage['tx_chatbot_position'] ?? '') ?: ($settings['position'] ?? 'bottom-right'); $startMessage = ($rootPage['tx_chatbot_start_message'] ?? '') ?: null; + $title = ($rootPage['tx_chatbot_title'] ?? '') ?: null; return ChatbotConfig::enabled( baseUrl: $baseUrl, apiKey: $apiKey, model: $model, everywhere: $everywhere, colorPrimary: $colorPrimary, colorBackground: $colorBackground, colorText: $colorText, - position: $position, startMessage: $startMessage, + colorTitle: $colorTitle, + position: $position, startMessage: $startMessage, title: $title, ); } diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php index 1086291..b6e231b 100644 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -108,6 +108,15 @@ 'default' => '#1a1a1a', ], ], + 'tx_chatbot_color_title' => [ + 'exclude' => true, + 'label' => $ll . 'pages.tx_chatbot_color_title', + 'config' => [ + 'type' => 'color', + 'size' => 10, + 'default' => '#ffffff', + ], + ], 'tx_chatbot_position' => [ 'exclude' => true, 'label' => $ll . 'pages.tx_chatbot_position', @@ -133,6 +142,27 @@ 'placeholder' => 'Hello! How can I help you today?', ], ], + 'tx_chatbot_title' => [ + 'exclude' => true, + 'label' => $ll . 'pages.tx_chatbot_title', + 'description' => $ll . 'pages.tx_chatbot_title.description', + 'config' => [ + 'type' => 'input', + 'eval' => 'trim', + 'size' => 40, + 'max' => 255, + 'placeholder' => 'Chat Assistant', + ], + ], + 'tx_chatbot_avatar' => [ + 'exclude' => true, + 'label' => $ll . 'pages.tx_chatbot_avatar', + 'config' => [ + 'type' => 'file', + 'allowed' => ['common-image-types'], + 'maxitems' => 1, + ], + ], ]; foreach ($columns as $field => $config) { @@ -148,7 +178,9 @@ 'tx_chatbot_enabled', 'tx_chatbot_everywhere', 'tx_chatbot_base_url', 'tx_chatbot_model', 'tx_chatbot_api_key', 'tx_chatbot_color_primary', 'tx_chatbot_color_background', 'tx_chatbot_color_text', + 'tx_chatbot_color_title', 'tx_chatbot_position', 'tx_chatbot_start_message', + 'tx_chatbot_title', 'tx_chatbot_avatar', ]); ExtensionManagementUtility::addToAllTCAtypes( @@ -167,7 +199,7 @@ // inherit the default-language value instead of overwriting it with a blank // (which previously could wipe out the API key in non-default languages). foreach (array_keys($columns) as $field) { - if ($field !== 'tx_chatbot_start_message') { + if ($field !== 'tx_chatbot_start_message' && $field !== 'tx_chatbot_title') { $GLOBALS['TCA']['pages']['columns'][$field]['l10n_mode'] = 'exclude'; } } diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf index 1c0d2c3..b1f59c4 100644 --- a/Resources/Private/Language/locallang_db.xlf +++ b/Resources/Private/Language/locallang_db.xlf @@ -12,9 +12,13 @@ Primary Color Background Color Text Color + Chat Title Color Widget Position Start Message Shown when the chat opens. This field is per-language: translate the root page to set a greeting for each language. Leave empty to use the built-in localized default. All other settings above are shared across languages. + Chat Title + Custom title shown in the chat header. This field is per-language. Leave empty to use the default "Chat Assistant". + Profile Photo Bottom Right Bottom Left Chatbot Widget diff --git a/Resources/Private/Partials/Chatbot/Widget.html b/Resources/Private/Partials/Chatbot/Widget.html index b1351b2..e3b7c11 100644 --- a/Resources/Private/Partials/Chatbot/Widget.html +++ b/Resources/Private/Partials/Chatbot/Widget.html @@ -15,11 +15,12 @@ id="moinferdi-chatbot" class="cb-widget cb-widget--{position}" role="region" - aria-label="{f:translate(key:'widget.title', extensionName:'Chatbot', default:'Chat assistant')}" + aria-label="{chatTitle}" data-endpoint="{endpointUrl}" data-model="{model}" data-start-message="{startMessage}" - style="--cb-primary:{colorPrimary}; --cb-bg:{colorBackground}; --cb-text:{colorText};" + data-avatar-url="{avatarUrl}" + style="--cb-primary:{colorPrimary}; --cb-bg:{colorBackground}; --cb-text:{colorText}; --cb-title-color:{colorTitle};" >