diff --git a/app/Console/Commands/Bundle/Describe.php b/app/Console/Commands/Bundle/Describe.php
new file mode 100644
index 0000000..4475192
--- /dev/null
+++ b/app/Console/Commands/Bundle/Describe.php
@@ -0,0 +1,174 @@
+signature = 'bundle:describe
+ { --r|recursive : Also upgrade sub-bundles }
+ { --source-disk= : Use specified content disk - Defaults to ' . env('CONTENT_DISK') . ' }
+ { path? : Path to a specific bundle to upgrade - Default to / }
+ ';
+
+ 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;
+ }
+ }
+ }
+ }
+}
diff --git a/app/Console/Commands/Bundle/DescribeAttachments.php b/app/Console/Commands/Bundle/DescribeAttachments.php
deleted file mode 100644
index 56813e0..0000000
--- a/app/Console/Commands/Bundle/DescribeAttachments.php
+++ /dev/null
@@ -1,87 +0,0 @@
-signature = 'bundle:describe-attachments
- { --r|recursive : Also upgrade sub-bundles }
- { --source-disk= : Use specified content disk - Defaults to ' . env('CONTENT_DISK') . ' }
- { path? : Path to a specific bundle to upgrade - Default to / }
- ';
-
- 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;
- }
- }
- }
- }
-}
diff --git a/app/Services/Ollama.php b/app/Services/Ollama.php
index c376139..da88d66 100644
--- a/app/Services/Ollama.php
+++ b/app/Services/Ollama.php
@@ -10,14 +10,48 @@ class Ollama
* Provides a description for specified image(s) using the llava model
* 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')), [
- 'model' => 'llava',
- 'prompt' => 'Describe this image in a sentence or two',
- 'stream' => false,
- 'images' => $base64JpegImages,
- ])->json();
+ $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',
+ 'prompt' => $prompt,
+ 'stream' => false,
+ 'keepalive' => 0,
+ 'images' => $base64JpegImages,
+ ])
+ ->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'];
}