# Tool OCR effi-ocr pour OpenWebUI ## Configuration Dans OpenWebUI (http://192.168.1.27:3000) : 1. **Aller dans** : Admin → Tools → Create Tool 2. **Nom** : effi-ocr 3. **Description** : Extrait le texte des images avec Tesseract OCR (précision française, preprocessing pour qualité) 4. **Coller le code ci-dessous** 5. **Save and Activate** 6. **Configurer l'URL** via la roue crantée (Valves) : - `ocr_url`: `http://192.168.1.11:8301` ## Fonctionnalités - **extract_text_from_image** : OCR sur un fichier image (jpg, png, pdf scanné, etc.) - **extract_text_from_images** : OCR sur plusieurs fichiers (séparés par virgules) - **list_recent_files** : Liste les fichiers récents uploadés - **search_files** : Recherche des fichiers par pattern ## Optimisations (inspirées de Kreuzberg) - Recherche multi-niveaux : exact match → substring → mots partiels - Gestion des noms partiels (ex: "WhatsApp" suffit pour trouver "WhatsApp Image 2026-04-05...") - Support des fichiers multiples - Timeout étendu (120s) pour fichiers volumineux ## Code du Tool ```python import os import glob import requests import io import json import mimetypes from pydantic import BaseModel, Field class Tools: class Valves(BaseModel): ocr_url: str = Field( default="http://192.168.1.11:8301", description="effi-ocr server URL", ) def __init__(self): self.valves = self.Valves() self.uploads_dir = "/app/backend/data/uploads" self.host_uploads_dir = "/home/effi/effi/data/uploads" def _normalize_language(self, lang): lang_map = { "french": "fra", "francais": "fra", "fr": "fra", "english": "eng", "eng": "eng", "german": "deu", "de": "deu", "spanish": "spa", "es": "spa", "italian": "ita", "it": "ita" } return lang_map.get(lang.lower(), lang) def extract_text_from_image(self, image_path, language="fra"): try: file_path = self._find_file(image_path) if not file_path: available = self._list_files() return "[ERROR] File not found: " + image_path + "\n\nAvailable files:\n" + available mime_type, _ = mimetypes.guess_type(file_path) mime_type = mime_type or 'image/jpeg' with open(file_path, 'rb') as f: file_content = f.read() files = { 'file': (os.path.basename(file_path), io.BytesIO(file_content), mime_type) } data = {'lang': self._normalize_language(language), 'preprocess': 'true'} response = requests.post( self.valves.ocr_url + "/ocr", files=files, data=data, timeout=120 ) if response.status_code == 200: result = response.json() if result.get('success'): text = result.get('text', '') char_count = result.get('characters_count', 0) word_count = result.get('words_count', 0) if text: return "OCR: " + os.path.basename(file_path) + " (" + str(char_count) + " chars, " + str(word_count) + " words)\n\n" + text else: return "[WARNING] No text extracted from " + os.path.basename(file_path) else: return "[ERROR] OCR failed: " + str(result.get('error', 'Unknown error')) else: return "[ERROR] HTTP " + str(response.status_code) + ": " + response.text[:200] except requests.Timeout: return "[ERROR] Timeout - file is too large" except requests.ConnectionError: return "[ERROR] Cannot connect to effi-ocr at " + self.valves.ocr_url except Exception as e: return "[ERROR] " + str(e) def extract_text_from_images(self, filenames, language="fra"): filenames = filenames.strip() if not filenames: return "[ERROR] No filenames provided. Usage: extract_text_from_images(\"file1.jpg,file2.jpg\")" if filenames.startswith('['): try: file_list = json.loads(filenames) except json.JSONDecodeError: return "[ERROR] Invalid JSON array: " + filenames else: file_list = [f.strip() for f in filenames.split(',')] if not file_list: return "[ERROR] No files to process" if len(file_list) == 1: return self.extract_text_from_image(file_list[0], language) results = [] total = len(file_list) for i, filename in enumerate(file_list, 1): filename = filename.strip() if not filename: continue if total > 1: results.append("--- Document " + str(i) + "/" + str(total) + ": " + filename + " ---") result = self.extract_text_from_image(filename, language) results.append(result) return "\n\n".join(results) def list_recent_files(self, count=10): return self._list_files(count) def search_files(self, pattern): try: search_dirs = [d for d in [self.uploads_dir, self.host_uploads_dir] if os.path.exists(d)] files = [] for directory in search_dirs: files.extend(glob.glob(os.path.join(directory, "*" + pattern + "*"))) files = [f for f in files if os.path.isfile(f)] if not files: return "[INFO] No files found matching: " + pattern result = "[INFO] " + str(len(files)) + " files found:\n\n" for f in files[:20]: name = os.path.basename(f) size = self._format_size(os.path.getsize(f)) result += "- " + name + " (" + size + ")\n" return result except Exception as e: return "[ERROR] " + str(e) def _list_files(self, count=10): try: files = [] search_dirs = [d for d in [self.uploads_dir, self.host_uploads_dir] if os.path.exists(d)] for directory in search_dirs: files.extend(glob.glob(os.path.join(directory, "*"))) files = [f for f in files if os.path.isfile(f)] files.sort(key=os.path.getmtime, reverse=True) if not files: return "[INFO] No files found in uploads directory." result = "[INFO] " + str(len(files)) + " recent files:\n\n" for f in files[:count]: name = os.path.basename(f) size = os.path.getsize(f) size_str = self._format_size(size) result += "- " + name + " (" + size_str + ")\n" result += "\nUse extract_text_from_image(\"filename\") to OCR a file." return result except Exception as e: return "[ERROR] " + str(e) def _find_file(self, filename): filename = filename.strip().strip('"').strip("'") if os.path.exists(filename): return filename search_dirs = [d for d in [self.uploads_dir, self.host_uploads_dir] if os.path.exists(d)] for directory in search_dirs: path = os.path.join(directory, filename) if os.path.exists(path): return path exact_matches = [] substring_matches = [] word_matches = [] search_name = filename.lower() search_parts = search_name.replace('.jpeg', ' ').replace('.jpg', ' ').replace('.png', ' ').replace('(1)', ' ').replace('(2)', ' ').replace('(3)', ' ').split() search_parts = [p for p in search_parts if len(p) > 2] for directory in search_dirs: all_files = glob.glob(os.path.join(directory, "*")) for f in all_files: basename = os.path.basename(f) basename_lower = basename.lower() if search_name == basename_lower: exact_matches.append((f, 1000 + len(basename))) continue if search_name in basename_lower: substring_matches.append((f, len(basename))) continue if '(' in filename and ')' in filename: base_no_suffix = search_name.split('(')[0].strip() if base_no_suffix in basename_lower: word_matches.append((f, 500 + len(basename))) continue if search_parts: match_count = sum(1 for part in search_parts if part in basename_lower) if match_count >= len(search_parts) * 0.7: word_matches.append((f, match_count * 100 + len(basename))) if exact_matches: exact_matches.sort(key=lambda x: x[1], reverse=True) return exact_matches[0][0] if substring_matches: substring_matches.sort(key=lambda x: x[1], reverse=True) return substring_matches[0][0] if word_matches: word_matches.sort(key=lambda x: x[1], reverse=True) return word_matches[0][0] return None def _format_size(self, size): if size < 1024: return str(size) + " B" elif size < 1024 * 1024: return str(int(size / 1024)) + " KB" else: return str(int(size / (1024 * 1024))) + " MB" def extract_text_from_image(image_path, language="fra"): tools = Tools() return tools.extract_text_from_image(image_path, language) def extract_text_from_images(filenames, language="fra"): tools = Tools() return tools.extract_text_from_images(filenames, language) def list_recent_files(count=10): tools = Tools() return tools.list_recent_files(count) def search_files(pattern): tools = Tools() return tools.search_files(pattern) ``` ## Utilisation ``` extract_text_from_image("photo.jpg") extract_text_from_image("WhatsApp Image 2026-04-05 at 10.20.56.jpeg", "fra") extract_text_from_images("page1.jpg,page2.jpg,page3.jpg") list_recent_files() list_recent_files(20) search_files("dnd") search_files("whatsapp") ``` --- *Updated 12 Avril 2026 - Valves configuration, inspired by Kreuzberg tool, improved file search*