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>
This commit is contained in:
@@ -30,6 +30,7 @@ mod replay;
|
||||
mod server;
|
||||
#[allow(dead_code)]
|
||||
mod state;
|
||||
mod sysinfo;
|
||||
mod tray;
|
||||
mod visual;
|
||||
|
||||
@@ -40,12 +41,20 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Trouve Edge sur Windows
|
||||
/// Trouve un navigateur compatible sur Windows (Edge, Chrome, Brave, Firefox)
|
||||
#[cfg(target_os = "windows")]
|
||||
fn find_edge() -> Option<String> {
|
||||
fn find_browser() -> Option<String> {
|
||||
let paths = [
|
||||
// Edge
|
||||
r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||||
r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
|
||||
// Chrome
|
||||
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
|
||||
// Brave
|
||||
r"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe",
|
||||
// Firefox (supporte --kiosk mais pas --app)
|
||||
r"C:\Program Files\Mozilla Firefox\firefox.exe",
|
||||
];
|
||||
for p in &paths {
|
||||
if std::path::Path::new(p).exists() {
|
||||
@@ -56,6 +65,37 @@ fn find_edge() -> Option<String> {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// --- DPI awareness (DOIT etre appele avant toute operation graphique) ---
|
||||
// Rend le process DPI-aware sur Windows pour que les API (enigo, xcap,
|
||||
// GetSystemMetrics, etc.) travaillent en coordonnees physiques (pixels reels)
|
||||
// au lieu de coordonnees logiques (virtualisees par le DPI scaling).
|
||||
// Sans cet appel, un ecran 2560x1600 a 150% DPI apparait comme 1707x1067
|
||||
// pour enigo et GetSystemMetrics, ce qui cause des erreurs de positionnement
|
||||
// pendant le replay.
|
||||
// PROCESS_PER_MONITOR_DPI_AWARE = 2 : le niveau le plus precis.
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// SetProcessDpiAwareness (shcore.dll) et SetProcessDPIAware (user32.dll)
|
||||
// ne sont pas toujours exposes par windows-sys selon les features.
|
||||
// On utilise des appels FFI raw pour eviter d'ajouter des features.
|
||||
#[link(name = "shcore")]
|
||||
extern "system" {
|
||||
fn SetProcessDpiAwareness(value: i32) -> i32;
|
||||
}
|
||||
#[link(name = "user32")]
|
||||
extern "system" {
|
||||
fn SetProcessDPIAware() -> i32;
|
||||
}
|
||||
unsafe {
|
||||
// Tenter SetProcessDpiAwareness(2) = PROCESS_PER_MONITOR_DPI_AWARE
|
||||
let hr = SetProcessDpiAwareness(2);
|
||||
if hr != 0 {
|
||||
// Fallback pour Windows < 8.1 : SetProcessDPIAware()
|
||||
SetProcessDPIAware();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialiser le logging
|
||||
env_logger::Builder::from_env(
|
||||
env_logger::Env::default().default_filter_or("info"),
|
||||
@@ -135,15 +175,41 @@ fn main() {
|
||||
let chat_state = state.clone();
|
||||
chat::run_chat_thread(&chat_config, chat_state);
|
||||
|
||||
// Synchroniser les workflows disponibles depuis le serveur
|
||||
let sync_config = config.clone();
|
||||
let workflows = {
|
||||
let client = Client::new();
|
||||
network::fetch_workflows(&client, &sync_config)
|
||||
};
|
||||
if workflows.is_empty() {
|
||||
println!("[MAIN] Aucun workflow disponible pour cette machine.");
|
||||
} else {
|
||||
println!(
|
||||
"[MAIN] {} workflow(s) disponible(s) :",
|
||||
workflows.len()
|
||||
);
|
||||
for wf in &workflows {
|
||||
println!(
|
||||
" - {} ({} noeuds, {} transitions)",
|
||||
wf.name, wf.nodes, wf.edges
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n[MAIN] Agent operationnel — tous les threads demarres.\n");
|
||||
|
||||
// Ouvrir Léa (Edge mode app) automatiquement au démarrage
|
||||
// Ouvrir Léa dans le navigateur disponible (mode app) au démarrage
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let chat_url = config.chat_url();
|
||||
if let Some(edge) = find_edge() {
|
||||
println!("[MAIN] Ouverture de Léa dans Edge...");
|
||||
let _ = std::process::Command::new(&edge)
|
||||
if let Some(browser) = find_browser() {
|
||||
let browser_name = if browser.contains("chrome") { "Chrome" }
|
||||
else if browser.contains("edge") || browser.contains("Edge") { "Edge" }
|
||||
else if browser.contains("brave") || browser.contains("Brave") { "Brave" }
|
||||
else if browser.contains("firefox") || browser.contains("Firefox") { "Firefox" }
|
||||
else { "navigateur" };
|
||||
println!("[MAIN] Ouverture de Léa dans {}...", browser_name);
|
||||
let _ = std::process::Command::new(&browser)
|
||||
.args(&[
|
||||
&format!("--app={}", chat_url),
|
||||
"--window-size=600,800",
|
||||
@@ -151,6 +217,8 @@ fn main() {
|
||||
"--no-first-run",
|
||||
])
|
||||
.spawn();
|
||||
} else {
|
||||
println!("[MAIN] Aucun navigateur trouvé — ouvrez manuellement : {}", chat_url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,9 +372,8 @@ fn health_check_loop(config: &Config, state: &AgentState) {
|
||||
|
||||
while state.is_running() {
|
||||
let url = format!("{}/stats", config.server_url);
|
||||
let connected = client
|
||||
.get(&url)
|
||||
.timeout(timeout)
|
||||
let request = client.get(&url).timeout(timeout);
|
||||
let connected = network::with_auth(request, config)
|
||||
.send()
|
||||
.map(|r| r.status().is_success())
|
||||
.unwrap_or(false);
|
||||
@@ -327,6 +394,8 @@ fn health_check_loop(config: &Config, state: &AgentState) {
|
||||
|
||||
/// Affiche la banniere de demarrage.
|
||||
fn print_banner(config: &Config) {
|
||||
let meta = sysinfo::get_screen_metadata();
|
||||
|
||||
println!("======================================================");
|
||||
println!(
|
||||
" RPA Vision Agent v{} (Rust)",
|
||||
@@ -342,6 +411,17 @@ fn print_banner(config: &Config) {
|
||||
println!(" JPEG : qualite {}", config.jpeg_quality);
|
||||
println!(" Floutage : {}", if config.blur_sensitive { "actif" } else { "inactif" });
|
||||
println!(" Logs : retention {} jours", config.log_retention_days);
|
||||
println!(" Auth : {}", if config.api_token.is_empty() { "aucune" } else { "Bearer token" });
|
||||
println!(" Workflows : synchronisation au demarrage");
|
||||
println!(
|
||||
" Ecran : {}x{} @ {}% DPI",
|
||||
meta.screen_resolution[0], meta.screen_resolution[1], meta.dpi_scale
|
||||
);
|
||||
println!(
|
||||
" Moniteur : #{} ({})",
|
||||
meta.monitor_index,
|
||||
if meta.monitor_index == 0 { "principal" } else { "secondaire" }
|
||||
);
|
||||
println!("======================================================");
|
||||
println!();
|
||||
println!(" [IA] Cet agent utilise l'intelligence artificielle.");
|
||||
|
||||
Reference in New Issue
Block a user