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};"
>