Skip to content

Commit c65470d

Browse files
committed
created module with limited feature to enrich description
1 parent 8914702 commit c65470d

File tree

9 files changed

+274
-0
lines changed

9 files changed

+274
-0
lines changed

Model/Config.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
declare(strict_types=1);
3+
namespace MageOS\CatalogDataAI\Model;
4+
5+
use Magento\Store\Model\Store;
6+
7+
class Config
8+
{
9+
public const XML_PATH_ENRICH_ENABLED = 'catalog_ai/settings/active';
10+
public const XML_PATH_USE_ASYNC = 'catalog_ai/settings/async';
11+
public const XML_PATH_OPENAI_API_KEY = 'catalog_ai/settings/openai_key';
12+
public const XML_PATH_OPENAI_API_MODEL = 'catalog_ai/settings/openai_model';
13+
public const XML_PATH_OPENAI_API_MAX_TOKENS = 'catalog_ai/settings/openai_max_tokens';
14+
15+
public function __construct(
16+
private \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
17+
) {}
18+
19+
public function isEnabled()
20+
{
21+
return $this->scopeConfig->isSetFlag(
22+
self::XML_PATH_ENRICH_ENABLED
23+
);
24+
}
25+
public function IsAsync()
26+
{
27+
return $this->scopeConfig->isSetFlag(
28+
self::XML_PATH_USE_ASYNC
29+
);
30+
}
31+
32+
public function getApiKey()
33+
{
34+
return $this->scopeConfig->getValue(
35+
self::XML_PATH_OPENAI_API_KEY
36+
);
37+
}
38+
public function getApiModel()
39+
{
40+
return $this->scopeConfig->getValue(
41+
self::XML_PATH_OPENAI_API_MODEL
42+
);
43+
}
44+
public function getApiMaxTokens()
45+
{
46+
return (int)$this->scopeConfig->getValue(
47+
self::XML_PATH_OPENAI_API_MAX_TOKENS
48+
);
49+
}
50+
51+
public function getProductPrompt(String $attributeCode)
52+
{
53+
$path = 'catalog_ai/product/' . $attributeCode;
54+
return $this->scopeConfig->getValue(
55+
$path
56+
);
57+
}
58+
public function getProductPromptToken(String $attributeCode)
59+
{
60+
$path = 'catalog_ai/product/' . $attributeCode;
61+
return $this->scopeConfig->getValue(
62+
$path
63+
);
64+
}
65+
}

Observer/Product.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace MageOS\CatalogDataAI\Observer;
5+
6+
use Magento\Framework\Event\ObserverInterface;
7+
use Magento\Framework\Event\Observer;
8+
use MageOS\CatalogDataAI\Model\Config;
9+
use OpenAI\Factory;
10+
use OpenAI\Client;
11+
use Psr\Log\LoggerInterface;
12+
13+
class Product implements ObserverInterface
14+
{
15+
private Client $client;
16+
public function __construct(
17+
private Factory $clientFactory,
18+
private Config $config,
19+
private LoggerInterface $logger
20+
) {
21+
$this->client = $this->clientFactory->withApiKey($this->config->getApiKey())
22+
->make();
23+
}
24+
25+
public function getAttributes()
26+
{
27+
return [
28+
'short_description',
29+
'description'
30+
];
31+
}
32+
33+
public function enrichAttribute($product, $attributeCode)
34+
{
35+
if($product->getData($attributeCode)) {
36+
return;
37+
}
38+
if($prompt = $this->config->getProductPrompt($attributeCode)) {
39+
40+
$prompt = $this->parsePrompt($prompt, $product);
41+
42+
$response = $this->client->completions()->create([
43+
'model' => $this->config->getApiModel(),
44+
'prompt' => $this->parsePrompt($prompt, $product),
45+
'max_tokens' => $this->config->getApiMaxTokens(),
46+
'temperature' => 0.5
47+
]);
48+
49+
// @TODO: no exception?
50+
if($result = $response->choices[0]) {
51+
$product->setData($attributeCode, $result->text);
52+
}
53+
}
54+
}
55+
56+
/**
57+
* @todo move to parser class/pool
58+
*/
59+
public function parsePrompt($prompt, $product): String
60+
{
61+
return str_replace(
62+
['{{name}}', '{{sku}}'],
63+
[$product->getName(), $product->getSku()],
64+
$prompt
65+
);
66+
}
67+
68+
public function execute(Observer $observer): void
69+
{
70+
if(!$this->config->isEnabled() || !$this->config->getApiKey()) {
71+
return;
72+
}
73+
/** @var \Magento\Catalog\Model\Product $product */
74+
$product = $observer->getProduct();
75+
76+
// only enrich new products
77+
// if(!$product->isObjectNew()) {
78+
// return;
79+
// }
80+
81+
foreach ($this->getAttributes() as $attributeCode) {
82+
$this->enrichAttribute($product, $attributeCode);
83+
}
84+
}
85+
}

composer.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "mageos/module-catalog-data-ai",
3+
"description": "Generate product descriptions and similar content with the help of AI.",
4+
"type": "magento2-module",
5+
"license": [
6+
"MIT"
7+
],
8+
"repositories": [
9+
{
10+
"type": "composer",
11+
"url": "https://mirror.mage-os.org"
12+
}
13+
],
14+
"require": {
15+
"php": "^8.1",
16+
"openai-php/client": "*"
17+
},
18+
"require-dev": {
19+
"phpunit/phpunit": "^9.5"
20+
},
21+
"autoload": {
22+
"files": [
23+
"registration.php"
24+
],
25+
"psr-4": {
26+
"MageOS\\\\CatalogDataAI\\\\": ""
27+
}
28+
}
29+
}

etc/acl.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
3+
<acl>
4+
<resources>
5+
<resource id="Magento_Backend::admin">
6+
<resource id="Magento_Backend::stores">
7+
<resource id="Magento_Backend::stores_settings">
8+
<resource id="Magento_Config::config">
9+
<resource id="Magento_Catalog::config_catalog_ai" title="AI Data Enrichment Section" translate="title" />
10+
</resource>
11+
</resource>
12+
</resource>
13+
</resource>
14+
</resources>
15+
</acl>
16+
</config>

etc/adminhtml/system.xml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
3+
<system>
4+
<section id="catalog_ai" translate="label" type="text" sortOrder="45" showInDefault="1" showInWebsite="1" showInStore="1">
5+
<class>separator-top</class>
6+
<label>AI Data Enrichment</label>
7+
<tab>catalog</tab>
8+
<resource>Magento_Catalog::config_catalog_ai</resource>
9+
<group id="settings" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="1">
10+
<label>Settings</label>
11+
<field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
12+
<label>Enabled</label>
13+
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
14+
</field>
15+
<field id="async" translate="label" type="select" sortOrder="20" showInDefault="1" canRestore="1">
16+
<label>Asynchronous enrichment</label>
17+
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
18+
</field>
19+
<field id="openai_key" translate="label comment" type="password" sortOrder="30" showInDefault="1" canRestore="1">
20+
<label>OpenAI API key</label>
21+
</field>
22+
<field id="openai_model" translate="label comment" type="text" sortOrder="40" showInDefault="1" canRestore="1">
23+
<label>OpenAI API Model</label>
24+
</field>
25+
<field id="openai_max_tokens" translate="label comment" type="text" sortOrder="40" showInDefault="1" canRestore="1">
26+
<label>OpenAI API Max Tokens</label>
27+
</field>
28+
</group>
29+
<group id="product" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="1">
30+
<label>Product Fields Auto-Generation</label>
31+
<field id="short_description" translate="label comment" type="textarea" sortOrder="10" showInDefault="1" canRestore="1">
32+
<label>Description</label>
33+
<comment>Use {{name}} as Product Name placeholder</comment>
34+
</field>
35+
<field id="description" translate="label comment" type="textarea" sortOrder="30" showInDefault="1" canRestore="1">
36+
<label>Description</label>
37+
<comment>Use {{name}} as Product Name placeholder</comment>
38+
</field>
39+
</group>
40+
</section>
41+
</system>
42+
</config>

etc/config.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
3+
<default>
4+
<catalog_ai>
5+
<settings>
6+
<openai_model>gpt-3.5-turbo-instruct</openai_model>
7+
<openai_max_tokens>1000</openai_max_tokens>
8+
</settings>
9+
<product>
10+
<short_description>white a very short product description for {{name}} to highlight reasoning for purchase, under 100 words</short_description>
11+
<description>white a detailed product description for {{name}} with features in bullet list, under 1000 words</description>
12+
</product>
13+
</catalog_ai>
14+
</default>
15+
</config>

etc/events.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
3+
<event name="catalog_product_save_before">
4+
<observer name="mageos_catalogdataai_enrich_product" instance="MageOS\CatalogDataAI\Observer\Product" />
5+
</event>
6+
</config>

etc/module.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
3+
<module name="MageOS_CatalogDataAI" setup_version="0.1.0">
4+
<sequence>
5+
<module name="Magento_Catalog"/>
6+
</sequence>
7+
</module>
8+
</config>

registration.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
\Magento\Framework\Component\ComponentRegistrar::register(
5+
\Magento\Framework\Component\ComponentRegistrar::MODULE,
6+
'MageOS_CatalogDataAI',
7+
__DIR__
8+
);

0 commit comments

Comments
 (0)