-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial Release 1.0
- Loading branch information
Showing
34 changed files
with
1,134 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
namespace Magecomp\Chatgptaicontent\Api; | ||
|
||
use Psr\Http\Message\StreamInterface; | ||
|
||
interface CompletionRequestInterface | ||
{ | ||
public function getApiPayload(string $text): array; | ||
public function convertToResponse(StreamInterface $stream): string; | ||
public function getJsConfig(): ?array; | ||
public function query(string $prompt): string; | ||
public function getType(): string; | ||
public function getQuery(array $params): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
namespace Magecomp\Chatgptaicontent\Api\Data; | ||
|
||
interface QueryAttributeInterface | ||
{ | ||
public function getValue(): string; | ||
public function getName(): string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
<?php | ||
namespace Magecomp\Chatgptaicontent\Block\Adminhtml\Product; | ||
|
||
use Magento\Catalog\Model\Locator\LocatorInterface; | ||
use Magento\Framework\View\Element\Template; | ||
use Magento\Store\Api\StoreRepositoryInterface; | ||
use Magecomp\Chatgptaicontent\Model\Config; | ||
use Magento\Framework\Serialize\Serializer\Json; | ||
|
||
class Helper extends Template | ||
{ | ||
private Config $config; | ||
private StoreRepositoryInterface $storeRepository; | ||
private LocatorInterface $locator; | ||
private Json $json; | ||
|
||
public function __construct( | ||
Template\Context $context, | ||
Config $config, | ||
StoreRepositoryInterface $storeRepository, | ||
LocatorInterface $locator, | ||
Json $json, | ||
array $data = [] | ||
) { | ||
parent::__construct($context, $data); | ||
$this->config = $config; | ||
$this->storeRepository = $storeRepository; | ||
$this->locator = $locator; | ||
$this->json = $json; | ||
} | ||
|
||
public function getComponentJsonConfig(): string | ||
{ | ||
$config = [ | ||
// 'component' => 'Magecomp_Chatgptaicontent/js/view/helper', | ||
'serviceUrl' => $this->getUrl('Magecomp_Chatgptaicontent/helper/validate'), | ||
'sku' => $this->locator->getProduct()->getSku(), | ||
'storeId' => $this->locator->getStore()->getId(), | ||
'stores' => $this->getStores() | ||
]; | ||
return $this->json->serialize($config); | ||
} | ||
|
||
public function getStores(): array | ||
{ | ||
$selectedStoreId = (int) $this->locator->getStore()->getId(); | ||
$storeIds = $this->config->getEnabledStoreIds(); | ||
|
||
$results = []; | ||
$first = null; | ||
foreach ($storeIds as $storeId) { | ||
$store = $this->storeRepository->getById($storeId); | ||
if ($selectedStoreId === $storeId) { | ||
$first = $store; | ||
continue; | ||
} | ||
$results[] = [ | ||
'label' => $storeId === 0 ? __('Default scope') : $store->getName(), | ||
'store_id' => $storeId, | ||
'selected' => false | ||
]; | ||
} | ||
|
||
if ($first) { | ||
array_unshift($results, [ | ||
'label' => __('Current scope'), | ||
'store_id' => $first->getId(), | ||
'selected' => true | ||
]); | ||
} | ||
|
||
return $results; | ||
} | ||
|
||
public function toHtml(): string | ||
{ | ||
$enabled = $this->config->getValue(Config::XML_PATH_ENABLED); | ||
if (!$enabled) { | ||
return ''; | ||
} | ||
return parent::toHtml(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
<?php | ||
|
||
namespace Magecomp\Chatgptaicontent\Controller\Adminhtml\Generate; | ||
|
||
use Magecomp\Chatgptaicontent\Model\OpenAI\OpenAiException; | ||
use InvalidArgumentException; | ||
use Magento\Backend\App\Action; | ||
use Magento\Backend\App\Action\Context; | ||
use Magento\Framework\App\Action\HttpPostActionInterface; | ||
use Magento\Framework\Controller\Result\JsonFactory; | ||
use Magecomp\Chatgptaicontent\Model\CompletionConfig; | ||
use Magento\Framework\Exception\LocalizedException; | ||
use Magento\Catalog\Api\ProductRepositoryInterface; | ||
use Magento\Framework\App\RequestInterface; | ||
|
||
|
||
class Index extends Action implements HttpPostActionInterface | ||
{ | ||
public const ADMIN_RESOURCE = 'Magecomp_Chatgptaicontent::generate'; | ||
|
||
private JsonFactory $jsonFactory; | ||
private CompletionConfig $completionConfig; | ||
protected $productRepository; | ||
protected $request; | ||
|
||
public function __construct( | ||
Context $context, | ||
JsonFactory $jsonFactory, | ||
CompletionConfig $completionConfig, | ||
ProductRepositoryInterface $productRepository, | ||
RequestInterface $request | ||
) { | ||
parent::__construct($context); | ||
$this->jsonFactory = $jsonFactory; | ||
$this->completionConfig = $completionConfig; | ||
$this->productRepository = $productRepository; | ||
$this->request = $request; | ||
} | ||
|
||
/** | ||
* @throws LocalizedException | ||
*/ | ||
public function execute() | ||
{ | ||
$resultPage = $this->jsonFactory->create(); | ||
|
||
$type = $this->completionConfig->getByType( | ||
$this->getRequest()->getParam('type') | ||
); | ||
|
||
if ($type === null) { | ||
throw new LocalizedException(__('Invalid request parameters')); | ||
} | ||
|
||
try { | ||
$prompt = $this->getRequest()->getParam('prompt'); | ||
$result = $type->query($prompt); | ||
|
||
} catch (OpenAiException | InvalidArgumentException $e) { | ||
$resultPage->setData([ | ||
'error' => $e->getMessage() | ||
]); | ||
return $resultPage; | ||
} | ||
|
||
$resultPage->setData([ | ||
'result' => $result,'type' => $this->getRequest()->getParam('type') | ||
]); | ||
|
||
return $resultPage; | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
protected function _isAllowed() | ||
{ | ||
return $this->_authorization->isAllowed('Magecomp_Chatgptaicontent::generate'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
namespace Magecomp\Chatgptaicontent\Model; | ||
|
||
use Magecomp\Chatgptaicontent\Api\CompletionRequestInterface; | ||
use Magento\Framework\App\RequestInterface; | ||
|
||
class CompletionConfig | ||
{ | ||
/** | ||
* @var CompletionRequestInterface[] | ||
*/ | ||
private array $pool; | ||
private Config $config; | ||
private RequestInterface $request; | ||
|
||
public function __construct( | ||
array $pool, | ||
Config $config, | ||
RequestInterface $request | ||
) { | ||
$this->pool = $pool; | ||
$this->config = $config; | ||
$this->request = $request; | ||
} | ||
|
||
public function getConfig(): array | ||
{ | ||
if (!$this->config->getValue(Config::XML_PATH_ENABLED)) { | ||
return [ | ||
'targets' => [] | ||
]; | ||
} | ||
|
||
$allowedStores = $this->config->getEnabledStoreIds(); | ||
$storeId = (int) $this->request->getParam('store', '0'); | ||
if (!in_array($storeId, $allowedStores)) { | ||
return [ | ||
'targets' => [] | ||
]; | ||
} | ||
|
||
$targets = []; | ||
|
||
foreach ($this->pool as $config) { | ||
$targets[$config->getType()] = $config->getJsConfig(); | ||
} | ||
|
||
$targets = array_filter($targets); | ||
|
||
return [ | ||
'targets' => $targets | ||
]; | ||
} | ||
|
||
public function getByType(string $type): ?CompletionRequestInterface | ||
{ | ||
foreach ($this->pool as $config) { | ||
|
||
if ($config->getType() === $type) { | ||
return $config; | ||
} | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
namespace Magecomp\Chatgptaicontent\Model\CompletionRequest; | ||
|
||
use Magecomp\Chatgptaicontent\Model\Config; | ||
use Magecomp\Chatgptaicontent\Model\OpenAI\ApiClient; | ||
use Magecomp\Chatgptaicontent\Model\OpenAI\OpenAiException; | ||
use InvalidArgumentException; | ||
use Laminas\Json\Decoder; | ||
use Laminas\Json\Json; | ||
use Magento\Framework\App\Config\ScopeConfigInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\StreamInterface; | ||
use Magecomp\Chatgptaicontent\Model\Normalizer; | ||
|
||
abstract class AbstractCompletion | ||
{ | ||
public const TYPE = ''; | ||
protected const CUT_RESULT_PREFIX = ''; | ||
protected ScopeConfigInterface $scopeConfig; | ||
protected ?ApiClient $apiClient = null; | ||
|
||
public function __construct( | ||
ScopeConfigInterface $scopeConfig | ||
) { | ||
$this->scopeConfig = $scopeConfig; | ||
} | ||
|
||
abstract public function getApiPayload(string $text): array; | ||
|
||
private function getClient(): ApiClient | ||
{ | ||
$token = $this->scopeConfig->getValue(Config::XML_PATH_TOKEN); | ||
if (empty($token)) { | ||
throw new InvalidArgumentException('API token is missing'); | ||
} | ||
if ($this->apiClient === null) { | ||
$this->apiClient = new ApiClient( | ||
$this->scopeConfig->getValue(Config::XML_PATH_BASE_URL), | ||
$this->scopeConfig->getValue(Config::XML_PATH_TOKEN) | ||
); | ||
} | ||
return $this->apiClient; | ||
} | ||
|
||
public function getQuery(array $params): string | ||
{ | ||
return $params['prompt'] ?? ''; | ||
} | ||
|
||
/** | ||
* @throws OpenAiException | ||
*/ | ||
public function query(string $prompt): string | ||
{ | ||
$payload = $this->getApiPayload( | ||
Normalizer::htmlToPlainText($prompt) | ||
); | ||
|
||
$result = $this->getClient()->post( | ||
'/v1/completions', | ||
$payload | ||
); | ||
|
||
$this->validateResponse($result); | ||
|
||
return $this->convertToResponse($result->getBody()); | ||
} | ||
|
||
protected function validateRequest(string $prompt): void | ||
{ | ||
if (empty($prompt) || strlen($prompt) < 10) { | ||
throw new InvalidArgumentException('Invalid query (must be at least 10 characters)'); | ||
} | ||
} | ||
|
||
/** | ||
* @throws OpenAiException | ||
*/ | ||
protected function validateResponse(ResponseInterface $result): void | ||
{ | ||
if ($result->getStatusCode() === 401) { | ||
throw new OpenAiException(__('API unauthorized. Token could be invalid.')); | ||
} | ||
|
||
if ($result->getStatusCode() >= 500) { | ||
throw new OpenAiException(__('Server error: %1', $result->getReasonPhrase())); | ||
} | ||
|
||
$data = Decoder::decode($result->getBody(), Json::TYPE_ARRAY); | ||
|
||
if (isset($data['error'])) { | ||
throw new OpenAiException(__( | ||
'%1: %2', | ||
$data['error']['type'] ?? 'unknown', | ||
$data['error']['message'] ?? 'unknown' | ||
)); | ||
} | ||
|
||
if (!isset($data['choices'])) { | ||
throw new OpenAiException(__('No results were returned by the server')); | ||
} | ||
} | ||
|
||
public function convertToResponse(StreamInterface $stream): string | ||
{ | ||
$streamText = (string) $stream; | ||
$data = Decoder::decode($streamText, Json::TYPE_ARRAY); | ||
|
||
$choices = $data['choices'] ?? []; | ||
$textData = reset($choices); | ||
|
||
$text = $textData['text'] ?? ''; | ||
$text = trim($text); | ||
$text = trim($text, '"'); | ||
|
||
if (substr($text, 0, strlen(static::CUT_RESULT_PREFIX)) == static::CUT_RESULT_PREFIX) { | ||
$text = substr($text, strlen(static::CUT_RESULT_PREFIX)); | ||
} | ||
|
||
return $text; | ||
} | ||
|
||
public function getType(): string | ||
{ | ||
return static::TYPE; | ||
} | ||
} |
Oops, something went wrong.