Ajout Effi-OCR (Tesseract) + Web Scraper pour OpenWebUI

- 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)
This commit is contained in:
Folzo
2026-04-06 16:34:27 +02:00
parent 5f1e19ff91
commit 93f31cdaeb
25 changed files with 2469 additions and 6 deletions
+175
View File
@@ -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*
+111
View File
@@ -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*
+188
View File
@@ -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'<script[^>]*>[^<]*</script>', '', html, flags=re.IGNORECASE | re.DOTALL)
text = re.sub(r'<style[^>]*>[^<]*</style>', '', 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('&amp;', '&')
text = text.replace('&lt;', '<')
text = text.replace('&gt;', '>')
text = text.replace('&quot;', '"')
text = text.replace('&#39;', "'")
text = text.replace('&nbsp;', ' ')
# 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'<title[^>]*>([^<]*)</title>', 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'<article[^>]*>(.*?)</article>',
r'<main[^>]*>(.*?)</main>',
r'<div[^>]*class=["\'][^"\']*(?:content|article|post)[^"\']*["\'][^>]*>(.*?)</div>',
]
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'<body[^>]*>(.*?)</body>', html, re.IGNORECASE | re.DOTALL)
content = body_match.group(1) if body_match else html
# Nettoyer le contenu
content = re.sub(r'<script[^>]*>[^<]*</script>', '', content, flags=re.IGNORECASE | re.DOTALL)
content = re.sub(r'<style[^>]*>[^<]*</style>', '', 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('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>')
content = content.replace('&quot;', '"').replace('&#39;', "'")
# 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*
+34
View File
@@ -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"]
+20
View File
@@ -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"]
+115
View File
@@ -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*
+128
View File
@@ -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*
+111
View File
@@ -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`.
+107
View File
@@ -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 "================================"
+30
View File
@@ -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
+34
View File
@@ -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
+25
View File
@@ -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
+44
View File
@@ -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
+190
View File
@@ -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 ""
+82
View File
@@ -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 ""
+58
View File
@@ -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 ""
+115
View File
@@ -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)
+215
View File
@@ -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"
+20
View File
@@ -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"]
+232
View File
@@ -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
+175
View File
@@ -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*
+22
View File
@@ -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
+208
View File
@@ -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)
+5
View File
@@ -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
+25 -6
View File
@@ -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