Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sabrineferchichi committed Nov 12, 2024
1 parent 7488d2a commit 495f50e
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 1 deletion.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,42 @@ When configuring the channel in the back-office (BO), it is necessary to add:
- **Locales**: Add the languages supported or used by the channel.

Ensure these settings are correctly configured for optimal channel integration.

## Commands

### `acseo:generate-product-descriptions`
This command generates and updates product descriptions for all products in the system, using provided text and optional keywords.

#### Usage

##### Options:
- **--locale=<locale>**: The locale to use for generating descriptions. The default locale is en (English).
- **--text=<text>**: The text to be used for generating descriptions. This will be processed to create a description for each product.
- **--keywords=<keywords>**: A comma-separated list of keywords that will be used to improve the description generation. For example: --keywords="keyword1, keyword2".

##### Example

```bash
php bin/console acseo:generate-product-descriptions --locale=en --text="Example product description" --keywords="shirt, cotton, casual"
```

This will generate descriptions for all products in the system, using the provided text and keywords.

### acseo:generate-product-descriptions-from-pictures
This command generates and updates product descriptions for all products in the system, using provided pictures (URLs or file paths) and optional keywords.

#### Usage

##### Options:
- **--locale=<locale>**: The locale to use for generating descriptions. The default locale is en (English).
- **--pictures=<pictures>**: A comma-separated list of picture URLs or file paths that will be used to generate descriptions. Each image will be processed to derive relevant information for the descriptions.
- **--keywords=<keywords>**: A comma-separated list of keywords to enhance the description generation. For example: --keywords="keyword1, keyword2".

##### Example

```bash
php bin/console acseo:generate-product-descriptions-from-pictures --locale=fr --pictures="http://example.com/image1.jpg,http://example.com/image2.jpg" --keywords="dress, summer, casual"
```

This will generate descriptions for all products in the system, based on the provided images and keywords.

Binary file added capture-v1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified capture.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
137 changes: 137 additions & 0 deletions src/Command/GenerateDescriptionsFromPicturesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

declare(strict_types=1);

namespace ACSEO\SyliusAITools\Command;

use ACSEO\SyliusAITools\Manager\DescriptionManager;
use Doctrine\ORM\EntityManagerInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Product\Repository\ProductRepositoryInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class GenerateDescriptionsFromPicturesCommand extends Command
{
protected static $defaultName = 'acseo:generate-product-descriptions-from-pictures';

public function __construct(
private DescriptionManager $descriptionManager,
private ProductRepositoryInterface $productRepository,
private EntityManagerInterface $entityManager
) {
parent::__construct();
}

protected function configure(): void
{
$this
->setDescription('Generate and update product descriptions for all products using pictures.')
->setHelp('This command generates and updates product descriptions for all available products based on provided pictures and locale.')
->addOption('locale', null, InputOption::VALUE_OPTIONAL, 'The locale to use for generating descriptions (default is "en")', 'en')
->addOption('pictures', null, InputOption::VALUE_OPTIONAL, 'Comma-separated list of picture URLs or paths to use for generating descriptions')
->addOption('keywords', null, InputOption::VALUE_OPTIONAL, 'Comma-separated keywords to use for generating descriptions')
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$symfonyStyle = new SymfonyStyle($input, $output);
$locale = $input->getOption('locale');
$pictures = $this->getPicturesFromInput($input);
$keywords = $input->getOption('keywords') ?: null;

if (empty($pictures)) {
$symfonyStyle->warning('No pictures found.');
}

$pictures = $this->convertUrlsToUploadedFiles($pictures);
$symfonyStyle->title(\sprintf('Generating Product Descriptions from Pictures (Locale: %s)', $locale));

$products = $this->productRepository->findAll();
if (0 === \count($products)) {
$symfonyStyle->warning('No products found.');

return Command::SUCCESS;
}

$descriptions = $this->getDescriptions($pictures, $locale, $keywords);

$this->processProducts($products, $descriptions, $symfonyStyle, $locale);

$symfonyStyle->success('Product descriptions generation from pictures completed.');

return Command::SUCCESS;
}

private function getPicturesFromInput(InputInterface $input): array
{
return $input->getOption('pictures') ? array_map('trim', explode(',', $input->getOption('pictures'))) : [];
}

private function getDescriptions(array $pictures, string $locale, ?string $keywords): array
{
return empty($pictures)
? []
: $this->descriptionManager->generateDescriptionsFromText([
'locale' => $locale,
'pictures' => $pictures,
'keywords' => $keywords,
]);
}

private function processProducts(array $products, array $descriptions, SymfonyStyle $symfonyStyle, string $locale): void
{
foreach ($products as $product) {
if ($product instanceof ProductInterface) {
$this->processProduct($product, $descriptions, $symfonyStyle, $locale);
}
}
}

private function processProduct(ProductInterface $product, array $descriptions, SymfonyStyle $symfonyStyle, string $locale): void
{
$symfonyStyle->section(\sprintf('Processing product: %s (ID: %d)', $product->getName(), $product->getId()));

$updatedProduct = $this->descriptionManager->generateAndUpdateProductDescription(
$descriptions,
(string) $product->getId(),
$locale
);

$this->persistUpdatedProduct($updatedProduct, $product, $symfonyStyle);
}

private function persistUpdatedProduct(?ProductInterface $updatedProduct, ProductInterface $product, SymfonyStyle $symfonyStyle): void
{
if ($updatedProduct instanceof ProductInterface) {
$this->entityManager->persist($updatedProduct);
$symfonyStyle->success(\sprintf('Description updated for product: %s (ID: %d)', $product->getName(), $product->getId()));

return;
}

$symfonyStyle->error(\sprintf('Failed to update description for product: %s (ID: %d)', $product->getName(), $product->getId()));
}

private function convertUrlsToUploadedFiles(array $pictureUrls): array
{
$uploadedFiles = [];
foreach ($pictureUrls as $url) {
$fileContents = file_get_contents($url);
if (false !== $fileContents) {
$tempFile = tempnam(sys_get_temp_dir(), 'product_picture_');
file_put_contents($tempFile, $fileContents);
/** @var string|null $mimeType */
$mimeType = mime_content_type($tempFile);
$uploadedFiles[] = new UploadedFile($tempFile, basename($url), $mimeType, null, true);
}
}

return $uploadedFiles;
}
}
128 changes: 128 additions & 0 deletions src/Command/GenerateDescriptionsFromTextCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

namespace ACSEO\SyliusAITools\Command;

use ACSEO\SyliusAITools\Manager\DescriptionManager;
use Doctrine\ORM\EntityManagerInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Product\Repository\ProductRepositoryInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class GenerateDescriptionsFromTextCommand extends Command
{
protected static $defaultName = 'acseo:generate-product-descriptions';

public function __construct(
private DescriptionManager $descriptionManager,
private ProductRepositoryInterface $productRepository,
private EntityManagerInterface $entityManager
) {
parent::__construct();
}

protected function configure(): void
{
$this
->setDescription('Generate and update product descriptions for all products.')
->setHelp('This command generates and updates product descriptions for all available products based on the given locale, text input, and keywords.')
->addOption('locale', null, InputOption::VALUE_OPTIONAL, 'The locale to use for generating descriptions (default is "en")', 'en')
->addOption('text', null, InputOption::VALUE_OPTIONAL, 'The base text to generate descriptions')
->addOption('keywords', null, InputOption::VALUE_OPTIONAL, 'Comma-separated keywords to use for generating descriptions')
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$symfonyStyle = new SymfonyStyle($input, $output);
$locale = $input->getOption('locale');
$text = $input->getOption('text') ?? null;
$keywords = $input->getOption('keywords') ?: null;

$symfonyStyle->title(\sprintf('Generating Product Descriptions (Locale: %s)', $locale));

$descriptions = $this->generateDescriptionsFromText($locale, $text, $keywords);
$products = $this->getProducts($symfonyStyle);

if (empty($products)) {
return Command::SUCCESS;
}

foreach ($products as $product) {
$this->processProduct($product, $descriptions, $locale, $symfonyStyle);
}

$symfonyStyle->success('Product descriptions generation completed.');

return Command::SUCCESS;
}

private function generateDescriptionsFromText(string $locale, ?string $text, ?string $keywords): array
{
if (!$text) {
return [];
}

return $this->descriptionManager->generateDescriptionsFromText([
'locale' => $locale,
'text' => $text,
'keywords' => $keywords,
]);
}

private function getProducts(SymfonyStyle $symfonyStyle): array
{
$products = $this->productRepository->findAll();
if (empty($products)) {
$symfonyStyle->warning('No products found.');
}

return $products;
}

private function processProduct(ProductInterface $product, array $descriptions, string $locale, SymfonyStyle $symfonyStyle): void
{
$symfonyStyle->section(\sprintf('Processing product: %s (ID: %d)', $product->getName(), $product->getId()));

if (empty($descriptions)) {
$descriptions = $this->generateProductDescriptions($product, $locale);
}

$updatedProduct = $this->descriptionManager->generateAndUpdateProductDescription(
$descriptions,
(string) $product->getId(),
$locale
);

$this->persistUpdatedProduct($updatedProduct, $product, $symfonyStyle);
}

private function generateProductDescriptions(ProductInterface $product, string $locale): array
{
$text = $product->getDescription();
$keywords = implode(',', array_map(fn ($taxon) => $taxon->getCode(), $product->getTaxons()->toArray()));

return $this->descriptionManager->generateDescriptionsFromText([
'locale' => $locale,
'text' => $text,
'keywords' => $keywords,
]);
}

private function persistUpdatedProduct(?ProductInterface $updatedProduct, ProductInterface $product, SymfonyStyle $symfonyStyle): void
{
if ($updatedProduct) {
$this->entityManager->persist($updatedProduct);
$symfonyStyle->success(\sprintf('Description updated for product: %s (ID: %d)', $product->getName(), $product->getId()));

return;
}

$symfonyStyle->error(\sprintf('Failed to update description for product: %s (ID: %d)', $product->getName(), $product->getId()));
}
}
2 changes: 1 addition & 1 deletion src/Form/ProductDescriptionFromPicturesFormType.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private function generateImageUrls(Product $product, string $virtualHost): array
return array_map(function ($image) use ($virtualHost) {
$imagePath = ltrim($image->getPath() ?? '', '/');

return \sprintf('%s/media/cache/sylius_small/%s', $virtualHost, $imagePath);
return \sprintf('%s/media/cache/resolve/sylius_shop_product_original/%s', $virtualHost, $imagePath);
}, $product->getImages()->toArray());
}
}
16 changes: 16 additions & 0 deletions src/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,19 @@ services:
$localeProvider: '@sylius.locale_provider'
tags:
- { name: 'twig.extension' }

ACSEO\SyliusAITools\Command\GenerateDescriptionsFromTextCommand:
arguments:
$descriptionManager: '@ACSEO\SyliusAITools\Manager\DescriptionManager'
$productRepository: '@sylius.repository.product'
$entityManager: '@doctrine.orm.entity_manager'
tags:
- { name: 'console.command' }

ACSEO\SyliusAITools\Command\GenerateDescriptionsFromPicturesCommand:
arguments:
$descriptionManager: '@ACSEO\SyliusAITools\Manager\DescriptionManager'
$productRepository: '@sylius.repository.product'
$entityManager: '@doctrine.orm.entity_manager'
tags:
- { name: 'console.command' }

0 comments on commit 495f50e

Please sign in to comment.