From 93f31cdaeb491c8fa7c570ede57757e88f7cc5fa Mon Sep 17 00:00:00 2001 From: Folzo Date: Mon, 6 Apr 2026 16:34:27 +0200 Subject: [PATCH] Ajout Effi-OCR (Tesseract) + Web Scraper pour OpenWebUI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Effi-OCR: Service OCR avec Tesseract (multilingue FR/EN) * API FastAPI sur port 8300 * Prétraitement images (contraste, niveaux de gris) * Tool OpenWebUI intégré - Web Scraper: Service Python (BeautifulSoup) sur port 11235 * Alternative à Crawl4AI (incompatible ARM64/J5040) * Scraping HTML basique mais fonctionnel * Intégré au docker-compose d'effi-server - Documentation: * ROADMAP-FONCTIONNALITES.md: Plan des 9 fonctionnalités * WEB-SCRAPER-CONFIG.md: Configuration OpenWebUI * WEB-SCRAPER-TOOL.md: Code du Tool - Script start-all-services.sh mis à jour (6 services) --- .opencode/ROADMAP-FONCTIONNALITES.md | 175 ++++++++++++++++ docs/WEB-SCRAPER-CONFIG.md | 111 +++++++++++ docs/WEB-SCRAPER-TOOL.md | 188 ++++++++++++++++++ effi-crawl4ai/Dockerfile.amd64 | 34 ++++ effi-crawl4ai/Dockerfile.scraper | 20 ++ effi-crawl4ai/README-FOLZO-CORP.md | 115 +++++++++++ effi-crawl4ai/README-INTEGRATION.md | 128 ++++++++++++ effi-crawl4ai/README.md | 111 +++++++++++ effi-crawl4ai/add-to-effi.sh | 107 ++++++++++ effi-crawl4ai/crawl4ai-service-to-add.yml | 30 +++ effi-crawl4ai/docker-compose.crawl4ai.yml | 34 ++++ effi-crawl4ai/docker-compose.yml | 25 +++ effi-crawl4ai/fix-crawl4ai.sh | 44 ++++ effi-crawl4ai/install-on-effiserver-v2.sh | 190 ++++++++++++++++++ effi-crawl4ai/install-on-effiserver.sh | 82 ++++++++ effi-crawl4ai/remove-from-effi.sh | 58 ++++++ effi-crawl4ai/scraper.py | 115 +++++++++++ effi-crawl4ai/update-effiserver.sh | 215 ++++++++++++++++++++ effi-ocr/Dockerfile | 20 ++ effi-ocr/README.md | 232 ++++++++++++++++++++++ effi-ocr/TOOL-OPENWEBUI.md | 175 ++++++++++++++++ effi-ocr/docker-compose.yml | 22 ++ effi-ocr/main.py | 208 +++++++++++++++++++ effi-ocr/requirements.txt | 5 + scripts/start-all-services.sh | 31 ++- 25 files changed, 2469 insertions(+), 6 deletions(-) create mode 100644 .opencode/ROADMAP-FONCTIONNALITES.md create mode 100644 docs/WEB-SCRAPER-CONFIG.md create mode 100644 docs/WEB-SCRAPER-TOOL.md create mode 100644 effi-crawl4ai/Dockerfile.amd64 create mode 100644 effi-crawl4ai/Dockerfile.scraper create mode 100644 effi-crawl4ai/README-FOLZO-CORP.md create mode 100644 effi-crawl4ai/README-INTEGRATION.md create mode 100644 effi-crawl4ai/README.md create mode 100644 effi-crawl4ai/add-to-effi.sh create mode 100644 effi-crawl4ai/crawl4ai-service-to-add.yml create mode 100644 effi-crawl4ai/docker-compose.crawl4ai.yml create mode 100644 effi-crawl4ai/docker-compose.yml create mode 100644 effi-crawl4ai/fix-crawl4ai.sh create mode 100644 effi-crawl4ai/install-on-effiserver-v2.sh create mode 100644 effi-crawl4ai/install-on-effiserver.sh create mode 100644 effi-crawl4ai/remove-from-effi.sh create mode 100644 effi-crawl4ai/scraper.py create mode 100644 effi-crawl4ai/update-effiserver.sh create mode 100644 effi-ocr/Dockerfile create mode 100644 effi-ocr/README.md create mode 100644 effi-ocr/TOOL-OPENWEBUI.md create mode 100644 effi-ocr/docker-compose.yml create mode 100644 effi-ocr/main.py create mode 100644 effi-ocr/requirements.txt diff --git a/.opencode/ROADMAP-FONCTIONNALITES.md b/.opencode/ROADMAP-FONCTIONNALITES.md new file mode 100644 index 0000000..8ce0b04 --- /dev/null +++ b/.opencode/ROADMAP-FONCTIONNALITES.md @@ -0,0 +1,175 @@ +# EFFI - ROADMAP FONCTIONNALITÉS + +**Date de création** : 6 Avril 2026 +**Dernière mise à jour** : 6 Avril 2026 +**Statut** : En attente d'implémentation + +--- + +## 🔥 PRIORITÉ HAUTE + +### 1. OCR / Tesseract +**Objectif** : Permettre à Effi de lire et extraire le texte des images (photos de livres, documents, etc.) + +**Use case** : L'ami de Folzo prend des photos de livres et veut les numériser + +**Implémentation** : +- Installer Tesseract sur folzo-Corp : `sudo dnf install tesseract tesseract-langpack-fra` +- Créer un endpoint API FastAPI pour l'OCR +- Intégrer dans OpenWebUI comme Tool + +**Ressources** : +- Tesseract OCR (open source) +- Python : `pytesseract` + `Pillow` + +--- + +### 2. Joplin Integration +**Objectif** : RAG (Retrieval-Augmented Generation) sur les notes Joplin existantes + +**Use case** : Poser des questions à Effi sur ses propres notes + +**Implémentation** : +- Exporter/exposer les notes Joplin via API +- Vectorisation et stockage dans Qdrant +- Créer un Tool "search_my_notes" + +**Ressources** : +- Joplin Web Clipper API +- Joplin Data API + +--- + +### 3. Web Scraping +**Objectif** : Permettre aux agents de lire et résumer n'importe quelle URL + +**Use case** : "Effi, résume-moi cet article" → paste URL + +**Implémentation** : +- Créer un Tool OpenWebUI +- Librairies : `requests` + `beautifulsoup4` ou `readability-lxml` +- Gestion du rate limiting +- Extraction texte propre (sans pubs/nav) + +**Ressources** : +- newspaper3k (extraction article) +- trafilatura (alternative) + +--- + +## ⚡ AMÉLIORATIONS + +### 4. Optimiser Web Search +**Objectif** : Améliorer la qualité et quantité des résultats de recherche web + +**Actions** : +- Augmenter le nombre de résultats (actuellement limité) +- Tester SearXNG (moteur de méta-recherche auto-hébergé) +- Configurer la région/langue (fr-FR) +- Filtres par date/type de contenu + +**Ressources** : +- SearXNG Docker +- DuckDuckGo API + +--- + +### 5. Home Assistant - Fix Latence +**Objectif** : Résoudre les problèmes de latence dans l'intégration Home Assistant + +**Problème actuel** : Latence élevée lors des appels API + +**Actions** : +- Diagnostiquer la cause (réseau ? configuration ?) +- Optimiser les appels (batch ? cache ?) +- Vérifier la connectivité entre effi-server et HA + +--- + +### 6. Calendrier - Intégration CalDAV +**Objectif** : Permettre à Effi de consulter et gérer le calendrier + +**Use case** : "Qu'est-ce que j'ai de prévu demain ?" + +**Implémentation** : +- Intégration CalDAV (Nextcloud, Google, etc.) +- Tool "check_calendar" +- Tool "add_event" + +**Ressources** : +- `caldav` Python library + +--- + +## 💡 FONCTIONNALITÉS FUN + +### 7. Voice Commands +**Objectif** : Activer/désactiver des services par la voix + +**Use case** : "Effi, lance ComfyUI" ou "Effi, éteins le serveur" + +**Implémentation** : +- Mots-clés déclencheurs (hotwords) +- Actions système via scripts +- Intégration avec les commandes vocales existantes + +--- + +### 8. Automation Scripts +**Objectif** : Tâches planifiées et automatisées + +**Exemples** : +- Backup automatique des conversations +- Rapport quotidien (météo, calendrier, news) +- Vérification santé des services +- Nettoyage automatique des vieux fichiers + +**Implémentation** : +- Cron jobs ou systemd timers +- Scripts Python dans `/scripts/automation/` + +--- + +### 9. More Models pour ComfyUI +**Objectif** : Ajouter plus de variété de modèles pour la génération d'images + +**Catégories** : +- **Art/Fantasy** : DreamShaper, EpicRealism +- **Anime/Manga** : AnythingXL, CounterfeitXL +- **Photorealistic** : RealVisXL, JuggernautXL (déjà présent) +- **3D/Rendering** : PixarXL, DisneyStyle + +**Script existant** : `comfyui/download-more-models.sh` + +--- + +## 📋 PROCHAINES SESSIONS + +### Session suggérée #1 : OCR/Tesseract +1. Installation Tesseract +2. Création API FastAPI +3. Tool OpenWebUI +4. Test avec photo de livre + +### Session suggérée #2 : Web Scraping +1. Création du Tool +2. Tests avec différents sites +3. Intégration agents + +### Session suggérée #3 : Joplin Integration +1. Configurer API Joplin +2. Export notes vers Qdrant +3. Tool search_my_notes + +--- + +## 📝 NOTES + +- Prioriser l'OCR (besoin immédiat pour l'ami de Folzo) +- Web Scraping utile pour tous les agents +- Joplin demande plus de travail (vectorisation) +- Les améliorations peuvent être faites en parallèle + +--- + +*Roadmap créée par Folzo - 6 Avril 2026* diff --git a/docs/WEB-SCRAPER-CONFIG.md b/docs/WEB-SCRAPER-CONFIG.md new file mode 100644 index 0000000..b57dde9 --- /dev/null +++ b/docs/WEB-SCRAPER-CONFIG.md @@ -0,0 +1,111 @@ +# Configuration Web Search and Crawl (OpenWebUI) + +Guide de configuration du Tool **Web Search and Crawl** dans OpenWebUI avec Crawl4AI. + +## Prérequis + +- [ ] Crawl4AI installé et démarré sur effi-server (voir `install-on-effiserver.sh`) +- [ ] OpenWebUI accessible sur http://192.168.1.27:3000 + +## Étapes de configuration + +### 1. Vérifier que Crawl4AI tourne + +Sur **effi-server** : +```bash +curl http://localhost:11235/health +# Doit retourner : {"status":"healthy"} +``` + +### 2. Accéder au Tool dans OpenWebUI + +1. Ouvrir http://192.168.1.27:3000 +2. Se connecter en admin +3. Aller dans **Admin Panel** → **Tools** +4. Trouver **Web Search and Crawl** +5. Cliquer sur l'icône ⚙️ (Settings/Valves) + +### 3. Configurer les Valves + +| Valve | Valeur | Description | +|-------|--------|-------------| +| **Crawl4AI Base URL** | `http://localhost:11235` | URL du service Crawl4AI | +| **Crawl4AI API Token** | (vide) | Token si configuré dans docker-compose | +| **Timeout** | `30000` | Timeout en millisecondes (30s) | +| **Max Length** | `10000` | Nombre max de caractères extraits | +| **Enable JavaScript** | ✅ | Activer l'exécution JavaScript | + +### 4. Activer le Tool + +1. Cocher la case **Enable** sur le Tool +2. Cliquer sur **Save** +3. Le Tool est maintenant actif ! + +## Test de fonctionnement + +Créer une nouvelle conversation et demander : + +``` +"Effi, cherche les dernières nouvelles sur Fedora 42 et résume-moi les articles" +``` + +L'agent devrait : +1. Utiliser la recherche web +2. Scraper les pages trouvées +3. Te présenter un résumé + +## Troubleshooting + +### "Crawl4AI not responding" + +Vérifier sur effi-server : +```bash +docker ps | grep crawl4ai +docker logs effi-crawl4ai +``` + +### "Timeout error" + +Augmenter la valve **Timeout** à `60000` (60 secondes) dans les settings du Tool. + +### "Memory error" + +La version CPU est limitée à 2GB. Pour les pages très lourdes : +- Augmenter dans `docker-compose.yml` : `memory: 3G` +- Redémarrer : `docker-compose up -d` + +## Commandes utiles + +**Redémarrer Crawl4AI** : +```bash +cd ~/effi-crawl4ai +docker-compose restart +``` + +**Voir les logs** : +```bash +docker logs -f effi-crawl4ai +``` + +**Mettre à jour l'image** : +```bash +cd ~/effi-crawl4ai +docker-compose pull +docker-compose up -d +``` + +## Fonctionnalités disponibles + +Une fois configuré, tu peux demander à Effi : + +| Requête | Description | +|---------|-------------| +| "Cherche X et résume" | Recherche + résumé automatique | +| "Extraire le contenu de [URL]" | Scraping direct d'une page | +| "Comparer [produit] sur plusieurs sites" | Multi-site scraping | +| "Vérifier les prix de [article]" | Surveillance de prix | +| "Trouver des documentations sur [sujet]" | Recherche technique | + +--- + +*Configuration créée le 6 Avril 2026* diff --git a/docs/WEB-SCRAPER-TOOL.md b/docs/WEB-SCRAPER-TOOL.md new file mode 100644 index 0000000..fce82ec --- /dev/null +++ b/docs/WEB-SCRAPER-TOOL.md @@ -0,0 +1,188 @@ +# Tool Web Scraping pour OpenWebUI + +## Configuration + +Dans OpenWebUI (http://192.168.1.27:3000) : + +1. **Aller dans** : Admin → Tools → Create Tool +2. **Nom** : Web Scraper +3. **ID** : web_scraper +4. **Description** : Extrait le contenu texte de n'importe quelle URL web + +## Code du Tool + +```python +from typing import Optional +import requests +from urllib.parse import urlparse +import re + +class Tools: + def __init__(self): + """Initialize Web Scraper Tool""" + self.timeout = 15 + self.headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.0' + } + + def scrape_url(self, url: str, max_chars: int = 5000) -> str: + """ + Scrape le contenu texte d'une URL web + + Args: + url: L'URL à scraper (doit commencer par http:// ou https://) + max_chars: Nombre maximum de caractères à retourner (défaut: 5000) + + Returns: + Le contenu texte extrait de la page + """ + try: + # Validation URL + if not url.startswith(('http://', 'https://')): + return "Erreur: L'URL doit commencer par http:// ou https://" + + parsed = urlparse(url) + if not parsed.netloc: + return "Erreur: URL invalide" + + # Télécharger la page + response = requests.get(url, headers=self.headers, timeout=self.timeout) + response.raise_for_status() + + # Détecter l'encodage + if response.encoding == 'ISO-8859-1': + response.encoding = response.apparent_encoding + + html = response.text + + # Extraction simple du texte (sans BeautifulSoup pour garder le Tool léger) + # Supprimer les scripts et styles + text = re.sub(r']*>[^<]*', '', html, flags=re.IGNORECASE | re.DOTALL) + text = re.sub(r']*>[^<]*', '', text, flags=re.IGNORECASE | re.DOTALL) + + # Supprimer les balises HTML + text = re.sub(r'<[^>]+>', ' ', text) + + # Nettoyer le texte + text = re.sub(r'\s+', ' ', text) # Espaces multiples + text = text.strip() + + # Décoder les entités HTML + text = text.replace('&', '&') + text = text.replace('<', '<') + text = text.replace('>', '>') + text = text.replace('"', '"') + text = text.replace(''', "'") + text = text.replace(' ', ' ') + + # Tronquer si nécessaire + if len(text) > max_chars: + text = text[:max_chars] + f"\n\n[...] {len(text) - max_chars} caractères supplémentaires" + + return f"Contenu extrait de {url}:\n\n{text}" + + except requests.exceptions.Timeout: + return f"Erreur: Timeout après {self.timeout}s pour {url}" + except requests.exceptions.ConnectionError: + return f"Erreur: Impossible de se connecter à {url}" + except requests.exceptions.HTTPError as e: + return f"Erreur HTTP {e.response.status_code}: {url}" + except Exception as e: + return f"Erreur lors du scraping: {str(e)}" + + def scrape_article(self, url: str) -> str: + """ + Scrape un article en essayant d'extraire le contenu principal uniquement + + Args: + url: L'URL de l'article + + Returns: + Le contenu principal de l'article (titre + texte) + """ + try: + if not url.startswith(('http://', 'https://')): + return "Erreur: L'URL doit commencer par http:// ou https://" + + response = requests.get(url, headers=self.headers, timeout=self.timeout) + response.raise_for_status() + + if response.encoding == 'ISO-8859-1': + response.encoding = response.apparent_encoding + + html = response.text + + # Extraire le titre + title_match = re.search(r']*>([^<]*)', html, re.IGNORECASE) + title = title_match.group(1).strip() if title_match else "Titre non trouvé" + + # Essayer d'extraire le contenu article (heuristique simple) + # Chercher les balises article, main, ou div avec classe contenant "content" + article_patterns = [ + r']*>(.*?)', + r']*>(.*?)', + r']*class=["\'][^"\']*(?:content|article|post)[^"\']*["\'][^>]*>(.*?)', + ] + + content = None + for pattern in article_patterns: + match = re.search(pattern, html, re.IGNORECASE | re.DOTALL) + if match: + content = match.group(1) + break + + # Si pas trouvé, prendre tout le body + if not content: + body_match = re.search(r']*>(.*?)', html, re.IGNORECASE | re.DOTALL) + content = body_match.group(1) if body_match else html + + # Nettoyer le contenu + content = re.sub(r']*>[^<]*', '', content, flags=re.IGNORECASE | re.DOTALL) + content = re.sub(r']*>[^<]*', '', content, flags=re.IGNORECASE | re.DOTALL) + content = re.sub(r'<[^>]+>', ' ', content) + content = re.sub(r'\s+', ' ', content).strip() + + # Décoder les entités HTML + content = content.replace('&', '&').replace('<', '<').replace('>', '>') + content = content.replace('"', '"').replace(''', "'") + + # Limiter la taille + if len(content) > 8000: + content = content[:8000] + "\n\n[...] Contenu tronqué" + + return f"Titre: {title}\nURL: {url}\n\n{content}" + + except Exception as e: + return f"Erreur: {str(e)}" +``` + +## Utilisation + +Une fois activé, les agents peuvent utiliser : + +- `scrape_url("https://example.com/article")` - Extraire tout le texte d'une page +- `scrape_article("https://example.com/article")` - Extraire le contenu article uniquement + +## Exemples de prompts + +``` +"Effi, résume-moi cet article : https://..." +→ L'agent utilise scrape_article automatiquement + +"Qu'est-ce qui est écrit sur cette page ? https://..." +→ L'agent utilise scrape_url + +"Cherche des infos sur X et lis les 3 premiers liens" +→ Web search + scrape_url pour chaque résultat +``` + +## Limitations + +- Extraction basique (pas de JavaScript) +- Timeout 15 secondes +- Sites avec protection anti-bot peuvent bloquer +- Ne fonctionne pas avec les paywalls + +--- + +*Tool créé le 6 Avril 2026* diff --git a/effi-crawl4ai/Dockerfile.amd64 b/effi-crawl4ai/Dockerfile.amd64 new file mode 100644 index 0000000..3069f2b --- /dev/null +++ b/effi-crawl4ai/Dockerfile.amd64 @@ -0,0 +1,34 @@ +# Crawl4AI pour architecture AMD64 (x86_64) +# Build local car l'image officielle est en ARM64 + +FROM python:3.11-slim + +WORKDIR /app + +# Installer les dépendances système +RUN apt-get update && apt-get install -y \ + curl \ + chromium \ + chromium-driver \ + && rm -rf /var/lib/apt/lists/* + +# Variables d'environnement +ENV PYTHONUNBUFFERED=1 +ENV PLAYWRIGHT_BROWSERS_PATH=0 +ENV PIP_NO_CACHE_DIR=1 + +# Installer Crawl4AI +RUN pip install crawl4ai + +# Installer Playwright +RUN pip install playwright && playwright install chromium + +# Port d'écoute +EXPOSE 11235 + +# Healthcheck +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:11235/health || exit 1 + +# Démarrer l'API +CMD ["python", "-m", "crawl4ai.server", "--host", "0.0.0.0", "--port", "11235"] diff --git a/effi-crawl4ai/Dockerfile.scraper b/effi-crawl4ai/Dockerfile.scraper new file mode 100644 index 0000000..bc794d5 --- /dev/null +++ b/effi-crawl4ai/Dockerfile.scraper @@ -0,0 +1,20 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Installer les dépendances +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir fastapi uvicorn requests beautifulsoup4 + +# Copier le code +COPY scraper.py . + +EXPOSE 11235 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:11235/health || exit 1 + +CMD ["uvicorn", "scraper:app", "--host", "0.0.0.0", "--port", "11235"] diff --git a/effi-crawl4ai/README-FOLZO-CORP.md b/effi-crawl4ai/README-FOLZO-CORP.md new file mode 100644 index 0000000..b4a42f4 --- /dev/null +++ b/effi-crawl4ai/README-FOLZO-CORP.md @@ -0,0 +1,115 @@ +# Crawl4AI sur folzo-Corp + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ folzo-Corp (GPU) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │ +│ │ Ollama │ │ComfyUI │ │Effi-STT │ │Effi-OCR │ │Crawl4AI│ │ +│ │ :11434 │ │ :8188 │ │ :8200 │ │ :8300 │ │:11235 │ │ +│ └────┬────┘ └─────────┘ └─────────┘ └─────────┘ └───┬────┘ │ +│ │ │ │ +│ │ http://192.168.1.11:11235 │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + │ HTTP API + │ +┌─────────────────────────────────────────────────────────────────┐ +│ effi-server (J5040) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ +│ │ OpenWebUI │ │ Qdrant │ │ Tool Config │ │ +│ │ :3000 │ │ :6333 │ │ Crawl4AI Base URL: │ │ +│ └──────┬──────┘ └─────────────┘ │ http://192.168.1.11:11235│ │ +│ │ └─────────────────────────┘ │ +│ │ ❌ PAS de Crawl4AI local │ +└─────────┼───────────────────────────────────────────────────────┘ + │ + └──────► Réseau effi-net +``` + +## Déploiement + +### 1. Sur folzo-Corp (déjà fait) + +Les fichiers sont dans `/home/folzo/Documents/OpenCode/Projets/Effi/effi-crawl4ai/` + +```bash +cd /home/folzo/Documents/OpenCode/Projets/Effi/effi-crawl4ai +docker-compose up -d +``` + +### 2. Sur effi-server (à faire) + +**Supprimer le service local qui ne fonctionne pas :** + +```bash +cd /root/effi + +# Option A : Script automatique +curl -o remove-from-effi.sh [URL_du_script] +bash remove-from-effi.sh + +# Option B : Manuel +docker-compose stop crawl4ai +docker-compose rm -f crawl4ai +docker rmi unclecode/crawl4ai:basic + +# Éditer docker-compose.yml et supprimer le service crawl4ai (section #8) +``` + +**Ouvrir le firewall sur folzo-Corp** : + +```bash +# Sur folzo-Corp +sudo firewall-cmd --add-port=11235/tcp --permanent +sudo firewall-cmd --reload +``` + +### 3. Configuration OpenWebUI + +Dans http://192.168.1.27:3000 : + +1. **Admin Panel** → **Tools** → **Web Search and Crawl** → ⚙️ +2. **Crawl4AI Base URL** : `http://192.168.1.11:11235` ← L'IP de folzo-Corp ! +3. **Timeout** : `30000` (ou `60000` si lenteur) +4. Activer le Tool ✅ + +## Avantages de cette architecture + +✅ **Crawl4AI** tourne sur CPU/GPU puissant (folzo-Corp) +✅ **Effi-server** (J5040) n'est pas surchargé +✅ **Réseau** local rapide entre les deux machines +✅ **Charge répartie** : scraping sur folzo-Corp, interface sur effi-server + +## Vérification + +```bash +# Sur folzo-Corp +curl http://localhost:11235/health +# Résultat : {"status":"healthy"} + +# Sur effi-server +curl http://192.168.1.11:11235/health +# Résultat : {"status":"healthy"} +``` + +## Test + +Dans OpenWebUI : +> *"Effi, cherche les dernières nouvelles sur Linux et résume-moi un article"* + +## Troubleshooting + +**"Connection refused" depuis effi-server** : +- Vérifier que Crawl4AI tourne sur folzo-Corp : `docker ps | grep crawl4ai` +- Vérifier le firewall sur folzo-Corp : `sudo firewall-cmd --list-ports` + +**Timeout** : +- Augmenter à 60000ms dans OpenWebUI +- La latence réseau entre les machines peut causer des timeouts + +--- + +*Architecture mise à jour pour J5040 + RX 7800 XT* diff --git a/effi-crawl4ai/README-INTEGRATION.md b/effi-crawl4ai/README-INTEGRATION.md new file mode 100644 index 0000000..521fd8f --- /dev/null +++ b/effi-crawl4ai/README-INTEGRATION.md @@ -0,0 +1,128 @@ +# Crawl4AI - Intégration Effi Server + +Intégration du service **Crawl4AI** au docker-compose existant de `/root/effi/`. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ effi-server │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ OpenWebUI │ │ Qdrant │ │ Crawl4AI │ │ +│ │ :3000 │ │ :6333 │ │ :11235 │ │ +│ └──────┬──────┘ └─────────────┘ └──────┬──────┘ │ +│ │ │ │ +│ └──────────────────────────────────┘ │ +│ Réseau: effi-net │ +└─────────────────────────────────────────────────────────┘ +``` + +## Installation + +### Option 1 : Script automatique (recommandé) + +Sur **effi-server**, dans `/root/effi/` : + +```bash +cd /root/effi + +# Copier le script add-to-effi.sh ici +# Puis exécuter : +bash add-to-effi.sh +``` + +Le script va : +1. Sauvegarder ton docker-compose.yml +2. Ajouter le service Crawl4AI +3. Démarrer le service +4. Vérifier qu'il fonctionne + +### Option 2 : Manuel + +1. **Sauvegarder** le docker-compose existant : +```bash +cd /root/effi +cp docker-compose.yml docker-compose.yml.backup +``` + +2. **Éditer** `docker-compose.yml` et ajouter avant la section `networks:` : + +```yaml + # ───────────────────────────────────────────────────────── + # 8. CRAWL4AI - Web Scraping pour OpenWebUI + # → http://localhost:11235 (accessible depuis OpenWebUI) + # ───────────────────────────────────────────────────────── + crawl4ai: + image: unclecode/crawl4ai:basic + container_name: crawl4ai + restart: unless-stopped + ports: + - "11235:11235" + environment: + - CRAWL4AI_API_TOKEN=${CRAWL4AI_API_TOKEN:-} + - MAX_CONCURRENT_TASKS=3 + - CRAWL4AI_ENABLE_GPU=false + volumes: + - /dev/shm:/dev/shm + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 512M + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11235/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - effi-net +``` + +3. **Démarrer** le service : +```bash +docker-compose up -d crawl4ai +``` + +## Configuration OpenWebUI + +1. Aller dans **Admin Panel** → **Tools** → **Web Search and Crawl** → ⚙️ +2. **Crawl4AI Base URL** : `http://localhost:11235` +3. **Timeout** : `30000` +4. Activer le Tool ✅ + +## Vérification + +```bash +# Sur effi-server +curl http://localhost:11235/health +# Résultat attendu : {"status":"healthy"} +``` + +## Test + +Dans OpenWebUI : +> *"Effi, cherche les dernières nouvelles sur Linux et résume-moi un article"* + +## Troubleshooting + +**Le service ne démarre pas** : +```bash +docker logs crawl4ai +docker-compose ps +``` + +**Timeout** : Augmenter la valve à `60000` dans OpenWebUI + +**Mémoire** : Le service est limité à 2GB (suffisant pour la plupart des sites) + +## Fichiers + +- `crawl4ai-service-to-add.yml` - Contenu à ajouter au docker-compose +- `add-to-effi.sh` - Script d'installation automatique +- `README.md` - Cette documentation + +--- + +*Intégration créée pour la stack Effi existante* diff --git a/effi-crawl4ai/README.md b/effi-crawl4ai/README.md new file mode 100644 index 0000000..32285c1 --- /dev/null +++ b/effi-crawl4ai/README.md @@ -0,0 +1,111 @@ +# Effi-Crawl4AI + +Service **Crawl4AI** pour le web scraping avancé dans OpenWebUI. + +## Architecture + +``` +┌─────────────────┐ HTTP API ┌─────────────────┐ +│ OpenWebUI │ ◄────────────────► │ Crawl4AI │ +│ (effi-server) │ Port 11235 │ (effi-server) │ +└─────────────────┘ └─────────────────┘ + │ │ + └────────────────────────────────────┘ + Même machine (localhost) +``` + +**Note** : Le service tourne sur effi-server (Ubuntu) sur CPU. Version `basic` (plus légère que `all`). + +## Déploiement sur effi-server + +1. **Copier les fichiers** sur effi-server : +```bash +scp -r /path/to/effi-crawl4ai user@192.168.1.27:/home/user/ +``` + +2. **Démarrer le service** : +```bash +cd /home/user/effi-crawl4ai +docker-compose up -d +``` + +## Vérification + +```bash +curl http://localhost:11235/health +``` + +## Configuration OpenWebUI + +Dans OpenWebUI (Admin → Tools → Web Search and Crawl) : + +**Valves à configurer :** +- **Crawl4AI Base URL** : `http://localhost:11235` (ou `http://127.0.0.1:11235`) +- **API Token** : (laisser vide si pas de token configuré) +- **Timeout** : `30000` (30 secondes) +``` +┌─────────────────┐ HTTP API ┌─────────────────┐ +│ OpenWebUI │ ◄────────────────► │ Crawl4AI │ +│ (effi-server) │ Port 11235 │ (folzo-Corp) │ +└─────────────────┘ └─────────────────┘ +``` + +## Démarrage + +```bash +docker-compose up -d +``` + +## Vérification + +```bash +curl http://192.168.1.11:11235/health +``` + +## Configuration OpenWebUI + +Dans OpenWebUI (Admin → Tools → Web Search and Crawl) : + +**Valves à configurer :** +- **Crawl4AI Base URL** : `http://192.168.1.11:11235` +- **API Token** : (laisser vide si pas de token configuré) +- **Timeout** : `30000` (30 secondes) + +## Utilisation + +Une fois configuré, l'agent peut : + +``` +"Effi, cherche des informations sur les dernières mises à jour de Fedora 42" +"Résume-moi cet article : https://example.com/article" +"Compare les prix de ce produit sur 3 sites différents" +``` + +## Fonctionnalités + +- ✅ Scraping JavaScript (pages dynamiques) +- ✅ Extraction structurée (JSON, markdown) +- ✅ Screenshot des pages +- ✅ Respect des robots.txt +- ✅ Rate limiting configurable + +## Troubleshooting + +**Erreur de connexion** : Vérifier que le conteneur tourne + +```bash +docker ps | grep crawl4ai +docker logs effi-crawl4ai +``` + +**Timeout** : Augmenter le timeout dans les Valves du Tool (60000ms = 60s) + +**Lenteur** : La version CPU est plus lente que la version GPU. Pour les pages lourdes en JavaScript, augmenter le timeout à 60-90s. + +## Limitations (version CPU) + +- ⏱️ Plus lent sur les pages JavaScript lourdes +- 🎯 Limité à 3 tâches concurrentes (vs 5 sur GPU) +- 💾 2GB RAM max (suffisant pour la plupart des sites) + +Pour du scraping intensif (100+ pages/heure), envisager de déplacer sur folzo-Corp avec l'image `unclecode/crawl4ai:all`. diff --git a/effi-crawl4ai/add-to-effi.sh b/effi-crawl4ai/add-to-effi.sh new file mode 100644 index 0000000..d5a8fdf --- /dev/null +++ b/effi-crawl4ai/add-to-effi.sh @@ -0,0 +1,107 @@ +#!/bin/bash +# Script d'ajout de Crawl4AI au docker-compose existant +# À exécuter sur effi-server dans /root/effi/ + +set -e + +echo "================================" +echo "Ajout de Crawl4AI à Effi Server" +echo "================================" +echo "" + +# Vérifier qu'on est dans le bon répertoire +if [ ! -f "docker-compose.yml" ]; then + echo "❌ Erreur: docker-compose.yml non trouvé" + echo " Ce script doit être exécuté dans /root/effi/" + exit 1 +fi + +echo "📄 Docker-compose trouvé" + +# Sauvegarder l'original +cp docker-compose.yml docker-compose.yml.backup.$(date +%Y%m%d_%H%M%S) +echo "💾 Backup créé: docker-compose.yml.backup.*" + +# Ajouter le service avant "networks:" +echo "" +echo "🔧 Ajout du service Crawl4AI..." + +# Créer un fichier temporaire avec le service à insérer +cat > /tmp/crawl4ai-service.txt << 'EOF' + # ───────────────────────────────────────────────────────── + # 8. CRAWL4AI - Web Scraping pour OpenWebUI + # → http://localhost:11235 (accessible depuis OpenWebUI) + # ───────────────────────────────────────────────────────── + crawl4ai: + image: unclecode/crawl4ai:basic + container_name: crawl4ai + restart: unless-stopped + ports: + - "11235:11235" + environment: + - CRAWL4AI_API_TOKEN=${CRAWL4AI_API_TOKEN:-} + - MAX_CONCURRENT_TASKS=3 + - CRAWL4AI_ENABLE_GPU=false + volumes: + - /dev/shm:/dev/shm + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 512M + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11235/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - effi-net + +EOF + +# Insérer avant la ligne "networks:" +awk ' +/^networks:/ { + while ((getline line < "/tmp/crawl4ai-service.txt") > 0) { + print line + } + close("/tmp/crawl4ai-service.txt") +} +{ print } +' docker-compose.yml > /tmp/docker-compose-new.yml + +# Remplacer le fichier original +mv /tmp/docker-compose-new.yml docker-compose.yml + +echo "✅ Service ajouté au docker-compose.yml" +echo "" + +# Démarrer le nouveau service +echo "🚀 Démarrage de Crawl4AI..." +docker-compose up -d crawl4ai + +echo "" +echo "⏳ Attente du démarrage (30s)..." +sleep 30 + +# Vérification +echo "" +echo "🔍 Vérification..." +if curl -s http://localhost:11235/health > /dev/null 2>&1; then + echo "✅ Crawl4AI est démarré !" + echo "" + echo "📋 Configuration OpenWebUI:" + echo " Admin → Tools → Web Search and Crawl → ⚙️" + echo " Crawl4AI Base URL: http://localhost:11235" + echo " Timeout: 30000" +else + echo "⚠️ Le service ne répond pas encore" + echo " Vérifier les logs: docker logs crawl4ai" +fi + +echo "" +echo "================================" +echo "Installation terminée !" +echo "================================" diff --git a/effi-crawl4ai/crawl4ai-service-to-add.yml b/effi-crawl4ai/crawl4ai-service-to-add.yml new file mode 100644 index 0000000..a2b1d72 --- /dev/null +++ b/effi-crawl4ai/crawl4ai-service-to-add.yml @@ -0,0 +1,30 @@ + # ───────────────────────────────────────────────────────── + # 8. CRAWL4AI - Web Scraping pour OpenWebUI + # → http://localhost:11235 (accessible depuis OpenWebUI) + # ───────────────────────────────────────────────────────── + crawl4ai: + image: unclecode/crawl4ai:basic + container_name: crawl4ai + restart: unless-stopped + ports: + - "11235:11235" + environment: + - CRAWL4AI_API_TOKEN=${CRAWL4AI_API_TOKEN:-} + - MAX_CONCURRENT_TASKS=3 + - CRAWL4AI_ENABLE_GPU=false + volumes: + - /dev/shm:/dev/shm + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 512M + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11235/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - effi-net diff --git a/effi-crawl4ai/docker-compose.crawl4ai.yml b/effi-crawl4ai/docker-compose.crawl4ai.yml new file mode 100644 index 0000000..228ec92 --- /dev/null +++ b/effi-crawl4ai/docker-compose.crawl4ai.yml @@ -0,0 +1,34 @@ +# Service Crawl4AI pour Effi +# À déployer sur folzo-Corp (GPU RX 7800 XT) +# Exposition du port 11235 pour l'API + +services: + crawl4ai: + image: unclecode/crawl4ai:all + container_name: effi-crawl4ai + ports: + - "11235:11235" + environment: + - CRAWL4AI_API_TOKEN=${CRAWL4AI_API_TOKEN:-} + - MAX_CONCURRENT_TASKS=5 + - CRAWL4AI_ENABLE_GPU=false + volumes: + - /dev/shm:/dev/shm + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11235/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + +networks: + default: + name: effi-network + external: true diff --git a/effi-crawl4ai/docker-compose.yml b/effi-crawl4ai/docker-compose.yml new file mode 100644 index 0000000..35171bb --- /dev/null +++ b/effi-crawl4ai/docker-compose.yml @@ -0,0 +1,25 @@ +# Alternative simple à Crawl4AI pour architecture x86_64 +# Service de scraping web basique mais fonctionnel + +services: + web-scraper: + build: + context: . + dockerfile: Dockerfile.scraper + container_name: effi-web-scraper + ports: + - "11235:11235" + environment: + - MAX_CONCURRENT_TASKS=3 + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11235/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +networks: + default: + name: effi-network + external: true diff --git a/effi-crawl4ai/fix-crawl4ai.sh b/effi-crawl4ai/fix-crawl4ai.sh new file mode 100644 index 0000000..41241af --- /dev/null +++ b/effi-crawl4ai/fix-crawl4ai.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Script de réparation de Crawl4AI +# À exécuter sur effi-server dans /root/effi/ + +echo "================================" +echo "Réparation de Crawl4AI" +echo "================================" +echo "" + +# 1. Arrêter et supprimer le conteneur +echo "🛑 Arrêt du conteneur..." +docker stop crawl4ai 2>/dev/null || true +docker rm crawl4ai 2>/dev/null || true + +# 2. Supprimer l'image corrompue +echo "🗑️ Suppression de l'image..." +docker rmi unclecode/crawl4ai:basic 2>/dev/null || true + +# 3. Nettoyer le cache Docker +echo "🧹 Nettoyage du cache..." +docker system prune -f + +# 4. Re-pull l'image avec plateforme explicite +echo "📥 Téléchargement de l'image..." +docker pull --platform linux/amd64 unclecode/crawl4ai:basic + +# 5. Redémarrer +echo "🚀 Redémarrage..." +docker-compose up -d crawl4ai + +echo "" +echo "⏳ Attente du démarrage (60s)..." +sleep 60 + +# 6. Vérification +echo "" +echo "🔍 Vérification..." +if curl -s http://localhost:11235/health > /dev/null 2>&1; then + echo "✅ Crawl4AI fonctionne !" + curl http://localhost:11235/health +else + echo "❌ Toujours en erreur. Logs :" + docker logs crawl4ai --tail 20 +fi diff --git a/effi-crawl4ai/install-on-effiserver-v2.sh b/effi-crawl4ai/install-on-effiserver-v2.sh new file mode 100644 index 0000000..f888301 --- /dev/null +++ b/effi-crawl4ai/install-on-effiserver-v2.sh @@ -0,0 +1,190 @@ +#!/bin/bash +# Installation du Web Scraper sur effi-server +# À exécuter dans /root/effi/ + +set -e + +echo "================================" +echo "Installation Web Scraper (Effi)" +echo "================================" +echo "" + +# Vérifier qu'on est dans le bon répertoire +if [ ! -f "docker-compose.yml" ]; then + echo "❌ Erreur: docker-compose.yml non trouvé" + exit 1 +fi + +echo "📁 Répertoire: $(pwd)" + +# Créer le répertoire pour le web scraper +mkdir -p web-scraper +cd web-scraper + +echo "📝 Création des fichiers..." + +# Dockerfile +cat > Dockerfile << 'EOF' +FROM python:3.11-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir fastapi uvicorn requests beautifulsoup4 + +COPY scraper.py . + +EXPOSE 11235 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:11235/health || exit 1 + +CMD ["uvicorn", "scraper:app", "--host", "0.0.0.0", "--port", "11235"] +EOF + +# Scraper Python +cat > scraper.py << 'EOF' +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from typing import Optional, List +import requests +from bs4 import BeautifulSoup + +app = FastAPI(title="Effi Web Scraper", version="1.0.0") + +class ScrapeRequest(BaseModel): + url: str + max_length: Optional[int] = 10000 + +class ScrapeResponse(BaseModel): + url: str + title: str + content: str + links: List[str] + success: bool + error: Optional[str] = None + +@app.get("/health") +def health(): + return {"status": "healthy"} + +@app.post("/scrape", response_model=ScrapeResponse) +def scrape(request: ScrapeRequest): + try: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + + response = requests.get(request.url, headers=headers, timeout=30) + response.raise_for_status() + + soup = BeautifulSoup(response.content, 'html.parser') + + title = soup.title.string if soup.title else "No title" + + for script in soup(["script", "style"]): + script.decompose() + + main_content = None + for selector in ['main', 'article', '[role="main"]', '.content', '#content', '.post-content']: + main_content = soup.select_one(selector) + if main_content: + break + + if not main_content: + main_content = soup.body if soup.body else soup + + text = main_content.get_text(separator='\n', strip=True) + lines = [line.strip() for line in text.split('\n') if line.strip()] + text = '\n'.join(lines) + + if len(text) > request.max_length: + text = text[:request.max_length] + f"\n\n[...] {len(text) - request.max_length} caractères supplémentaires" + + links = [] + for a in soup.find_all('a', href=True): + href = a['href'] + if href.startswith('http'): + links.append(href) + links = links[:20] + + return ScrapeResponse( + url=request.url, + title=title, + content=text, + links=links, + success=True + ) + + except Exception as e: + return ScrapeResponse( + url=request.url, + title="Error", + content="", + links=[], + success=False, + error=str(e) + ) + +@app.post("/scrape/sync") +def scrape_sync(url: str, max_length: int = 10000): + request = ScrapeRequest(url=url, max_length=max_length) + result = scrape(request) + if not result.success: + raise HTTPException(status_code=400, detail=result.error) + return { + "url": result.url, + "html": result.content, + "markdown": result.content, + "success": True + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=11235) +EOF + +echo "✅ Fichiers créés" +echo "" + +# Construire et démarrer +echo "🔨 Construction de l'image..." +docker build -t effi-web-scraper . + +echo "" +echo "🚀 Démarrage du service..." +docker run -d \ + --name effi-web-scraper \ + --restart unless-stopped \ + -p 11235:11235 \ + --network effi-net \ + effi-web-scraper + +echo "" +echo "⏳ Attente du démarrage (15s)..." +sleep 15 + +# Vérification +echo "" +echo "🔍 Vérification..." +if curl -s http://localhost:11235/health > /dev/null 2>&1; then + echo "✅ Web Scraper est démarré !" + curl http://localhost:11235/health +else + echo "⚠️ Le service ne répond pas encore" + docker logs effi-web-scraper +fi + +echo "" +echo "================================" +echo "Installation terminée !" +echo "================================" +echo "" +echo "📋 Configuration OpenWebUI:" +echo " Admin → Tools → Web Search and Crawl → ⚙️" +echo " Crawl4AI Base URL: http://localhost:11235" +echo " Timeout: 30000" +echo "" diff --git a/effi-crawl4ai/install-on-effiserver.sh b/effi-crawl4ai/install-on-effiserver.sh new file mode 100644 index 0000000..3a73e50 --- /dev/null +++ b/effi-crawl4ai/install-on-effiserver.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Script d'installation de Crawl4AI sur effi-server +# À copier et exécuter sur effi-server (192.168.1.27) + +set -e + +echo "================================" +echo "Installation Crawl4AI (effi-server)" +echo "================================" +echo "" + +# 1. Créer le répertoire +INSTALL_DIR="$HOME/effi-crawl4ai" +mkdir -p "$INSTALL_DIR" +cd "$INSTALL_DIR" + +echo "📁 Répertoire créé : $INSTALL_DIR" + +# 2. Créer le docker-compose.yml +cat > docker-compose.yml << 'EOF' +# Service Crawl4AI pour Effi (effi-server) +# Version CPU - plus légère + +services: + crawl4ai: + image: unclecode/crawl4ai:basic + container_name: effi-crawl4ai + ports: + - "11235:11235" + environment: + - CRAWL4AI_API_TOKEN=${CRAWL4AI_API_TOKEN:-} + - MAX_CONCURRENT_TASKS=3 + - CRAWL4AI_ENABLE_GPU=false + volumes: + - /dev/shm:/dev/shm + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 512M + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11235/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s +EOF + +echo "📝 docker-compose.yml créé" + +# 3. Démarrer le service +echo "" +echo "🚀 Démarrage de Crawl4AI..." +docker-compose up -d + +# 4. Attendre le démarrage +echo "" +echo "⏳ Attente du démarrage (30s)..." +sleep 30 + +# 5. Vérification +echo "" +echo "🔍 Vérification..." +if curl -s http://localhost:11235/health > /dev/null 2>&1; then + echo "✅ Crawl4AI est démarré et accessible sur http://localhost:11235" +else + echo "⚠️ Le service ne répond pas encore. Vérifier les logs :" + echo " docker logs effi-crawl4ai" +fi + +echo "" +echo "================================" +echo "Installation terminée !" +echo "================================" +echo "" +echo "📋 Prochaines étapes :" +echo " 1. Dans OpenWebUI → Admin → Tools → Web Search and Crawl" +echo " 2. Configurer la valve 'Crawl4AI Base URL' : http://localhost:11235" +echo " 3. Activer le Tool" +echo "" diff --git a/effi-crawl4ai/remove-from-effi.sh b/effi-crawl4ai/remove-from-effi.sh new file mode 100644 index 0000000..f76abef --- /dev/null +++ b/effi-crawl4ai/remove-from-effi.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# Script de suppression de Crawl4AI local sur effi-server +# À exécuter sur effi-server dans /root/effi/ + +echo "================================" +echo "Suppression de Crawl4AI local" +echo "================================" +echo "" + +# Vérifier qu'on est dans le bon répertoire +if [ ! -f "docker-compose.yml" ]; then + echo "❌ Erreur: docker-compose.yml non trouvé" + echo " Ce script doit être exécuté dans /root/effi/" + exit 1 +fi + +echo "🛑 Arrêt du conteneur crawl4ai..." +docker-compose stop crawl4ai 2>/dev/null || true +docker-compose rm -f crawl4ai 2>/dev/null || true + +echo "🗑️ Suppression de l'image..." +docker rmi unclecode/crawl4ai:basic 2>/dev/null || true + +echo "" +echo "🔧 Suppression du service du docker-compose.yml..." + +# Créer une version sans le service crawl4ai +awk ' +/^ # ─────────────────────────────────────────────────────────/ && printed_service == 0 { + getline + if ($0 ~ /8\. CRAWL4AI/) { + # On est sur le service crawl4ai, on skip jusqu'au prochain service + while (getline && !(/^ # ─────────────────────────────────────────────────────────/ || /^networks:/)) {} + # On a atteint la fin du service, on continue normalement + printed_service = 1 + if (/^networks:/) { + print + next + } + } + # Ce n'est pas crawl4ai, on affiche + print prev + print + next +} +{ print } +' docker-compose.yml > /tmp/docker-compose-clean.yml + +# Remplacer +mv /tmp/docker-compose-clean.yml docker-compose.yml + +echo "✅ Service crawl4ai supprimé du docker-compose.yml" +echo "" +echo "📋 Prochaines étapes :" +echo " 1. Crawl4AI tournera sur folzo-Corp (192.168.1.11:11235)" +echo " 2. Dans OpenWebUI, configurer le Base URL :" +echo " http://192.168.1.11:11235" +echo "" diff --git a/effi-crawl4ai/scraper.py b/effi-crawl4ai/scraper.py new file mode 100644 index 0000000..f482a05 --- /dev/null +++ b/effi-crawl4ai/scraper.py @@ -0,0 +1,115 @@ +""" +Service de scraping web simple pour OpenWebUI +Alternative à Crawl4AI compatible x86_64 +""" + +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from typing import Optional, List +import requests +from bs4 import BeautifulSoup +import re + +app = FastAPI(title="Effi Web Scraper", version="1.0.0") + +class ScrapeRequest(BaseModel): + url: str + max_length: Optional[int] = 10000 + extract_markdown: Optional[bool] = True + +class ScrapeResponse(BaseModel): + url: str + title: str + content: str + links: List[str] + success: bool + error: Optional[str] = None + +@app.get("/health") +def health(): + return {"status": "healthy"} + +@app.post("/scrape", response_model=ScrapeResponse) +def scrape(request: ScrapeRequest): + try: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + } + + response = requests.get(request.url, headers=headers, timeout=30) + response.raise_for_status() + + soup = BeautifulSoup(response.content, 'html.parser') + + # Titre + title = soup.title.string if soup.title else "No title" + + # Supprimer scripts et styles + for script in soup(["script", "style"]): + script.decompose() + + # Extraction du texte principal + # Essayer de trouver le contenu principal + main_content = None + for selector in ['main', 'article', '[role="main"]', '.content', '#content', '.post-content']: + main_content = soup.select_one(selector) + if main_content: + break + + if not main_content: + main_content = soup.body if soup.body else soup + + # Extraire le texte + text = main_content.get_text(separator='\n', strip=True) + + # Nettoyer + lines = [line.strip() for line in text.split('\n') if line.strip()] + text = '\n'.join(lines) + + # Limiter la taille + if len(text) > request.max_length: + text = text[:request.max_length] + f"\n\n[...] {len(text) - request.max_length} caractères supplémentaires" + + # Extraire les liens + links = [] + for a in soup.find_all('a', href=True): + href = a['href'] + if href.startswith('http'): + links.append(href) + links = links[:20] # Limiter à 20 liens + + return ScrapeResponse( + url=request.url, + title=title, + content=text, + links=links, + success=True + ) + + except Exception as e: + return ScrapeResponse( + url=request.url, + title="Error", + content="", + links=[], + success=False, + error=str(e) + ) + +@app.post("/scrape/sync") +def scrape_sync(url: str, max_length: int = 10000): + """Endpoint compatible avec l'API Crawl4AI""" + request = ScrapeRequest(url=url, max_length=max_length) + result = scrape(request) + if not result.success: + raise HTTPException(status_code=400, detail=result.error) + return { + "url": result.url, + "html": result.content, + "markdown": result.content, + "success": True + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=11235) diff --git a/effi-crawl4ai/update-effiserver.sh b/effi-crawl4ai/update-effiserver.sh new file mode 100644 index 0000000..d2bf03b --- /dev/null +++ b/effi-crawl4ai/update-effiserver.sh @@ -0,0 +1,215 @@ +#!/bin/bash +# Script de mise à jour du Web Scraper sur effi-server +# À exécuter dans /root/effi/ + +set -e + +echo "================================" +echo "Mise à jour Web Scraper" +echo "================================" +echo "" + +# 1. Nettoyer l'ancien conteneur standalone +echo "🧹 Nettoyage de l'ancien conteneur..." +docker stop effi-web-scraper 2>/dev/null || true +docker rm effi-web-scraper 2>/dev/null || true + +# 2. Nettoyer le réseau si créé +echo "🗑️ Suppression du réseau effi-net temporaire..." +docker network rm effi-net 2>/dev/null || true + +# 3. Supprimer l'ancienne section crawl4ai du docker-compose +echo "📝 Mise à jour du docker-compose.yml..." + +# Créer le service web-scraper à insérer +cat > /tmp/web-scraper-service.txt << 'EOF' + # ───────────────────────────────────────────────────────── + # 8. WEB SCRAPER - Scraping pour OpenWebUI + # → http://localhost:11235 + # ───────────────────────────────────────────────────────── + web-scraper: + build: ./web-scraper + container_name: effi-web-scraper + restart: unless-stopped + ports: + - "11235:11235" + networks: + - effi-net + +EOF + +# Backup du docker-compose +cp docker-compose.yml docker-compose.yml.backup.$(date +%Y%m%d_%H%M%S) + +# Utiliser awk pour remplacer la section 8 (crawl4ai) par web-scraper +awk ' +/^networks:/ { + # On est à la fin du fichier services, inserer le nouveau service avant + system("cat /tmp/web-scraper-service.txt") +} +{ print } +' docker-compose.yml > /tmp/docker-compose-new.yml + +mv /tmp/docker-compose-new.yml docker-compose.yml + +echo "✅ Section #8 mise à jour (crawl4ai → web-scraper)" + +# 4. Créer le répertoire et les fichiers du web-scraper +echo "" +echo "📁 Création du répertoire web-scraper..." +mkdir -p web-scraper + +cat > web-scraper/Dockerfile << 'EOF' +FROM python:3.11-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir fastapi uvicorn requests beautifulsoup4 + +COPY scraper.py . + +EXPOSE 11235 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:11235/health || exit 1 + +CMD ["uvicorn", "scraper:app", "--host", "0.0.0.0", "--port", "11235"] +EOF + +cat > web-scraper/scraper.py << 'EOF' +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from typing import Optional, List +import requests +from bs4 import BeautifulSoup + +app = FastAPI(title="Effi Web Scraper", version="1.0.0") + +class ScrapeRequest(BaseModel): + url: str + max_length: Optional[int] = 10000 + +class ScrapeResponse(BaseModel): + url: str + title: str + content: str + links: List[str] + success: bool + error: Optional[str] = None + +@app.get("/health") +def health(): + return {"status": "healthy"} + +@app.post("/scrape", response_model=ScrapeResponse) +def scrape(request: ScrapeRequest): + try: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + + response = requests.get(request.url, headers=headers, timeout=30) + response.raise_for_status() + + soup = BeautifulSoup(response.content, 'html.parser') + + title = soup.title.string if soup.title else "No title" + + for script in soup(["script", "style"]): + script.decompose() + + main_content = None + for selector in ['main', 'article', '[role="main"]', '.content', '#content', '.post-content']: + main_content = soup.select_one(selector) + if main_content: + break + + if not main_content: + main_content = soup.body if soup.body else soup + + text = main_content.get_text(separator='\n', strip=True) + lines = [line.strip() for line in text.split('\n') if line.strip()] + text = '\n'.join(lines) + + if len(text) > request.max_length: + text = text[:request.max_length] + f"\n\n[...] {len(text) - request.max_length} caractères supplémentaires" + + links = [] + for a in soup.find_all('a', href=True): + href = a['href'] + if href.startswith('http'): + links.append(href) + links = links[:20] + + return ScrapeResponse( + url=request.url, + title=title, + content=text, + links=links, + success=True + ) + + except Exception as e: + return ScrapeResponse( + url=request.url, + title="Error", + content="", + links=[], + success=False, + error=str(e) + ) + +@app.post("/scrape/sync") +def scrape_sync(url: str, max_length: int = 10000): + request = ScrapeRequest(url=url, max_length=max_length) + result = scrape(request) + if not result.success: + raise HTTPException(status_code=400, detail=result.error) + return { + "url": result.url, + "html": result.content, + "markdown": result.content, + "success": True + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=11235) +EOF + +echo "✅ Fichiers créés dans web-scraper/" + +# 5. Relancer docker-compose +echo "" +echo "🚀 Démarrage du service web-scraper..." +docker-compose up -d web-scraper + +echo "" +echo "⏳ Attente du démarrage (30s)..." +sleep 30 + +# 6. Vérification +echo "" +echo "🔍 Vérification..." +if curl -s http://localhost:11235/health > /dev/null 2>&1; then + echo "✅ Web Scraper est fonctionnel !" + curl http://localhost:11235/health +else + echo "⚠️ Le service ne répond pas encore" + docker logs effi-web-scraper +fi + +echo "" +echo "================================" +echo "Installation terminée !" +echo "================================" +echo "" +echo "📋 Configuration OpenWebUI:" +echo " Admin → Tools → Web Search and Crawl → ⚙️" +echo " Crawl4AI Base URL: http://localhost:11235" +echo " Timeout: 30000" +echo " ✅ Activer le Tool" diff --git a/effi-ocr/Dockerfile b/effi-ocr/Dockerfile new file mode 100644 index 0000000..6085dc3 --- /dev/null +++ b/effi-ocr/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.11-slim +# Installer Tesseract et les langues +RUN apt-get update && apt-get install -y \ + tesseract-ocr \ + tesseract-ocr-fra \ + tesseract-ocr-eng \ + libtesseract-dev \ + && rm -rf /var/lib/apt/lists/* +# Créer le répertoire de travail +WORKDIR /app +# Copier les dépendances +COPY requirements.txt . +# Installer les dépendances Python +RUN pip install --no-cache-dir -r requirements.txt +# Copier le code +COPY main.py . +# Exposer le port +EXPOSE 8300 +# Lancer l'application +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8300"] diff --git a/effi-ocr/README.md b/effi-ocr/README.md new file mode 100644 index 0000000..aafc8a4 --- /dev/null +++ b/effi-ocr/README.md @@ -0,0 +1,232 @@ +# 📝 Effi-OCR + +Service OCR (Optical Character Recognition) pour Effi. Extrait le texte des images et photos de documents. + +--- + +## 🎯 Fonctionnalités + +- Extraction de texte à partir d'images (JPEG, PNG, GIF, BMP, TIFF, WebP) +- Support multilingue (Français, Anglais, Allemand, Espagnol, Italien) +- Prétraitement automatique pour améliorer la qualité +- API REST simple et rapide +- Intégration OpenWebUI via Tool + +--- + +## 🚀 Démarrage rapide + +```bash +# Construire et démarrer +cd ~/Documents/OpenCode/Projets/Effi/effi-ocr +sudo docker compose up -d --build + +# Vérifier le service +curl http://localhost:8300/health +``` + +--- + +## 📡 API Endpoints + +### Health Check +```bash +GET /health +``` + +### OCR - Upload fichier +```bash +POST /ocr +Content-Type: multipart/form-data + +Parameters: + - file: Image à analyser + - lang: Code langue (fra, eng, fra+eng, etc.) + - preprocess: true/false (prétraitement auto) +``` + +Exemple: +```bash +curl -X POST -F "file=@photo.jpg" -F "lang=fra" http://localhost:8300/ocr +``` + +### OCR - Base64 +```bash +POST /ocr/base64 +Content-Type: application/json + +{ + "image_base64": "...", + "lang": "fra", + "preprocess": true +} +``` + +### Langues disponibles +```bash +GET /languages +``` + +--- + +## 🔧 Configuration + +### Variables d'environnement + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `OCR_URL` | `http://192.168.1.11:8300` | URL du service OCR | + +### Langues supportées + +- `fra` - Français +- `eng` - Anglais +- `fra+eng` - Bilingue FR+EN +- `deu` - Allemand +- `spa` - Espagnol +- `ita` - Italien + +--- + +## 🐳 Docker + +### Construction +```bash +sudo docker compose build +``` + +### Démarrage +```bash +sudo docker compose up -d +``` + +### Logs +```bash +sudo docker logs effi-ocr -f +``` + +### Arrêt +```bash +sudo docker compose down +``` + +--- + +## 🔗 Intégration OpenWebUI + +Voir [TOOL-OPENWEBUI.md](TOOL-OPENWEBUI.md) pour la configuration complète du Tool. + +**Résumé rapide:** +1. Admin → Tools → Create Tool +2. Copier le code Python fourni +3. Configurer la variable `OCR_URL` +4. Activer le Tool dans les paramètres de conversation + +--- + +## 🧪 Tests + +### Test de santé +```bash +curl http://localhost:8300/health +``` + +### Test OCR simple +```bash +curl -X POST \ + -F "file=@/chemin/vers/image.jpg" \ + -F "lang=fra" \ + http://localhost:8300/ocr +``` + +### Test avec prétraitement désactivé +```bash +curl -X POST \ + -F "file=@image.png" \ + -F "lang=eng" \ + -F "preprocess=false" \ + http://localhost:8300/ocr +``` + +--- + +## 📊 Performance + +- **Temps de traitement** : ~1-3 secondes par image (dépend de la taille) +- **Formats supportés** : JPEG, PNG, GIF, BMP, TIFF, WebP +- **Taille max recommandée** : 2048x2048 pixels +- **Prétraitement** : Conversion niveaux de gris + contraste (optionnel) + +--- + +## 🐛 Dépannage + +### Service ne répond pas +```bash +# Vérifier si le conteneur tourne +sudo docker ps | grep effi-ocr + +# Redémarrer +sudo docker restart effi-ocr + +# Voir les logs +sudo docker logs effi-ocr +``` + +### Tesseract non trouvé +L'erreur indique que Tesseract n'est pas installé dans le conteneur. Reconstruire: +```bash +sudo docker compose down +sudo docker compose up -d --build +``` + +### OCR de mauvaise qualité +- Utiliser `lang=fra+eng` pour les documents mixtes +- S'assurer que l'image est bien éclairée +- Vérifier la résolution (300 DPI recommandé pour documents) +- Activer le prétraitement (par défaut: true) + +--- + +## 📁 Structure + +``` +effi-ocr/ +├── main.py # API FastAPI +├── Dockerfile # Configuration Docker +├── docker-compose.yml # Orchestration +├── requirements.txt # Dépendances Python +├── README.md # Ce fichier +└── TOOL-OPENWEBUI.md # Guide Tool OpenWebUI +``` + +--- + +## 📝 Dépendances + +- Python 3.11 +- FastAPI +- Pillow (traitement d'images) +- pytesseract (wrapper Tesseract) +- Tesseract OCR 5.x + +--- + +## 🎨 Cas d'usage + +- **Numérisation de livres** : Extraire le texte de photos de pages +- **Factures** : Récupérer les informations de factures scannées +- **Documents** : OCR sur PDF et images de documents +- **Notes manuscrites** : Reconnaissance de texte manuscrit (limitée) + +--- + +## 🔒 Sécurité + +- Le service écoute uniquement sur localhost (127.0.0.1:8300) +- Aucune donnée n'est stockée persistante +- Les images sont traitées en mémoire et supprimées après + +--- + +Créé le 6 Avril 2026 diff --git a/effi-ocr/TOOL-OPENWEBUI.md b/effi-ocr/TOOL-OPENWEBUI.md new file mode 100644 index 0000000..e379894 --- /dev/null +++ b/effi-ocr/TOOL-OPENWEBUI.md @@ -0,0 +1,175 @@ +# Tool OCR pour OpenWebUI + +## Configuration + +Dans OpenWebUI (http://192.168.1.27:3000) : + +1. **Aller dans** : Admin → Tools → Create Tool +2. **Nom** : OCR Extracteur de Texte +3. **ID** : ocr_extractor +4. **Description** : Extrait le texte des images pour les livres et documents + +## Code du Tool + +```python +from typing import Optional +import requests +import base64 +import os + +class Tools: + def __init__(self): + """Initialize OCR Tool""" + self.ocr_url = os.getenv("OCR_URL", "http://192.168.1.11:8300") + + def extract_text_from_image(self, image_path: str, language: str = "fra") -> str: + """ + Extract text from an image file using OCR + + Args: + image_path: Path to the image file (jpg, png, etc.) + language: Language code (fra, eng, fra+eng, etc.) + + Returns: + Extracted text from the image + """ + try: + # Read image file + with open(image_path, 'rb') as f: + files = {'file': f} + data = {'lang': language} + + response = requests.post( + f"{self.ocr_url}/ocr", + files=files, + data=data, + timeout=30 + ) + + 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) + return f"Texte extrait ({char_count} caractères, {word_count} mots):\n\n{text}" + else: + return f"Erreur OCR: {result.get('error', 'Unknown error')}" + else: + return f"Erreur HTTP {response.status_code}: {response.text}" + + except FileNotFoundError: + return f"Erreur: Fichier non trouvé: {image_path}" + except Exception as e: + return f"Erreur lors de l'OCR: {str(e)}" + + def extract_text_from_base64(self, image_base64: str, language: str = "fra") -> str: + """ + Extract text from a base64 encoded image + + Args: + image_base64: Base64 encoded image string + language: Language code (fra, eng, fra+eng, etc.) + + Returns: + Extracted text from the image + """ + try: + response = requests.post( + f"{self.ocr_url}/ocr/base64", + json={ + 'image_base64': image_base64, + 'lang': language, + 'preprocess': True + }, + timeout=30 + ) + + 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) + return f"Texte extrait ({char_count} caractères, {word_count} mots):\n\n{text}" + else: + return f"Erreur OCR: extraction échouée" + else: + return f"Erreur HTTP {response.status_code}: {response.text}" + + except Exception as e: + return f"Erreur lors de l'OCR: {str(e)}" + + def get_available_languages(self) -> str: + """ + Get list of available OCR languages + + Returns: + List of available language codes + """ + try: + response = requests.get(f"{self.ocr_url}/languages", timeout=10) + + if response.status_code == 200: + result = response.json() + languages = result.get('available_languages', []) + return f"Langues disponibles: {', '.join(languages)}" + else: + return f"Erreur: {response.status_code}" + + except Exception as e: + return f"Erreur: {str(e)}" +``` + +## Variables d'environnement + +Dans les Settings d'OpenWebUI (Admin → Settings → Environment Variables) : + +``` +OCR_URL=http://192.168.1.11:8300 +``` + +## Utilisation + +Une fois le Tool créé, les agents pourront l'utiliser avec ces fonctions : + +- `extract_text_from_image("/chemin/vers/image.jpg", "fra")` - Extraire le texte d'un fichier image +- `extract_text_from_base64("base64encoded...", "fra")` - Extraire d'une image encodée en base64 +- `get_available_languages()` - Lister les langues disponibles + +## Exemple de prompt + +``` +"Effi, lis-moi ce texte" [upload d'image] +→ L'agent utilise automatiquement le Tool OCR +``` + +## Langues disponibles + +- `fra` - Français +- `eng` - Anglais +- `fra+eng` - Français + Anglais (bilingue) +- `deu` - Allemand +- `spa` - Espagnol +- `ita` - Italien + +## Dépannage + +**Erreur de connexion** : +```bash +# Vérifier que le service OCR tourne +curl http://192.168.1.11:8300/health + +# Redémarrer si nécessaire +cd ~/Documents/OpenCode/Projets/Effi/effi-ocr +sudo docker compose restart +``` + +**Texte mal extrait** : +- Essayer `fra+eng` pour les documents bilingues +- Vérifier la qualité de l'image (résolution, contraste) +- L'OCR fonctionne mieux avec des images bien éclairées + +--- + +*Tool créé le 6 Avril 2026* diff --git a/effi-ocr/docker-compose.yml b/effi-ocr/docker-compose.yml new file mode 100644 index 0000000..432cb98 --- /dev/null +++ b/effi-ocr/docker-compose.yml @@ -0,0 +1,22 @@ +services: + effi-ocr: + build: . + container_name: effi-ocr + ports: + - "8300:8300" + restart: unless-stopped + environment: + - PYTHONUNBUFFERED=1 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8300/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + networks: + - effi-network + +networks: + effi-network: + external: true + name: effi-network diff --git a/effi-ocr/main.py b/effi-ocr/main.py new file mode 100644 index 0000000..aefba3d --- /dev/null +++ b/effi-ocr/main.py @@ -0,0 +1,208 @@ +""" +Effi-OCR : Service OCR pour Effi +API FastAPI qui expose Tesseract via une interface REST +""" + +from fastapi import FastAPI, File, UploadFile, HTTPException +from fastapi.responses import JSONResponse +from PIL import Image +import pytesseract +import io +import logging +from typing import Optional + +# Configuration logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +app = FastAPI( + title="Effi-OCR", + description="Service OCR pour extraction de texte d'images", + version="1.0.0" +) + + +@app.get("/health") +async def health_check(): + """Endpoint de santé pour monitoring""" + try: + # Vérifier que Tesseract est accessible + import subprocess + result = subprocess.run(['tesseract', '--version'], capture_output=True, text=True) + version_line = result.stdout.split('\n')[0] if result.stdout else "unknown" + tesseract_version = str(version_line) # Forcer en string + except Exception as e: + tesseract_version = f"error: {str(e)}" + + return { + "status": "healthy", + "service": "effi-ocr", + "version": "1.0.0", + "tesseract_version": tesseract_version + } + + +@app.post("/ocr") +async def ocr_image( + file: UploadFile = File(...), + lang: str = "fra", + preprocess: bool = True +): + """ + Extraire le texte d'une image + + Args: + file: Image à analyser (jpg, png, etc.) + lang: Code langue (fra, eng, fra+eng, etc.) + preprocess: Appliquer un prétraitement pour améliorer la qualité + + Returns: + JSON avec le texte extrait et les métadonnées + """ + logger.info(f"OCR demandé - lang: {lang}, fichier: {file.filename}") + + # Vérifier le type de fichier + allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff', 'image/webp'] + if file.content_type not in allowed_types: + raise HTTPException( + status_code=400, + detail=f"Type de fichier non supporté: {file.content_type}. Types acceptés: {', '.join(allowed_types)}" + ) + + try: + # Lire l'image + contents = await file.read() + image = Image.open(io.BytesIO(contents)) + + # Convertir en RGB si nécessaire (pour les images avec transparence) + if image.mode in ('RGBA', 'LA', 'P'): + image = image.convert('RGB') + + # Prétraitement optionnel pour améliorer la qualité OCR + if preprocess: + # Convertir en niveaux de gris pour meilleure détection texte + image = image.convert('L') + # Augmenter le contraste + from PIL import ImageEnhance + enhancer = ImageEnhance.Contrast(image) + image = enhancer.enhance(2.0) + + # OCR avec Tesseract + text = pytesseract.image_to_string(image, lang=lang) + + # Nettoyer le texte + text = text.strip() + + # Informations sur l'image + width, height = image.size + + logger.info(f"OCR terminé - {len(text)} caractères extraits") + + return JSONResponse(content={ + "success": True, + "text": text, + "language": lang, + "filename": file.filename, + "image_size": {"width": width, "height": height}, + "characters_count": len(text), + "words_count": len(text.split()) if text else 0 + }) + + except Exception as e: + logger.error(f"Erreur OCR: {str(e)}") + raise HTTPException(status_code=500, detail=f"Erreur lors de l'OCR: {str(e)}") + + +@app.post("/ocr/base64") +async def ocr_base64( + image_base64: str, + lang: str = "fra", + preprocess: bool = True +): + """ + Extraire le texte d'une image en base64 + + Args: + image_base64: Image encodée en base64 + lang: Code langue (fra, eng, fra+eng, etc.) + preprocess: Appliquer un prétraitement + + Returns: + JSON avec le texte extrait + """ + import base64 + + logger.info(f"OCR base64 demandé - lang: {lang}") + + try: + # Décoder le base64 + image_data = base64.b64decode(image_base64) + image = Image.open(io.BytesIO(image_data)) + + # Convertir en RGB si nécessaire + if image.mode in ('RGBA', 'LA', 'P'): + image = image.convert('RGB') + + # Prétraitement + if preprocess: + image = image.convert('L') + from PIL import ImageEnhance + enhancer = ImageEnhance.Contrast(image) + image = enhancer.enhance(2.0) + + # OCR + text = pytesseract.image_to_string(image, lang=lang) + text = text.strip() + + width, height = image.size + + logger.info(f"OCR base64 terminé - {len(text)} caractères") + + return { + "success": True, + "text": text, + "language": lang, + "image_size": {"width": width, "height": height}, + "characters_count": len(text), + "words_count": len(text.split()) if text else 0 + } + + except Exception as e: + logger.error(f"Erreur OCR base64: {str(e)}") + raise HTTPException(status_code=500, detail=f"Erreur lors de l'OCR: {str(e)}") + + +@app.get("/languages") +async def list_languages(): + """Lister les langues disponibles pour l'OCR""" + try: + # Commande tesseract pour lister les langues + import subprocess + result = subprocess.run( + ['tesseract', '--list-langs'], + capture_output=True, + text=True + ) + + # Parser la sortie + lines = result.stdout.strip().split('\n') + # La première ligne est "List of available languages (xxx):" + languages = lines[1:] if len(lines) > 1 else [] + + return { + "available_languages": languages, + "count": len(languages) + } + except Exception as e: + logger.error(f"Erreur listage langues: {str(e)}") + # Fallback avec les langues communes + return { + "available_languages": ["fra", "eng", "fra+eng", "deu", "spa", "ita"], + "count": 6, + "note": "Liste par défaut, erreur lors de la récupération" + } + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8300) diff --git a/effi-ocr/requirements.txt b/effi-ocr/requirements.txt new file mode 100644 index 0000000..9a579d7 --- /dev/null +++ b/effi-ocr/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.109.0 +uvicorn[standard]==0.27.0 +python-multipart==0.0.6 +pillow==10.2.0 +pytesseract==0.3.10 diff --git a/scripts/start-all-services.sh b/scripts/start-all-services.sh index f00a53f..ccb00f8 100755 --- a/scripts/start-all-services.sh +++ b/scripts/start-all-services.sh @@ -44,6 +44,8 @@ echo "" start_service "Ollama" "$HOME" "docker-compose.yml" start_service "ComfyUI" "$HOME/Documents/OpenCode/Projets/Effi/comfyui" start_service "Effi-STT" "$HOME/Documents/OpenCode/Projets/Effi/effi-stt" +start_service "Effi-OCR" "$HOME/Documents/OpenCode/Projets/Effi/effi-ocr" +start_service "Effi-Crawl4AI" "$HOME/Documents/OpenCode/Projets/Effi/effi-crawl4ai" start_service "openedai-speech" "$HOME/openedai-speech" echo "" @@ -56,7 +58,7 @@ sleep 5 # Vérifier que tout répond services_ok=0 -services_total=4 +services_total=6 echo -n "Ollama (11434)... " if curl -s http://localhost:11434 > /dev/null 2>&1; then @@ -82,6 +84,22 @@ else echo -e "${RED}✗${NC}" fi +echo -n "Effi-OCR (8300)... " +if curl -s http://localhost:8300/health > /dev/null 2>&1; then + echo -e "${GREEN}✓${NC}" + ((services_ok++)) +else + echo -e "${RED}✗${NC}" +fi + +echo -n "Effi-Crawl4AI (11235)... " +if curl -s http://localhost:11235/health > /dev/null 2>&1; then + echo -e "${GREEN}✓${NC}" + ((services_ok++)) +else + echo -e "${RED}✗${NC}" +fi + echo -n "openedai-speech (8100)... " if curl -s http://localhost:8100/health > /dev/null 2>&1; then echo -e "${GREEN}✓${NC}" @@ -103,10 +121,11 @@ if [ $services_ok -eq $services_total ]; then else echo -e "${RED}Certains services n'ont pas démarré.${NC}" echo "" - echo "Pour voir les logs :" - echo " sudo docker logs ollama" - echo " sudo docker logs comfyui" - echo " sudo docker logs effi-stt" - echo " sudo docker logs openedai-speech" +echo "Pour voir les logs :" +echo " sudo docker logs ollama" +echo " sudo docker logs comfyui" +echo " sudo docker logs effi-stt" +echo " sudo docker logs effi-ocr" +echo " sudo docker logs openedai-speech" exit 1 fi