2024-05-15 01:01:47 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Console\Commands\Bundle;
|
|
|
|
|
|
|
|
use App\Classes\AttachmentsManager;
|
|
|
|
use App\Classes\Bundle;
|
|
|
|
use App\Console\Commands\Bundle\Traits\ReadsBundles;
|
|
|
|
use App\Console\Commands\Bundle\Traits\SelectsDisks;
|
2024-05-16 22:20:21 +02:00
|
|
|
use App\ImageFilters\ForAI;
|
|
|
|
use App\Services\AI\AIHandler;
|
|
|
|
use App\Services\AI\Facades\AI;
|
2024-05-15 01:01:47 +02:00
|
|
|
use App\Services\Translator;
|
2024-05-16 22:20:21 +02:00
|
|
|
use Carbon\CarbonInterval;
|
2024-05-15 01:01:47 +02:00
|
|
|
use Illuminate\Console\Command;
|
2024-05-16 22:20:21 +02:00
|
|
|
use Illuminate\Support\Facades\Process;
|
2024-05-15 01:01:47 +02:00
|
|
|
use Intervention\Image\Laravel\Facades\Image;
|
2024-05-16 22:20:21 +02:00
|
|
|
use Text_LanguageDetect;
|
2024-05-15 01:01:47 +02:00
|
|
|
|
|
|
|
use function Laravel\Prompts\progress;
|
|
|
|
use function Laravel\Prompts\select;
|
|
|
|
use function Laravel\Prompts\textarea;
|
|
|
|
|
|
|
|
class Describe extends Command
|
|
|
|
{
|
|
|
|
use ReadsBundles;
|
|
|
|
use SelectsDisks;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The console command description.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $description = 'Add description to bundles';
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
$this->signature = 'bundle:describe
|
|
|
|
{ --r|recursive : Also upgrade sub-bundles }
|
|
|
|
{ --source-disk= : Use specified content disk - Defaults to <info>' . env('CONTENT_DISK') . '</info> }
|
|
|
|
{ path? : Path to a specific bundle to upgrade - Default to <info>/</info> }
|
|
|
|
';
|
|
|
|
|
|
|
|
parent::__construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute the console command.
|
|
|
|
*/
|
|
|
|
public function handle()
|
|
|
|
{
|
|
|
|
$this->selectDisk()
|
|
|
|
->selectBundles()
|
|
|
|
->perform();
|
|
|
|
}
|
|
|
|
|
2024-05-16 22:20:21 +02:00
|
|
|
private function formatNanoseconds(int $nanoseconds): string
|
|
|
|
{
|
|
|
|
// Conversion des nanosecondes en secondes
|
|
|
|
$seconds = $nanoseconds / 1e9;
|
|
|
|
|
|
|
|
// Création d'un intervalle de temps à partir des secondes
|
|
|
|
$interval = CarbonInterval::seconds($seconds)->cascade();
|
|
|
|
|
|
|
|
// Formatage de l'intervalle de temps en une chaîne lisible
|
|
|
|
return $interval->forHumans();
|
|
|
|
}
|
|
|
|
|
2024-05-15 01:01:47 +02:00
|
|
|
private function perform()
|
|
|
|
{
|
|
|
|
progress('Updating bundles...', $this->bundles, function (Bundle $bundle, $progress) {
|
|
|
|
$this->handleBundle($bundle, $progress);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleBundle(Bundle $bundle, $progress)
|
|
|
|
{
|
|
|
|
$allowedSections = [
|
|
|
|
'/blog/',
|
|
|
|
'/critiques/',
|
|
|
|
];
|
|
|
|
|
|
|
|
if (!in_array($bundle->getSection()->getPath(), $allowedSections)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->describeContent($bundle, $progress);
|
|
|
|
$this->describeAttachments($bundle, $progress);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function describeContent(Bundle $bundle, $progress)
|
|
|
|
{
|
|
|
|
$bundle->load();
|
|
|
|
|
|
|
|
if ($bundle->metadata()->has('description')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$content = $bundle->markdown()->get();
|
|
|
|
|
|
|
|
if (empty($content)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-16 22:20:21 +02:00
|
|
|
$isSelected = false;
|
2024-05-15 01:01:47 +02:00
|
|
|
|
2024-05-16 22:20:21 +02:00
|
|
|
while (!$isSelected) {
|
|
|
|
$result = $this->askAiToSummarizeBundle($bundle);
|
|
|
|
|
|
|
|
switch ($result) {
|
|
|
|
case 'fresh':
|
|
|
|
$text = textarea(
|
|
|
|
label: sprintf('Custom description for "%s"', $bundle->getArticleTitle()),
|
|
|
|
required: true
|
|
|
|
);
|
|
|
|
|
|
|
|
$bundle->metadata()->set('description', [
|
|
|
|
'text' => $text,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$bundle->metadata()->save();
|
|
|
|
|
|
|
|
$isSelected = true;
|
|
|
|
break;
|
|
|
|
case 'regenerate':
|
|
|
|
break;
|
|
|
|
case 'skip':
|
|
|
|
$isSelected = true;
|
|
|
|
break;
|
|
|
|
case 'skip_forever':
|
|
|
|
$bundle->metadata()->set('description', '');
|
|
|
|
$bundle->metadata()->save();
|
2024-05-15 01:01:47 +02:00
|
|
|
|
2024-05-16 22:20:21 +02:00
|
|
|
$isSelected = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$ld = new Text_LanguageDetect();
|
|
|
|
|
|
|
|
$ld->setNameMode(2);
|
|
|
|
|
|
|
|
$text = $result['response'];
|
|
|
|
$lang = $ld->detectSimple($text);
|
|
|
|
$currentLocale = app()->getLocale();
|
|
|
|
$currentLanguage = substr($currentLocale, 0, 2);
|
|
|
|
|
|
|
|
if ($currentLanguage !== $lang) {
|
|
|
|
$text = Translator::translate($text, $lang, $currentLanguage);
|
|
|
|
}
|
|
|
|
|
2024-05-17 00:12:55 +02:00
|
|
|
$newText = textarea(
|
2024-05-16 22:20:21 +02:00
|
|
|
label: sprintf('Confirm description for "%s"', $bundle->getArticleTitle()),
|
|
|
|
default: $text,
|
|
|
|
required: true
|
|
|
|
);
|
|
|
|
|
|
|
|
$bundle->metadata()->set('description', [
|
2024-05-17 00:12:55 +02:00
|
|
|
'text' => $newText,
|
2024-05-16 22:20:21 +02:00
|
|
|
'generator' => $result['handler'],
|
|
|
|
'model' => $result['model'],
|
2024-05-17 00:12:55 +02:00
|
|
|
'edited' => $text !== $newText,
|
2024-05-16 22:20:21 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
$bundle->metadata()->save();
|
|
|
|
|
|
|
|
$isSelected = true;
|
|
|
|
break;
|
2024-05-15 01:01:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function describeAttachments(Bundle $bundle, $progress)
|
|
|
|
{
|
2024-05-16 22:20:21 +02:00
|
|
|
foreach ($bundle->attachments(AttachmentsManager::Images)->manager()->get('files') ?? [] as $ref => $data) {
|
2024-05-15 01:01:47 +02:00
|
|
|
if (array_key_exists('alt', $data)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-05-16 22:20:21 +02:00
|
|
|
$this->describeAttachment($bundle, $ref, $data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function describeAttachment(Bundle $bundle, string $ref, array $data)
|
|
|
|
{
|
|
|
|
$manager = $bundle->attachments(AttachmentsManager::Images);
|
|
|
|
$fullPath = $manager->getAttachmentFullPath($ref);
|
|
|
|
$content = $this->sourceDisk->get($fullPath);
|
|
|
|
$content = Image::read($content)->modify(new ForAI())->toJpeg();
|
|
|
|
$base64 = base64_encode($content);
|
|
|
|
$isSelected = false;
|
|
|
|
|
|
|
|
while (!$isSelected) {
|
|
|
|
$result = $this->askAiToDescribeAttachment($fullPath, $base64);
|
|
|
|
|
|
|
|
switch ($result) {
|
|
|
|
case 'fresh':
|
|
|
|
$text = textarea(
|
|
|
|
label: sprintf('Custom description for %s', $fullPath),
|
|
|
|
required: true
|
|
|
|
);
|
|
|
|
|
|
|
|
$manager->manager()->set(sprintf('files.%s.alt', $ref), [
|
|
|
|
'text' => $text,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$manager->manager()->save();
|
|
|
|
|
|
|
|
$isSelected = true;
|
|
|
|
break;
|
|
|
|
case 'regenerate':
|
|
|
|
break;
|
|
|
|
case 'skip':
|
|
|
|
$isSelected = true;
|
|
|
|
break;
|
|
|
|
case 'skip_forever':
|
|
|
|
$manager->manager()->set(sprintf('files.%s.alt', $ref), '');
|
|
|
|
$manager->manager()->save();
|
|
|
|
|
|
|
|
$isSelected = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$ld = new Text_LanguageDetect();
|
|
|
|
|
|
|
|
$ld->setNameMode(2);
|
|
|
|
|
|
|
|
$text = $result['response'];
|
|
|
|
$lang = $ld->detectSimple($text);
|
|
|
|
$currentLocale = app()->getLocale();
|
|
|
|
$currentLanguage = substr($currentLocale, 0, 2);
|
|
|
|
|
|
|
|
if ($currentLanguage !== $lang) {
|
|
|
|
$text = Translator::translate($text, $lang, $currentLanguage);
|
|
|
|
}
|
|
|
|
|
2024-05-17 00:12:55 +02:00
|
|
|
$newText = textarea(
|
2024-05-16 22:20:21 +02:00
|
|
|
label: sprintf('Confirm description for %s', $fullPath),
|
|
|
|
default: $text,
|
|
|
|
required: true
|
|
|
|
);
|
|
|
|
|
|
|
|
$manager->manager()->set(sprintf('files.%s.alt', $ref), [
|
2024-05-17 00:12:55 +02:00
|
|
|
'text' => $newText,
|
2024-05-16 22:20:21 +02:00
|
|
|
'generator' => $result['handler'],
|
|
|
|
'model' => $result['model'],
|
2024-05-17 00:12:55 +02:00
|
|
|
'edited' => $newText !== $text,
|
2024-05-16 22:20:21 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
$manager->manager()->save();
|
|
|
|
|
|
|
|
$isSelected = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function askAiToSummarizeBundle(Bundle $bundle)
|
|
|
|
{
|
2024-05-17 00:12:55 +02:00
|
|
|
$prompt = 'Summarize the content of the following Markdown text:
|
2024-05-16 22:20:21 +02:00
|
|
|
```markdown
|
|
|
|
# ' . $bundle->getArticleTitle() . '
|
|
|
|
' . $bundle->markdown()->get() . '
|
2024-05-17 00:12:55 +02:00
|
|
|
```
|
|
|
|
The goal is to create a summary that captures the spirit and key points of the page. Use concise phrases, but ensure they reflect the tone and content of the Markdown text.
|
|
|
|
The summary should be written in English or French and not exceed 255 characters. It will be used in the `<meta description>` tag of the page to help search engines understand the content of the file.
|
|
|
|
Do not include any header to the answer.
|
2024-05-16 22:20:21 +02:00
|
|
|
';
|
|
|
|
$results = AI::providers([
|
|
|
|
AI::make('open-webui')->forService(AIHandler::SERVICE_SUMMARIZE_CONTENT)->withPrompt($prompt),
|
|
|
|
])->run();
|
|
|
|
|
|
|
|
$propositions = [];
|
|
|
|
$index = 1;
|
|
|
|
|
|
|
|
foreach ($results as $handler => $models) {
|
|
|
|
foreach ($models as $model => $result) {
|
|
|
|
$this->newLine(1);
|
|
|
|
|
|
|
|
$parts = [
|
|
|
|
sprintf('[<info>%d</info>] Proposed by <comment>%s</comment> ', $index, $handler),
|
|
|
|
sprintf('and using the <comment>%s</comment> model', $model),
|
|
|
|
];
|
|
|
|
|
|
|
|
if (!empty($result['total_duration'])) {
|
|
|
|
$parts[] = sprintf(' (done in <comment>%s</comment>)', $this->formatNanoseconds($result['total_duration']));
|
2024-05-15 01:01:47 +02:00
|
|
|
}
|
|
|
|
|
2024-05-16 22:20:21 +02:00
|
|
|
$this->line(implode('', $parts));
|
|
|
|
|
|
|
|
$response = trim($result['response'] ?? 'Error');
|
|
|
|
|
|
|
|
$this->line($response);
|
|
|
|
|
|
|
|
$propositions[$index] = [
|
|
|
|
'handler' => $handler,
|
|
|
|
'model' => $model,
|
|
|
|
'response' => $response,
|
|
|
|
];
|
|
|
|
|
|
|
|
$index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$options = [
|
|
|
|
'fresh' => 'Let me write my own',
|
|
|
|
'regenerate' => 'Regenerate suggestions',
|
|
|
|
'skip' => 'Skip',
|
|
|
|
'skip_forever' => 'Never ask again for this file',
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach (array_keys($propositions) as $index) {
|
|
|
|
$options[strval($index)] = $index;
|
|
|
|
}
|
|
|
|
|
|
|
|
$selection = select(
|
|
|
|
sprintf('Which description do you prefer for "%s" ?', $bundle->getArticleTitle()),
|
|
|
|
$options,
|
|
|
|
'regenerate',
|
|
|
|
10
|
|
|
|
);
|
|
|
|
|
|
|
|
if (is_numeric($selection)) {
|
|
|
|
return $propositions[$selection];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $selection;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function askAiToDescribeAttachment(string $fullPath, string $base64)
|
|
|
|
{
|
|
|
|
$results = AI::providers([
|
|
|
|
AI::make('open-webui')->forService(AIHandler::SERVICE_DESCRIBE_IMAGE)->withFiles([$base64]),
|
|
|
|
])->run();
|
|
|
|
|
|
|
|
$propositions = [];
|
|
|
|
$index = 1;
|
|
|
|
|
|
|
|
foreach ($results as $handler => $models) {
|
|
|
|
foreach ($models as $model => $result) {
|
|
|
|
$this->newLine(1);
|
|
|
|
|
|
|
|
$parts = [
|
|
|
|
sprintf('[<info>%d</info>] Proposed by <comment>%s</comment> ', $index, $handler),
|
|
|
|
sprintf('and using the <comment>%s</comment> model', $model),
|
|
|
|
];
|
|
|
|
|
|
|
|
if (!empty($result['total_duration'])) {
|
|
|
|
$parts[] = sprintf(' (done in <comment>%s</comment>)', $this->formatNanoseconds($result['total_duration']));
|
2024-05-15 01:01:47 +02:00
|
|
|
}
|
2024-05-16 22:20:21 +02:00
|
|
|
|
|
|
|
$this->line(implode('', $parts));
|
|
|
|
|
|
|
|
$response = trim($result['response'] ?? 'Error');
|
|
|
|
|
|
|
|
$this->line($response);
|
|
|
|
|
|
|
|
$propositions[$index] = [
|
|
|
|
'handler' => $handler,
|
|
|
|
'model' => $model,
|
|
|
|
'response' => $response,
|
|
|
|
];
|
|
|
|
|
|
|
|
$index++;
|
2024-05-15 01:01:47 +02:00
|
|
|
}
|
|
|
|
}
|
2024-05-16 22:20:21 +02:00
|
|
|
|
|
|
|
$options = [
|
|
|
|
'fresh' => 'Let me write my own',
|
|
|
|
'regenerate' => 'Regenerate suggestions',
|
|
|
|
'skip' => 'Skip',
|
|
|
|
'skip_forever' => 'Never ask again for this file',
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach (array_keys($propositions) as $index) {
|
|
|
|
$options[strval($index)] = $index;
|
|
|
|
}
|
|
|
|
|
|
|
|
$result = Process::tty()->run(sprintf('lsix /content%s', $fullPath));
|
|
|
|
|
|
|
|
echo $result->output();
|
|
|
|
|
|
|
|
$selection = select(
|
|
|
|
sprintf('Which description do you prefer for %s ?', $fullPath),
|
|
|
|
$options,
|
|
|
|
'regenerate',
|
|
|
|
10
|
|
|
|
);
|
|
|
|
|
|
|
|
if (is_numeric($selection)) {
|
|
|
|
return $propositions[$selection];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $selection;
|
2024-05-15 01:01:47 +02:00
|
|
|
}
|
|
|
|
}
|