Skip to content
Snippets Groups Projects
Verified Commit 950b4a5c authored by Sofiane Lasri's avatar Sofiane Lasri
Browse files

feat: add AI translation job for creation descriptions

- Added `TranslateCreationJob` to handle the translation of creation descriptions from French to English using an AI provider service.
- Updated the `CreationController` to dispatch the translation job when requested via a new route.
- Added a button in the admin creations index view to trigger the translation process, enhancing user interaction.
- Included frontend JavaScript for button state handling during the translation request.
parent 02013808
Branches
No related tags found
1 merge request!63Resolve "Ajouter traduction Anglaise par IA"
Pipeline #1083 passed
......@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Jobs\TranslateCreationJob;
use App\Models\Category;
use App\Models\Creation;
use App\Models\Translation;
......@@ -350,4 +351,15 @@ public function removeAdditionalImage(int $creationId, int $uploadedPictureId):
'success' => true,
]);
}
public function translateWithAi(int $creationId): JsonResponse
{
$creation = Creation::findOrFail($creationId);
$job = new TranslateCreationJob($creation);
dispatch($job);
return response()->json([
'success' => true,
]);
}
}
<?php
namespace App\Jobs;
use App\Models\Creation;
use App\Models\Translation;
use App\Services\AiProviderService;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class TranslateCreationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private readonly Creation $creation) {}
public function handle(AiProviderService $aiProviderService): void
{
$frenchShortDesc = $this->creation->shortDescriptionTranslationKey->getTranslation('fr');
$frenchDesc = $this->creation->descriptionTranslationKey->getTranslation('fr');
$shortDescriptionTranslationKeyId = $this->creation->shortDescriptionTranslationKey->id;
$descriptionTranslationKeyId = $this->creation->descriptionTranslationKey->id;
if (! empty($frenchShortDesc)) {
Translation::updateOrCreate(
['translation_key_id' => $shortDescriptionTranslationKeyId, 'locale' => 'en'],
['text' => $this->translate($frenchShortDesc, $aiProviderService)]
);
Cache::forget("translation_key_{$shortDescriptionTranslationKeyId}_en");
}
if (! empty($frenchDesc)) {
Translation::updateOrCreate(
['translation_key_id' => $descriptionTranslationKeyId, 'locale' => 'en'],
['text' => $this->translate($frenchDesc, $aiProviderService)]
);
Cache::forget("translation_key_{$descriptionTranslationKeyId}_en");
}
}
/**
* Translate the given text from French to English
*
* @param string $text The text to translate
* @param AiProviderService $aiProviderService The AI provider service
* @return string The translated text
*/
private function translate(string $text, AiProviderService $aiProviderService): string
{
try {
$result = $aiProviderService->prompt(
'You are a helpful assistant that translates french markdown text in english and that outputs JSON in the format {message:string}. Markdown is supported.',
$text
);
return $result['message'] ?? '';
} catch (Exception $e) {
Log::error("Failed to translate text: {$text}", [
'exception' => $e->getMessage(),
]);
}
return '';
}
}
......@@ -28,6 +28,11 @@
size="sm" tag="a" variant="danger">
Supprimer
</x-bs.button>
<x-bs.button size="sm" variant="secondary" id="translate-{{ $creation->id }}"
onclick="translateWithAi({{ $creation->id }})">
{{ \Spatie\Emoji\Emoji::flagsForFlagUnitedKingdom() }}
Traduire
</x-bs.button>
</x-slot:footer>
</x-bs.card>
@endforeach
......@@ -38,3 +43,24 @@
@endif
</div>
@endsection
@pushonce('scripts')
<script type="text/javascript">
function translateWithAi(creationId) {
const button = document.querySelector(`#translate-${creationId}`);
button.disabled = true;
fetch(`/admin/creations/${creationId}/translate-with-ai`)
.then(response => response.json())
.then(data => {
if (data.success) {
button.classList.remove('btn-secondary');
button.classList.add('btn-success');
} else {
button.disabled = false;
button.classList.remove('btn-secondary');
button.classList.add('btn-warning');
}
});
}
</script>
@endpushonce
......@@ -79,6 +79,8 @@
->name('delete');
Route::delete('/{creation}/remove-addtionnal-image/{image}', [CreationController::class, 'removeAdditionalImage'])
->name('remove-additionnal-image');
Route::get('/{creation}/translate-with-ai', [CreationController::class, 'translateWithAi'])
->name('translate-with-ai');
});
Route::prefix('social-media-links')->name('social-media-links.')->group(function () {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment