Added the bundle:describe
artisan command to use ollama to generate descriptions
This commit is contained in:
parent
d4cb852564
commit
40f4fca0ab
174
app/Console/Commands/Bundle/Describe.php
Normal file
174
app/Console/Commands/Bundle/Describe.php
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
<?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;
|
||||||
|
use App\Services\Ollama;
|
||||||
|
use App\Services\Translator;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Intervention\Image\Laravel\Facades\Image;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected = false;
|
||||||
|
$fresh = false;
|
||||||
|
|
||||||
|
while (!$selected) {
|
||||||
|
if (!$fresh) {
|
||||||
|
$text = Ollama::summarizeText($bundle->getArticleTitle(), $content);
|
||||||
|
// $sentence = Translator::translate($sentence);
|
||||||
|
} else {
|
||||||
|
$text = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = textarea(sprintf('Description for %s', $bundle->getPath()), '', $text);
|
||||||
|
|
||||||
|
$selection = select(
|
||||||
|
'What should we do?',
|
||||||
|
[
|
||||||
|
'keep' => 'Use current description',
|
||||||
|
'generate' => 'Generate new description',
|
||||||
|
'fresh' => 'Write own description',
|
||||||
|
'none' => 'Use no text at all',
|
||||||
|
],
|
||||||
|
$description !== $text ? 'keep' : 'generate'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($selection === 'keep' || $selection === 'none') {
|
||||||
|
$bundle->metadata()->set('description', $selection === 'keep' ? $description : '');
|
||||||
|
$bundle->metadata()->save();
|
||||||
|
|
||||||
|
$selected = true;
|
||||||
|
} elseif ($selection === 'fresh') {
|
||||||
|
$fresh = true;
|
||||||
|
} else {
|
||||||
|
$fresh = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function describeAttachments(Bundle $bundle, $progress)
|
||||||
|
{
|
||||||
|
$attachmentsManager = $bundle->attachments(AttachmentsManager::Images);
|
||||||
|
|
||||||
|
foreach ($attachmentsManager->manager()->get('files') ?? [] as $ref => $data) {
|
||||||
|
if (array_key_exists('alt', $data)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected = false;
|
||||||
|
$fresh = false;
|
||||||
|
$fullPath = $attachmentsManager->getAttachmentFullPath($ref);
|
||||||
|
$content = $this->sourceDisk->get($fullPath);
|
||||||
|
$content = Image::read($content)->toJpeg();
|
||||||
|
$base64 = base64_encode($content);
|
||||||
|
|
||||||
|
while (!$selected) {
|
||||||
|
if (!$fresh) {
|
||||||
|
$text = Ollama::describeImage([$base64], $bundle->getArticleTitle());
|
||||||
|
// $sentence = Translator::translate($sentence);
|
||||||
|
} else {
|
||||||
|
$text = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = textarea(sprintf('Image description for %s', $fullPath), '', $text);
|
||||||
|
|
||||||
|
$selection = select(
|
||||||
|
'What should we do?',
|
||||||
|
[
|
||||||
|
'keep' => 'Use current description',
|
||||||
|
'generate' => 'Generate new description',
|
||||||
|
'fresh' => 'Write own description',
|
||||||
|
'none' => 'Use no text at all',
|
||||||
|
],
|
||||||
|
$description !== $text ? 'keep' : 'generate'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($selection === 'keep' || $selection === 'none') {
|
||||||
|
$attachmentsManager->manager()->set(sprintf('files.%s.alt', $ref), $selection === 'keep' ? $description : '');
|
||||||
|
$attachmentsManager->manager()->save();
|
||||||
|
|
||||||
|
$selected = true;
|
||||||
|
} elseif ($selection === 'fresh') {
|
||||||
|
$fresh = true;
|
||||||
|
} else {
|
||||||
|
$fresh = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
<?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;
|
|
||||||
use App\Services\Ollama;
|
|
||||||
use App\Services\Translator;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Intervention\Image\Laravel\Facades\Image;
|
|
||||||
|
|
||||||
use function Laravel\Prompts\confirm;
|
|
||||||
use function Laravel\Prompts\progress;
|
|
||||||
use function Laravel\Prompts\textarea;
|
|
||||||
|
|
||||||
class DescribeAttachments extends Command
|
|
||||||
{
|
|
||||||
use ReadsBundles;
|
|
||||||
use SelectsDisks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Add description to attachments';
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->signature = 'bundle:describe-attachments
|
|
||||||
{ --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();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function perform()
|
|
||||||
{
|
|
||||||
progress('Updating bundles...', $this->bundles, function (Bundle $bundle, $progress) {
|
|
||||||
$this->handleBundle($bundle, $progress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleBundle(Bundle $bundle, $progress)
|
|
||||||
{
|
|
||||||
$attachmentsManager = $bundle->attachments(AttachmentsManager::Images);
|
|
||||||
|
|
||||||
foreach ($attachmentsManager->manager()->get('files') ?? [] as $ref => $data) {
|
|
||||||
if (!empty($data['alt'])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$altSelected = false;
|
|
||||||
$fullPath = $attachmentsManager->getAttachmentFullPath($ref);
|
|
||||||
$content = $this->sourceDisk->get($fullPath);
|
|
||||||
$content = Image::read($content)->toJpeg();
|
|
||||||
$base64 = base64_encode($content);
|
|
||||||
|
|
||||||
while (!$altSelected) {
|
|
||||||
$sentence = Ollama::describeImage([$base64]);
|
|
||||||
$translated = Translator::translate($sentence);
|
|
||||||
$alt = textarea(sprintf('Image description for %s', $fullPath), '', $translated);
|
|
||||||
|
|
||||||
if (confirm('Use this text as the `alt` attribute for image?', true, 'Yes', 'No', false, null, $alt)) {
|
|
||||||
$attachmentsManager->manager()->set(sprintf('files.%s.alt', $ref), $alt);
|
|
||||||
$attachmentsManager->manager()->save();
|
|
||||||
|
|
||||||
$altSelected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,14 +10,48 @@ class Ollama
|
||||||
* Provides a description for specified image(s) using the llava model
|
* Provides a description for specified image(s) using the llava model
|
||||||
* with ollama
|
* with ollama
|
||||||
*/
|
*/
|
||||||
public static function describeImage(array $base64JpegImages)
|
public static function describeImage(array $base64JpegImages, ?string $title = null)
|
||||||
{
|
{
|
||||||
$result = Http::throw()->timeout(240)->post(sprintf('%s/api/generate', env('OLLAMA_HOST')), [
|
$prompt = 'Décris cette image en français en une phrase ou deux';
|
||||||
|
|
||||||
|
if (!empty($title)) {
|
||||||
|
$prompt .= ". Le titre de l'article où elle est utilisée est \"" . $title . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = Http::throw()
|
||||||
|
->timeout(240)
|
||||||
|
->withHeader('Authorization', 'Bearer ' . env('OPENWEBUI_KEY'))
|
||||||
|
->post(sprintf('%s/ollama/api/generate', env('OLLAMA_HOST')), [
|
||||||
'model' => 'llava',
|
'model' => 'llava',
|
||||||
'prompt' => 'Describe this image in a sentence or two',
|
'prompt' => $prompt,
|
||||||
'stream' => false,
|
'stream' => false,
|
||||||
|
'keepalive' => 0,
|
||||||
'images' => $base64JpegImages,
|
'images' => $base64JpegImages,
|
||||||
])->json();
|
])
|
||||||
|
->json();
|
||||||
|
|
||||||
|
return $result['response'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function summarizeText(string $title, string $text)
|
||||||
|
{
|
||||||
|
$result = Http::throw()
|
||||||
|
->timeout(240)
|
||||||
|
->withHeader('Authorization', 'Bearer ' . env('OPENWEBUI_KEY'))
|
||||||
|
->post(sprintf('%s/ollama/api/generate', env('OLLAMA_HOST')), [
|
||||||
|
'model' => 'llama3',
|
||||||
|
'prompt' => sprintf(
|
||||||
|
'
|
||||||
|
Résume le texte suivant en français et en moins de 255 caractères :
|
||||||
|
# %s
|
||||||
|
%s',
|
||||||
|
$title,
|
||||||
|
$text
|
||||||
|
),
|
||||||
|
'stream' => false,
|
||||||
|
'keepalive' => 0,
|
||||||
|
])
|
||||||
|
->json();
|
||||||
|
|
||||||
return $result['response'];
|
return $result['response'];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user