Files
Effi/effi-ocr-legacy/TOOL-OPENWEBUI.md
Folzo fdcf513baf feat: add effi-ocr tool with Valves configuration and improved file search
- Added Valves (Pydantic) pattern for ocr_url configuration via wheel icon
- Inspired by Kreuzberg tool: multi-level file search (exact, substring, word matches)
- Support for multi-file OCR with extract_text_from_images
- Added search_files function for pattern matching
- effi-ocr runs on port 8301 (Tesseract with preprocessing)
2026-04-12 17:52:42 +02:00

10 KiB

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

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