feat: capture Windows temps réel via mini serveur HTTP (port 5006)

- CaptureServer : serveur HTTP daemon sur l'agent Windows
- Capture fraîche mss en ~94ms à chaque requête
- Plus de lecture de vieux heartbeats sur disque
- Fallback capture locale si agent indisponible
- Firewall Windows port 5006 configuré

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-03-18 11:20:57 +01:00
parent ae65be2555
commit d4871249ea
4 changed files with 172 additions and 62 deletions

View File

@@ -124,57 +124,36 @@ def capture_screen():
@cross_origin()
def capture_windows():
"""
Récupère le dernier screenshot du PC Windows (via streaming server).
Capture l'ecran Windows EN TEMPS REEL via le mini-serveur de l'agent.
Le client Agent V1 envoie des heartbeats toutes les 5s.
On récupère le plus récent comme capture.
L'agent V1 expose un serveur HTTP sur le port 5006 qui retourne
un screenshot frais a chaque requete (< 100ms).
Plus besoin de lire de vieux fichiers heartbeat sur disque.
"""
import glob
from pathlib import Path
import requests as http_client
# Remonter jusqu'à la racine du projet (rpa_vision_v3/)
project_root = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
live_dir = project_root / "data" / "training" / "live_sessions"
# Chercher aussi dans les sous-dossiers machine (multi-machine)
import time as _time
# Chercher le screenshot le plus récent dans TOUS les dossiers
all_shots = []
for pattern in ["bg_*/shots/heartbeat_*.png", "sess_*/shots/heartbeat_*.png",
"*/bg_*/shots/heartbeat_*.png", "bg_*/shots/shot_*_full.png",
"sess_*/shots/shot_*_full.png"]:
all_shots.extend(live_dir.glob(pattern))
if not all_shots:
return jsonify({'error': 'Aucun screenshot Windows. Lancez l\'agent V1 sur le PC cible.'}), 404
# Trier par date de modification (le plus récent en premier)
all_shots.sort(key=lambda p: p.stat().st_mtime, reverse=True)
latest_shot = all_shots[0]
age_seconds = _time.time() - latest_shot.stat().st_mtime
if not latest_shot:
return jsonify({'error': 'Aucun screenshot Windows disponible'}), 404
agent_host = os.environ.get('RPA_WINDOWS_AGENT_HOST', '192.168.1.11')
agent_port = int(os.environ.get('RPA_WINDOWS_AGENT_PORT', '5006'))
agent_url = f'http://{agent_host}:{agent_port}/capture'
try:
from PIL import Image
img = Image.open(latest_shot)
buf = io.BytesIO()
img.save(buf, format='PNG')
img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
resp = http_client.get(agent_url, timeout=10)
if resp.ok:
return jsonify(resp.json())
return jsonify({
'image': img_base64,
'width': img.width,
'height': img.height,
'format': 'png',
'source': 'windows',
'file': str(latest_shot.name),
'session': latest_shot.parent.parent.name,
'age_seconds': round(age_seconds, 1),
'fresh': age_seconds < 30,
})
'error': f'Agent a repondu {resp.status_code}',
'detail': resp.text[:500],
}), 503
except http_client.ConnectionError:
return jsonify({
'error': f'Agent Windows non connecte ({agent_host}:{agent_port})',
'hint': 'Verifiez que l\'agent V1 est lance sur le PC Windows '
'et que le port 5006 est accessible.',
}), 503
except http_client.Timeout:
return jsonify({
'error': 'Timeout — l\'agent n\'a pas repondu dans les 10s',
}), 504
except Exception as e:
return jsonify({'error': str(e)}), 500

View File

@@ -111,25 +111,28 @@ export default function CapturePanel({
}
};
// Capture intelligente — auto-détection OS
// Capture intelligente — tente d'abord l'agent Windows distant,
// puis fallback sur la capture locale si l'agent est injoignable.
const doSmartCapture = async () => {
const isWindows = navigator.platform?.includes('Win') || navigator.userAgent?.includes('Windows');
if (isWindows) {
try {
const resp = await fetch('/api/screen-capture/capture-windows', { method: 'POST' });
const data = await resp.json();
if (data.image) {
setCurrentCapture({
screenshot_base64: data.image,
width: data.width,
height: data.height,
source: 'windows',
} as any);
}
} catch {}
} else {
onCapture();
try {
const resp = await fetch('/api/screen-capture/capture-windows', { method: 'POST' });
const data = await resp.json();
if (resp.ok && data.image) {
setCurrentCapture({
screenshot_base64: data.image,
width: data.width,
height: data.height,
source: data.source || 'windows',
} as any);
return;
}
// Agent indisponible — fallback capture locale
console.warn('Agent Windows indisponible, fallback local:', data.error);
} catch (err) {
console.warn('Erreur capture Windows, fallback local:', err);
}
// Fallback : capture locale (ecran du serveur Linux)
onCapture();
};
const handleTimerCapture = () => {