diff --git a/Api/RequestInterface.php b/Api/RequestInterface.php index bcf7a57..2fe33fc 100644 --- a/Api/RequestInterface.php +++ b/Api/RequestInterface.php @@ -7,7 +7,7 @@ interface RequestInterface { /** - * Retrieve products id. + * Retrieve product id. * @return int */ public function getId(): int; @@ -17,4 +17,10 @@ public function getId(): int; * @return bool */ public function getOverwrite(): bool; + + /** + * Retrieve store id. + * @return int + */ + public function getStoreId(): int; } diff --git a/Controller/Adminhtml/Product/MassEnrich.php b/Controller/Adminhtml/Product/MassEnrich.php index 5e5abf1..5099659 100644 --- a/Controller/Adminhtml/Product/MassEnrich.php +++ b/Controller/Adminhtml/Product/MassEnrich.php @@ -13,6 +13,7 @@ use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\LocalizedException; +use Magento\Store\Model\StoreManagerInterface; use Magento\Ui\Component\MassAction\Filter; use MageOS\CatalogDataAI\Model\Config; use MageOS\CatalogDataAI\Model\Product\Publisher; @@ -27,6 +28,7 @@ public function __construct( private readonly Config $config, private readonly Publisher $publisher, private readonly ProductRepositoryInterface $productRepository, + private readonly StoreManagerInterface $storeManager, ) { parent::__construct($context); } @@ -42,11 +44,12 @@ public function execute(): Redirect $collection = $this->filter->getCollection($this->collectionFactory->create()); $productEnriched = 0; + $storeId = (int)$this->storeManager->getStore()->getId(); if ($this->config->isEnabled()) { /** @var Product $product */ foreach ($collection->getItems() as $product) { //@TODO: we hit rate limit, change to batching the request - $this->publisher->execute($product->getId(), $this->overwrite); + $this->publisher->execute($product->getId(), $this->overwrite, $storeId); $productEnriched++; } diff --git a/Model/Config.php b/Model/Config.php index a4bce11..0b61940 100644 --- a/Model/Config.php +++ b/Model/Config.php @@ -28,6 +28,21 @@ class Config private array $attributePromptsMap = []; + private const LOCALE_LANGUAGE_MAP = [ + 'af' => 'Afrikaans', 'ar' => 'Arabic', 'bg' => 'Bulgarian', 'bn' => 'Bengali', + 'ca' => 'Catalan', 'cs' => 'Czech', 'cy' => 'Welsh', 'da' => 'Danish', + 'de' => 'German', 'el' => 'Greek', 'en' => 'English', 'es' => 'Spanish', + 'et' => 'Estonian', 'fa' => 'Persian', 'fi' => 'Finnish', 'fr' => 'French', + 'gl' => 'Galician', 'he' => 'Hebrew', 'hi' => 'Hindi', 'hr' => 'Croatian', + 'hu' => 'Hungarian', 'id' => 'Indonesian', 'it' => 'Italian', 'ja' => 'Japanese', + 'ka' => 'Georgian', 'ko' => 'Korean', 'lt' => 'Lithuanian', 'lv' => 'Latvian', + 'mk' => 'Macedonian', 'ms' => 'Malay', 'nb' => 'Norwegian', 'nl' => 'Dutch', + 'pl' => 'Polish', 'pt' => 'Portuguese', 'ro' => 'Romanian', 'ru' => 'Russian', + 'sk' => 'Slovak', 'sl' => 'Slovenian', 'sq' => 'Albanian', 'sr' => 'Serbian', + 'sv' => 'Swedish', 'th' => 'Thai', 'tr' => 'Turkish', 'uk' => 'Ukrainian', + 'vi' => 'Vietnamese', 'zh' => 'Chinese', + ]; + public function __construct( private readonly ScopeConfigInterface $scopeConfig, private readonly Json $json @@ -109,11 +124,27 @@ public function canEnrich(Product $product): bool return $this->isEnabled() && $this->getApiKey() && $product->isObjectNew(); } - public function getSystemPrompt(): mixed + public function getSystemPrompt(): string { - return $this->scopeConfig->getValue( - self::XML_PATH_OPENAI_API_ADVANCED_SYSTEM_PROMPT + $basePrompt = (string)$this->scopeConfig->getValue( + self::XML_PATH_OPENAI_API_ADVANCED_SYSTEM_PROMPT, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + + $locale = $this->scopeConfig->getValue( + 'general/locale/code', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); + + if ($locale) { + $langCode = substr($locale, 0, 2); + $language = self::LOCALE_LANGUAGE_MAP[$langCode] ?? null; + if ($language && $langCode !== 'en') { + $basePrompt = 'Respond in ' . $language . '. ' . $basePrompt; + } + } + + return $basePrompt; } public function getTemperature(): float diff --git a/Model/Product/Consumer.php b/Model/Product/Consumer.php index 92a4c76..e4ae5bb 100644 --- a/Model/Product/Consumer.php +++ b/Model/Product/Consumer.php @@ -7,27 +7,23 @@ use Magento\Catalog\Model\ProductRepository; use Magento\Store\Model\StoreManagerInterface; -/** - * Class Consumer - * @package Gaiterjones\RabbitMQ\MessageQueues\Product - */ class Consumer { - /** - * Consumer constructor. - */ public function __construct( - private readonly Enricher $enricher, - private readonly ProductRepository $productRepository, + private readonly Enricher $enricher, + private readonly ProductRepository $productRepository, private readonly StoreManagerInterface $storeManager ) { } public function execute(Request $request): void { - // @TODO: enrich for all stores if different value or language - $this->storeManager->setCurrentStore(0); - $product = $this->productRepository->getById($request->getId()); + $this->storeManager->setCurrentStore($request->getStoreId()); + $product = $this->productRepository->getById( + $request->getId(), + false, + $request->getStoreId() + ); $product->setData('mageos_catalogai_overwrite', $request->getOverwrite()); $this->enricher->execute($product); @@ -35,5 +31,4 @@ public function execute(Request $request): void $this->productRepository->save($product); } } - } diff --git a/Model/Product/Publisher.php b/Model/Product/Publisher.php index ec9a80f..6ed8093 100644 --- a/Model/Product/Publisher.php +++ b/Model/Product/Publisher.php @@ -19,15 +19,12 @@ public function __construct( ) { } - /** - * @param int|string $productId - * @param bool $overwrite - */ - public function execute(int|string $productId, bool $overwrite = false): void + public function execute(int|string $productId, bool $overwrite = false, int $storeId = 0): void { $request = $this->requestFactory->create([ 'id' => (int)$productId, 'overwrite' => $overwrite, + 'storeId' => $storeId, ]); $this->publisher->publish(self::TOPIC_NAME, $request); } diff --git a/Model/Product/Request.php b/Model/Product/Request.php index cf94864..c6d02dd 100644 --- a/Model/Product/Request.php +++ b/Model/Product/Request.php @@ -13,23 +13,23 @@ class Request implements RequestInterface { public function __construct( private readonly int $id, - private readonly bool $overwrite + private readonly bool $overwrite, + private readonly int $storeId = 0 ) { } - /** - * @inheritDoc - */ public function getId(): int { return $this->id; } - /** - * @inheritDoc - */ public function getOverwrite(): bool { return $this->overwrite; } + + public function getStoreId(): int + { + return $this->storeId; + } } diff --git a/Observer/Product/SaveAfter.php b/Observer/Product/SaveAfter.php index 5f31352..98af254 100644 --- a/Observer/Product/SaveAfter.php +++ b/Observer/Product/SaveAfter.php @@ -28,7 +28,11 @@ public function execute(Observer $observer): void $this->persistDeferredEnrichments($product); if ($this->config->canEnrich($product) && $this->config->isAsync()) { - $this->publisher->execute($product->getId(), false); + $this->publisher->execute( + $product->getId(), + false, + (int)$product->getStoreId() + ); } } diff --git a/Test/Unit/Model/ConfigTest.php b/Test/Unit/Model/ConfigTest.php index 55717de..793f361 100644 --- a/Test/Unit/Model/ConfigTest.php +++ b/Test/Unit/Model/ConfigTest.php @@ -178,14 +178,51 @@ public function testGetApiModelReturnsEmptyStringWhenNull(): void public function testGetSystemPrompt(): void { - $this->scopeConfig->expects($this->once()) - ->method('getValue') - ->with(Config::XML_PATH_OPENAI_API_ADVANCED_SYSTEM_PROMPT) - ->willReturn('You are a helpful assistant'); + $this->scopeConfig->method('getValue') + ->willReturnMap([ + [Config::XML_PATH_OPENAI_API_ADVANCED_SYSTEM_PROMPT, ScopeInterface::SCOPE_STORE, null, 'You are a helpful assistant'], + ['general/locale/code', ScopeInterface::SCOPE_STORE, null, 'en_US'], + ]); $this->assertSame('You are a helpful assistant', $this->config->getSystemPrompt()); } + public function test_get_system_prompt_prepends_language_for_non_english_locale(): void + { + $this->scopeConfig->method('getValue') + ->willReturnMap([ + [Config::XML_PATH_OPENAI_API_ADVANCED_SYSTEM_PROMPT, ScopeInterface::SCOPE_STORE, null, 'Be a content generator.'], + ['general/locale/code', ScopeInterface::SCOPE_STORE, null, 'fr_FR'], + ]); + + $result = $this->config->getSystemPrompt(); + + $this->assertStringContainsString('Respond in French', $result); + $this->assertStringContainsString('Be a content generator.', $result); + } + + public function test_get_system_prompt_skips_language_prefix_for_english_locale(): void + { + $this->scopeConfig->method('getValue') + ->willReturnMap([ + [Config::XML_PATH_OPENAI_API_ADVANCED_SYSTEM_PROMPT, ScopeInterface::SCOPE_STORE, null, 'Be a content generator.'], + ['general/locale/code', ScopeInterface::SCOPE_STORE, null, 'en_US'], + ]); + + $this->assertSame('Be a content generator.', $this->config->getSystemPrompt()); + } + + public function test_get_system_prompt_handles_unknown_locale_gracefully(): void + { + $this->scopeConfig->method('getValue') + ->willReturnMap([ + [Config::XML_PATH_OPENAI_API_ADVANCED_SYSTEM_PROMPT, ScopeInterface::SCOPE_STORE, null, 'Be a content generator.'], + ['general/locale/code', ScopeInterface::SCOPE_STORE, null, 'xx_YY'], + ]); + + $this->assertSame('Be a content generator.', $this->config->getSystemPrompt()); + } + public function testGetApiMaxTokens(): void { $this->scopeConfig->expects($this->once()) diff --git a/Test/Unit/Model/Product/PublisherTest.php b/Test/Unit/Model/Product/PublisherTest.php index 48fddb0..e92c98e 100644 --- a/Test/Unit/Model/Product/PublisherTest.php +++ b/Test/Unit/Model/Product/PublisherTest.php @@ -42,6 +42,7 @@ public function testExecuteCreatesRequestWithCorrectParams(): void ->with([ 'id' => $productId, 'overwrite' => $overwrite, + 'storeId' => 0, ]) ->willReturn($request); @@ -80,6 +81,7 @@ public function testExecuteDefaultOverwriteIsFalse(): void ->with([ 'id' => $productId, 'overwrite' => false, + 'storeId' => 0, ]) ->willReturn($request); @@ -102,6 +104,7 @@ public function testExecuteCastsStringIdToInt(): void ->with([ 'id' => 42, 'overwrite' => $overwrite, + 'storeId' => 0, ]) ->willReturn($request); diff --git a/Test/Unit/Model/Product/RequestTest.php b/Test/Unit/Model/Product/RequestTest.php index d152724..555880c 100644 --- a/Test/Unit/Model/Product/RequestTest.php +++ b/Test/Unit/Model/Product/RequestTest.php @@ -11,7 +11,7 @@ use MageOS\CatalogDataAI\Model\Product\Request; use PHPUnit\Framework\TestCase; -class RequestTest extends TestCase +final class RequestTest extends TestCase { public function testGetIdReturnsConstructorValue(): void { @@ -30,4 +30,20 @@ public function testGetOverwriteReturnsFalse(): void $request = new Request(1, false); $this->assertFalse($request->getOverwrite()); } + + public function test_request_carries_store_id(): void + { + $request = new Request(42, true, 3); + + $this->assertSame(42, $request->getId()); + $this->assertTrue($request->getOverwrite()); + $this->assertSame(3, $request->getStoreId()); + } + + public function test_store_id_defaults_to_zero(): void + { + $request = new Request(42, false); + + $this->assertSame(0, $request->getStoreId()); + } } diff --git a/etc/module.xml b/etc/module.xml index 6fe838a..637dd60 100755 --- a/etc/module.xml +++ b/etc/module.xml @@ -3,6 +3,7 @@ +