url = rtrim(config('services.ollama.url', 'http://localhost:11434'), '/'); $this->model = config('services.ollama.model', 'glm-ocr'); } /** * Analyse un document joint via l'API Ollama 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 Ollama. */ 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 Ollama pour extraire les données du devis. */ protected function analyzeContent(string $fileContent, string $mimeType, string $fileName): array { if ($mimeType === 'application/pdf') { throw new \Exception("Le modèle local (Ollama/GLM-OCR) ne prend en charge que les images (PNG, JPG, JPEG, WEBP). Pour analyser un fichier PDF, veuillez utiliser Gemini ou convertir votre document PDF en image."); } // Encodage Base64 $base64Data = base64_encode($fileContent); // Prompt strict demandant un format JSON conforme aux attentes du formulaire sans inclure de placeholders dans la structure JSON $prompt = "Tu es un assistant administratif spécialisé dans l'analyse de devis. Analyse le document joint pour en extraire très précisément les informations requises et retourne-les au format JSON uniquement. Instructions d'extraction : - title : Donne un titre descriptif et court basé sur l'objet du devis (ex: 'Achat matériel' ou 'Prestation installation'). - date_devis : Extrais la date d'émission au format YYYY-MM-DD (renvoie null si absente). - supplier : Extrais le nom de l'entreprise émettrice / fournisseur. - quote_number : Extrais le numéro du devis. - tva_rate : Détermine le taux de TVA standard constaté (nombre décimal, ex: 20.0). - total_ht : Extrais le montant total HT du devis (nombre décimal). - items : Liste de TOUTES les lignes d'articles réelles détaillées. Pour chaque article : * type : Choisis une valeur parmi ces 5 valeurs exactes uniquement : 'Matériel', 'Licence', 'Prestation', 'Infrastructure', 'Autre'. * designation : La description / libellé de la ligne d'article. * quantite : La quantité (nombre). * prix_unitaire : Le prix unitaire HT (nombre). Retourne UNIQUEMENT le JSON brut suivant cette structure exacte, sans commentaires, sans texte explicatif, et sans bloc markdown : { \"title\": \"\", \"date_devis\": \"\", \"supplier\": \"\", \"quote_number\": \"\", \"tva_rate\": 20.0, \"total_ht\": 0.0, \"items\": [ { \"type\": \"\", \"designation\": \"\", \"quantite\": 1, \"prix_unitaire\": 0.0 } ] }"; $url = "{$this->url}/api/generate"; Log::info("Début de l'analyse OCR Ollama (GLM-OCR) pour le fichier : {$fileName}"); // Appel API Ollama $response = Http::withHeaders([ 'Content-Type' => 'application/json', ])->timeout(120)->post($url, [ 'model' => $this->model, 'prompt' => $prompt, 'images' => [$base64Data], 'stream' => false, 'format' => 'json', 'options' => [ 'temperature' => 0.0, ] ]); if ($response->failed()) { Log::error("Échec de l'appel Ollama API : " . $response->body()); throw new \Exception("Erreur de communication avec Ollama : " . $response->status()); } $data = $response->json(); $textResult = $data['response'] ?? null; if (!$textResult) { Log::error("Ollama n'a renvoyé aucun contenu exploitable."); throw new \Exception("L'analyse du devis n'a retourné aucun résultat."); } // Nettoyer si du markdown entoure le JSON (parfois Ollama insiste malgré format: json) $textResult = trim($textResult); if (str_starts_with($textResult, '```')) { $textResult = preg_replace('/^```(?:json)?\s*/i', '', $textResult); $textResult = preg_replace('/```$/', '', $textResult); $textResult = trim($textResult); } $decoded = json_decode($textResult, true); if (json_last_error() !== JSON_ERROR_NONE) { Log::error("Impossible de parser le JSON retourné par Ollama : " . $textResult); throw new \Exception("Le format des données retournées par l'OCR Ollama est invalide."); } Log::info("OCR Ollama réussi pour le fichier : {$fileName}"); return $decoded; } }