feat: implement user management system and integrate AI services with Ollama and Gemini
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Attachment;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class GeminiService
|
||||
{
|
||||
protected ?string $apiKey;
|
||||
protected string $model;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apiKey = config('services.gemini.key');
|
||||
$this->model = config('services.gemini.model', 'gemini-1.5-flash');
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyse un document joint via l'API Gemini et extrait les détails structurés du devis.
|
||||
*/
|
||||
public function analyzeQuote(Attachment $attachment): array
|
||||
{
|
||||
$disk = Storage::disk('public');
|
||||
if (!$disk->exists($attachment->file_path)) {
|
||||
throw new \Exception("Le fichier joint n'existe pas sur le disque.");
|
||||
}
|
||||
|
||||
$path = $disk->path($attachment->file_path);
|
||||
$fileContent = file_get_contents($path);
|
||||
$mimeType = $disk->mimeType($attachment->file_path);
|
||||
|
||||
return $this->analyzeContent($fileContent, $mimeType, $attachment->file_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyse directement un fichier téléchargé temporairement via l'API Gemini.
|
||||
*/
|
||||
public function analyzeUploadedFile(\Illuminate\Http\UploadedFile $file): array
|
||||
{
|
||||
$fileContent = file_get_contents($file->getRealPath());
|
||||
$mimeType = $file->getMimeType();
|
||||
|
||||
return $this->analyzeContent($fileContent, $mimeType, $file->getClientOriginalName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Exécute l'appel à l'API Gemini pour extraire les données du devis.
|
||||
*/
|
||||
protected function analyzeContent(string $fileContent, string $mimeType, string $fileName): array
|
||||
{
|
||||
if (empty($this->apiKey)) {
|
||||
throw new \Exception("La clé API Gemini n'est pas configurée dans l'environnement.");
|
||||
}
|
||||
|
||||
// Encodage Base64 pour l'API Gemini
|
||||
$base64Data = base64_encode($fileContent);
|
||||
|
||||
// Prompt strict demandant un format JSON conforme aux attentes du formulaire
|
||||
$prompt = "Tu es un assistant administratif spécialisé dans l'analyse de devis informatiques et de prestations.
|
||||
Analyse le document joint (devis) et extrais de façon très précise les informations suivantes au format JSON uniquement.
|
||||
|
||||
Structure JSON attendue :
|
||||
{
|
||||
\"title\": \"Un titre descriptif pour le devis (ex: Achat matériel informatique ou Prestation d'installation)\",
|
||||
\"date_devis\": \"La date d'émission du devis au format YYYY-MM-DD (si non trouvée, renvoie null)\",
|
||||
\"supplier\": \"Le nom de l'entreprise émettrice / fournisseur\",
|
||||
\"quote_number\": \"Le numéro de devis du fournisseur\",
|
||||
\"tva_rate\": 20.0, // le taux de TVA standard constaté dans le document (valeur numérique, ex: 20 ou 5.5)
|
||||
\"total_ht\": 1250.00, // Le montant total HT du devis (nombre décimal)
|
||||
\"items\": [
|
||||
{
|
||||
\"type\": \"Matériel|Licence|Prestation|Infrastructure|Autre\", // Choisis la catégorie la plus adaptée parmi ces 5 valeurs exactes
|
||||
\"designation\": \"La description de l'article ou de la prestation\",
|
||||
\"quantite\": 1, // La quantité (entier ou décimal)
|
||||
\"prix_unitaire\": 120.50 // Le prix unitaire HT (nombre décimal)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Règles importantes :
|
||||
1. Extrais TOUTES les lignes d'articles réelles présentes dans le devis. Ne résume pas ou ne groupe pas les lignes si elles sont détaillées.
|
||||
2. Pour chaque article, détermine la catégorie la plus adaptée ('Matériel', 'Licence', 'Prestation', 'Infrastructure', 'Autre').
|
||||
3. Ne réponds QUE par le JSON brut, valide, sans aucun texte explicatif avant ou après, et sans bloc de code markdown.
|
||||
4. Assure-toi que les montants et quantités sont des nombres.";
|
||||
|
||||
$url = "https://generativelanguage.googleapis.com/v1beta/models/{$this->model}:generateContent?key={$this->apiKey}";
|
||||
|
||||
Log::info("Début de l'analyse OCR Gemini pour le fichier : {$fileName}");
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
])->post($url, [
|
||||
'contents' => [
|
||||
[
|
||||
'parts' => [
|
||||
['text' => $prompt],
|
||||
[
|
||||
'inlineData' => [
|
||||
'mimeType' => $mimeType,
|
||||
'data' => $base64Data
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'generationConfig' => [
|
||||
'responseMimeType' => 'application/json',
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error("Échec de l'appel Gemini API : " . $response->body());
|
||||
throw new \Exception("Erreur de communication avec l'API Gemini : " . $response->status());
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$textResult = $data['candidates'][0]['content']['parts'][0]['text'] ?? null;
|
||||
|
||||
if (!$textResult) {
|
||||
Log::error("Gemini n'a renvoyé aucun contenu exploitable.");
|
||||
throw new \Exception("L'analyse du devis n'a retourné aucun résultat.");
|
||||
}
|
||||
|
||||
$decoded = json_decode($textResult, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
Log::error("Impossible de parser le JSON retourné par Gemini : " . $textResult);
|
||||
throw new \Exception("Le format des données retournées par l'OCR est invalide.");
|
||||
}
|
||||
|
||||
Log::info("OCR Gemini réussi pour le fichier : {$fileName}");
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user