From 40f4fca0ab5274073680928c723d6259540412a9 Mon Sep 17 00:00:00 2001 From: Richard Dern Date: Wed, 15 May 2024 01:01:47 +0200 Subject: [PATCH] Added the `bundle:describe` artisan command to use ollama to generate descriptions --- app/Console/Commands/Bundle/Describe.php | 174 ++++++++++++++++++ .../Commands/Bundle/DescribeAttachments.php | 87 --------- app/Services/Ollama.php | 48 ++++- 3 files changed, 215 insertions(+), 94 deletions(-) create mode 100644 app/Console/Commands/Bundle/Describe.php delete mode 100644 app/Console/Commands/Bundle/DescribeAttachments.php 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']; }