Skip to content

Commit

Permalink
Initial Release
Browse files Browse the repository at this point in the history
Initial Release 1.0
  • Loading branch information
magecomp authored Apr 5, 2023
1 parent 4bc1223 commit 019334f
Show file tree
Hide file tree
Showing 34 changed files with 1,134 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Api/CompletionRequestInterface.php
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;
}
9 changes: 9 additions & 0 deletions Api/Data/QueryAttributeInterface.php
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;
}
83 changes: 83 additions & 0 deletions Block/Adminhtml/Product/Helper.php
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();
}
}
79 changes: 79 additions & 0 deletions Controller/Adminhtml/Generate/Index.php
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');
}
}
66 changes: 66 additions & 0 deletions Model/CompletionConfig.php
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;
}
}
128 changes: 128 additions & 0 deletions Model/CompletionRequest/AbstractCompletion.php
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;
}
}
Loading

0 comments on commit 019334f

Please sign in to comment.