Files
rpa_vision_v3/agent_rust/src/capture.rs
Dom d5deac3029 feat: replay visuel VLM-first, worker séparé, package Léa, AZERTY, sécurité HTTPS
Pipeline replay visuel :
- VLM-first : l'agent appelle Ollama directement pour trouver les éléments
- Template matching en fallback (seuil strict 0.90)
- Stop immédiat si élément non trouvé (pas de clic blind)
- Replay depuis session brute (/replay-session) sans attendre le VLM
- Vérification post-action (screenshot hash avant/après)
- Gestion des popups (Enter/Escape/Tab+Enter)

Worker VLM séparé :
- run_worker.py : process distinct du serveur HTTP
- Communication par fichiers (_worker_queue.txt + _replay_active.lock)
- Le serveur HTTP ne fait plus jamais de VLM → toujours réactif
- Service systemd rpa-worker.service

Capture clavier :
- raw_keys (vk + press/release) pour replay exact indépendant du layout
- Fix AZERTY : ToUnicodeEx + AltGr detection
- Enter capturé comme \n, Tab comme \t
- Filtrage modificateurs seuls (Ctrl/Alt/Shift parasites)
- Fusion text_input consécutifs, dédup key_combo

Sécurité & Internet :
- HTTPS Let's Encrypt (lea.labs + vwb.labs.laurinebazin.design)
- Token API fixe dans .env.local
- HTTP Basic Auth sur VWB
- Security headers (HSTS, CSP, nosniff)
- CORS domaines publics, plus de wildcard

Infrastructure :
- DPI awareness (SetProcessDpiAwareness) Python + Rust
- Métadonnées système (dpi_scale, window_bounds, monitors, os_theme)
- Template matching multi-scale [0.5, 2.0]
- Résolution dynamique (plus de hardcode 1920x1080)
- VLM prefill fix (47x speedup, 3.5s au lieu de 180s)

Modules :
- core/auth/ : credential vault (Fernet AES), TOTP (RFC 6238), auth handler
- core/federation/ : LearningPack export/import anonymisé, FAISS global
- deploy/ : package Léa (config.txt, Lea.bat, install.bat, LISEZMOI.txt)

UX :
- Filtrage OS (VWB + Chat montrent que les workflows de l'OS courant)
- Bibliothèque persistante (cache local + SQLite)
- Clustering hybride (titre fenêtre + DBSCAN)
- EdgeConstraints + PostConditions peuplés
- GraphBuilder compound actions (toutes les frappes)

Agent Rust :
- Token Bearer auth (network.rs)
- sysinfo.rs (DPI, résolution, window bounds via Win32 API)
- config.txt lu automatiquement
- Support Chrome/Brave/Firefox (pas que Edge)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:19:18 +01:00

116 lines
3.7 KiB
Rust

//! Capture d'écran via xcap.
//!
//! Fournit la capture du moniteur principal, l'encodage JPEG en base64,
//! et un hash perceptuel rapide pour la déduplication des heartbeats.
use base64::Engine;
use image::codecs::jpeg::JpegEncoder;
use image::DynamicImage;
use std::io::Cursor;
/// Capture le moniteur principal et retourne un DynamicImage.
///
/// Utilise xcap pour la capture cross-platform (DXGI sur Windows, X11/Wayland sur Linux).
pub fn capture_screenshot() -> Option<DynamicImage> {
let monitors = match xcap::Monitor::all() {
Ok(m) => m,
Err(e) => {
eprintln!("[CAPTURE] Erreur enumeration moniteurs : {}", e);
return None;
}
};
let primary = monitors
.into_iter()
.find(|m| m.is_primary().unwrap_or(false));
let monitor = match primary {
Some(m) => m,
None => {
eprintln!("[CAPTURE] Aucun moniteur principal trouve");
return None;
}
};
match monitor.capture_image() {
Ok(rgba_image) => Some(DynamicImage::ImageRgba8(rgba_image)),
Err(e) => {
eprintln!("[CAPTURE] Erreur capture ecran : {}", e);
None
}
}
}
/// Encode une image en JPEG et retourne le résultat en base64.
///
/// La qualité doit être entre 1 (mauvaise) et 100 (excellente).
/// 85 est un bon compromis taille/qualité pour le streaming réseau.
pub fn screenshot_to_jpeg_base64(img: &DynamicImage, quality: u8) -> String {
let rgb = img.to_rgb8();
let mut buffer = Cursor::new(Vec::new());
let mut encoder = JpegEncoder::new_with_quality(&mut buffer, quality);
if let Err(e) = encoder.encode(
rgb.as_raw(),
rgb.width(),
rgb.height(),
image::ExtendedColorType::Rgb8,
) {
eprintln!("[CAPTURE] Erreur encodage JPEG : {}", e);
return String::new();
}
base64::engine::general_purpose::STANDARD.encode(buffer.into_inner())
}
/// Encode une image en JPEG et retourne les bytes bruts.
pub fn screenshot_to_jpeg_bytes(img: &DynamicImage, quality: u8) -> Vec<u8> {
let rgb = img.to_rgb8();
let mut buffer = Cursor::new(Vec::new());
let mut encoder = JpegEncoder::new_with_quality(&mut buffer, quality);
if let Err(e) = encoder.encode(
rgb.as_raw(),
rgb.width(),
rgb.height(),
image::ExtendedColorType::Rgb8,
) {
eprintln!("[CAPTURE] Erreur encodage JPEG : {}", e);
return Vec::new();
}
buffer.into_inner()
}
/// Calcule un hash perceptuel rapide pour la déduplication.
///
/// Réduit l'image à 16x16 en niveaux de gris, puis calcule
/// un hash simple basé sur les pixels. Identique à la logique
/// Python (_quick_hash) dans agent_v1.
pub fn image_hash(img: &DynamicImage) -> u64 {
let small = img.resize_exact(16, 16, image::imageops::FilterType::Nearest);
let gray = small.to_luma8();
// Hash FNV-1a simple sur les pixels (rapide, pas besoin de crypto)
let mut hash: u64 = 0xcbf29ce484222325;
for pixel in gray.as_raw() {
hash ^= *pixel as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
/// Retourne les dimensions du moniteur principal (largeur, hauteur).
///
/// xcap utilise DXGI sur Windows qui retourne toujours les pixels physiques,
/// independamment du DPI awareness. Ceci est coherent avec les coordonnees
/// physiques d'enigo quand le process est DPI-aware.
pub fn screen_dimensions() -> Option<(u32, u32)> {
let monitors = xcap::Monitor::all().ok()?;
let primary = monitors
.into_iter()
.find(|m| m.is_primary().unwrap_or(false))?;
let w = primary.width().ok()?;
let h = primary.height().ok()?;
Some((w, h))
}