refactor: nettoyage agent + fix SomEngine review (singleton partagé, cache, thread-safe)
Nettoyage Windows agent : - Suppression lea_ui inutilisés (chat_widget, overlay, styles, etc. — -1991 lignes) - Suppression window_info*.py dupliqués (racine + core/ — -494 lignes) - build/ + dist/ supprimés (48 MB PyInstaller abandonné, gitignorés) Fix SomEngine (review quality guardian) : - Singleton GPU partagé via get_shared_engine() (1 instance au lieu de 2) - Thread-safe avec threading.Lock (double-checked locking) - Cache SomResult par screenshot_id (max 50, évite YOLO+OCR redondants) - Fuite fichier temp docTR corrigée (finally block) - Chemin YOLO configurable via SOM_YOLO_WEIGHTS env var - Guard som_image None avant VLM - Match texte partiel : len(label) >= 3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ from __future__ import annotations
|
||||
import base64
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
@@ -33,8 +34,10 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Chemin vers les poids YOLO d'OmniParser
|
||||
_YOLO_WEIGHTS = Path("/home/dom/ai/OmniParser/weights/icon_detect/model.pt")
|
||||
# Chemin vers les poids YOLO d'OmniParser (configurable via env)
|
||||
_YOLO_WEIGHTS = Path(
|
||||
os.environ.get("SOM_YOLO_WEIGHTS", "/home/dom/ai/OmniParser/weights/icon_detect/model.pt")
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -165,17 +168,17 @@ class SomEngine:
|
||||
# ── 2. docTR : OCR pour lire le texte ──
|
||||
if self._ocr is not None:
|
||||
try:
|
||||
import numpy as np
|
||||
from doctr.io import DocumentFile
|
||||
# Convertir PIL → fichier temporaire pour docTR
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
|
||||
screenshot.save(tmp, format="JPEG", quality=90)
|
||||
tmp_path = tmp.name
|
||||
doc = DocumentFile.from_images([tmp_path])
|
||||
import os
|
||||
os.unlink(tmp_path)
|
||||
result_ocr = self._ocr(doc)
|
||||
try:
|
||||
doc = DocumentFile.from_images([tmp_path])
|
||||
result_ocr = self._ocr(doc)
|
||||
finally:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
for page in result_ocr.pages:
|
||||
for block in page.blocks:
|
||||
@@ -288,3 +291,25 @@ class SomEngine:
|
||||
buf = io.BytesIO()
|
||||
image.save(buf, format="JPEG", quality=quality)
|
||||
return base64.b64encode(buf.getvalue()).decode()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Singleton partagé (lazy-loaded, thread-safe)
|
||||
# ---------------------------------------------------------------------------
|
||||
_shared_engine: Optional[SomEngine] = None
|
||||
_shared_lock = __import__("threading").Lock()
|
||||
|
||||
|
||||
def get_shared_engine(device: str = "cuda") -> Optional[SomEngine]:
|
||||
"""Singleton SomEngine partagé entre tous les modules."""
|
||||
global _shared_engine
|
||||
if _shared_engine is None:
|
||||
with _shared_lock:
|
||||
if _shared_engine is None:
|
||||
try:
|
||||
_shared_engine = SomEngine(device=device)
|
||||
logger.info("SomEngine singleton partagé initialisé")
|
||||
except Exception as e:
|
||||
logger.warning("SomEngine non disponible : %s", e)
|
||||
return None
|
||||
return _shared_engine
|
||||
|
||||
Reference in New Issue
Block a user